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.to_string(), 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.to_string(), 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(anyhow::Error::from)?;
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(anyhow::Error::from)?;
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| {
212            store
213                .try_create_node(labels, properties)
214                .ok_or_else(|| anyhow!("failed to create node because storage is exhausted"))
215        })
216        .map_err(LoraError::from_anyhow)
217    }
218
219    pub fn graph_create_relationship(
220        &self,
221        src: NodeId,
222        dst: NodeId,
223        rel_type: &str,
224        properties: BTreeMap<String, LoraValue>,
225    ) -> Result<Option<RelationshipRecord>, LoraError> {
226        let properties = values_to_properties(properties).map_err(LoraError::from_anyhow)?;
227        self.with_logged_store_mut(|store| {
228            Ok(store.create_relationship(src, dst, rel_type, properties))
229        })
230        .map_err(LoraError::from_anyhow)
231    }
232
233    pub fn graph_set_node_property(
234        &self,
235        node_id: NodeId,
236        key: String,
237        value: LoraValue,
238    ) -> Result<bool, LoraError> {
239        let value = lora_value_to_property(value).map_err(anyhow::Error::from)?;
240        self.with_logged_store_mut(|store| Ok(store.set_node_property(node_id, key, value)))
241            .map_err(LoraError::from_anyhow)
242    }
243
244    pub fn graph_remove_node_property(
245        &self,
246        node_id: NodeId,
247        key: &str,
248    ) -> Result<bool, LoraError> {
249        self.with_logged_store_mut(|store| Ok(store.remove_node_property(node_id, key)))
250            .map_err(LoraError::from_anyhow)
251    }
252
253    pub fn graph_add_node_label(&self, node_id: NodeId, label: &str) -> Result<bool, LoraError> {
254        self.with_logged_store_mut(|store| Ok(store.add_node_label(node_id, label)))
255            .map_err(LoraError::from_anyhow)
256    }
257
258    pub fn graph_remove_node_label(&self, node_id: NodeId, label: &str) -> Result<bool, LoraError> {
259        self.with_logged_store_mut(|store| Ok(store.remove_node_label(node_id, label)))
260            .map_err(LoraError::from_anyhow)
261    }
262
263    pub fn graph_set_node_labels(
264        &self,
265        node_id: NodeId,
266        labels: Vec<String>,
267    ) -> Result<bool, LoraError> {
268        self.with_logged_store_mut(|store| Ok(store.set_node_labels(node_id, labels)))
269            .map_err(LoraError::from_anyhow)
270    }
271
272    pub fn graph_replace_node_properties(
273        &self,
274        node_id: NodeId,
275        properties: BTreeMap<String, LoraValue>,
276    ) -> Result<bool, LoraError> {
277        let properties = values_to_properties(properties).map_err(LoraError::from_anyhow)?;
278        self.with_logged_store_mut(|store| Ok(store.replace_node_properties(node_id, properties)))
279            .map_err(LoraError::from_anyhow)
280    }
281
282    pub fn graph_merge_node_properties(
283        &self,
284        node_id: NodeId,
285        properties: BTreeMap<String, LoraValue>,
286    ) -> Result<bool, LoraError> {
287        let properties = values_to_properties(properties).map_err(LoraError::from_anyhow)?;
288        self.with_logged_store_mut(|store| Ok(store.merge_node_properties(node_id, properties)))
289            .map_err(LoraError::from_anyhow)
290    }
291
292    pub fn graph_set_relationship_property(
293        &self,
294        rel_id: RelationshipId,
295        key: String,
296        value: LoraValue,
297    ) -> Result<bool, LoraError> {
298        let value = lora_value_to_property(value).map_err(anyhow::Error::from)?;
299        self.with_logged_store_mut(|store| Ok(store.set_relationship_property(rel_id, key, value)))
300            .map_err(LoraError::from_anyhow)
301    }
302
303    pub fn graph_remove_relationship_property(
304        &self,
305        rel_id: RelationshipId,
306        key: &str,
307    ) -> Result<bool, LoraError> {
308        self.with_logged_store_mut(|store| Ok(store.remove_relationship_property(rel_id, key)))
309            .map_err(LoraError::from_anyhow)
310    }
311
312    pub fn graph_replace_relationship_properties(
313        &self,
314        rel_id: RelationshipId,
315        properties: BTreeMap<String, LoraValue>,
316    ) -> Result<bool, LoraError> {
317        let properties = values_to_properties(properties).map_err(LoraError::from_anyhow)?;
318        self.with_logged_store_mut(|store| {
319            Ok(store.replace_relationship_properties(rel_id, properties))
320        })
321        .map_err(LoraError::from_anyhow)
322    }
323
324    pub fn graph_merge_relationship_properties(
325        &self,
326        rel_id: RelationshipId,
327        properties: BTreeMap<String, LoraValue>,
328    ) -> Result<bool, LoraError> {
329        let properties = values_to_properties(properties).map_err(LoraError::from_anyhow)?;
330        self.with_logged_store_mut(|store| {
331            Ok(store.merge_relationship_properties(rel_id, properties))
332        })
333        .map_err(LoraError::from_anyhow)
334    }
335
336    pub fn graph_delete_relationship(&self, rel_id: RelationshipId) -> Result<bool, LoraError> {
337        self.with_logged_store_mut(|store| Ok(store.delete_relationship(rel_id)))
338            .map_err(LoraError::from_anyhow)
339    }
340
341    pub fn graph_delete_node(&self, node_id: NodeId) -> Result<bool, LoraError> {
342        self.with_logged_store_mut(|store| Ok(store.delete_node(node_id)))
343            .map_err(LoraError::from_anyhow)
344    }
345
346    pub fn graph_detach_delete_node(&self, node_id: NodeId) -> Result<bool, LoraError> {
347        self.with_logged_store_mut(|store| Ok(store.detach_delete_node(node_id)))
348            .map_err(LoraError::from_anyhow)
349    }
350}