Skip to main content

meshdb_executor/
writer.rs

1use crate::error::Result;
2use meshdb_core::{Edge, EdgeId, Node, NodeId};
3use meshdb_storage::{
4    ConstraintScope, PropertyConstraintKind, PropertyConstraintSpec, StorageEngine,
5};
6
7/// `(label, property)` pair identifying a node property index.
8/// Used by `GraphReader::list_property_indexes` and
9/// `GraphWriter::list_property_indexes` so the executor can enumerate
10/// node-scoped indexes without depending on the storage-layer
11/// `PropertyIndexSpec` struct directly. Type alias kept simple — a
12/// tuple is all the executor ever needed; the richer spec type lives
13/// one layer down.
14pub type NodeIndexSpec = (String, String);
15
16/// `(edge_type, property)` pair identifying an edge property index.
17/// Relationship-scope analogue of [`NodeIndexSpec`]. Kept as a
18/// tuple for consistency; SHOW INDEXES / DDL planning don't need the
19/// richer spec type.
20pub type EdgeIndexSpec = (String, String);
21
22/// Sink for mutating graph operations produced by the executor. Isolates
23/// write-side concerns from read-side traversal so we can plug in either a
24/// direct-to-storage writer (single-node mode) or a Raft-backed writer that
25/// proposes each mutation through consensus (cluster mode).
26///
27/// Methods are sync because the executor's iterator model is sync.
28/// Async-backed implementations (e.g. the Raft writer) bridge via
29/// `Handle::block_on`; callers must run the executor inside
30/// `spawn_blocking` so they don't stall the tokio runtime.
31pub trait GraphWriter {
32    fn put_node(&self, node: &Node) -> Result<()>;
33    fn put_edge(&self, edge: &Edge) -> Result<()>;
34    fn delete_edge(&self, id: EdgeId) -> Result<()>;
35    fn detach_delete_node(&self, id: NodeId) -> Result<()>;
36
37    /// Declare a new property index. Default impl errors so remote
38    /// writers (Raft, routing) that don't yet support cluster-aware
39    /// DDL surface the limitation immediately. Storage-backed writers
40    /// override this via the blanket impl.
41    fn create_property_index(&self, _label: &str, _property: &str) -> Result<()> {
42        Err(crate::error::Error::Unsupported(
43            "property-index DDL is not supported by this writer".into(),
44        ))
45    }
46
47    /// Tear down a property index. Mirrors [`Self::create_property_index`].
48    fn drop_property_index(&self, _label: &str, _property: &str) -> Result<()> {
49        Err(crate::error::Error::Unsupported(
50            "property-index DDL is not supported by this writer".into(),
51        ))
52    }
53
54    /// Snapshot the currently-registered property indexes as
55    /// `(label, property)` pairs for `SHOW INDEXES`. Default impl
56    /// returns an empty list — remote writers will wire real
57    /// fan-out in Phase C.
58    fn list_property_indexes(&self) -> Result<Vec<NodeIndexSpec>> {
59        Ok(Vec::new())
60    }
61
62    /// Relationship-scope analogue of
63    /// [`Self::create_property_index`]. Default impl errors so remote
64    /// writers that haven't plumbed edge-index DDL yet surface the
65    /// limitation immediately; storage-backed writers override via
66    /// the blanket impl.
67    fn create_edge_property_index(&self, _edge_type: &str, _property: &str) -> Result<()> {
68        Err(crate::error::Error::Unsupported(
69            "edge-property-index DDL is not supported by this writer".into(),
70        ))
71    }
72
73    /// Tear down an edge property index. Mirrors
74    /// [`Self::create_edge_property_index`].
75    fn drop_edge_property_index(&self, _edge_type: &str, _property: &str) -> Result<()> {
76        Err(crate::error::Error::Unsupported(
77            "edge-property-index DDL is not supported by this writer".into(),
78        ))
79    }
80
81    /// Snapshot the currently-registered edge property indexes as
82    /// `(edge_type, property)` pairs. Default impl returns an empty
83    /// list.
84    fn list_edge_property_indexes(&self) -> Result<Vec<EdgeIndexSpec>> {
85        Ok(Vec::new())
86    }
87
88    /// Declare a new property constraint. Default impl errors so
89    /// remote writers that haven't plumbed constraint DDL yet surface
90    /// the limitation immediately — storage-backed writers override
91    /// via the blanket impl. `properties` is a list to accommodate
92    /// composite kinds (`NodeKey`); single-property kinds pass a
93    /// one-element slice.
94    fn create_property_constraint(
95        &self,
96        _name: Option<&str>,
97        _scope: &ConstraintScope,
98        _properties: &[String],
99        _kind: PropertyConstraintKind,
100        _if_not_exists: bool,
101    ) -> Result<PropertyConstraintSpec> {
102        Err(crate::error::Error::Unsupported(
103            "constraint DDL is not supported by this writer".into(),
104        ))
105    }
106
107    /// Tear down a constraint by name. Mirrors
108    /// [`Self::create_property_constraint`].
109    fn drop_property_constraint(&self, _name: &str, _if_exists: bool) -> Result<()> {
110        Err(crate::error::Error::Unsupported(
111            "constraint DDL is not supported by this writer".into(),
112        ))
113    }
114
115    /// Snapshot the currently-registered constraints for
116    /// `SHOW CONSTRAINTS`. Default impl returns an empty list.
117    fn list_property_constraints(&self) -> Result<Vec<PropertyConstraintSpec>> {
118        Ok(Vec::new())
119    }
120}
121
122/// Blanket impl: any **sized** type that implements [`StorageEngine`]
123/// is automatically a [`GraphWriter`]. See the matching
124/// [`crate::reader::GraphReader`] blanket for rationale, and
125/// [`StorageWriterAdapter`] for the trait-object adapter.
126impl<T: StorageEngine> GraphWriter for T {
127    fn put_node(&self, node: &Node) -> Result<()> {
128        StorageEngine::put_node(self, node)?;
129        Ok(())
130    }
131
132    fn put_edge(&self, edge: &Edge) -> Result<()> {
133        StorageEngine::put_edge(self, edge)?;
134        Ok(())
135    }
136
137    fn delete_edge(&self, id: EdgeId) -> Result<()> {
138        if StorageEngine::get_edge(self, id)?.is_some() {
139            StorageEngine::delete_edge(self, id)?;
140        }
141        Ok(())
142    }
143
144    fn detach_delete_node(&self, id: NodeId) -> Result<()> {
145        StorageEngine::detach_delete_node(self, id)?;
146        Ok(())
147    }
148
149    fn create_property_index(&self, label: &str, property: &str) -> Result<()> {
150        StorageEngine::create_property_index(self, label, property)?;
151        Ok(())
152    }
153
154    fn drop_property_index(&self, label: &str, property: &str) -> Result<()> {
155        StorageEngine::drop_property_index(self, label, property)?;
156        Ok(())
157    }
158
159    fn list_property_indexes(&self) -> Result<Vec<NodeIndexSpec>> {
160        Ok(StorageEngine::list_property_indexes(self)
161            .into_iter()
162            .map(|s| (s.label, s.property))
163            .collect())
164    }
165
166    fn create_edge_property_index(&self, edge_type: &str, property: &str) -> Result<()> {
167        StorageEngine::create_edge_property_index(self, edge_type, property)?;
168        Ok(())
169    }
170
171    fn drop_edge_property_index(&self, edge_type: &str, property: &str) -> Result<()> {
172        StorageEngine::drop_edge_property_index(self, edge_type, property)?;
173        Ok(())
174    }
175
176    fn list_edge_property_indexes(&self) -> Result<Vec<EdgeIndexSpec>> {
177        Ok(StorageEngine::list_edge_property_indexes(self)
178            .into_iter()
179            .map(|s| (s.edge_type, s.property))
180            .collect())
181    }
182
183    fn create_property_constraint(
184        &self,
185        name: Option<&str>,
186        scope: &ConstraintScope,
187        properties: &[String],
188        kind: PropertyConstraintKind,
189        if_not_exists: bool,
190    ) -> Result<PropertyConstraintSpec> {
191        Ok(StorageEngine::create_property_constraint(
192            self,
193            name,
194            scope,
195            properties,
196            kind,
197            if_not_exists,
198        )?)
199    }
200
201    fn drop_property_constraint(&self, name: &str, if_exists: bool) -> Result<()> {
202        StorageEngine::drop_property_constraint(self, name, if_exists)?;
203        Ok(())
204    }
205
206    fn list_property_constraints(&self) -> Result<Vec<PropertyConstraintSpec>> {
207        Ok(StorageEngine::list_property_constraints(self))
208    }
209}
210
211/// Adapter that lets a `&dyn StorageEngine` act as a `GraphWriter`.
212/// See [`crate::reader::StorageReaderAdapter`] for the rationale.
213pub struct StorageWriterAdapter<'a>(pub &'a dyn StorageEngine);
214
215impl GraphWriter for StorageWriterAdapter<'_> {
216    fn put_node(&self, node: &Node) -> Result<()> {
217        self.0.put_node(node)?;
218        Ok(())
219    }
220
221    fn put_edge(&self, edge: &Edge) -> Result<()> {
222        self.0.put_edge(edge)?;
223        Ok(())
224    }
225
226    fn delete_edge(&self, id: EdgeId) -> Result<()> {
227        if self.0.get_edge(id)?.is_some() {
228            self.0.delete_edge(id)?;
229        }
230        Ok(())
231    }
232
233    fn detach_delete_node(&self, id: NodeId) -> Result<()> {
234        self.0.detach_delete_node(id)?;
235        Ok(())
236    }
237
238    fn create_property_index(&self, label: &str, property: &str) -> Result<()> {
239        self.0.create_property_index(label, property)?;
240        Ok(())
241    }
242
243    fn drop_property_index(&self, label: &str, property: &str) -> Result<()> {
244        self.0.drop_property_index(label, property)?;
245        Ok(())
246    }
247
248    fn list_property_indexes(&self) -> Result<Vec<NodeIndexSpec>> {
249        Ok(self
250            .0
251            .list_property_indexes()
252            .into_iter()
253            .map(|s| (s.label, s.property))
254            .collect())
255    }
256
257    fn create_edge_property_index(&self, edge_type: &str, property: &str) -> Result<()> {
258        self.0.create_edge_property_index(edge_type, property)?;
259        Ok(())
260    }
261
262    fn drop_edge_property_index(&self, edge_type: &str, property: &str) -> Result<()> {
263        self.0.drop_edge_property_index(edge_type, property)?;
264        Ok(())
265    }
266
267    fn list_edge_property_indexes(&self) -> Result<Vec<EdgeIndexSpec>> {
268        Ok(self
269            .0
270            .list_edge_property_indexes()
271            .into_iter()
272            .map(|s| (s.edge_type, s.property))
273            .collect())
274    }
275
276    fn create_property_constraint(
277        &self,
278        name: Option<&str>,
279        scope: &ConstraintScope,
280        properties: &[String],
281        kind: PropertyConstraintKind,
282        if_not_exists: bool,
283    ) -> Result<PropertyConstraintSpec> {
284        Ok(self
285            .0
286            .create_property_constraint(name, scope, properties, kind, if_not_exists)?)
287    }
288
289    fn drop_property_constraint(&self, name: &str, if_exists: bool) -> Result<()> {
290        self.0.drop_property_constraint(name, if_exists)?;
291        Ok(())
292    }
293
294    fn list_property_constraints(&self) -> Result<Vec<PropertyConstraintSpec>> {
295        Ok(self.0.list_property_constraints())
296    }
297}