Skip to main content

lora_store/
graph.rs

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