Skip to main content

meshdb_executor/
reader.rs

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