Skip to main content

nodedb_lite/storage/
spatial_segment_ext.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! `SpatialSegmentExt` — pagedb segment operations for R-tree checkpoint data.
4//!
5//! The `StorageEngine` trait handles sparse, sorted, key-value state. For R-tree
6//! checkpoint blobs (one per (collection, field) pair, potentially large,
7//! sequentially read on cold-open restore) pagedb segments are the appropriate
8//! backing. A single segment per (collection, field) reduces B+ tree pressure from
9//! O(rtree_node_count) entries to O(1) per index.
10//!
11//! The following keys remain on the B+ tree (`Namespace::Spatial`):
12//! - `spatial:_collections` — `Vec<(String, String)>` catalog (collection, field)
13//! - `spatial:_next_id` — `u64` next entry ID
14//! - `spatial:{collection}:{field}:docmap` — doc_id → entry_id mapping
15//!
16//! Only the `spatial:{collection}:{field}:rtree` blob (CRC32C-wrapped R-tree
17//! checkpoint bytes) moves to pagedb segments.
18//!
19//! Only compiled on non-WASM targets. WASM stays on the KV blob path.
20
21use crate::error::LiteError;
22
23/// Extension trait: write, open, and delete R-tree checkpoint segments backed
24/// by pagedb encrypted segment files.
25///
26/// One segment per (collection, field) pair (e.g. `"orders"`, `"location"`).
27/// The segment contains the CRC32C-wrapped R-tree checkpoint bytes exactly as
28/// produced by `crate::storage::checksum::wrap` — no additional framing beyond
29/// the 8-byte length-prefix envelope used to survive pagedb's page-boundary
30/// zero-padding.
31///
32/// This trait is object-safe so `StorageEngine` implementations can return
33/// `Option<&dyn SpatialSegmentExt>` via `as_spatial_segment_ext()`.
34#[async_trait::async_trait]
35pub trait SpatialSegmentExt: Send + Sync {
36    /// Write the CRC32C-wrapped R-tree checkpoint blob for `(collection, field)`.
37    ///
38    /// Chunks the length-prefixed payload into 4 KiB pagedb pages, creates a new
39    /// encrypted segment, and links it under `spatial/rtree/{collection}/{field}`.
40    /// If a segment already exists under that name it is atomically replaced (old
41    /// segment is tombstoned and reaped by the next `Db::gc_now` call).
42    async fn write_spatial_segment(
43        &self,
44        collection: &str,
45        field: &str,
46        bytes: &[u8],
47    ) -> Result<(), LiteError>;
48
49    /// Open a previously written R-tree segment for `(collection, field)`.
50    ///
51    /// Returns the CRC32C-wrapped R-tree checkpoint bytes in a `Box<[u8]>`,
52    /// identical to the bytes passed to `write_spatial_segment`.
53    ///
54    /// Returns `None` if no segment exists under `spatial/rtree/{collection}/{field}`.
55    async fn open_spatial_segment(
56        &self,
57        collection: &str,
58        field: &str,
59    ) -> Result<Option<Box<[u8]>>, LiteError>;
60
61    /// Delete the R-tree segment for `(collection, field)`.
62    ///
63    /// No-op if no segment exists. The segment file is tombstoned and reaped by
64    /// the next `Db::gc_now` cycle.
65    async fn delete_spatial_segment(&self, collection: &str, field: &str) -> Result<(), LiteError>;
66}