Skip to main content

lora_store/
graph.rs

1use crate::spatial::LoraPoint;
2use crate::temporal::{
3    LoraDate, LoraDateTime, LoraDuration, LoraLocalDateTime, LoraLocalTime, LoraTime,
4};
5use lora_ast::Direction;
6use std::collections::{BTreeMap, BTreeSet};
7
8pub type NodeId = u64;
9pub type RelationshipId = u64;
10
11#[derive(Debug, Clone, PartialEq)]
12pub enum PropertyValue {
13    Null,
14    Bool(bool),
15    Int(i64),
16    Float(f64),
17    String(String),
18    List(Vec<PropertyValue>),
19    Map(BTreeMap<String, PropertyValue>),
20    Date(LoraDate),
21    Time(LoraTime),
22    LocalTime(LoraLocalTime),
23    DateTime(LoraDateTime),
24    LocalDateTime(LoraLocalDateTime),
25    Duration(LoraDuration),
26    Point(LoraPoint),
27}
28
29pub type Properties = BTreeMap<String, PropertyValue>;
30
31#[derive(Debug, Clone, PartialEq)]
32pub struct NodeRecord {
33    pub id: NodeId,
34    pub labels: Vec<String>,
35    pub properties: Properties,
36}
37
38impl NodeRecord {
39    pub fn has_label(&self, label: &str) -> bool {
40        self.labels.iter().any(|l| l == label)
41    }
42
43    pub fn property(&self, key: &str) -> Option<&PropertyValue> {
44        self.properties.get(key)
45    }
46}
47
48#[derive(Debug, Clone, PartialEq)]
49pub struct RelationshipRecord {
50    pub id: RelationshipId,
51    pub src: NodeId,
52    pub dst: NodeId,
53    pub rel_type: String,
54    pub properties: Properties,
55}
56
57impl RelationshipRecord {
58    pub fn property(&self, key: &str) -> Option<&PropertyValue> {
59        self.properties.get(key)
60    }
61
62    pub fn other_node(&self, node_id: NodeId) -> Option<NodeId> {
63        if self.src == node_id {
64            Some(self.dst)
65        } else if self.dst == node_id {
66            Some(self.src)
67        } else {
68            None
69        }
70    }
71
72    pub fn matches_direction_from(&self, node_id: NodeId, direction: Direction) -> bool {
73        match direction {
74            Direction::Right => self.src == node_id,
75            Direction::Left => self.dst == node_id,
76            Direction::Undirected => self.src == node_id || self.dst == node_id,
77        }
78    }
79}
80
81#[derive(Debug, Clone, PartialEq)]
82pub struct ExpandedRelationship {
83    pub relationship: RelationshipRecord,
84    pub other_node: NodeRecord,
85}
86
87pub trait GraphStorage {
88    // ---------- Node scans / lookup ----------
89
90    fn all_nodes(&self) -> Vec<NodeRecord>;
91    fn nodes_by_label(&self, label: &str) -> Vec<NodeRecord>;
92
93    /// Borrow-based lookup. Required primitive; `node()` defaults to
94    /// `node_ref(id).cloned()` so every implementation only has to supply the
95    /// borrow variant.
96    fn node_ref(&self, id: NodeId) -> Option<&NodeRecord>;
97
98    fn node(&self, id: NodeId) -> Option<NodeRecord> {
99        self.node_ref(id).cloned()
100    }
101
102    fn has_node(&self, id: NodeId) -> bool {
103        self.node_ref(id).is_some()
104    }
105
106    fn node_count(&self) -> usize {
107        self.all_node_ids().len()
108    }
109
110    /// ID-only scan. Default falls back to cloning; implementations should
111    /// override for O(nodes) without cloning records/properties.
112    fn all_node_ids(&self) -> Vec<NodeId> {
113        self.all_nodes().into_iter().map(|n| n.id).collect()
114    }
115
116    /// Index lookup returning only IDs. Default falls back to cloning.
117    fn node_ids_by_label(&self, label: &str) -> Vec<NodeId> {
118        self.nodes_by_label(label)
119            .into_iter()
120            .map(|n| n.id)
121            .collect()
122    }
123
124    // ---------- Relationship scans / lookup ----------
125
126    fn all_relationships(&self) -> Vec<RelationshipRecord>;
127    fn relationships_by_type(&self, rel_type: &str) -> Vec<RelationshipRecord>;
128
129    fn relationship_ref(&self, id: RelationshipId) -> Option<&RelationshipRecord>;
130
131    fn relationship(&self, id: RelationshipId) -> Option<RelationshipRecord> {
132        self.relationship_ref(id).cloned()
133    }
134
135    fn has_relationship(&self, id: RelationshipId) -> bool {
136        self.relationship_ref(id).is_some()
137    }
138
139    fn relationship_count(&self) -> usize {
140        self.all_rel_ids().len()
141    }
142
143    fn all_rel_ids(&self) -> Vec<RelationshipId> {
144        self.all_relationships().into_iter().map(|r| r.id).collect()
145    }
146
147    fn rel_ids_by_type(&self, rel_type: &str) -> Vec<RelationshipId> {
148        self.relationships_by_type(rel_type)
149            .into_iter()
150            .map(|r| r.id)
151            .collect()
152    }
153
154    // ---------- Schema / introspection ----------
155
156    fn all_labels(&self) -> Vec<String> {
157        let mut labels = BTreeSet::new();
158        for node in self.all_nodes() {
159            for label in node.labels {
160                labels.insert(label);
161            }
162        }
163        labels.into_iter().collect()
164    }
165
166    fn all_relationship_types(&self) -> Vec<String> {
167        let mut types = BTreeSet::new();
168        for rel in self.all_relationships() {
169            types.insert(rel.rel_type);
170        }
171        types.into_iter().collect()
172    }
173
174    fn all_node_property_keys(&self) -> Vec<String> {
175        let mut keys = BTreeSet::new();
176        for node in self.all_nodes() {
177            for key in node.properties.keys() {
178                keys.insert(key.clone());
179            }
180        }
181        keys.into_iter().collect()
182    }
183
184    fn all_relationship_property_keys(&self) -> Vec<String> {
185        let mut keys = BTreeSet::new();
186        for rel in self.all_relationships() {
187            for key in rel.properties.keys() {
188                keys.insert(key.clone());
189            }
190        }
191        keys.into_iter().collect()
192    }
193
194    fn all_property_keys(&self) -> Vec<String> {
195        let mut keys = BTreeSet::new();
196
197        for key in self.all_node_property_keys() {
198            keys.insert(key);
199        }
200
201        for key in self.all_relationship_property_keys() {
202            keys.insert(key);
203        }
204
205        keys.into_iter().collect()
206    }
207
208    fn label_property_keys(&self, label: &str) -> Vec<String> {
209        let mut keys = BTreeSet::new();
210        for node in self.nodes_by_label(label) {
211            for key in node.properties.keys() {
212                keys.insert(key.clone());
213            }
214        }
215        keys.into_iter().collect()
216    }
217
218    fn rel_type_property_keys(&self, rel_type: &str) -> Vec<String> {
219        let mut keys = BTreeSet::new();
220        for rel in self.relationships_by_type(rel_type) {
221            for key in rel.properties.keys() {
222                keys.insert(key.clone());
223            }
224        }
225        keys.into_iter().collect()
226    }
227
228    fn has_label_name(&self, label: &str) -> bool {
229        self.all_labels().iter().any(|l| l == label)
230    }
231
232    fn has_relationship_type_name(&self, rel_type: &str) -> bool {
233        self.all_relationship_types().iter().any(|t| t == rel_type)
234    }
235
236    fn has_property_key(&self, key: &str) -> bool {
237        self.all_property_keys().iter().any(|k| k == key)
238    }
239
240    fn label_has_property_key(&self, label: &str, key: &str) -> bool {
241        self.nodes_by_label(label)
242            .into_iter()
243            .any(|n| n.properties.contains_key(key))
244    }
245
246    fn rel_type_has_property_key(&self, rel_type: &str, key: &str) -> bool {
247        self.relationships_by_type(rel_type)
248            .into_iter()
249            .any(|r| r.properties.contains_key(key))
250    }
251
252    // ---------- Property helpers ----------
253
254    fn node_has_label(&self, node_id: NodeId, label: &str) -> bool {
255        self.node_ref(node_id)
256            .map(|n| n.labels.iter().any(|l| l == label))
257            .unwrap_or(false)
258    }
259
260    fn node_labels(&self, node_id: NodeId) -> Option<Vec<String>> {
261        self.node_ref(node_id).map(|n| n.labels.clone())
262    }
263
264    fn node_properties(&self, node_id: NodeId) -> Option<Properties> {
265        self.node_ref(node_id).map(|n| n.properties.clone())
266    }
267
268    fn node_property(&self, node_id: NodeId, key: &str) -> Option<PropertyValue> {
269        self.node_ref(node_id)
270            .and_then(|n| n.properties.get(key).cloned())
271    }
272
273    fn relationship_type(&self, rel_id: RelationshipId) -> Option<String> {
274        self.relationship_ref(rel_id).map(|r| r.rel_type.clone())
275    }
276
277    fn relationship_properties(&self, rel_id: RelationshipId) -> Option<Properties> {
278        self.relationship_ref(rel_id).map(|r| r.properties.clone())
279    }
280
281    fn relationship_property(&self, rel_id: RelationshipId, key: &str) -> Option<PropertyValue> {
282        self.relationship_ref(rel_id)
283            .and_then(|r| r.properties.get(key).cloned())
284    }
285
286    // ---------- Relationship endpoint helpers ----------
287
288    fn relationship_endpoints(&self, rel_id: RelationshipId) -> Option<(NodeId, NodeId)> {
289        self.relationship_ref(rel_id).map(|r| (r.src, r.dst))
290    }
291
292    fn relationship_source(&self, rel_id: RelationshipId) -> Option<NodeId> {
293        self.relationship_ref(rel_id).map(|r| r.src)
294    }
295
296    fn relationship_target(&self, rel_id: RelationshipId) -> Option<NodeId> {
297        self.relationship_ref(rel_id).map(|r| r.dst)
298    }
299
300    fn other_node(&self, rel_id: RelationshipId, node_id: NodeId) -> Option<NodeId> {
301        self.relationship_ref(rel_id)
302            .and_then(|r| r.other_node(node_id))
303    }
304
305    // ---------- Traversal ----------
306
307    fn outgoing_relationships(&self, node_id: NodeId) -> Vec<RelationshipRecord>;
308    fn incoming_relationships(&self, node_id: NodeId) -> Vec<RelationshipRecord>;
309
310    fn relationships_of(&self, node_id: NodeId, direction: Direction) -> Vec<RelationshipRecord> {
311        match direction {
312            Direction::Right => self.outgoing_relationships(node_id),
313            Direction::Left => self.incoming_relationships(node_id),
314            Direction::Undirected => {
315                let mut rels = self.outgoing_relationships(node_id);
316                rels.extend(self.incoming_relationships(node_id));
317                rels
318            }
319        }
320    }
321
322    /// ID-only variant of `relationships_of`. Default uses `expand_ids` so
323    /// implementations without adjacency overrides still avoid record clones.
324    fn relationship_ids_of(&self, node_id: NodeId, direction: Direction) -> Vec<RelationshipId> {
325        self.expand_ids(node_id, direction, &[])
326            .into_iter()
327            .map(|(rel_id, _)| rel_id)
328            .collect()
329    }
330
331    fn expand(
332        &self,
333        node_id: NodeId,
334        direction: Direction,
335        types: &[String],
336    ) -> Vec<(RelationshipRecord, NodeRecord)> {
337        let rels = self.relationships_of(node_id, direction);
338
339        rels.into_iter()
340            .filter(|r| types.is_empty() || types.iter().any(|t| t == &r.rel_type))
341            .filter_map(|r| {
342                let other_id = r.other_node(node_id)?;
343                let other = self.node(other_id)?;
344                Some((r, other))
345            })
346            .collect()
347    }
348
349    /// Lightweight traversal used on hot paths: only returns `(RelationshipId, NodeId)`
350    /// pairs, avoiding the record + property-map clones of `expand()`.
351    ///
352    /// Callers that need rel/node records can look them up with
353    /// `relationship_ref` / `node_ref`.
354    fn expand_ids(
355        &self,
356        node_id: NodeId,
357        direction: Direction,
358        types: &[String],
359    ) -> Vec<(RelationshipId, NodeId)> {
360        self.expand(node_id, direction, types)
361            .into_iter()
362            .map(|(r, n)| (r.id, n.id))
363            .collect()
364    }
365
366    fn expand_detailed(
367        &self,
368        node_id: NodeId,
369        direction: Direction,
370        types: &[String],
371    ) -> Vec<ExpandedRelationship> {
372        self.expand(node_id, direction, types)
373            .into_iter()
374            .map(|(relationship, other_node)| ExpandedRelationship {
375                relationship,
376                other_node,
377            })
378            .collect()
379    }
380
381    fn neighbors(
382        &self,
383        node_id: NodeId,
384        direction: Direction,
385        types: &[String],
386    ) -> Vec<NodeRecord> {
387        self.expand(node_id, direction, types)
388            .into_iter()
389            .map(|(_, node)| node)
390            .collect()
391    }
392
393    fn degree(&self, node_id: NodeId, direction: Direction) -> usize {
394        match direction {
395            Direction::Left => self.incoming_relationships(node_id).len(),
396            Direction::Right => self.outgoing_relationships(node_id).len(),
397            Direction::Undirected => {
398                self.outgoing_relationships(node_id).len()
399                    + self.incoming_relationships(node_id).len()
400            }
401        }
402    }
403
404    fn is_isolated(&self, node_id: NodeId) -> bool {
405        self.degree(node_id, Direction::Undirected) == 0
406    }
407
408    // ---------- Optional optimization hooks ----------
409
410    fn find_nodes_by_property(
411        &self,
412        label: Option<&str>,
413        key: &str,
414        value: &PropertyValue,
415    ) -> Vec<NodeRecord> {
416        let ids = match label {
417            Some(label) => self.node_ids_by_label(label),
418            None => self.all_node_ids(),
419        };
420
421        ids.into_iter()
422            .filter_map(|id| {
423                let n = self.node_ref(id)?;
424                if n.properties.get(key) == Some(value) {
425                    Some(n.clone())
426                } else {
427                    None
428                }
429            })
430            .collect()
431    }
432
433    fn find_relationships_by_property(
434        &self,
435        rel_type: Option<&str>,
436        key: &str,
437        value: &PropertyValue,
438    ) -> Vec<RelationshipRecord> {
439        let ids = match rel_type {
440            Some(rel_type) => self.rel_ids_by_type(rel_type),
441            None => self.all_rel_ids(),
442        };
443
444        ids.into_iter()
445            .filter_map(|id| {
446                let r = self.relationship_ref(id)?;
447                if r.properties.get(key) == Some(value) {
448                    Some(r.clone())
449                } else {
450                    None
451                }
452            })
453            .collect()
454    }
455
456    fn node_exists_with_label_and_property(
457        &self,
458        label: &str,
459        key: &str,
460        value: &PropertyValue,
461    ) -> bool {
462        !self
463            .find_nodes_by_property(Some(label), key, value)
464            .is_empty()
465    }
466
467    fn relationship_exists_with_type_and_property(
468        &self,
469        rel_type: &str,
470        key: &str,
471        value: &PropertyValue,
472    ) -> bool {
473        !self
474            .find_relationships_by_property(Some(rel_type), key, value)
475            .is_empty()
476    }
477}
478
479pub trait GraphStorageMut: GraphStorage {
480    // ---------- Creation ----------
481
482    fn create_node(&mut self, labels: Vec<String>, properties: Properties) -> NodeRecord;
483
484    fn create_relationship(
485        &mut self,
486        src: NodeId,
487        dst: NodeId,
488        rel_type: &str,
489        properties: Properties,
490    ) -> Option<RelationshipRecord>;
491
492    // ---------- Node mutation ----------
493
494    fn set_node_property(&mut self, node_id: NodeId, key: String, value: PropertyValue) -> bool;
495
496    fn remove_node_property(&mut self, node_id: NodeId, key: &str) -> bool;
497
498    fn replace_node_properties(&mut self, node_id: NodeId, properties: Properties) -> bool {
499        if !self.has_node(node_id) {
500            return false;
501        }
502
503        let existing_keys = match self.node_properties(node_id) {
504            Some(props) => props.into_keys().collect::<Vec<_>>(),
505            None => return false,
506        };
507
508        for key in existing_keys {
509            self.remove_node_property(node_id, &key);
510        }
511
512        for (k, v) in properties {
513            self.set_node_property(node_id, k, v);
514        }
515
516        true
517    }
518
519    fn merge_node_properties(&mut self, node_id: NodeId, properties: Properties) -> bool {
520        if !self.has_node(node_id) {
521            return false;
522        }
523
524        for (k, v) in properties {
525            self.set_node_property(node_id, k, v);
526        }
527
528        true
529    }
530
531    fn add_node_label(&mut self, node_id: NodeId, label: &str) -> bool;
532    fn remove_node_label(&mut self, node_id: NodeId, label: &str) -> bool;
533
534    fn set_node_labels(&mut self, node_id: NodeId, labels: Vec<String>) -> bool {
535        if !self.has_node(node_id) {
536            return false;
537        }
538
539        let current = match self.node_labels(node_id) {
540            Some(labels) => labels,
541            None => return false,
542        };
543
544        for label in &current {
545            self.remove_node_label(node_id, label);
546        }
547
548        for label in &labels {
549            self.add_node_label(node_id, label);
550        }
551
552        true
553    }
554
555    // ---------- Relationship mutation ----------
556
557    fn set_relationship_property(
558        &mut self,
559        rel_id: RelationshipId,
560        key: String,
561        value: PropertyValue,
562    ) -> bool;
563
564    fn remove_relationship_property(&mut self, rel_id: RelationshipId, key: &str) -> bool;
565
566    fn replace_relationship_properties(
567        &mut self,
568        rel_id: RelationshipId,
569        properties: Properties,
570    ) -> bool {
571        if !self.has_relationship(rel_id) {
572            return false;
573        }
574
575        let existing_keys = match self.relationship_properties(rel_id) {
576            Some(props) => props.into_keys().collect::<Vec<_>>(),
577            None => return false,
578        };
579
580        for key in existing_keys {
581            self.remove_relationship_property(rel_id, &key);
582        }
583
584        for (k, v) in properties {
585            self.set_relationship_property(rel_id, k, v);
586        }
587
588        true
589    }
590
591    fn merge_relationship_properties(
592        &mut self,
593        rel_id: RelationshipId,
594        properties: Properties,
595    ) -> bool {
596        if !self.has_relationship(rel_id) {
597            return false;
598        }
599
600        for (k, v) in properties {
601            self.set_relationship_property(rel_id, k, v);
602        }
603
604        true
605    }
606
607    // ---------- Deletion ----------
608
609    fn delete_relationship(&mut self, rel_id: RelationshipId) -> bool;
610
611    /// Returns false if the node still has attached relationships.
612    fn delete_node(&mut self, node_id: NodeId) -> bool;
613
614    /// Deletes the node and all attached relationships.
615    fn detach_delete_node(&mut self, node_id: NodeId) -> bool;
616
617    fn delete_relationships_of(&mut self, node_id: NodeId, direction: Direction) -> usize {
618        let rel_ids = self.relationship_ids_of(node_id, direction);
619
620        let mut deleted = 0;
621        for rel_id in rel_ids {
622            if self.delete_relationship(rel_id) {
623                deleted += 1;
624            }
625        }
626        deleted
627    }
628
629    // ---------- Convenience helpers ----------
630
631    fn get_or_create_node(
632        &mut self,
633        labels: Vec<String>,
634        match_key: &str,
635        match_value: &PropertyValue,
636        init_properties: Properties,
637    ) -> NodeRecord {
638        for label in &labels {
639            let matches = self.find_nodes_by_property(Some(label), match_key, match_value);
640            if let Some(node) = matches.into_iter().next() {
641                return node;
642            }
643        }
644
645        self.create_node(labels, init_properties)
646    }
647}