nodedb_cluster/distributed_array/local_executor.rs
1// SPDX-License-Identifier: BUSL-1.1
2
3//! Local Data-Plane execution trait for array shard operations.
4//!
5//! `ArrayLocalExecutor` is defined here in `nodedb-cluster` and
6//! implemented in the main `nodedb` binary, which has access to the
7//! SPSC bridge and the Data Plane array engine. The shard-side handler
8//! (`handler.rs`) holds an `Arc<dyn ArrayLocalExecutor>` and calls
9//! through it to execute slice and surrogate-bitmap-scan operations
10//! on the local node.
11//!
12//! This keeps `nodedb-cluster` free of a compile-time dependency on
13//! `nodedb` while still producing real results from the Data Plane.
14
15use crate::distributed_array::merge::ArrayAggPartial;
16use crate::distributed_array::wire::{
17 ArrayShardAggReq, ArrayShardDeleteReq, ArrayShardPutReq, ArrayShardSliceReq,
18};
19use crate::error::Result;
20
21/// Result of a local shard slice: the per-row bytes plus the bitemporal
22/// below-horizon signal.
23///
24/// `truncated_before_horizon` is computed by the Data Plane (it is `true`
25/// when the `system_time` cutoff predates every stored tile version, so the
26/// shard produced zero rows for that reason). It MUST be threaded back to the
27/// coordinator so the OR-reduce across shards can surface an incomplete-result
28/// signal to the client — dropping it would silently report complete results.
29pub struct ArraySliceExec {
30 pub rows: Vec<Vec<u8>>,
31 pub truncated_before_horizon: bool,
32}
33
34/// Result of a local shard partial aggregate: the per-group partial states
35/// plus the bitemporal below-horizon signal (see [`ArraySliceExec`]).
36pub struct ArrayAggExec {
37 pub partials: Vec<ArrayAggPartial>,
38 pub truncated_before_horizon: bool,
39}
40
41/// Execute array operations against the local Data Plane.
42///
43/// The implementor (in `nodedb`) routes the call through the SPSC bridge
44/// to the appropriate TPC core and returns the serialised zerompk result
45/// rows or bitmap bytes.
46#[async_trait::async_trait]
47pub trait ArrayLocalExecutor: Send + Sync + 'static {
48 /// Execute a coord-range slice and return zerompk-encoded row bytes.
49 ///
50 /// `array_id_msgpack` — zerompk encoding of `nodedb_array::types::ArrayId`.
51 /// `slice_msgpack` — zerompk encoding of `nodedb_array::query::Slice`.
52 /// `attr_projection` — attribute index list; empty means all attributes.
53 /// `limit` — maximum rows to return per shard (0 = unlimited).
54 /// `cell_filter_msgpack` — zerompk encoding of `SurrogateBitmap`; empty
55 /// means no filter.
56 /// `shard_hilbert_range` — optional `[lo, hi]` Hilbert-prefix range; when
57 /// set only tiles whose prefix falls in this range are returned, preventing
58 /// duplicate rows in single-node harnesses where all vShards share one
59 /// Data Plane. `None` = no Hilbert filter.
60 ///
61 /// Returns the per-row bytes (one element per matching row, each the
62 /// native-msgpack encoding of that row) plus the `truncated_before_horizon`
63 /// signal from the Data Plane.
64 async fn exec_slice(&self, req: &ArrayShardSliceReq) -> Result<ArraySliceExec>;
65
66 /// Execute a surrogate-bitmap scan and return the zerompk-encoded
67 /// `SurrogateBitmap` bytes for matching cells.
68 ///
69 /// `array_id_msgpack` — zerompk encoding of `nodedb_array::types::ArrayId`.
70 /// `slice_msgpack` — zerompk encoding of `nodedb_array::query::Slice`.
71 async fn exec_surrogate_bitmap_scan(
72 &self,
73 array_id_msgpack: &[u8],
74 slice_msgpack: &[u8],
75 ) -> Result<Vec<u8>>;
76
77 /// Execute a partial aggregate on this shard and return the partial states.
78 ///
79 /// The Data Plane computes the aggregate with `return_partial = true`, so it
80 /// returns partial states (plus the `truncated_before_horizon` signal)
81 /// rather than finalized scalars. The coordinator merges partials from all
82 /// shards before finalizing.
83 async fn exec_agg(&self, req: &ArrayShardAggReq) -> Result<ArrayAggExec>;
84
85 /// Apply a cell-batch write to the local array engine.
86 ///
87 /// `req.cells_msgpack` is a zerompk encoding of
88 /// `Vec<nodedb::engine::array::wal::ArrayPutCell>`. All cells belong to
89 /// the same Hilbert-prefix tile. The shard handler has already validated
90 /// that this shard owns the tile; the executor dispatches directly to the
91 /// Data Plane without further routing checks.
92 async fn exec_put(&self, req: &ArrayShardPutReq) -> Result<u64>;
93
94 /// Delete cells by exact coordinates from the local array engine.
95 ///
96 /// Takes the full `ArrayShardDeleteReq` (not just the coord bytes) because
97 /// the implementor needs the request's Hilbert routing metadata
98 /// (`representative_hilbert_prefix` / `prefix_bits`) to derive the owning
99 /// vShard when it replicates the delete to that shard's data Raft group,
100 /// mirroring [`Self::exec_put`].
101 ///
102 /// Returns the `applied_lsn` (equal to `req.wal_lsn` on success).
103 async fn exec_delete(&self, req: &ArrayShardDeleteReq) -> Result<u64>;
104}