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