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    /// Snapshot every registered constraint visible through this
43    /// reader, for `SHOW CONSTRAINTS` and `db.constraints()`. Default
44    /// impl returns empty; storage-backed readers override.
45    fn list_property_constraints(&self) -> Result<Vec<PropertyConstraintSpec>> {
46        Ok(Vec::new())
47    }
48    fn outgoing(&self, id: NodeId) -> Result<Vec<(EdgeId, NodeId)>>;
49    fn incoming(&self, id: NodeId) -> Result<Vec<(EdgeId, NodeId)>>;
50}
51
52/// Blanket impl: any **sized** type that implements [`StorageEngine`]
53/// is automatically a [`GraphReader`]. Covers the concrete
54/// [`meshdb_storage::RocksDbStorageEngine`] — a `&RocksDbStorageEngine`
55/// coerces to `&dyn GraphReader` via this blanket.
56///
57/// Not covered: `dyn StorageEngine` itself. Rust does not transitively
58/// coerce `&dyn StorageEngine` to `&dyn GraphReader` because trait
59/// objects of unrelated traits carry different vtables and there's no
60/// supertrait relationship connecting them. Call sites that hold a
61/// `&dyn StorageEngine` should use [`StorageReaderAdapter`] to wrap it
62/// as a `GraphReader`.
63impl<T: StorageEngine> GraphReader for T {
64    fn get_node(&self, id: NodeId) -> Result<Option<Node>> {
65        Ok(StorageEngine::get_node(self, id)?)
66    }
67
68    fn get_edge(&self, id: EdgeId) -> Result<Option<Edge>> {
69        Ok(StorageEngine::get_edge(self, id)?)
70    }
71
72    fn all_node_ids(&self) -> Result<Vec<NodeId>> {
73        Ok(StorageEngine::all_node_ids(self)?)
74    }
75
76    fn nodes_by_label(&self, label: &str) -> Result<Vec<NodeId>> {
77        Ok(StorageEngine::nodes_by_label(self, label)?)
78    }
79
80    fn nodes_by_property(
81        &self,
82        label: &str,
83        property: &str,
84        value: &Property,
85    ) -> Result<Vec<NodeId>> {
86        Ok(StorageEngine::nodes_by_property(
87            self, label, property, value,
88        )?)
89    }
90
91    fn list_property_indexes(&self) -> Result<Vec<(String, String)>> {
92        Ok(StorageEngine::list_property_indexes(self)
93            .into_iter()
94            .map(|s| (s.label, s.property))
95            .collect())
96    }
97
98    fn list_property_constraints(&self) -> Result<Vec<PropertyConstraintSpec>> {
99        Ok(StorageEngine::list_property_constraints(self))
100    }
101
102    fn outgoing(&self, id: NodeId) -> Result<Vec<(EdgeId, NodeId)>> {
103        Ok(StorageEngine::outgoing(self, id)?)
104    }
105
106    fn incoming(&self, id: NodeId) -> Result<Vec<(EdgeId, NodeId)>> {
107        Ok(StorageEngine::incoming(self, id)?)
108    }
109}
110
111/// Adapter that lets a `&dyn StorageEngine` act as a `GraphReader`.
112/// Needed because trait objects of unrelated traits don't coerce —
113/// see the note on the blanket `impl<T: StorageEngine> GraphReader for T`.
114/// Wraps a trait-object reference; no heap allocation. Works for both
115/// reads and writes via the sibling [`crate::writer::StorageWriterAdapter`].
116pub struct StorageReaderAdapter<'a>(pub &'a dyn StorageEngine);
117
118impl GraphReader for StorageReaderAdapter<'_> {
119    fn get_node(&self, id: NodeId) -> Result<Option<Node>> {
120        Ok(self.0.get_node(id)?)
121    }
122
123    fn get_edge(&self, id: EdgeId) -> Result<Option<Edge>> {
124        Ok(self.0.get_edge(id)?)
125    }
126
127    fn all_node_ids(&self) -> Result<Vec<NodeId>> {
128        Ok(self.0.all_node_ids()?)
129    }
130
131    fn nodes_by_label(&self, label: &str) -> Result<Vec<NodeId>> {
132        Ok(self.0.nodes_by_label(label)?)
133    }
134
135    fn nodes_by_property(
136        &self,
137        label: &str,
138        property: &str,
139        value: &Property,
140    ) -> Result<Vec<NodeId>> {
141        Ok(self.0.nodes_by_property(label, property, value)?)
142    }
143
144    fn list_property_indexes(&self) -> Result<Vec<(String, String)>> {
145        Ok(self
146            .0
147            .list_property_indexes()
148            .into_iter()
149            .map(|s| (s.label, s.property))
150            .collect())
151    }
152
153    fn list_property_constraints(&self) -> Result<Vec<PropertyConstraintSpec>> {
154        Ok(self.0.list_property_constraints())
155    }
156
157    fn outgoing(&self, id: NodeId) -> Result<Vec<(EdgeId, NodeId)>> {
158        Ok(self.0.outgoing(id)?)
159    }
160
161    fn incoming(&self, id: NodeId) -> Result<Vec<(EdgeId, NodeId)>> {
162        Ok(self.0.incoming(id)?)
163    }
164}