Skip to main content

meshdb_executor/
reader.rs

1use crate::error::Result;
2use meshdb_core::{Edge, EdgeId, Node, NodeId, Property};
3use meshdb_storage::{PropertyConstraintSpec, StorageEngine};
4
5/// Read-side counterpart to [`crate::GraphWriter`]. Gives the executor a
6/// uniform view of the graph regardless of whether the data behind it lives
7/// entirely in a local storage engine (single-node or full-replica Raft
8/// mode) or is sharded across cluster peers (routing mode, where a
9/// partitioned reader fans out point reads to owners and scatter-gathers
10/// bulk scans).
11///
12/// Methods are sync because the executor's iterator model is sync. Async-
13/// backed implementations (e.g. a remote reader that talks gRPC) bridge via
14/// `Handle::block_on`; callers must run the executor inside `spawn_blocking`
15/// so they don't stall the tokio runtime.
16pub trait GraphReader: Send + Sync {
17    fn get_node(&self, id: NodeId) -> Result<Option<Node>>;
18    fn get_edge(&self, id: EdgeId) -> Result<Option<Edge>>;
19    fn all_node_ids(&self) -> Result<Vec<NodeId>>;
20    fn nodes_by_label(&self, label: &str) -> Result<Vec<NodeId>>;
21    /// Equality lookup via a property index. Callers (planner) must
22    /// have verified the `(label, property)` index exists before
23    /// emitting this call — fallback implementations are free to do a
24    /// label-scan-and-filter for impls that don't maintain their own
25    /// property index, but the storage-backed reader treats a call on
26    /// a non-existent index as an empty result since no entries are
27    /// maintained.
28    fn nodes_by_property(
29        &self,
30        label: &str,
31        property: &str,
32        value: &Property,
33    ) -> Result<Vec<NodeId>>;
34    /// Snapshot the `(label, property)` pairs of every property
35    /// index visible through this reader. Used by `SHOW INDEXES`.
36    /// Default impl returns empty — the storage-backed reader
37    /// overrides via the blanket impl, and partitioned/overlay
38    /// readers delegate to their bases.
39    fn list_property_indexes(&self) -> Result<Vec<(String, String)>> {
40        Ok(Vec::new())
41    }
42    /// Relationship-scope analogue of [`Self::list_property_indexes`].
43    /// Returns `(edge_type, property)` pairs for every registered
44    /// edge property index. Default impl returns empty; overlay
45    /// and partitioned readers delegate to their bases.
46    fn list_edge_property_indexes(&self) -> Result<Vec<(String, String)>> {
47        Ok(Vec::new())
48    }
49    /// Snapshot every registered constraint visible through this
50    /// reader, for `SHOW CONSTRAINTS` and `db.constraints()`. Default
51    /// impl returns empty; storage-backed readers override.
52    fn list_property_constraints(&self) -> Result<Vec<PropertyConstraintSpec>> {
53        Ok(Vec::new())
54    }
55    fn outgoing(&self, id: NodeId) -> Result<Vec<(EdgeId, NodeId)>>;
56    fn incoming(&self, id: NodeId) -> Result<Vec<(EdgeId, NodeId)>>;
57}
58
59/// Blanket impl: any **sized** type that implements [`StorageEngine`]
60/// is automatically a [`GraphReader`]. Covers the concrete
61/// [`meshdb_storage::RocksDbStorageEngine`] — a `&RocksDbStorageEngine`
62/// coerces to `&dyn GraphReader` via this blanket.
63///
64/// Not covered: `dyn StorageEngine` itself. Rust does not transitively
65/// coerce `&dyn StorageEngine` to `&dyn GraphReader` because trait
66/// objects of unrelated traits carry different vtables and there's no
67/// supertrait relationship connecting them. Call sites that hold a
68/// `&dyn StorageEngine` should use [`StorageReaderAdapter`] to wrap it
69/// as a `GraphReader`.
70impl<T: StorageEngine> GraphReader for T {
71    fn get_node(&self, id: NodeId) -> Result<Option<Node>> {
72        Ok(StorageEngine::get_node(self, id)?)
73    }
74
75    fn get_edge(&self, id: EdgeId) -> Result<Option<Edge>> {
76        Ok(StorageEngine::get_edge(self, id)?)
77    }
78
79    fn all_node_ids(&self) -> Result<Vec<NodeId>> {
80        Ok(StorageEngine::all_node_ids(self)?)
81    }
82
83    fn nodes_by_label(&self, label: &str) -> Result<Vec<NodeId>> {
84        Ok(StorageEngine::nodes_by_label(self, label)?)
85    }
86
87    fn nodes_by_property(
88        &self,
89        label: &str,
90        property: &str,
91        value: &Property,
92    ) -> Result<Vec<NodeId>> {
93        Ok(StorageEngine::nodes_by_property(
94            self, label, property, value,
95        )?)
96    }
97
98    fn list_property_indexes(&self) -> Result<Vec<(String, String)>> {
99        Ok(StorageEngine::list_property_indexes(self)
100            .into_iter()
101            .map(|s| (s.label, s.property))
102            .collect())
103    }
104
105    fn list_edge_property_indexes(&self) -> Result<Vec<(String, String)>> {
106        Ok(StorageEngine::list_edge_property_indexes(self)
107            .into_iter()
108            .map(|s| (s.edge_type, s.property))
109            .collect())
110    }
111
112    fn list_property_constraints(&self) -> Result<Vec<PropertyConstraintSpec>> {
113        Ok(StorageEngine::list_property_constraints(self))
114    }
115
116    fn outgoing(&self, id: NodeId) -> Result<Vec<(EdgeId, NodeId)>> {
117        Ok(StorageEngine::outgoing(self, id)?)
118    }
119
120    fn incoming(&self, id: NodeId) -> Result<Vec<(EdgeId, NodeId)>> {
121        Ok(StorageEngine::incoming(self, id)?)
122    }
123}
124
125/// Adapter that lets a `&dyn StorageEngine` act as a `GraphReader`.
126/// Needed because trait objects of unrelated traits don't coerce —
127/// see the note on the blanket `impl<T: StorageEngine> GraphReader for T`.
128/// Wraps a trait-object reference; no heap allocation. Works for both
129/// reads and writes via the sibling [`crate::writer::StorageWriterAdapter`].
130pub struct StorageReaderAdapter<'a>(pub &'a dyn StorageEngine);
131
132impl GraphReader for StorageReaderAdapter<'_> {
133    fn get_node(&self, id: NodeId) -> Result<Option<Node>> {
134        Ok(self.0.get_node(id)?)
135    }
136
137    fn get_edge(&self, id: EdgeId) -> Result<Option<Edge>> {
138        Ok(self.0.get_edge(id)?)
139    }
140
141    fn all_node_ids(&self) -> Result<Vec<NodeId>> {
142        Ok(self.0.all_node_ids()?)
143    }
144
145    fn nodes_by_label(&self, label: &str) -> Result<Vec<NodeId>> {
146        Ok(self.0.nodes_by_label(label)?)
147    }
148
149    fn nodes_by_property(
150        &self,
151        label: &str,
152        property: &str,
153        value: &Property,
154    ) -> Result<Vec<NodeId>> {
155        Ok(self.0.nodes_by_property(label, property, value)?)
156    }
157
158    fn list_property_indexes(&self) -> Result<Vec<(String, String)>> {
159        Ok(self
160            .0
161            .list_property_indexes()
162            .into_iter()
163            .map(|s| (s.label, s.property))
164            .collect())
165    }
166
167    fn list_edge_property_indexes(&self) -> Result<Vec<(String, String)>> {
168        Ok(self
169            .0
170            .list_edge_property_indexes()
171            .into_iter()
172            .map(|s| (s.edge_type, s.property))
173            .collect())
174    }
175
176    fn list_property_constraints(&self) -> Result<Vec<PropertyConstraintSpec>> {
177        Ok(self.0.list_property_constraints())
178    }
179
180    fn outgoing(&self, id: NodeId) -> Result<Vec<(EdgeId, NodeId)>> {
181        Ok(self.0.outgoing(id)?)
182    }
183
184    fn incoming(&self, id: NodeId) -> Result<Vec<(EdgeId, NodeId)>> {
185        Ok(self.0.incoming(id)?)
186    }
187}