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/// Sink for mutating graph operations produced by the executor. Isolates
8/// write-side concerns from read-side traversal so we can plug in either a
9/// direct-to-storage writer (single-node mode) or a Raft-backed writer that
10/// proposes each mutation through consensus (cluster mode).
11///
12/// Methods are sync because the executor's iterator model is sync.
13/// Async-backed implementations (e.g. the Raft writer) bridge via
14/// `Handle::block_on`; callers must run the executor inside
15/// `spawn_blocking` so they don't stall the tokio runtime.
16pub trait GraphWriter {
17    fn put_node(&self, node: &Node) -> Result<()>;
18    fn put_edge(&self, edge: &Edge) -> Result<()>;
19    fn delete_edge(&self, id: EdgeId) -> Result<()>;
20    fn detach_delete_node(&self, id: NodeId) -> Result<()>;
21
22    /// Declare a new property index. Default impl errors so remote
23    /// writers (Raft, routing) that don't yet support cluster-aware
24    /// DDL surface the limitation immediately. Storage-backed writers
25    /// override this via the blanket impl.
26    fn create_property_index(&self, _label: &str, _property: &str) -> Result<()> {
27        Err(crate::error::Error::Unsupported(
28            "property-index DDL is not supported by this writer".into(),
29        ))
30    }
31
32    /// Tear down a property index. Mirrors [`Self::create_property_index`].
33    fn drop_property_index(&self, _label: &str, _property: &str) -> Result<()> {
34        Err(crate::error::Error::Unsupported(
35            "property-index DDL is not supported by this writer".into(),
36        ))
37    }
38
39    /// Snapshot the currently-registered property indexes as
40    /// `(label, property)` pairs for `SHOW INDEXES`. Default impl
41    /// returns an empty list — remote writers will wire real
42    /// fan-out in Phase C.
43    fn list_property_indexes(&self) -> Result<Vec<(String, String)>> {
44        Ok(Vec::new())
45    }
46
47    /// Declare a new property constraint. Default impl errors so
48    /// remote writers that haven't plumbed constraint DDL yet surface
49    /// the limitation immediately — storage-backed writers override
50    /// via the blanket impl. `properties` is a list to accommodate
51    /// composite kinds (`NodeKey`); single-property kinds pass a
52    /// one-element slice.
53    fn create_property_constraint(
54        &self,
55        _name: Option<&str>,
56        _scope: &ConstraintScope,
57        _properties: &[String],
58        _kind: PropertyConstraintKind,
59        _if_not_exists: bool,
60    ) -> Result<PropertyConstraintSpec> {
61        Err(crate::error::Error::Unsupported(
62            "constraint DDL is not supported by this writer".into(),
63        ))
64    }
65
66    /// Tear down a constraint by name. Mirrors
67    /// [`Self::create_property_constraint`].
68    fn drop_property_constraint(&self, _name: &str, _if_exists: bool) -> Result<()> {
69        Err(crate::error::Error::Unsupported(
70            "constraint DDL is not supported by this writer".into(),
71        ))
72    }
73
74    /// Snapshot the currently-registered constraints for
75    /// `SHOW CONSTRAINTS`. Default impl returns an empty list.
76    fn list_property_constraints(&self) -> Result<Vec<PropertyConstraintSpec>> {
77        Ok(Vec::new())
78    }
79}
80
81/// Blanket impl: any **sized** type that implements [`StorageEngine`]
82/// is automatically a [`GraphWriter`]. See the matching
83/// [`crate::reader::GraphReader`] blanket for rationale, and
84/// [`StorageWriterAdapter`] for the trait-object adapter.
85impl<T: StorageEngine> GraphWriter for T {
86    fn put_node(&self, node: &Node) -> Result<()> {
87        StorageEngine::put_node(self, node)?;
88        Ok(())
89    }
90
91    fn put_edge(&self, edge: &Edge) -> Result<()> {
92        StorageEngine::put_edge(self, edge)?;
93        Ok(())
94    }
95
96    fn delete_edge(&self, id: EdgeId) -> Result<()> {
97        if StorageEngine::get_edge(self, id)?.is_some() {
98            StorageEngine::delete_edge(self, id)?;
99        }
100        Ok(())
101    }
102
103    fn detach_delete_node(&self, id: NodeId) -> Result<()> {
104        StorageEngine::detach_delete_node(self, id)?;
105        Ok(())
106    }
107
108    fn create_property_index(&self, label: &str, property: &str) -> Result<()> {
109        StorageEngine::create_property_index(self, label, property)?;
110        Ok(())
111    }
112
113    fn drop_property_index(&self, label: &str, property: &str) -> Result<()> {
114        StorageEngine::drop_property_index(self, label, property)?;
115        Ok(())
116    }
117
118    fn list_property_indexes(&self) -> Result<Vec<(String, String)>> {
119        Ok(StorageEngine::list_property_indexes(self)
120            .into_iter()
121            .map(|s| (s.label, s.property))
122            .collect())
123    }
124
125    fn create_property_constraint(
126        &self,
127        name: Option<&str>,
128        scope: &ConstraintScope,
129        properties: &[String],
130        kind: PropertyConstraintKind,
131        if_not_exists: bool,
132    ) -> Result<PropertyConstraintSpec> {
133        Ok(StorageEngine::create_property_constraint(
134            self,
135            name,
136            scope,
137            properties,
138            kind,
139            if_not_exists,
140        )?)
141    }
142
143    fn drop_property_constraint(&self, name: &str, if_exists: bool) -> Result<()> {
144        StorageEngine::drop_property_constraint(self, name, if_exists)?;
145        Ok(())
146    }
147
148    fn list_property_constraints(&self) -> Result<Vec<PropertyConstraintSpec>> {
149        Ok(StorageEngine::list_property_constraints(self))
150    }
151}
152
153/// Adapter that lets a `&dyn StorageEngine` act as a `GraphWriter`.
154/// See [`crate::reader::StorageReaderAdapter`] for the rationale.
155pub struct StorageWriterAdapter<'a>(pub &'a dyn StorageEngine);
156
157impl GraphWriter for StorageWriterAdapter<'_> {
158    fn put_node(&self, node: &Node) -> Result<()> {
159        self.0.put_node(node)?;
160        Ok(())
161    }
162
163    fn put_edge(&self, edge: &Edge) -> Result<()> {
164        self.0.put_edge(edge)?;
165        Ok(())
166    }
167
168    fn delete_edge(&self, id: EdgeId) -> Result<()> {
169        if self.0.get_edge(id)?.is_some() {
170            self.0.delete_edge(id)?;
171        }
172        Ok(())
173    }
174
175    fn detach_delete_node(&self, id: NodeId) -> Result<()> {
176        self.0.detach_delete_node(id)?;
177        Ok(())
178    }
179
180    fn create_property_index(&self, label: &str, property: &str) -> Result<()> {
181        self.0.create_property_index(label, property)?;
182        Ok(())
183    }
184
185    fn drop_property_index(&self, label: &str, property: &str) -> Result<()> {
186        self.0.drop_property_index(label, property)?;
187        Ok(())
188    }
189
190    fn list_property_indexes(&self) -> Result<Vec<(String, String)>> {
191        Ok(self
192            .0
193            .list_property_indexes()
194            .into_iter()
195            .map(|s| (s.label, s.property))
196            .collect())
197    }
198
199    fn create_property_constraint(
200        &self,
201        name: Option<&str>,
202        scope: &ConstraintScope,
203        properties: &[String],
204        kind: PropertyConstraintKind,
205        if_not_exists: bool,
206    ) -> Result<PropertyConstraintSpec> {
207        Ok(self
208            .0
209            .create_property_constraint(name, scope, properties, kind, if_not_exists)?)
210    }
211
212    fn drop_property_constraint(&self, name: &str, if_exists: bool) -> Result<()> {
213        self.0.drop_property_constraint(name, if_exists)?;
214        Ok(())
215    }
216
217    fn list_property_constraints(&self) -> Result<Vec<PropertyConstraintSpec>> {
218        Ok(self.0.list_property_constraints())
219    }
220}