Skip to main content

lora_store/memory/
impls.rs

1//! `GraphStorage` / `GraphStorageMut` impls for [`InMemoryGraph`]. Both
2//! delegate into the inherent helpers defined in `super`.
3
4use std::collections::BTreeSet;
5
6use lora_ast::Direction;
7
8use crate::encoded::PropEdit;
9use crate::{
10    ConstraintDefinition, ConstraintRequest, CreateConstraintError, CreateConstraintOutcome,
11    CreateIndexError, CreateIndexOutcome, DropConstraintError, DropConstraintOutcome,
12    DropIndexError, DropIndexOutcome, GraphStats, GraphStorage, GraphStorageMut, IndexDefinition,
13    IndexRequest, LoraVector, MutationEvent, NodeId, NodeRecord, NodeRef, Properties,
14    PropertyValue, RelRef, RelationshipId, RelationshipRecord, StoredIndexEntity,
15};
16
17use super::graph::PendingAdjRemovals;
18use super::property_index::PropertyIndexKey;
19use super::InMemoryGraph;
20
21impl GraphStorage for InMemoryGraph {
22    // ---------- Required primitives ----------
23
24    fn list_indexes(&self) -> Vec<IndexDefinition> {
25        self.index_catalog_read().list()
26    }
27
28    fn get_index(&self, name: &str) -> Option<IndexDefinition> {
29        self.index_catalog_read().get(name).cloned()
30    }
31
32    fn fulltext_search(&self, name: &str, query: &str, limit: Option<usize>) -> Vec<(u64, f64)> {
33        // The index lives on exactly one entity scope; check both.
34        let node_hits = self
35            .fulltext_indexes_read(StoredIndexEntity::Node)
36            .get(name)
37            .map(|idx| idx.query(query, limit));
38        if let Some(hits) = node_hits {
39            if !hits.is_empty() {
40                return hits;
41            }
42        }
43        let rel_hits = self
44            .fulltext_indexes_read(StoredIndexEntity::Relationship)
45            .get(name)
46            .map(|idx| idx.query(query, limit));
47        rel_hits.unwrap_or_default()
48    }
49
50    fn vector_search(
51        &self,
52        name: &str,
53        query: &LoraVector,
54        k: usize,
55        restrict_to: Option<&std::collections::BTreeSet<u64>>,
56    ) -> Vec<(u64, f64)> {
57        // Lazy populate: an index created with
58        // `vector.populate.async: true` stays Populating until the
59        // first query forces the backfill. The check is cheap (one
60        // catalog read), and the populate itself takes the write
61        // lock — concurrent queries pile up on the read lock and
62        // proceed once the writer drops.
63        self.lazy_populate_vector_index(name);
64
65        // Like fulltext_search, an index lives on exactly one entity
66        // scope. Probe both registries; the catalog has already
67        // enforced kind/entity agreement so only one will hit.
68        if let Some(hits) =
69            self.vector_indexes_read(StoredIndexEntity::Node)
70                .query(name, query, k, restrict_to)
71        {
72            if !hits.is_empty() {
73                return hits;
74            }
75        }
76        self.vector_indexes_read(StoredIndexEntity::Relationship)
77            .query(name, query, k, restrict_to)
78            .unwrap_or_default()
79    }
80
81    fn list_constraints(&self) -> Vec<ConstraintDefinition> {
82        self.constraint_catalog_read().list()
83    }
84
85    fn get_constraint(&self, name: &str) -> Option<ConstraintDefinition> {
86        self.constraint_catalog_read().get(name).cloned()
87    }
88
89    fn check_node_create_against_constraints(
90        &self,
91        labels: &[String],
92        properties: &Properties,
93    ) -> Result<(), String> {
94        if !self.has_active_constraints() {
95            return Ok(());
96        }
97        let catalog = self.constraint_catalog_read();
98        crate::memory::constraint_enforce::check_node_create(&catalog, self, labels, properties)
99            .map_err(|e| format!("[{}] {e}", e.gql_status()))
100    }
101
102    fn check_relationship_create_against_constraints(
103        &self,
104        rel_type: &str,
105        properties: &Properties,
106    ) -> Result<(), String> {
107        if !self.has_active_constraints() {
108            return Ok(());
109        }
110        let catalog = self.constraint_catalog_read();
111        crate::memory::constraint_enforce::check_relationship_create(
112            &catalog, self, rel_type, properties,
113        )
114        .map_err(|e| format!("[{}] {e}", e.gql_status()))
115    }
116
117    fn check_node_create_deferring_existence(
118        &self,
119        labels: &[String],
120        properties: &Properties,
121    ) -> Result<(), String> {
122        if !self.has_active_constraints() {
123            return Ok(());
124        }
125        let catalog = self.constraint_catalog_read();
126        crate::memory::constraint_enforce::check_node_create_deferred(
127            &catalog, self, labels, properties,
128        )
129        .map_err(|e| format!("[{}] {e}", e.gql_status()))
130    }
131
132    fn check_relationship_create_deferring_existence(
133        &self,
134        rel_type: &str,
135        properties: &Properties,
136    ) -> Result<(), String> {
137        if !self.has_active_constraints() {
138            return Ok(());
139        }
140        let catalog = self.constraint_catalog_read();
141        crate::memory::constraint_enforce::check_relationship_create_deferred(
142            &catalog, self, rel_type, properties,
143        )
144        .map_err(|e| format!("[{}] {e}", e.gql_status()))
145    }
146
147    fn check_node_existence_constraints(&self, node_id: NodeId) -> Result<(), String> {
148        if !self.has_active_constraints() {
149            return Ok(());
150        }
151        let catalog = self.constraint_catalog_read();
152        crate::memory::constraint_enforce::check_node_existence(&catalog, self, node_id)
153            .map_err(|e| format!("[{}] {e}", e.gql_status()))
154    }
155
156    fn check_relationship_existence_constraints(
157        &self,
158        rel_id: RelationshipId,
159    ) -> Result<(), String> {
160        if !self.has_active_constraints() {
161            return Ok(());
162        }
163        let catalog = self.constraint_catalog_read();
164        crate::memory::constraint_enforce::check_relationship_existence(&catalog, self, rel_id)
165            .map_err(|e| format!("[{}] {e}", e.gql_status()))
166    }
167
168    fn check_node_set_property_against_constraints(
169        &self,
170        node_id: NodeId,
171        key: &str,
172        value: &PropertyValue,
173    ) -> Result<(), String> {
174        if !self.has_active_constraints() {
175            return Ok(());
176        }
177        let catalog = self.constraint_catalog_read();
178        crate::memory::constraint_enforce::check_node_set_property(
179            &catalog, self, node_id, key, value,
180        )
181        .map_err(|e| format!("[{}] {e}", e.gql_status()))
182    }
183
184    fn check_node_remove_property_against_constraints(
185        &self,
186        node_id: NodeId,
187        key: &str,
188    ) -> Result<(), String> {
189        if !self.has_active_constraints() {
190            return Ok(());
191        }
192        let catalog = self.constraint_catalog_read();
193        crate::memory::constraint_enforce::check_node_remove_property(&catalog, self, node_id, key)
194            .map_err(|e| format!("[{}] {e}", e.gql_status()))
195    }
196
197    fn check_node_replace_properties_against_constraints(
198        &self,
199        node_id: NodeId,
200        properties: &Properties,
201    ) -> Result<(), String> {
202        if !self.has_active_constraints() {
203            return Ok(());
204        }
205        let catalog = self.constraint_catalog_read();
206        crate::memory::constraint_enforce::check_node_replace_properties(
207            &catalog, self, node_id, properties,
208        )
209        .map_err(|e| format!("[{}] {e}", e.gql_status()))
210    }
211
212    fn check_relationship_set_property_against_constraints(
213        &self,
214        rel_id: RelationshipId,
215        key: &str,
216        value: &PropertyValue,
217    ) -> Result<(), String> {
218        if !self.has_active_constraints() {
219            return Ok(());
220        }
221        let catalog = self.constraint_catalog_read();
222        crate::memory::constraint_enforce::check_relationship_set_property(
223            &catalog, self, rel_id, key, value,
224        )
225        .map_err(|e| format!("[{}] {e}", e.gql_status()))
226    }
227
228    fn check_relationship_remove_property_against_constraints(
229        &self,
230        rel_id: RelationshipId,
231        key: &str,
232    ) -> Result<(), String> {
233        if !self.has_active_constraints() {
234            return Ok(());
235        }
236        let catalog = self.constraint_catalog_read();
237        crate::memory::constraint_enforce::check_relationship_remove_property(
238            &catalog, self, rel_id, key,
239        )
240        .map_err(|e| format!("[{}] {e}", e.gql_status()))
241    }
242
243    fn check_relationship_replace_properties_against_constraints(
244        &self,
245        rel_id: RelationshipId,
246        properties: &Properties,
247    ) -> Result<(), String> {
248        if !self.has_active_constraints() {
249            return Ok(());
250        }
251        let catalog = self.constraint_catalog_read();
252        crate::memory::constraint_enforce::check_relationship_replace_properties(
253            &catalog, self, rel_id, properties,
254        )
255        .map_err(|e| format!("[{}] {e}", e.gql_status()))
256    }
257
258    fn check_node_add_label_against_constraints(
259        &self,
260        node_id: NodeId,
261        label: &str,
262    ) -> Result<(), String> {
263        if !self.has_active_constraints() {
264            return Ok(());
265        }
266        let catalog = self.constraint_catalog_read();
267        crate::memory::constraint_enforce::check_node_add_label(&catalog, self, node_id, label)
268            .map_err(|e| format!("[{}] {e}", e.gql_status()))
269    }
270
271    fn check_node_replace_properties_deferring_existence(
272        &self,
273        node_id: NodeId,
274        properties: &Properties,
275    ) -> Result<(), String> {
276        if !self.has_active_constraints() {
277            return Ok(());
278        }
279        let catalog = self.constraint_catalog_read();
280        crate::memory::constraint_enforce::check_node_replace_properties_deferred(
281            &catalog, self, node_id, properties,
282        )
283        .map_err(|e| format!("[{}] {e}", e.gql_status()))
284    }
285
286    fn check_relationship_replace_properties_deferring_existence(
287        &self,
288        rel_id: RelationshipId,
289        properties: &Properties,
290    ) -> Result<(), String> {
291        if !self.has_active_constraints() {
292            return Ok(());
293        }
294        let catalog = self.constraint_catalog_read();
295        crate::memory::constraint_enforce::check_relationship_replace_properties_deferred(
296            &catalog, self, rel_id, properties,
297        )
298        .map_err(|e| format!("[{}] {e}", e.gql_status()))
299    }
300
301    fn check_node_add_label_deferring_existence(
302        &self,
303        node_id: NodeId,
304        label: &str,
305    ) -> Result<(), String> {
306        if !self.has_active_constraints() {
307            return Ok(());
308        }
309        let catalog = self.constraint_catalog_read();
310        crate::memory::constraint_enforce::check_node_add_label_deferred(
311            &catalog, self, node_id, label,
312        )
313        .map_err(|e| format!("[{}] {e}", e.gql_status()))
314    }
315
316    fn graph_stats(&self) -> GraphStats {
317        InMemoryGraph::graph_stats(self)
318    }
319
320    fn plan_signature(&self) -> u64 {
321        InMemoryGraph::plan_signature(self)
322    }
323
324    fn node_text_candidates(
325        &self,
326        label: &str,
327        property: &str,
328        query: &str,
329    ) -> Option<Vec<NodeId>> {
330        let registry = self.text_indexes_read(crate::StoredIndexEntity::Node);
331        let candidates = registry.candidates(label, property, query)?;
332        Some(candidates.into_iter().collect())
333    }
334
335    fn node_range_candidates(
336        &self,
337        label: &str,
338        property: &str,
339        lo: Option<&PropertyValue>,
340        hi: Option<&PropertyValue>,
341    ) -> Option<Vec<NodeId>> {
342        let registry = self.sorted_indexes_read(crate::StoredIndexEntity::Node);
343        // The sorted index returns a [lo, hi] inclusive candidate set.
344        // The executor refilters each id against the precise predicate
345        // (handling `>` vs `>=`, `<` vs `<=`).
346        let candidates = registry.range_candidates(label, property, lo, hi)?;
347        Some(candidates.into_iter().collect())
348    }
349
350    fn node_range_other_temporal_kind_ids(
351        &self,
352        label: &str,
353        property: &str,
354        like: &PropertyValue,
355    ) -> Option<Vec<NodeId>> {
356        self.sorted_indexes_read(crate::StoredIndexEntity::Node)
357            .other_temporal_kind_ids(label, property, like)
358    }
359
360    fn node_range_ordered_chunk(
361        &self,
362        label: &str,
363        property: &str,
364        lo: Option<&PropertyValue>,
365        hi: Option<&PropertyValue>,
366        descending: bool,
367        after: Option<(&PropertyValue, NodeId)>,
368        max: usize,
369    ) -> Option<Vec<NodeId>> {
370        self.sorted_indexes_read(crate::StoredIndexEntity::Node)
371            .ordered_chunk(label, property, lo, hi, descending, after, max)
372    }
373
374    fn node_point_within_bbox(
375        &self,
376        label: &str,
377        property: &str,
378        ll: (f64, f64),
379        ur: (f64, f64),
380    ) -> Option<Vec<NodeId>> {
381        let registry = self.point_indexes_read(crate::StoredIndexEntity::Node);
382        let candidates = registry.within_bbox(label, property, ll, ur)?;
383        Some(candidates.into_iter().collect())
384    }
385
386    fn node_point_within_distance(
387        &self,
388        label: &str,
389        property: &str,
390        center: (f64, f64),
391        max_distance: f64,
392    ) -> Option<Vec<NodeId>> {
393        let registry = self.point_indexes_read(crate::StoredIndexEntity::Node);
394        let candidates = registry.within_distance(label, property, center, max_distance)?;
395        Some(candidates.into_iter().collect())
396    }
397
398    fn relationship_text_candidates(
399        &self,
400        rel_type: &str,
401        property: &str,
402        query: &str,
403    ) -> Option<Vec<RelationshipId>> {
404        let registry = self.text_indexes_read(crate::StoredIndexEntity::Relationship);
405        let candidates = registry.candidates(rel_type, property, query)?;
406        Some(candidates.into_iter().collect())
407    }
408
409    fn relationship_range_candidates(
410        &self,
411        rel_type: &str,
412        property: &str,
413        lo: Option<&PropertyValue>,
414        hi: Option<&PropertyValue>,
415    ) -> Option<Vec<RelationshipId>> {
416        let registry = self.sorted_indexes_read(crate::StoredIndexEntity::Relationship);
417        let candidates = registry.range_candidates(rel_type, property, lo, hi)?;
418        Some(candidates.into_iter().collect())
419    }
420
421    fn relationship_range_other_temporal_kind_ids(
422        &self,
423        rel_type: &str,
424        property: &str,
425        like: &PropertyValue,
426    ) -> Option<Vec<RelationshipId>> {
427        self.sorted_indexes_read(crate::StoredIndexEntity::Relationship)
428            .other_temporal_kind_ids(rel_type, property, like)
429    }
430
431    fn relationship_point_within_bbox(
432        &self,
433        rel_type: &str,
434        property: &str,
435        ll: (f64, f64),
436        ur: (f64, f64),
437    ) -> Option<Vec<RelationshipId>> {
438        let registry = self.point_indexes_read(crate::StoredIndexEntity::Relationship);
439        let candidates = registry.within_bbox(rel_type, property, ll, ur)?;
440        Some(candidates.into_iter().collect())
441    }
442
443    fn relationship_point_within_distance(
444        &self,
445        rel_type: &str,
446        property: &str,
447        center: (f64, f64),
448        max_distance: f64,
449    ) -> Option<Vec<RelationshipId>> {
450        let registry = self.point_indexes_read(crate::StoredIndexEntity::Relationship);
451        let candidates = registry.within_distance(rel_type, property, center, max_distance)?;
452        Some(candidates.into_iter().collect())
453    }
454
455    fn contains_node(&self, id: NodeId) -> bool {
456        self.has_node_at(id)
457    }
458
459    fn node(&self, id: NodeId) -> Option<NodeRecord> {
460        self.node_at(id).map(|r| r.to_record())
461    }
462
463    fn all_node_ids(&self) -> Vec<NodeId> {
464        self.iter_node_ids().collect()
465    }
466
467    fn node_ids_by_label(&self, label: &str) -> Vec<NodeId> {
468        match self.nodes_by_label.get(label) {
469            Some(ids) => ids.to_vec(),
470            None => Vec::new(),
471        }
472    }
473
474    fn scan_node_ids(
475        &self,
476        label: Option<&str>,
477        cursor: &mut u64,
478        max: usize,
479        out: &mut Vec<NodeId>,
480    ) -> bool {
481        let max = max.max(1);
482        match label {
483            Some(label) => {
484                let Some(ids) = self.nodes_by_label.get(label) else {
485                    return false;
486                };
487                // The cursor is the next id the scan may return.
488                fill_page(ids.iter_from(*cursor), cursor, max, out)
489            }
490            None => {
491                // The cursor is the next slot the scan may return.
492                let Ok(from) = usize::try_from(*cursor) else {
493                    return false;
494                };
495                let slots = self.nodes.slots_from(from).map(|slot| slot as NodeId);
496                fill_page(slots, cursor, max, out)
497            }
498        }
499    }
500
501    fn contains_relationship(&self, id: RelationshipId) -> bool {
502        self.has_rel_at(id)
503    }
504
505    fn relationship(&self, id: RelationshipId) -> Option<RelationshipRecord> {
506        self.rel_at(id).map(|r| r.to_record())
507    }
508
509    fn all_rel_ids(&self) -> Vec<RelationshipId> {
510        self.iter_rel_ids().collect()
511    }
512
513    fn rel_ids_by_type(&self, rel_type: &str) -> Vec<RelationshipId> {
514        match self.relationships_by_type.get(rel_type) {
515            Some(ids) => ids.to_vec(),
516            None => Vec::new(),
517        }
518    }
519
520    fn relationship_endpoints(&self, id: RelationshipId) -> Option<(NodeId, NodeId)> {
521        self.rel_endpoints_at(id)
522    }
523
524    fn expand_ids(
525        &self,
526        node_id: NodeId,
527        direction: Direction,
528        types: &[String],
529    ) -> Vec<(RelationshipId, NodeId)> {
530        if !self.has_node_at(node_id) {
531            return Vec::new();
532        }
533
534        let mut out: Vec<(RelationshipId, NodeId)> = Vec::new();
535        let _ = self.try_for_each_adjacent_id_unchecked(
536            node_id,
537            direction,
538            types,
539            |rel_id, other_id| {
540                out.push((rel_id, other_id));
541                Ok::<(), ()>(())
542            },
543        );
544        out
545    }
546
547    fn try_for_each_expand_id<F, E>(
548        &self,
549        node_id: NodeId,
550        direction: Direction,
551        types: &[String],
552        visit: F,
553    ) -> Result<(), E>
554    where
555        F: FnMut(RelationshipId, NodeId) -> Result<(), E>,
556        Self: Sized,
557    {
558        self.try_for_each_adjacent_id(node_id, direction, types, visit)
559    }
560
561    fn all_labels(&self) -> Vec<String> {
562        self.nodes_by_label.keys().map(|k| k.to_string()).collect()
563    }
564
565    fn all_relationship_types(&self) -> Vec<String> {
566        self.relationships_by_type
567            .keys()
568            .map(|k| k.to_string())
569            .collect()
570    }
571
572    // ---------- Optimization hooks: zero-clone borrow access ----------
573
574    fn with_node<F, R>(&self, id: NodeId, f: F) -> Option<R>
575    where
576        F: FnOnce(NodeRef<'_>) -> R,
577        Self: Sized,
578    {
579        self.node_at(id).map(f)
580    }
581
582    fn with_relationship<F, R>(&self, id: RelationshipId, f: F) -> Option<R>
583    where
584        F: FnOnce(RelRef<'_>) -> R,
585        Self: Sized,
586    {
587        self.rel_at(id).map(f)
588    }
589
590    // ---------- Overrides: counts + existence ----------
591
592    fn has_node(&self, id: NodeId) -> bool {
593        self.has_node_at(id)
594    }
595
596    fn has_relationship(&self, id: RelationshipId) -> bool {
597        self.has_rel_at(id)
598    }
599
600    fn node_count(&self) -> usize {
601        self.live_node_count
602    }
603
604    fn relationship_count(&self) -> usize {
605        self.live_rel_count
606    }
607
608    fn node_count_by_label(&self, label: &str) -> usize {
609        self.nodes_by_label.get(label).map_or(0, |ids| ids.len())
610    }
611
612    // ---------- Overrides: record-returning scans (direct iteration) ----------
613
614    fn all_nodes(&self) -> Vec<NodeRecord> {
615        self.iter_node_refs().map(|r| r.to_record()).collect()
616    }
617
618    fn nodes_by_label(&self, label: &str) -> Vec<NodeRecord> {
619        self.nodes_by_label
620            .get(label)
621            .into_iter()
622            .flat_map(|ids| ids.iter())
623            .filter_map(|id| self.node_at(id).map(|r| r.to_record()))
624            .collect()
625    }
626
627    fn all_relationships(&self) -> Vec<RelationshipRecord> {
628        self.iter_rel_refs().map(|r| r.to_record()).collect()
629    }
630
631    fn relationships_by_type(&self, rel_type: &str) -> Vec<RelationshipRecord> {
632        self.relationships_by_type
633            .get(rel_type)
634            .into_iter()
635            .flat_map(|ids| ids.iter())
636            .filter_map(|id| self.rel_at(id).map(|r| r.to_record()))
637            .collect()
638    }
639
640    fn relationship_ids_of(&self, node_id: NodeId, direction: Direction) -> Vec<RelationshipId> {
641        self.relationship_ids_for_direction(node_id, direction)
642    }
643
644    fn outgoing_relationships(&self, node_id: NodeId) -> Vec<RelationshipRecord> {
645        self.outgoing_at(node_id)
646            .into_iter()
647            .flat_map(|adj| adj.iter())
648            .filter_map(|entry| self.rel_at(entry.rel).map(|r| r.to_record()))
649            .collect()
650    }
651
652    fn incoming_relationships(&self, node_id: NodeId) -> Vec<RelationshipRecord> {
653        self.incoming_at(node_id)
654            .into_iter()
655            .flat_map(|adj| adj.iter())
656            .filter_map(|entry| self.rel_at(entry.rel).map(|r| r.to_record()))
657            .collect()
658    }
659
660    fn relationships_of(&self, node_id: NodeId, direction: Direction) -> Vec<RelationshipRecord> {
661        let mut out = Vec::new();
662        let _ = self.try_for_each_expand_id(node_id, direction, &[], |rel_id, _| {
663            if let Some(rel) = self.rel_at(rel_id) {
664                out.push(rel.to_record());
665            }
666            Ok::<(), ()>(())
667        });
668        out
669    }
670
671    fn degree(&self, node_id: NodeId, direction: Direction) -> usize {
672        // Counting walks the list: adjacency stores no length.
673        let out = || self.outgoing_at(node_id).map_or(0, |adj| adj.count());
674        match direction {
675            Direction::Right => out(),
676            Direction::Left => self.incoming_at(node_id).map_or(0, |adj| adj.count()),
677            Direction::Undirected => {
678                let incoming_non_self = self
679                    .incoming_at(node_id)
680                    .into_iter()
681                    .flat_map(|adj| adj.iter())
682                    .filter(|entry| entry.neighbour != node_id)
683                    .count();
684                out() + incoming_non_self
685            }
686        }
687    }
688
689    fn expand(
690        &self,
691        node_id: NodeId,
692        direction: Direction,
693        types: &[String],
694    ) -> Vec<(RelationshipRecord, NodeRecord)> {
695        if !self.has_node_at(node_id) {
696            return Vec::new();
697        }
698
699        let mut out = Vec::new();
700        let _ = self.try_for_each_expand_id(node_id, direction, types, |rel_id, other_id| {
701            if let (Some(rel), Some(other)) = (self.rel_at(rel_id), self.node_at(other_id)) {
702                out.push((rel.to_record(), other.to_record()));
703            }
704            Ok::<(), ()>(())
705        });
706        out
707    }
708
709    fn neighbors(
710        &self,
711        node_id: NodeId,
712        direction: Direction,
713        types: &[String],
714    ) -> Vec<NodeRecord> {
715        let mut out = Vec::new();
716        let _ = self.try_for_each_expand_id(node_id, direction, types, |_, other_id| {
717            if let Some(node) = self.node_at(other_id) {
718                out.push(node.to_record());
719            }
720            Ok::<(), ()>(())
721        });
722        out
723    }
724
725    fn all_node_property_keys(&self) -> Vec<String> {
726        let mut keys = BTreeSet::new();
727        for node in self.iter_node_refs() {
728            for key in node.properties().keys() {
729                keys.insert(key.to_string());
730            }
731        }
732        keys.into_iter().collect()
733    }
734
735    // ---------- Overrides: traversal (direct adjacency) ----------
736
737    fn all_relationship_property_keys(&self) -> Vec<String> {
738        let mut keys = BTreeSet::new();
739        for rel in self.iter_rel_refs() {
740            for key in rel.properties().keys() {
741                keys.insert(key.to_string());
742            }
743        }
744        keys.into_iter().collect()
745    }
746
747    fn label_property_keys(&self, label: &str) -> Vec<String> {
748        let mut keys = BTreeSet::new();
749
750        if let Some(ids) = self.nodes_by_label.get(label) {
751            for id in ids.iter() {
752                if let Some(node) = self.node_at(id) {
753                    for key in node.properties().keys() {
754                        keys.insert(key.to_string());
755                    }
756                }
757            }
758        }
759
760        keys.into_iter().collect()
761    }
762
763    fn rel_type_property_keys(&self, rel_type: &str) -> Vec<String> {
764        let mut keys = BTreeSet::new();
765
766        if let Some(ids) = self.relationships_by_type.get(rel_type) {
767            for id in ids.iter() {
768                if let Some(rel) = self.rel_at(id) {
769                    for key in rel.properties().keys() {
770                        keys.insert(key.to_string());
771                    }
772                }
773            }
774        }
775
776        keys.into_iter().collect()
777    }
778
779    fn find_nodes_by_property(
780        &self,
781        label: Option<&str>,
782        key: &str,
783        value: &PropertyValue,
784    ) -> Vec<NodeRecord>
785    where
786        Self: Sized,
787    {
788        if PropertyIndexKey::from_value(value).is_none() {
789            return self.scan_nodes_by_property(label, key, value);
790        }
791
792        self.ensure_node_property_index(key);
793        if label.is_none() {
794            self.ensure_any_scope_node_property_index(key);
795        }
796        let indexes = self.indexes_read();
797
798        match label {
799            Some(label) => {
800                let Some(ids) = indexes.node_properties.scoped_ids_for(label, key, value) else {
801                    return Vec::new();
802                };
803                ids.iter()
804                    .filter_map(|id| self.node_at(id).map(|r| r.to_record()))
805                    .collect()
806            }
807            None => indexes
808                .node_properties
809                .ids_for(key, value)
810                .into_iter()
811                .flat_map(|ids| ids.iter())
812                .filter_map(|id| self.node_at(id).map(|r| r.to_record()))
813                .collect(),
814        }
815    }
816
817    fn find_node_ids_by_property(
818        &self,
819        label: Option<&str>,
820        key: &str,
821        value: &PropertyValue,
822    ) -> Vec<NodeId>
823    where
824        Self: Sized,
825    {
826        if PropertyIndexKey::from_value(value).is_none() {
827            return self.scan_node_ids_by_property(label, key, value);
828        }
829
830        self.ensure_node_property_index(key);
831        if label.is_none() {
832            self.ensure_any_scope_node_property_index(key);
833        }
834        let indexes = self.indexes_read();
835
836        match label {
837            Some(label) => indexes
838                .node_properties
839                .scoped_ids_for(label, key, value)
840                .map(|ids| ids.to_vec())
841                .unwrap_or_default(),
842            None => indexes
843                .node_properties
844                .ids_for(key, value)
845                .map(|ids| ids.to_vec())
846                .unwrap_or_default(),
847        }
848    }
849    // ---------- Overrides: schema introspection ----------
850
851    fn find_relationships_by_property(
852        &self,
853        rel_type: Option<&str>,
854        key: &str,
855        value: &PropertyValue,
856    ) -> Vec<RelationshipRecord>
857    where
858        Self: Sized,
859    {
860        if PropertyIndexKey::from_value(value).is_none() {
861            return self.scan_relationships_by_property(rel_type, key, value);
862        }
863
864        self.ensure_relationship_property_index(key);
865        if rel_type.is_none() {
866            self.ensure_any_scope_relationship_property_index(key);
867        }
868        let indexes = self.indexes_read();
869
870        match rel_type {
871            Some(rel_type) => {
872                let Some(ids) = indexes
873                    .relationship_properties
874                    .scoped_ids_for(rel_type, key, value)
875                else {
876                    return Vec::new();
877                };
878                ids.iter()
879                    .filter_map(|id| self.rel_at(id).map(|r| r.to_record()))
880                    .collect()
881            }
882            None => indexes
883                .relationship_properties
884                .ids_for(key, value)
885                .into_iter()
886                .flat_map(|ids| ids.iter())
887                .filter_map(|id| self.rel_at(id).map(|r| r.to_record()))
888                .collect(),
889        }
890    }
891
892    fn find_relationship_ids_by_property(
893        &self,
894        rel_type: Option<&str>,
895        key: &str,
896        value: &PropertyValue,
897    ) -> Vec<RelationshipId>
898    where
899        Self: Sized,
900    {
901        if PropertyIndexKey::from_value(value).is_none() {
902            return self.scan_relationship_ids_by_property(rel_type, key, value);
903        }
904
905        self.ensure_relationship_property_index(key);
906        if rel_type.is_none() {
907            self.ensure_any_scope_relationship_property_index(key);
908        }
909        let indexes = self.indexes_read();
910
911        match rel_type {
912            Some(rel_type) => indexes
913                .relationship_properties
914                .scoped_ids_for(rel_type, key, value)
915                .map(|ids| ids.to_vec())
916                .unwrap_or_default(),
917            None => indexes
918                .relationship_properties
919                .ids_for(key, value)
920                .map(|ids| ids.to_vec())
921                .unwrap_or_default(),
922        }
923    }
924
925    fn node_exists_with_label_and_property(
926        &self,
927        label: &str,
928        key: &str,
929        value: &PropertyValue,
930    ) -> bool
931    where
932        Self: Sized,
933    {
934        if PropertyIndexKey::from_value(value).is_none() {
935            return self.any_node_by_property(label, key, value);
936        }
937
938        self.ensure_node_property_index(key);
939        let indexes = self.indexes_read();
940        indexes
941            .node_properties
942            .scoped_ids_for(label, key, value)
943            .map(|ids| !ids.is_empty())
944            .unwrap_or(false)
945    }
946
947    fn relationship_exists_with_type_and_property(
948        &self,
949        rel_type: &str,
950        key: &str,
951        value: &PropertyValue,
952    ) -> bool
953    where
954        Self: Sized,
955    {
956        if PropertyIndexKey::from_value(value).is_none() {
957            return self.any_relationship_by_property(rel_type, key, value);
958        }
959
960        self.ensure_relationship_property_index(key);
961        let indexes = self.indexes_read();
962        indexes
963            .relationship_properties
964            .scoped_ids_for(rel_type, key, value)
965            .map(|ids| !ids.is_empty())
966            .unwrap_or(false)
967    }
968}
969
970impl InMemoryGraph {
971    /// [`GraphStorageMut::delete_relationship`], leaving the edits of long
972    /// adjacency lists in `pending` when it is given. The caller flushes
973    /// them before anything reads adjacency again.
974    fn delete_relationship_deferring(
975        &mut self,
976        rel_id: RelationshipId,
977        pending: Option<&mut PendingAdjRemovals>,
978    ) -> bool {
979        let applied = match self.take_rel(rel_id) {
980            Some(rel) => {
981                if let Some(sink) = &self.deleted_sink {
982                    sink.relationship_deleted(&rel);
983                }
984                self.on_relationship_deleted(&rel, pending);
985                true
986            }
987            None => false,
988        };
989        if applied {
990            self.emit(|| MutationEvent::DeleteRelationship { rel_id });
991        }
992        applied
993    }
994
995    /// [`GraphStorageMut::detach_delete_node`] with the far ends' long
996    /// lists left in `pending`. The node's own lists are read and cleared
997    /// first, so an entry still pending there (from an earlier node of the
998    /// same batch) only names a relationship that is gone, which the
999    /// delete skips, as it would have found the entry removed.
1000    fn detach_delete_deferring(
1001        &mut self,
1002        node_id: NodeId,
1003        pending: &mut PendingAdjRemovals,
1004    ) -> bool {
1005        if !self.has_node_at(node_id) {
1006            return false;
1007        }
1008
1009        let rel_ids = self.incident_relationship_ids(node_id);
1010
1011        // We deliberately fire per-relationship DeleteRelationship events
1012        // here and a DetachDeleteNode event at the end. A WAL replayer that
1013        // sees DetachDeleteNode can ignore the preceding DeleteRelationship
1014        // events, or replay them, after which the DetachDeleteNode is a
1015        // no-op on the remaining (now-empty) node. The
1016        // emit-before-delete choice costs one extra event per mutation but
1017        // keeps the replay contract simple: "apply every event in order".
1018        //
1019        // The node's own lists go first: each removal below then edits only
1020        // the other endpoint's list (a self-loop finds nothing on either side).
1021        self.clear_adjacency(node_id);
1022        for rel_id in rel_ids {
1023            let _ = self.delete_relationship_deferring(rel_id, Some(pending));
1024        }
1025
1026        if self.delete_node(node_id) {
1027            self.emit(|| MutationEvent::DetachDeleteNode { node_id });
1028            true
1029        } else {
1030            false
1031        }
1032    }
1033}
1034
1035impl GraphStorageMut for InMemoryGraph {
1036    fn try_create_node(
1037        &mut self,
1038        labels: Vec<String>,
1039        properties: Properties,
1040    ) -> Option<NodeRecord> {
1041        let (id, idx) = self.try_reserve_next_node_slot()?;
1042        let labels = Self::normalize_labels(labels);
1043
1044        let node = NodeRecord {
1045            id,
1046            labels,
1047            properties,
1048        };
1049
1050        self.put_node_at_slot(idx, &node);
1051
1052        self.on_node_created(&node);
1053
1054        // Reserving the slot also grew both adjacency tables to cover
1055        // this id.
1056
1057        self.emit(|| MutationEvent::CreateNode {
1058            id,
1059            labels: node.labels.to_strings(),
1060            properties: node.properties.clone(),
1061        });
1062
1063        Some(node)
1064    }
1065
1066    fn create_relationship(
1067        &mut self,
1068        src: NodeId,
1069        dst: NodeId,
1070        rel_type: &str,
1071        properties: Properties,
1072    ) -> Option<RelationshipRecord> {
1073        if !self.has_node_at(src) || !self.has_node_at(dst) {
1074            return None;
1075        }
1076
1077        let trimmed = rel_type.trim();
1078        if trimmed.is_empty() {
1079            return None;
1080        }
1081
1082        let (id, idx) = self.try_reserve_next_rel_slot()?;
1083        let rel = RelationshipRecord {
1084            id,
1085            src,
1086            dst,
1087            rel_type: trimmed.into(),
1088            properties,
1089        };
1090
1091        self.put_rel_at_slot(idx, &rel);
1092        self.on_relationship_created(&rel);
1093
1094        self.emit(|| MutationEvent::CreateRelationship {
1095            id,
1096            src,
1097            dst,
1098            rel_type: rel.rel_type.to_string(),
1099            properties: rel.properties.clone(),
1100        });
1101
1102        Some(rel)
1103    }
1104
1105    fn set_node_property(&mut self, node_id: NodeId, key: String, value: PropertyValue) -> bool {
1106        let Some(old) = self.edit_node_property(node_id, &key, PropEdit::Set(&value)) else {
1107            return false;
1108        };
1109        self.on_node_property_set(node_id, &key, old.as_ref(), &value);
1110
1111        self.emit(|| MutationEvent::SetNodeProperty {
1112            node_id,
1113            key: key.clone(),
1114            value: value.clone(),
1115        });
1116
1117        true
1118    }
1119
1120    fn remove_node_property(&mut self, node_id: NodeId, key: &str) -> bool {
1121        let Some(removed) = self
1122            .edit_node_property(node_id, key, PropEdit::Remove)
1123            .flatten()
1124        else {
1125            return false;
1126        };
1127
1128        self.on_node_property_removed(node_id, key, &removed);
1129
1130        self.emit(|| MutationEvent::RemoveNodeProperty {
1131            node_id,
1132            key: key.to_string(),
1133        });
1134
1135        true
1136    }
1137
1138    fn add_node_label(&mut self, node_id: NodeId, label: &str) -> bool {
1139        let label = label.trim();
1140        if label.is_empty() {
1141            return false;
1142        }
1143
1144        let applied = match self.node_at(node_id) {
1145            Some(node) if node.has_label(label) => return false,
1146            Some(_) => self
1147                .update_node(node_id, |node| node.labels.push(label))
1148                .is_some(),
1149            None => false,
1150        };
1151        if applied {
1152            self.on_node_label_added(node_id, label);
1153            self.emit(|| MutationEvent::AddNodeLabel {
1154                node_id,
1155                label: label.to_string(),
1156            });
1157        }
1158        applied
1159    }
1160
1161    fn remove_node_label(&mut self, node_id: NodeId, label: &str) -> bool {
1162        let applied = self
1163            .node_at(node_id)
1164            .is_some_and(|node| node.has_label(label))
1165            && self
1166                .update_node(node_id, |node| node.labels.retain(|l| l != label))
1167                .is_some();
1168        if applied {
1169            self.on_node_label_removed(node_id, label);
1170            self.emit(|| MutationEvent::RemoveNodeLabel {
1171                node_id,
1172                label: label.to_string(),
1173            });
1174        }
1175        applied
1176    }
1177
1178    fn set_relationship_property(
1179        &mut self,
1180        rel_id: RelationshipId,
1181        key: String,
1182        value: PropertyValue,
1183    ) -> bool {
1184        let Some(old) = self.edit_rel_property(rel_id, &key, PropEdit::Set(&value)) else {
1185            return false;
1186        };
1187        self.on_relationship_property_set(rel_id, &key, old.as_ref(), &value);
1188
1189        self.emit(|| MutationEvent::SetRelationshipProperty {
1190            rel_id,
1191            key: key.clone(),
1192            value: value.clone(),
1193        });
1194
1195        true
1196    }
1197
1198    fn remove_relationship_property(&mut self, rel_id: RelationshipId, key: &str) -> bool {
1199        let Some(removed) = self
1200            .edit_rel_property(rel_id, key, PropEdit::Remove)
1201            .flatten()
1202        else {
1203            return false;
1204        };
1205
1206        self.on_relationship_property_removed(rel_id, key, &removed);
1207
1208        self.emit(|| MutationEvent::RemoveRelationshipProperty {
1209            rel_id,
1210            key: key.to_string(),
1211        });
1212
1213        true
1214    }
1215
1216    fn delete_relationship(&mut self, rel_id: RelationshipId) -> bool {
1217        self.delete_relationship_deferring(rel_id, None)
1218    }
1219
1220    fn delete_relationships(&mut self, rel_ids: &[RelationshipId]) -> usize {
1221        let mut pending = PendingAdjRemovals::new();
1222        let deleted = rel_ids
1223            .iter()
1224            .filter(|&&rel_id| self.delete_relationship_deferring(rel_id, Some(&mut pending)))
1225            .count();
1226        self.flush_adjacency_removals(&mut pending);
1227        deleted
1228    }
1229
1230    fn delete_node(&mut self, node_id: NodeId) -> bool {
1231        if !self.has_node_at(node_id) {
1232            return false;
1233        }
1234
1235        if self.has_incident_relationships(node_id) {
1236            return false;
1237        }
1238
1239        let node = match self.take_node(node_id) {
1240            Some(node) => node,
1241            None => return false,
1242        };
1243
1244        if let Some(sink) = &self.deleted_sink {
1245            sink.node_deleted(&node);
1246        }
1247        self.on_node_deleted(&node);
1248
1249        // take_node already cleared the node's adjacency lists.
1250
1251        self.emit(|| MutationEvent::DeleteNode { node_id });
1252
1253        true
1254    }
1255
1256    fn detach_delete_node(&mut self, node_id: NodeId) -> bool {
1257        self.detach_delete_nodes(&[node_id]) == 1
1258    }
1259
1260    fn detach_delete_nodes(&mut self, node_ids: &[NodeId]) -> usize {
1261        let mut pending = PendingAdjRemovals::new();
1262        let deleted = node_ids
1263            .iter()
1264            .filter(|&&node_id| self.detach_delete_deferring(node_id, &mut pending))
1265            .count();
1266        self.flush_adjacency_removals(&mut pending);
1267        deleted
1268    }
1269
1270    fn clear(&mut self) {
1271        // Keep the recorder across clear so observers can see the Clear
1272        // event plus whatever follows. Matches WAL semantics where the log
1273        // is the source of truth across a truncation.
1274        let recorder = self.recorder.take();
1275        let deleted_sink = self.deleted_sink.take();
1276        *self = Self::default();
1277        self.recorder = recorder;
1278        self.deleted_sink = deleted_sink;
1279        self.emit(|| MutationEvent::Clear);
1280    }
1281
1282    fn create_index(
1283        &mut self,
1284        request: IndexRequest,
1285        if_not_exists: bool,
1286    ) -> Result<CreateIndexOutcome, CreateIndexError> {
1287        // `register_index` only needs `&self` (catalog + property-index
1288        // registries are behind RwLock for interior mutability), but we
1289        // keep the trait signature `&mut self` so downstream callers can
1290        // hold a uniquely-owned mutation lock. That mirrors the pattern
1291        // used for property writes elsewhere in this impl.
1292        self.register_index(request, if_not_exists)
1293    }
1294
1295    fn drop_index(
1296        &mut self,
1297        name: &str,
1298        if_exists: bool,
1299    ) -> Result<DropIndexOutcome, DropIndexError> {
1300        self.drop_named_index(name, if_exists)
1301    }
1302
1303    fn create_constraint(
1304        &mut self,
1305        request: ConstraintRequest,
1306        if_not_exists: bool,
1307    ) -> Result<CreateConstraintOutcome, CreateConstraintError> {
1308        self.register_constraint(request, if_not_exists)
1309    }
1310
1311    fn drop_constraint(
1312        &mut self,
1313        name: &str,
1314        if_exists: bool,
1315    ) -> Result<DropConstraintOutcome, DropConstraintError> {
1316        self.drop_named_constraint(name, if_exists)
1317    }
1318}
1319
1320/// Move up to `max` of the ascending `ids` into `out` and leave `cursor`
1321/// just past the last one moved. Returns whether any id is left.
1322#[inline]
1323fn fill_page(
1324    mut ids: impl Iterator<Item = NodeId>,
1325    cursor: &mut u64,
1326    max: usize,
1327    out: &mut Vec<NodeId>,
1328) -> bool {
1329    for id in ids.by_ref().take(max) {
1330        out.push(id);
1331        *cursor = id.saturating_add(1);
1332    }
1333    ids.next().is_some()
1334}