Skip to main content

lora_database/database/
graph_api.rs

1//! Direct graph read + mutation surface on [`Database`].
2//!
3//! These `graph_*` methods are the storage-agnostic façade language
4//! bindings reach for when they need a single record / list / mutation
5//! without compiling a Cypher query. Reads route through
6//! [`Database::with_store`] (lock-free snapshot); mutations route
7//! through [`Database::with_logged_store_mut`] so the WAL stays in
8//! step with the in-memory state.
9
10use std::any::Any;
11use std::collections::BTreeMap;
12
13use anyhow::{anyhow, Result};
14use lora_executor::{lora_value_to_property, LoraValue};
15use lora_store::{
16    GraphStorage, GraphStorageMut, NodeId, NodeRecord, RelationshipId, RelationshipRecord,
17};
18
19use crate::database::{values_to_properties, Database, GraphDirection};
20use crate::error::LoraError;
21
22impl<S> Database<S>
23where
24    S: GraphStorage + GraphStorageMut + Any + Clone + Send + Sync + 'static,
25{
26    // ---------- Direct graph read surface ----------
27
28    pub fn graph_contains_node(&self, id: NodeId) -> bool {
29        self.with_store(|store| store.contains_node(id))
30    }
31
32    pub fn graph_node(&self, id: NodeId) -> Option<NodeRecord> {
33        self.with_store(|store| store.node(id))
34    }
35
36    pub fn graph_all_node_ids(&self) -> Vec<NodeId> {
37        self.with_store(|store| store.all_node_ids())
38    }
39
40    pub fn graph_node_ids_by_label(&self, label: &str) -> Vec<NodeId> {
41        self.with_store(|store| store.node_ids_by_label(label))
42    }
43
44    pub fn graph_all_nodes(&self) -> Vec<NodeRecord> {
45        self.with_store(|store| store.all_nodes())
46    }
47
48    pub fn graph_nodes_by_label(&self, label: &str) -> Vec<NodeRecord> {
49        self.with_store(|store| store.nodes_by_label(label))
50    }
51
52    pub fn graph_node_has_label(&self, id: NodeId, label: &str) -> bool {
53        self.with_store(|store| store.node_has_label(id, label))
54    }
55
56    pub fn graph_node_labels(&self, id: NodeId) -> Option<Vec<String>> {
57        self.with_store(|store| store.node_labels(id))
58    }
59
60    pub fn graph_node_properties(&self, id: NodeId) -> Option<BTreeMap<String, LoraValue>> {
61        self.with_store(|store| {
62            store.node_properties(id).map(|props| {
63                props
64                    .into_iter()
65                    .map(|(key, value)| (key, LoraValue::from(value)))
66                    .collect()
67            })
68        })
69    }
70
71    pub fn graph_node_property(&self, id: NodeId, key: &str) -> Option<LoraValue> {
72        self.with_store(|store| store.node_property(id, key).map(LoraValue::from))
73    }
74
75    pub fn graph_contains_relationship(&self, id: RelationshipId) -> bool {
76        self.with_store(|store| store.contains_relationship(id))
77    }
78
79    pub fn graph_relationship(&self, id: RelationshipId) -> Option<RelationshipRecord> {
80        self.with_store(|store| store.relationship(id))
81    }
82
83    pub fn graph_all_relationship_ids(&self) -> Vec<RelationshipId> {
84        self.with_store(|store| store.all_rel_ids())
85    }
86
87    pub fn graph_relationship_ids_by_type(&self, rel_type: &str) -> Vec<RelationshipId> {
88        self.with_store(|store| store.rel_ids_by_type(rel_type))
89    }
90
91    pub fn graph_all_relationships(&self) -> Vec<RelationshipRecord> {
92        self.with_store(|store| store.all_relationships())
93    }
94
95    pub fn graph_relationships_by_type(&self, rel_type: &str) -> Vec<RelationshipRecord> {
96        self.with_store(|store| store.relationships_by_type(rel_type))
97    }
98
99    pub fn graph_relationship_endpoints(&self, id: RelationshipId) -> Option<(NodeId, NodeId)> {
100        self.with_store(|store| store.relationship_endpoints(id))
101    }
102
103    pub fn graph_relationship_type(&self, id: RelationshipId) -> Option<String> {
104        self.with_store(|store| store.relationship_type(id))
105    }
106
107    pub fn graph_relationship_properties(
108        &self,
109        id: RelationshipId,
110    ) -> Option<BTreeMap<String, LoraValue>> {
111        self.with_store(|store| {
112            store.relationship_properties(id).map(|props| {
113                props
114                    .into_iter()
115                    .map(|(key, value)| (key, LoraValue::from(value)))
116                    .collect()
117            })
118        })
119    }
120
121    pub fn graph_relationship_property(&self, id: RelationshipId, key: &str) -> Option<LoraValue> {
122        self.with_store(|store| store.relationship_property(id, key).map(LoraValue::from))
123    }
124
125    pub fn graph_relationship_ids_of(
126        &self,
127        node_id: NodeId,
128        direction: GraphDirection,
129    ) -> Vec<RelationshipId> {
130        self.with_store(|store| store.relationship_ids_of(node_id, direction.as_store_direction()))
131    }
132
133    pub fn graph_degree(&self, node_id: NodeId, direction: GraphDirection) -> usize {
134        self.with_store(|store| store.degree(node_id, direction.as_store_direction()))
135    }
136
137    pub fn graph_neighbors(
138        &self,
139        node_id: NodeId,
140        direction: GraphDirection,
141        types: &[String],
142    ) -> Vec<NodeRecord> {
143        self.with_store(|store| store.neighbors(node_id, direction.as_store_direction(), types))
144    }
145
146    pub fn graph_expand_ids(
147        &self,
148        node_id: NodeId,
149        direction: GraphDirection,
150        types: &[String],
151    ) -> Vec<(RelationshipId, NodeId)> {
152        self.with_store(|store| store.expand_ids(node_id, direction.as_store_direction(), types))
153    }
154
155    pub fn graph_all_labels(&self) -> Vec<String> {
156        self.with_store(|store| store.all_labels())
157    }
158
159    pub fn graph_all_relationship_types(&self) -> Vec<String> {
160        self.with_store(|store| store.all_relationship_types())
161    }
162
163    pub fn graph_all_property_keys(&self) -> Vec<String> {
164        self.with_store(|store| store.all_property_keys())
165    }
166
167    pub fn graph_all_node_property_keys(&self) -> Vec<String> {
168        self.with_store(|store| store.all_node_property_keys())
169    }
170
171    pub fn graph_all_relationship_property_keys(&self) -> Vec<String> {
172        self.with_store(|store| store.all_relationship_property_keys())
173    }
174
175    pub fn graph_label_property_keys(&self, label: &str) -> Vec<String> {
176        self.with_store(|store| store.label_property_keys(label))
177    }
178
179    pub fn graph_relationship_type_property_keys(&self, rel_type: &str) -> Vec<String> {
180        self.with_store(|store| store.rel_type_property_keys(rel_type))
181    }
182
183    pub fn graph_find_node_ids_by_property(
184        &self,
185        label: Option<&str>,
186        key: &str,
187        value: LoraValue,
188    ) -> Result<Vec<NodeId>> {
189        let value = lora_value_to_property(value).map_err(|e| anyhow!(e))?;
190        Ok(self.with_store(|store| store.find_node_ids_by_property(label, key, &value)))
191    }
192
193    pub fn graph_find_relationship_ids_by_property(
194        &self,
195        rel_type: Option<&str>,
196        key: &str,
197        value: LoraValue,
198    ) -> Result<Vec<RelationshipId>> {
199        let value = lora_value_to_property(value).map_err(|e| anyhow!(e))?;
200        Ok(self.with_store(|store| store.find_relationship_ids_by_property(rel_type, key, &value)))
201    }
202
203    // ---------- Direct graph mutation surface ----------
204
205    pub fn graph_create_node(
206        &self,
207        labels: Vec<String>,
208        properties: BTreeMap<String, LoraValue>,
209    ) -> Result<NodeRecord, LoraError> {
210        let properties = values_to_properties(properties).map_err(LoraError::from_anyhow)?;
211        self.with_logged_store_mut(|store| Ok(store.create_node(labels, properties)))
212            .map_err(LoraError::from_anyhow)
213    }
214
215    pub fn graph_create_relationship(
216        &self,
217        src: NodeId,
218        dst: NodeId,
219        rel_type: &str,
220        properties: BTreeMap<String, LoraValue>,
221    ) -> Result<Option<RelationshipRecord>, LoraError> {
222        let properties = values_to_properties(properties).map_err(LoraError::from_anyhow)?;
223        self.with_logged_store_mut(|store| {
224            Ok(store.create_relationship(src, dst, rel_type, properties))
225        })
226        .map_err(LoraError::from_anyhow)
227    }
228
229    pub fn graph_set_node_property(
230        &self,
231        node_id: NodeId,
232        key: String,
233        value: LoraValue,
234    ) -> Result<bool, LoraError> {
235        let value = lora_value_to_property(value).map_err(|e| anyhow!(e))?;
236        self.with_logged_store_mut(|store| Ok(store.set_node_property(node_id, key, value)))
237            .map_err(LoraError::from_anyhow)
238    }
239
240    pub fn graph_remove_node_property(
241        &self,
242        node_id: NodeId,
243        key: &str,
244    ) -> Result<bool, LoraError> {
245        self.with_logged_store_mut(|store| Ok(store.remove_node_property(node_id, key)))
246            .map_err(LoraError::from_anyhow)
247    }
248
249    pub fn graph_add_node_label(&self, node_id: NodeId, label: &str) -> Result<bool, LoraError> {
250        self.with_logged_store_mut(|store| Ok(store.add_node_label(node_id, label)))
251            .map_err(LoraError::from_anyhow)
252    }
253
254    pub fn graph_remove_node_label(&self, node_id: NodeId, label: &str) -> Result<bool, LoraError> {
255        self.with_logged_store_mut(|store| Ok(store.remove_node_label(node_id, label)))
256            .map_err(LoraError::from_anyhow)
257    }
258
259    pub fn graph_set_node_labels(
260        &self,
261        node_id: NodeId,
262        labels: Vec<String>,
263    ) -> Result<bool, LoraError> {
264        self.with_logged_store_mut(|store| Ok(store.set_node_labels(node_id, labels)))
265            .map_err(LoraError::from_anyhow)
266    }
267
268    pub fn graph_replace_node_properties(
269        &self,
270        node_id: NodeId,
271        properties: BTreeMap<String, LoraValue>,
272    ) -> Result<bool, LoraError> {
273        let properties = values_to_properties(properties).map_err(LoraError::from_anyhow)?;
274        self.with_logged_store_mut(|store| Ok(store.replace_node_properties(node_id, properties)))
275            .map_err(LoraError::from_anyhow)
276    }
277
278    pub fn graph_merge_node_properties(
279        &self,
280        node_id: NodeId,
281        properties: BTreeMap<String, LoraValue>,
282    ) -> Result<bool, LoraError> {
283        let properties = values_to_properties(properties).map_err(LoraError::from_anyhow)?;
284        self.with_logged_store_mut(|store| Ok(store.merge_node_properties(node_id, properties)))
285            .map_err(LoraError::from_anyhow)
286    }
287
288    pub fn graph_set_relationship_property(
289        &self,
290        rel_id: RelationshipId,
291        key: String,
292        value: LoraValue,
293    ) -> Result<bool, LoraError> {
294        let value = lora_value_to_property(value).map_err(|e| anyhow!(e))?;
295        self.with_logged_store_mut(|store| Ok(store.set_relationship_property(rel_id, key, value)))
296            .map_err(LoraError::from_anyhow)
297    }
298
299    pub fn graph_remove_relationship_property(
300        &self,
301        rel_id: RelationshipId,
302        key: &str,
303    ) -> Result<bool, LoraError> {
304        self.with_logged_store_mut(|store| Ok(store.remove_relationship_property(rel_id, key)))
305            .map_err(LoraError::from_anyhow)
306    }
307
308    pub fn graph_replace_relationship_properties(
309        &self,
310        rel_id: RelationshipId,
311        properties: BTreeMap<String, LoraValue>,
312    ) -> Result<bool, LoraError> {
313        let properties = values_to_properties(properties).map_err(LoraError::from_anyhow)?;
314        self.with_logged_store_mut(|store| {
315            Ok(store.replace_relationship_properties(rel_id, properties))
316        })
317        .map_err(LoraError::from_anyhow)
318    }
319
320    pub fn graph_merge_relationship_properties(
321        &self,
322        rel_id: RelationshipId,
323        properties: BTreeMap<String, LoraValue>,
324    ) -> Result<bool, LoraError> {
325        let properties = values_to_properties(properties).map_err(LoraError::from_anyhow)?;
326        self.with_logged_store_mut(|store| {
327            Ok(store.merge_relationship_properties(rel_id, properties))
328        })
329        .map_err(LoraError::from_anyhow)
330    }
331
332    pub fn graph_delete_relationship(&self, rel_id: RelationshipId) -> Result<bool, LoraError> {
333        self.with_logged_store_mut(|store| Ok(store.delete_relationship(rel_id)))
334            .map_err(LoraError::from_anyhow)
335    }
336
337    pub fn graph_delete_node(&self, node_id: NodeId) -> Result<bool, LoraError> {
338        self.with_logged_store_mut(|store| Ok(store.delete_node(node_id)))
339            .map_err(LoraError::from_anyhow)
340    }
341
342    pub fn graph_detach_delete_node(&self, node_id: NodeId) -> Result<bool, LoraError> {
343        self.with_logged_store_mut(|store| Ok(store.detach_delete_node(node_id)))
344            .map_err(LoraError::from_anyhow)
345    }
346}