Skip to main content

lora_store/
graph.rs

1use crate::binary::LoraBinary;
2use crate::spatial::LoraPoint;
3use crate::temporal::{
4    LoraDate, LoraDateTime, LoraDuration, LoraLocalDateTime, LoraLocalTime, LoraTime,
5};
6use crate::vector::LoraVector;
7use lora_ast::Direction;
8use serde::{Deserialize, Serialize};
9use std::collections::{BTreeMap, BTreeSet};
10
11pub type NodeId = u64;
12pub type RelationshipId = u64;
13
14#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
15pub enum PropertyValue {
16    Null,
17    Bool(bool),
18    Int(i64),
19    Float(f64),
20    String(String),
21    Binary(LoraBinary),
22    List(Vec<PropertyValue>),
23    Map(BTreeMap<String, PropertyValue>),
24    Date(LoraDate),
25    Time(LoraTime),
26    LocalTime(LoraLocalTime),
27    DateTime(LoraDateTime),
28    LocalDateTime(LoraLocalDateTime),
29    Duration(LoraDuration),
30    Point(LoraPoint),
31    Vector(LoraVector),
32}
33
34pub type Properties = BTreeMap<String, PropertyValue>;
35
36#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
37pub struct NodeRecord {
38    pub id: NodeId,
39    pub labels: Vec<String>,
40    pub properties: Properties,
41}
42
43impl NodeRecord {
44    pub fn has_label(&self, label: &str) -> bool {
45        self.labels.iter().any(|l| l == label)
46    }
47
48    pub fn property(&self, key: &str) -> Option<&PropertyValue> {
49        self.properties.get(key)
50    }
51}
52
53#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
54pub struct RelationshipRecord {
55    pub id: RelationshipId,
56    pub src: NodeId,
57    pub dst: NodeId,
58    pub rel_type: String,
59    pub properties: Properties,
60}
61
62impl RelationshipRecord {
63    pub fn property(&self, key: &str) -> Option<&PropertyValue> {
64        self.properties.get(key)
65    }
66
67    pub fn other_node(&self, node_id: NodeId) -> Option<NodeId> {
68        if self.src == node_id {
69            Some(self.dst)
70        } else if self.dst == node_id {
71            Some(self.src)
72        } else {
73            None
74        }
75    }
76
77    pub fn matches_direction_from(&self, node_id: NodeId, direction: Direction) -> bool {
78        match direction {
79            Direction::Right => self.src == node_id,
80            Direction::Left => self.dst == node_id,
81            Direction::Undirected => self.src == node_id || self.dst == node_id,
82        }
83    }
84}
85
86#[derive(Debug, Clone, PartialEq)]
87pub struct ExpandedRelationship {
88    pub relationship: RelationshipRecord,
89    pub other_node: NodeRecord,
90}
91
92// ============================================================================
93// GraphStorage — the read-side storage contract
94//
95// The trait is intentionally layered into three groups: a small set of
96// backend-neutral required primitives, a pair of optional optimization hooks
97// (`with_node` / `with_relationship`), and a large cloud of defaulted helpers
98// that derive from the primitives.
99//
100// Adding a new backend means implementing the required primitives (roughly a
101// dozen methods) plus — optionally — overriding the hooks for zero-copy or the
102// record-scan helpers for bulk perf. Implementors SHOULD NOT need to rewrite
103// the catalog / traversal helper surface unless they can beat the default
104// composition.
105// ============================================================================
106
107pub trait GraphStorage {
108    // ---------- Required node primitives ----------
109
110    /// Cheap existence check. Should not clone or materialize the record.
111    fn contains_node(&self, id: NodeId) -> bool;
112
113    /// Point lookup returning an owned record. Backends that can hand out
114    /// borrows should also implement [`BorrowedGraphStorage::node_ref`] and
115    /// override [`with_node`] to avoid clones on the hot path.
116    fn node(&self, id: NodeId) -> Option<NodeRecord>;
117
118    /// Enumerate every node id. Should be O(nodes) without cloning records.
119    fn all_node_ids(&self) -> Vec<NodeId>;
120
121    /// Enumerate node ids carrying the given label. Implementations that keep
122    /// a label index should override this.
123    fn node_ids_by_label(&self, label: &str) -> Vec<NodeId>;
124
125    // ---------- Required relationship primitives ----------
126
127    fn contains_relationship(&self, id: RelationshipId) -> bool;
128
129    fn relationship(&self, id: RelationshipId) -> Option<RelationshipRecord>;
130
131    fn all_rel_ids(&self) -> Vec<RelationshipId>;
132
133    fn rel_ids_by_type(&self, rel_type: &str) -> Vec<RelationshipId>;
134
135    /// Endpoint pair `(src, dst)` for a relationship. Required because
136    /// traversal uses it on hot paths; a backend that stores endpoints
137    /// alongside the id index can answer this without fetching properties.
138    fn relationship_endpoints(&self, id: RelationshipId) -> Option<(NodeId, NodeId)>;
139
140    // ---------- Required traversal primitive ----------
141
142    /// Expand a node's incident relationships filtered by direction and
143    /// (optional) types. This is the single traversal primitive; variable-
144    /// length paths, degree, and adjacency helpers are all derived from it.
145    fn expand_ids(
146        &self,
147        node_id: NodeId,
148        direction: Direction,
149        types: &[String],
150    ) -> Vec<(RelationshipId, NodeId)>;
151
152    // ---------- Required catalog primitives ----------
153
154    fn all_labels(&self) -> Vec<String>;
155    fn all_relationship_types(&self) -> Vec<String>;
156
157    // ---------- Optional optimization hooks ----------
158    //
159    // Generic methods gated on `Self: Sized` so they don't affect object
160    // safety. Backends override these to supply borrow-based access on hot
161    // paths; defaults clone through `node` / `relationship`.
162
163    fn with_node<F, R>(&self, id: NodeId, f: F) -> Option<R>
164    where
165        F: FnOnce(&NodeRecord) -> R,
166        Self: Sized,
167    {
168        self.node(id).as_ref().map(f)
169    }
170
171    fn with_relationship<F, R>(&self, id: RelationshipId, f: F) -> Option<R>
172    where
173        F: FnOnce(&RelationshipRecord) -> R,
174        Self: Sized,
175    {
176        self.relationship(id).as_ref().map(f)
177    }
178
179    // ---------- Defaulted: counts / existence aliases ----------
180
181    fn has_node(&self, id: NodeId) -> bool {
182        self.contains_node(id)
183    }
184
185    fn has_relationship(&self, id: RelationshipId) -> bool {
186        self.contains_relationship(id)
187    }
188
189    fn node_count(&self) -> usize {
190        self.all_node_ids().len()
191    }
192
193    fn relationship_count(&self) -> usize {
194        self.all_rel_ids().len()
195    }
196
197    // ---------- Defaulted: record-returning scans ----------
198    //
199    // These synthesize full-record scans from id scans + point lookups. That
200    // is correct for any backend and fast enough for small graphs, but a
201    // backend that can scan records in one pass (in-memory via a BTreeMap
202    // `.values()`, a column store via a streaming read) should override.
203
204    fn all_nodes(&self) -> Vec<NodeRecord> {
205        self.all_node_ids()
206            .into_iter()
207            .filter_map(|id| self.node(id))
208            .collect()
209    }
210
211    fn nodes_by_label(&self, label: &str) -> Vec<NodeRecord> {
212        self.node_ids_by_label(label)
213            .into_iter()
214            .filter_map(|id| self.node(id))
215            .collect()
216    }
217
218    fn all_relationships(&self) -> Vec<RelationshipRecord> {
219        self.all_rel_ids()
220            .into_iter()
221            .filter_map(|id| self.relationship(id))
222            .collect()
223    }
224
225    fn relationships_by_type(&self, rel_type: &str) -> Vec<RelationshipRecord> {
226        self.rel_ids_by_type(rel_type)
227            .into_iter()
228            .filter_map(|id| self.relationship(id))
229            .collect()
230    }
231
232    // ---------- Defaulted: traversal helpers ----------
233
234    fn relationship_ids_of(&self, node_id: NodeId, direction: Direction) -> Vec<RelationshipId> {
235        self.expand_ids(node_id, direction, &[])
236            .into_iter()
237            .map(|(rel_id, _)| rel_id)
238            .collect()
239    }
240
241    fn outgoing_relationships(&self, node_id: NodeId) -> Vec<RelationshipRecord> {
242        self.relationship_ids_of(node_id, Direction::Right)
243            .into_iter()
244            .filter_map(|id| self.relationship(id))
245            .collect()
246    }
247
248    fn incoming_relationships(&self, node_id: NodeId) -> Vec<RelationshipRecord> {
249        self.relationship_ids_of(node_id, Direction::Left)
250            .into_iter()
251            .filter_map(|id| self.relationship(id))
252            .collect()
253    }
254
255    fn relationships_of(&self, node_id: NodeId, direction: Direction) -> Vec<RelationshipRecord> {
256        self.relationship_ids_of(node_id, direction)
257            .into_iter()
258            .filter_map(|id| self.relationship(id))
259            .collect()
260    }
261
262    fn degree(&self, node_id: NodeId, direction: Direction) -> usize {
263        self.expand_ids(node_id, direction, &[]).len()
264    }
265
266    fn is_isolated(&self, node_id: NodeId) -> bool {
267        self.degree(node_id, Direction::Undirected) == 0
268    }
269
270    fn expand(
271        &self,
272        node_id: NodeId,
273        direction: Direction,
274        types: &[String],
275    ) -> Vec<(RelationshipRecord, NodeRecord)> {
276        self.expand_ids(node_id, direction, types)
277            .into_iter()
278            .filter_map(|(rid, nid)| {
279                let rel = self.relationship(rid)?;
280                let node = self.node(nid)?;
281                Some((rel, node))
282            })
283            .collect()
284    }
285
286    fn expand_detailed(
287        &self,
288        node_id: NodeId,
289        direction: Direction,
290        types: &[String],
291    ) -> Vec<ExpandedRelationship> {
292        self.expand(node_id, direction, types)
293            .into_iter()
294            .map(|(relationship, other_node)| ExpandedRelationship {
295                relationship,
296                other_node,
297            })
298            .collect()
299    }
300
301    fn neighbors(
302        &self,
303        node_id: NodeId,
304        direction: Direction,
305        types: &[String],
306    ) -> Vec<NodeRecord> {
307        self.expand_ids(node_id, direction, types)
308            .into_iter()
309            .filter_map(|(_, nid)| self.node(nid))
310            .collect()
311    }
312
313    // ---------- Defaulted: narrow node accessors ----------
314
315    fn node_has_label(&self, node_id: NodeId, label: &str) -> bool
316    where
317        Self: Sized,
318    {
319        self.with_node(node_id, |n| n.labels.iter().any(|l| l == label))
320            .unwrap_or(false)
321    }
322
323    fn node_labels(&self, node_id: NodeId) -> Option<Vec<String>>
324    where
325        Self: Sized,
326    {
327        self.with_node(node_id, |n| n.labels.clone())
328    }
329
330    fn node_properties(&self, node_id: NodeId) -> Option<Properties>
331    where
332        Self: Sized,
333    {
334        self.with_node(node_id, |n| n.properties.clone())
335    }
336
337    fn node_property(&self, node_id: NodeId, key: &str) -> Option<PropertyValue>
338    where
339        Self: Sized,
340    {
341        self.with_node(node_id, |n| n.properties.get(key).cloned())
342            .flatten()
343    }
344
345    // ---------- Defaulted: narrow relationship accessors ----------
346
347    fn relationship_type(&self, rel_id: RelationshipId) -> Option<String>
348    where
349        Self: Sized,
350    {
351        self.with_relationship(rel_id, |r| r.rel_type.clone())
352    }
353
354    fn relationship_properties(&self, rel_id: RelationshipId) -> Option<Properties>
355    where
356        Self: Sized,
357    {
358        self.with_relationship(rel_id, |r| r.properties.clone())
359    }
360
361    fn relationship_property(&self, rel_id: RelationshipId, key: &str) -> Option<PropertyValue>
362    where
363        Self: Sized,
364    {
365        self.with_relationship(rel_id, |r| r.properties.get(key).cloned())
366            .flatten()
367    }
368
369    fn relationship_source(&self, rel_id: RelationshipId) -> Option<NodeId> {
370        self.relationship_endpoints(rel_id).map(|(s, _)| s)
371    }
372
373    fn relationship_target(&self, rel_id: RelationshipId) -> Option<NodeId> {
374        self.relationship_endpoints(rel_id).map(|(_, d)| d)
375    }
376
377    fn other_node(&self, rel_id: RelationshipId, node_id: NodeId) -> Option<NodeId> {
378        let (src, dst) = self.relationship_endpoints(rel_id)?;
379        if src == node_id {
380            Some(dst)
381        } else if dst == node_id {
382            Some(src)
383        } else {
384            None
385        }
386    }
387
388    // ---------- Defaulted: catalog helpers ----------
389
390    fn has_label_name(&self, label: &str) -> bool {
391        self.all_labels().iter().any(|l| l == label)
392    }
393
394    fn has_relationship_type_name(&self, rel_type: &str) -> bool {
395        self.all_relationship_types().iter().any(|t| t == rel_type)
396    }
397
398    fn all_node_property_keys(&self) -> Vec<String>
399    where
400        Self: Sized,
401    {
402        let mut keys = BTreeSet::new();
403        for id in self.all_node_ids() {
404            self.with_node(id, |n| {
405                for key in n.properties.keys() {
406                    keys.insert(key.clone());
407                }
408            });
409        }
410        keys.into_iter().collect()
411    }
412
413    fn all_relationship_property_keys(&self) -> Vec<String>
414    where
415        Self: Sized,
416    {
417        let mut keys = BTreeSet::new();
418        for id in self.all_rel_ids() {
419            self.with_relationship(id, |r| {
420                for key in r.properties.keys() {
421                    keys.insert(key.clone());
422                }
423            });
424        }
425        keys.into_iter().collect()
426    }
427
428    fn all_property_keys(&self) -> Vec<String>
429    where
430        Self: Sized,
431    {
432        let mut keys = BTreeSet::new();
433        for key in self.all_node_property_keys() {
434            keys.insert(key);
435        }
436        for key in self.all_relationship_property_keys() {
437            keys.insert(key);
438        }
439        keys.into_iter().collect()
440    }
441
442    fn has_property_key(&self, key: &str) -> bool
443    where
444        Self: Sized,
445    {
446        self.all_node_property_keys().iter().any(|k| k == key)
447            || self
448                .all_relationship_property_keys()
449                .iter()
450                .any(|k| k == key)
451    }
452
453    fn label_property_keys(&self, label: &str) -> Vec<String>
454    where
455        Self: Sized,
456    {
457        let mut keys = BTreeSet::new();
458        for id in self.node_ids_by_label(label) {
459            self.with_node(id, |n| {
460                for key in n.properties.keys() {
461                    keys.insert(key.clone());
462                }
463            });
464        }
465        keys.into_iter().collect()
466    }
467
468    fn rel_type_property_keys(&self, rel_type: &str) -> Vec<String>
469    where
470        Self: Sized,
471    {
472        let mut keys = BTreeSet::new();
473        for id in self.rel_ids_by_type(rel_type) {
474            self.with_relationship(id, |r| {
475                for key in r.properties.keys() {
476                    keys.insert(key.clone());
477                }
478            });
479        }
480        keys.into_iter().collect()
481    }
482
483    fn label_has_property_key(&self, label: &str, key: &str) -> bool
484    where
485        Self: Sized,
486    {
487        self.node_ids_by_label(label).into_iter().any(|id| {
488            self.with_node(id, |n| n.properties.contains_key(key))
489                .unwrap_or(false)
490        })
491    }
492
493    fn rel_type_has_property_key(&self, rel_type: &str, key: &str) -> bool
494    where
495        Self: Sized,
496    {
497        self.rel_ids_by_type(rel_type).into_iter().any(|id| {
498            self.with_relationship(id, |r| r.properties.contains_key(key))
499                .unwrap_or(false)
500        })
501    }
502
503    // ---------- Defaulted: property-filter lookups ----------
504
505    fn find_nodes_by_property(
506        &self,
507        label: Option<&str>,
508        key: &str,
509        value: &PropertyValue,
510    ) -> Vec<NodeRecord>
511    where
512        Self: Sized,
513    {
514        let ids = match label {
515            Some(label) => self.node_ids_by_label(label),
516            None => self.all_node_ids(),
517        };
518
519        ids.into_iter()
520            .filter_map(|id| {
521                let matches = self
522                    .with_node(id, |n| n.properties.get(key) == Some(value))
523                    .unwrap_or(false);
524                if matches {
525                    self.node(id)
526                } else {
527                    None
528                }
529            })
530            .collect()
531    }
532
533    fn find_node_ids_by_property(
534        &self,
535        label: Option<&str>,
536        key: &str,
537        value: &PropertyValue,
538    ) -> Vec<NodeId>
539    where
540        Self: Sized,
541    {
542        self.find_nodes_by_property(label, key, value)
543            .into_iter()
544            .map(|n| n.id)
545            .collect()
546    }
547
548    fn find_relationships_by_property(
549        &self,
550        rel_type: Option<&str>,
551        key: &str,
552        value: &PropertyValue,
553    ) -> Vec<RelationshipRecord>
554    where
555        Self: Sized,
556    {
557        let ids = match rel_type {
558            Some(rel_type) => self.rel_ids_by_type(rel_type),
559            None => self.all_rel_ids(),
560        };
561
562        ids.into_iter()
563            .filter_map(|id| {
564                let matches = self
565                    .with_relationship(id, |r| r.properties.get(key) == Some(value))
566                    .unwrap_or(false);
567                if matches {
568                    self.relationship(id)
569                } else {
570                    None
571                }
572            })
573            .collect()
574    }
575
576    fn find_relationship_ids_by_property(
577        &self,
578        rel_type: Option<&str>,
579        key: &str,
580        value: &PropertyValue,
581    ) -> Vec<RelationshipId>
582    where
583        Self: Sized,
584    {
585        self.find_relationships_by_property(rel_type, key, value)
586            .into_iter()
587            .map(|r| r.id)
588            .collect()
589    }
590
591    fn node_exists_with_label_and_property(
592        &self,
593        label: &str,
594        key: &str,
595        value: &PropertyValue,
596    ) -> bool
597    where
598        Self: Sized,
599    {
600        self.node_ids_by_label(label).into_iter().any(|id| {
601            self.with_node(id, |n| n.properties.get(key) == Some(value))
602                .unwrap_or(false)
603        })
604    }
605
606    fn relationship_exists_with_type_and_property(
607        &self,
608        rel_type: &str,
609        key: &str,
610        value: &PropertyValue,
611    ) -> bool
612    where
613        Self: Sized,
614    {
615        self.rel_ids_by_type(rel_type).into_iter().any(|id| {
616            self.with_relationship(id, |r| r.properties.get(key) == Some(value))
617                .unwrap_or(false)
618        })
619    }
620}
621
622// ============================================================================
623// GraphCatalog — narrow schema-query slice used by the analyzer.
624//
625// Blanket-implemented for every `GraphStorage`, so the analyzer can bound on
626// `GraphCatalog` without every backend having to implement a second trait.
627// ============================================================================
628
629pub trait GraphCatalog {
630    fn node_count(&self) -> usize;
631    fn relationship_count(&self) -> usize;
632    fn has_label_name(&self, label: &str) -> bool;
633    fn has_relationship_type_name(&self, rel_type: &str) -> bool;
634    fn has_property_key(&self, key: &str) -> bool;
635}
636
637impl<T: GraphStorage> GraphCatalog for T {
638    fn node_count(&self) -> usize {
639        GraphStorage::node_count(self)
640    }
641    fn relationship_count(&self) -> usize {
642        GraphStorage::relationship_count(self)
643    }
644    fn has_label_name(&self, label: &str) -> bool {
645        GraphStorage::has_label_name(self, label)
646    }
647    fn has_relationship_type_name(&self, rel_type: &str) -> bool {
648        GraphStorage::has_relationship_type_name(self, rel_type)
649    }
650    fn has_property_key(&self, key: &str) -> bool {
651        GraphStorage::has_property_key(self, key)
652    }
653}
654
655// ============================================================================
656// BorrowedGraphStorage — optional capability for backends that can hand out
657// long-lived borrows into internal records.
658//
659// The executor prefers `with_node` / `with_relationship` on hot paths because
660// they work for both borrow-capable and owned-only backends. This trait is
661// available for callers that really do want a `&NodeRecord` outliving the
662// closure — mostly internal optimization paths and tests.
663// ============================================================================
664
665pub trait BorrowedGraphStorage: GraphStorage {
666    fn node_ref(&self, id: NodeId) -> Option<&NodeRecord>;
667    fn relationship_ref(&self, id: RelationshipId) -> Option<&RelationshipRecord>;
668
669    fn node_refs(&self) -> Box<dyn Iterator<Item = &NodeRecord> + '_> {
670        Box::new(
671            self.all_node_ids()
672                .into_iter()
673                .filter_map(|id| self.node_ref(id)),
674        )
675    }
676
677    fn node_refs_by_label(&self, label: &str) -> Box<dyn Iterator<Item = &NodeRecord> + '_> {
678        Box::new(
679            self.node_ids_by_label(label)
680                .into_iter()
681                .filter_map(|id| self.node_ref(id)),
682        )
683    }
684
685    fn relationship_refs(&self) -> Box<dyn Iterator<Item = &RelationshipRecord> + '_> {
686        Box::new(
687            self.all_rel_ids()
688                .into_iter()
689                .filter_map(|id| self.relationship_ref(id)),
690        )
691    }
692
693    fn relationship_refs_by_type(
694        &self,
695        rel_type: &str,
696    ) -> Box<dyn Iterator<Item = &RelationshipRecord> + '_> {
697        Box::new(
698            self.rel_ids_by_type(rel_type)
699                .into_iter()
700                .filter_map(|id| self.relationship_ref(id)),
701        )
702    }
703}
704
705// ============================================================================
706// GraphStorageMut — write-side storage contract.
707//
708// A backend that implements `GraphStorage` can additionally implement
709// `GraphStorageMut` to support create / mutate / delete / admin operations.
710// Everything above the `Defaulted convenience helpers` block is a required
711// primitive; everything below is defaulted and can be overridden for perf.
712// ============================================================================
713
714pub trait GraphStorageMut: GraphStorage {
715    // ---------- Creation ----------
716
717    fn create_node(&mut self, labels: Vec<String>, properties: Properties) -> NodeRecord;
718
719    fn create_relationship(
720        &mut self,
721        src: NodeId,
722        dst: NodeId,
723        rel_type: &str,
724        properties: Properties,
725    ) -> Option<RelationshipRecord>;
726
727    // ---------- Node mutation ----------
728
729    fn set_node_property(&mut self, node_id: NodeId, key: String, value: PropertyValue) -> bool;
730
731    fn remove_node_property(&mut self, node_id: NodeId, key: &str) -> bool;
732
733    fn add_node_label(&mut self, node_id: NodeId, label: &str) -> bool;
734    fn remove_node_label(&mut self, node_id: NodeId, label: &str) -> bool;
735
736    // ---------- Relationship mutation ----------
737
738    fn set_relationship_property(
739        &mut self,
740        rel_id: RelationshipId,
741        key: String,
742        value: PropertyValue,
743    ) -> bool;
744
745    fn remove_relationship_property(&mut self, rel_id: RelationshipId, key: &str) -> bool;
746
747    // ---------- Deletion ----------
748
749    fn delete_relationship(&mut self, rel_id: RelationshipId) -> bool;
750
751    /// Returns false if the node still has attached relationships.
752    fn delete_node(&mut self, node_id: NodeId) -> bool;
753
754    /// Deletes the node and all attached relationships.
755    fn detach_delete_node(&mut self, node_id: NodeId) -> bool;
756
757    // ---------- Admin / lifecycle ----------
758
759    /// Drop every node and every relationship, returning the store to an
760    /// empty state. Provided as a trait method so callers (bindings, admin
761    /// tools) can reset a graph without knowing the concrete backend.
762    ///
763    /// Future snapshot / WAL / restore entry points will also hang off the
764    /// `GraphStorageMut` surface — `clear` is the first of them.
765    fn clear(&mut self);
766
767    // ---------- Defaulted convenience helpers ----------
768
769    fn replace_node_properties(&mut self, node_id: NodeId, properties: Properties) -> bool
770    where
771        Self: Sized,
772    {
773        if !self.contains_node(node_id) {
774            return false;
775        }
776
777        let existing_keys = match self.node_properties(node_id) {
778            Some(props) => props.into_keys().collect::<Vec<_>>(),
779            None => return false,
780        };
781
782        for key in existing_keys {
783            self.remove_node_property(node_id, &key);
784        }
785
786        for (k, v) in properties {
787            self.set_node_property(node_id, k, v);
788        }
789
790        true
791    }
792
793    fn merge_node_properties(&mut self, node_id: NodeId, properties: Properties) -> bool {
794        if !self.contains_node(node_id) {
795            return false;
796        }
797
798        for (k, v) in properties {
799            self.set_node_property(node_id, k, v);
800        }
801
802        true
803    }
804
805    fn set_node_labels(&mut self, node_id: NodeId, labels: Vec<String>) -> bool
806    where
807        Self: Sized,
808    {
809        if !self.contains_node(node_id) {
810            return false;
811        }
812
813        let current = match self.node_labels(node_id) {
814            Some(labels) => labels,
815            None => return false,
816        };
817
818        for label in &current {
819            self.remove_node_label(node_id, label);
820        }
821
822        for label in &labels {
823            self.add_node_label(node_id, label);
824        }
825
826        true
827    }
828
829    fn replace_relationship_properties(
830        &mut self,
831        rel_id: RelationshipId,
832        properties: Properties,
833    ) -> bool
834    where
835        Self: Sized,
836    {
837        if !self.contains_relationship(rel_id) {
838            return false;
839        }
840
841        let existing_keys = match self.relationship_properties(rel_id) {
842            Some(props) => props.into_keys().collect::<Vec<_>>(),
843            None => return false,
844        };
845
846        for key in existing_keys {
847            self.remove_relationship_property(rel_id, &key);
848        }
849
850        for (k, v) in properties {
851            self.set_relationship_property(rel_id, k, v);
852        }
853
854        true
855    }
856
857    fn merge_relationship_properties(
858        &mut self,
859        rel_id: RelationshipId,
860        properties: Properties,
861    ) -> bool {
862        if !self.contains_relationship(rel_id) {
863            return false;
864        }
865
866        for (k, v) in properties {
867            self.set_relationship_property(rel_id, k, v);
868        }
869
870        true
871    }
872
873    fn delete_relationships_of(&mut self, node_id: NodeId, direction: Direction) -> usize {
874        let rel_ids = self.relationship_ids_of(node_id, direction);
875
876        let mut deleted = 0;
877        for rel_id in rel_ids {
878            if self.delete_relationship(rel_id) {
879                deleted += 1;
880            }
881        }
882        deleted
883    }
884
885    fn get_or_create_node(
886        &mut self,
887        labels: Vec<String>,
888        match_key: &str,
889        match_value: &PropertyValue,
890        init_properties: Properties,
891    ) -> NodeRecord
892    where
893        Self: Sized,
894    {
895        for label in &labels {
896            let matches = self.find_nodes_by_property(Some(label), match_key, match_value);
897            if let Some(node) = matches.into_iter().next() {
898                return node;
899            }
900        }
901
902        self.create_node(labels, init_properties)
903    }
904}