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 node_text_candidates(
320        &self,
321        label: &str,
322        property: &str,
323        query: &str,
324    ) -> Option<Vec<NodeId>> {
325        let registry = self.text_indexes_read(crate::StoredIndexEntity::Node);
326        let candidates = registry.candidates(label, property, query)?;
327        Some(candidates.into_iter().collect())
328    }
329
330    fn node_range_candidates(
331        &self,
332        label: &str,
333        property: &str,
334        lo: Option<&PropertyValue>,
335        hi: Option<&PropertyValue>,
336    ) -> Option<Vec<NodeId>> {
337        let registry = self.sorted_indexes_read(crate::StoredIndexEntity::Node);
338        // The sorted index returns a [lo, hi] inclusive candidate set.
339        // The executor refilters each id against the precise predicate
340        // (handling `>` vs `>=`, `<` vs `<=`).
341        let candidates = registry.range_candidates(label, property, lo, hi)?;
342        Some(candidates.into_iter().collect())
343    }
344
345    fn node_range_other_temporal_kind_ids(
346        &self,
347        label: &str,
348        property: &str,
349        like: &PropertyValue,
350    ) -> Option<Vec<NodeId>> {
351        self.sorted_indexes_read(crate::StoredIndexEntity::Node)
352            .other_temporal_kind_ids(label, property, like)
353    }
354
355    fn node_range_ordered_chunk(
356        &self,
357        label: &str,
358        property: &str,
359        lo: Option<&PropertyValue>,
360        hi: Option<&PropertyValue>,
361        descending: bool,
362        after: Option<(&PropertyValue, NodeId)>,
363        max: usize,
364    ) -> Option<Vec<NodeId>> {
365        self.sorted_indexes_read(crate::StoredIndexEntity::Node)
366            .ordered_chunk(label, property, lo, hi, descending, after, max)
367    }
368
369    fn node_point_within_bbox(
370        &self,
371        label: &str,
372        property: &str,
373        ll: (f64, f64),
374        ur: (f64, f64),
375    ) -> Option<Vec<NodeId>> {
376        let registry = self.point_indexes_read(crate::StoredIndexEntity::Node);
377        let candidates = registry.within_bbox(label, property, ll, ur)?;
378        Some(candidates.into_iter().collect())
379    }
380
381    fn node_point_within_distance(
382        &self,
383        label: &str,
384        property: &str,
385        center: (f64, f64),
386        max_distance: f64,
387    ) -> Option<Vec<NodeId>> {
388        let registry = self.point_indexes_read(crate::StoredIndexEntity::Node);
389        let candidates = registry.within_distance(label, property, center, max_distance)?;
390        Some(candidates.into_iter().collect())
391    }
392
393    fn relationship_text_candidates(
394        &self,
395        rel_type: &str,
396        property: &str,
397        query: &str,
398    ) -> Option<Vec<RelationshipId>> {
399        let registry = self.text_indexes_read(crate::StoredIndexEntity::Relationship);
400        let candidates = registry.candidates(rel_type, property, query)?;
401        Some(candidates.into_iter().collect())
402    }
403
404    fn relationship_range_candidates(
405        &self,
406        rel_type: &str,
407        property: &str,
408        lo: Option<&PropertyValue>,
409        hi: Option<&PropertyValue>,
410    ) -> Option<Vec<RelationshipId>> {
411        let registry = self.sorted_indexes_read(crate::StoredIndexEntity::Relationship);
412        let candidates = registry.range_candidates(rel_type, property, lo, hi)?;
413        Some(candidates.into_iter().collect())
414    }
415
416    fn relationship_range_other_temporal_kind_ids(
417        &self,
418        rel_type: &str,
419        property: &str,
420        like: &PropertyValue,
421    ) -> Option<Vec<RelationshipId>> {
422        self.sorted_indexes_read(crate::StoredIndexEntity::Relationship)
423            .other_temporal_kind_ids(rel_type, property, like)
424    }
425
426    fn relationship_point_within_bbox(
427        &self,
428        rel_type: &str,
429        property: &str,
430        ll: (f64, f64),
431        ur: (f64, f64),
432    ) -> Option<Vec<RelationshipId>> {
433        let registry = self.point_indexes_read(crate::StoredIndexEntity::Relationship);
434        let candidates = registry.within_bbox(rel_type, property, ll, ur)?;
435        Some(candidates.into_iter().collect())
436    }
437
438    fn relationship_point_within_distance(
439        &self,
440        rel_type: &str,
441        property: &str,
442        center: (f64, f64),
443        max_distance: f64,
444    ) -> Option<Vec<RelationshipId>> {
445        let registry = self.point_indexes_read(crate::StoredIndexEntity::Relationship);
446        let candidates = registry.within_distance(rel_type, property, center, max_distance)?;
447        Some(candidates.into_iter().collect())
448    }
449
450    fn contains_node(&self, id: NodeId) -> bool {
451        self.has_node_at(id)
452    }
453
454    fn node(&self, id: NodeId) -> Option<NodeRecord> {
455        self.node_at(id).map(|r| r.to_record())
456    }
457
458    fn all_node_ids(&self) -> Vec<NodeId> {
459        self.iter_node_ids().collect()
460    }
461
462    fn node_ids_by_label(&self, label: &str) -> Vec<NodeId> {
463        match self.nodes_by_label.get(label) {
464            Some(ids) => ids.to_vec(),
465            None => Vec::new(),
466        }
467    }
468
469    fn scan_node_ids(
470        &self,
471        label: Option<&str>,
472        cursor: &mut u64,
473        max: usize,
474        out: &mut Vec<NodeId>,
475    ) -> bool {
476        let max = max.max(1);
477        match label {
478            Some(label) => {
479                let Some(ids) = self.nodes_by_label.get(label) else {
480                    return false;
481                };
482                // The cursor is the next id the scan may return.
483                fill_page(ids.iter_from(*cursor), cursor, max, out)
484            }
485            None => {
486                // The cursor is the next slot the scan may return.
487                let Ok(from) = usize::try_from(*cursor) else {
488                    return false;
489                };
490                let slots = self.nodes.slots_from(from).map(|slot| slot as NodeId);
491                fill_page(slots, cursor, max, out)
492            }
493        }
494    }
495
496    fn contains_relationship(&self, id: RelationshipId) -> bool {
497        self.has_rel_at(id)
498    }
499
500    fn relationship(&self, id: RelationshipId) -> Option<RelationshipRecord> {
501        self.rel_at(id).map(|r| r.to_record())
502    }
503
504    fn all_rel_ids(&self) -> Vec<RelationshipId> {
505        self.iter_rel_ids().collect()
506    }
507
508    fn rel_ids_by_type(&self, rel_type: &str) -> Vec<RelationshipId> {
509        match self.relationships_by_type.get(rel_type) {
510            Some(ids) => ids.to_vec(),
511            None => Vec::new(),
512        }
513    }
514
515    fn relationship_endpoints(&self, id: RelationshipId) -> Option<(NodeId, NodeId)> {
516        self.rel_endpoints_at(id)
517    }
518
519    fn expand_ids(
520        &self,
521        node_id: NodeId,
522        direction: Direction,
523        types: &[String],
524    ) -> Vec<(RelationshipId, NodeId)> {
525        if !self.has_node_at(node_id) {
526            return Vec::new();
527        }
528
529        let mut out: Vec<(RelationshipId, NodeId)> = Vec::new();
530        let _ = self.try_for_each_adjacent_id_unchecked(
531            node_id,
532            direction,
533            types,
534            |rel_id, other_id| {
535                out.push((rel_id, other_id));
536                Ok::<(), ()>(())
537            },
538        );
539        out
540    }
541
542    fn try_for_each_expand_id<F, E>(
543        &self,
544        node_id: NodeId,
545        direction: Direction,
546        types: &[String],
547        visit: F,
548    ) -> Result<(), E>
549    where
550        F: FnMut(RelationshipId, NodeId) -> Result<(), E>,
551        Self: Sized,
552    {
553        self.try_for_each_adjacent_id(node_id, direction, types, visit)
554    }
555
556    fn all_labels(&self) -> Vec<String> {
557        self.nodes_by_label.keys().cloned().collect()
558    }
559
560    fn all_relationship_types(&self) -> Vec<String> {
561        self.relationships_by_type.keys().cloned().collect()
562    }
563
564    // ---------- Optimization hooks: zero-clone borrow access ----------
565
566    fn with_node<F, R>(&self, id: NodeId, f: F) -> Option<R>
567    where
568        F: FnOnce(NodeRef<'_>) -> R,
569        Self: Sized,
570    {
571        self.node_at(id).map(f)
572    }
573
574    fn with_relationship<F, R>(&self, id: RelationshipId, f: F) -> Option<R>
575    where
576        F: FnOnce(RelRef<'_>) -> R,
577        Self: Sized,
578    {
579        self.rel_at(id).map(f)
580    }
581
582    // ---------- Overrides: counts + existence ----------
583
584    fn has_node(&self, id: NodeId) -> bool {
585        self.has_node_at(id)
586    }
587
588    fn has_relationship(&self, id: RelationshipId) -> bool {
589        self.has_rel_at(id)
590    }
591
592    fn node_count(&self) -> usize {
593        self.live_node_count
594    }
595
596    fn relationship_count(&self) -> usize {
597        self.live_rel_count
598    }
599
600    fn node_count_by_label(&self, label: &str) -> usize {
601        self.nodes_by_label.get(label).map_or(0, |ids| ids.len())
602    }
603
604    // ---------- Overrides: record-returning scans (direct iteration) ----------
605
606    fn all_nodes(&self) -> Vec<NodeRecord> {
607        self.iter_node_refs().map(|r| r.to_record()).collect()
608    }
609
610    fn nodes_by_label(&self, label: &str) -> Vec<NodeRecord> {
611        self.nodes_by_label
612            .get(label)
613            .into_iter()
614            .flat_map(|ids| ids.iter())
615            .filter_map(|id| self.node_at(id).map(|r| r.to_record()))
616            .collect()
617    }
618
619    fn all_relationships(&self) -> Vec<RelationshipRecord> {
620        self.iter_rel_refs().map(|r| r.to_record()).collect()
621    }
622
623    fn relationships_by_type(&self, rel_type: &str) -> Vec<RelationshipRecord> {
624        self.relationships_by_type
625            .get(rel_type)
626            .into_iter()
627            .flat_map(|ids| ids.iter())
628            .filter_map(|id| self.rel_at(id).map(|r| r.to_record()))
629            .collect()
630    }
631
632    fn relationship_ids_of(&self, node_id: NodeId, direction: Direction) -> Vec<RelationshipId> {
633        self.relationship_ids_for_direction(node_id, direction)
634    }
635
636    fn outgoing_relationships(&self, node_id: NodeId) -> Vec<RelationshipRecord> {
637        self.outgoing_at(node_id)
638            .into_iter()
639            .flat_map(|adj| adj.iter())
640            .filter_map(|entry| self.rel_at(entry.rel).map(|r| r.to_record()))
641            .collect()
642    }
643
644    fn incoming_relationships(&self, node_id: NodeId) -> Vec<RelationshipRecord> {
645        self.incoming_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 relationships_of(&self, node_id: NodeId, direction: Direction) -> Vec<RelationshipRecord> {
653        let mut out = Vec::new();
654        let _ = self.try_for_each_expand_id(node_id, direction, &[], |rel_id, _| {
655            if let Some(rel) = self.rel_at(rel_id) {
656                out.push(rel.to_record());
657            }
658            Ok::<(), ()>(())
659        });
660        out
661    }
662
663    fn degree(&self, node_id: NodeId, direction: Direction) -> usize {
664        // Counting walks the list: adjacency stores no length.
665        let out = || self.outgoing_at(node_id).map_or(0, |adj| adj.count());
666        match direction {
667            Direction::Right => out(),
668            Direction::Left => self.incoming_at(node_id).map_or(0, |adj| adj.count()),
669            Direction::Undirected => {
670                let incoming_non_self = self
671                    .incoming_at(node_id)
672                    .into_iter()
673                    .flat_map(|adj| adj.iter())
674                    .filter(|entry| entry.neighbour != node_id)
675                    .count();
676                out() + incoming_non_self
677            }
678        }
679    }
680
681    fn expand(
682        &self,
683        node_id: NodeId,
684        direction: Direction,
685        types: &[String],
686    ) -> Vec<(RelationshipRecord, NodeRecord)> {
687        if !self.has_node_at(node_id) {
688            return Vec::new();
689        }
690
691        let mut out = Vec::new();
692        let _ = self.try_for_each_expand_id(node_id, direction, types, |rel_id, other_id| {
693            if let (Some(rel), Some(other)) = (self.rel_at(rel_id), self.node_at(other_id)) {
694                out.push((rel.to_record(), other.to_record()));
695            }
696            Ok::<(), ()>(())
697        });
698        out
699    }
700
701    fn neighbors(
702        &self,
703        node_id: NodeId,
704        direction: Direction,
705        types: &[String],
706    ) -> Vec<NodeRecord> {
707        let mut out = Vec::new();
708        let _ = self.try_for_each_expand_id(node_id, direction, types, |_, other_id| {
709            if let Some(node) = self.node_at(other_id) {
710                out.push(node.to_record());
711            }
712            Ok::<(), ()>(())
713        });
714        out
715    }
716
717    fn all_node_property_keys(&self) -> Vec<String> {
718        let mut keys = BTreeSet::new();
719        for node in self.iter_node_refs() {
720            for key in node.properties().keys() {
721                keys.insert(key.to_string());
722            }
723        }
724        keys.into_iter().collect()
725    }
726
727    // ---------- Overrides: traversal (direct adjacency) ----------
728
729    fn all_relationship_property_keys(&self) -> Vec<String> {
730        let mut keys = BTreeSet::new();
731        for rel in self.iter_rel_refs() {
732            for key in rel.properties().keys() {
733                keys.insert(key.to_string());
734            }
735        }
736        keys.into_iter().collect()
737    }
738
739    fn label_property_keys(&self, label: &str) -> Vec<String> {
740        let mut keys = BTreeSet::new();
741
742        if let Some(ids) = self.nodes_by_label.get(label) {
743            for id in ids.iter() {
744                if let Some(node) = self.node_at(id) {
745                    for key in node.properties().keys() {
746                        keys.insert(key.to_string());
747                    }
748                }
749            }
750        }
751
752        keys.into_iter().collect()
753    }
754
755    fn rel_type_property_keys(&self, rel_type: &str) -> Vec<String> {
756        let mut keys = BTreeSet::new();
757
758        if let Some(ids) = self.relationships_by_type.get(rel_type) {
759            for id in ids.iter() {
760                if let Some(rel) = self.rel_at(id) {
761                    for key in rel.properties().keys() {
762                        keys.insert(key.to_string());
763                    }
764                }
765            }
766        }
767
768        keys.into_iter().collect()
769    }
770
771    fn find_nodes_by_property(
772        &self,
773        label: Option<&str>,
774        key: &str,
775        value: &PropertyValue,
776    ) -> Vec<NodeRecord>
777    where
778        Self: Sized,
779    {
780        if PropertyIndexKey::from_value(value).is_none() {
781            return self.scan_nodes_by_property(label, key, value);
782        }
783
784        self.ensure_node_property_index(key);
785        if label.is_none() {
786            self.ensure_any_scope_node_property_index(key);
787        }
788        let indexes = self.indexes_read();
789
790        match label {
791            Some(label) => {
792                let Some(ids) = indexes.node_properties.scoped_ids_for(label, key, value) else {
793                    return Vec::new();
794                };
795                ids.iter()
796                    .filter_map(|id| self.node_at(id).map(|r| r.to_record()))
797                    .collect()
798            }
799            None => indexes
800                .node_properties
801                .ids_for(key, value)
802                .into_iter()
803                .flat_map(|ids| ids.iter())
804                .filter_map(|id| self.node_at(id).map(|r| r.to_record()))
805                .collect(),
806        }
807    }
808
809    fn find_node_ids_by_property(
810        &self,
811        label: Option<&str>,
812        key: &str,
813        value: &PropertyValue,
814    ) -> Vec<NodeId>
815    where
816        Self: Sized,
817    {
818        if PropertyIndexKey::from_value(value).is_none() {
819            return self.scan_node_ids_by_property(label, key, value);
820        }
821
822        self.ensure_node_property_index(key);
823        if label.is_none() {
824            self.ensure_any_scope_node_property_index(key);
825        }
826        let indexes = self.indexes_read();
827
828        match label {
829            Some(label) => indexes
830                .node_properties
831                .scoped_ids_for(label, key, value)
832                .map(|ids| ids.to_vec())
833                .unwrap_or_default(),
834            None => indexes
835                .node_properties
836                .ids_for(key, value)
837                .map(|ids| ids.to_vec())
838                .unwrap_or_default(),
839        }
840    }
841    // ---------- Overrides: schema introspection ----------
842
843    fn find_relationships_by_property(
844        &self,
845        rel_type: Option<&str>,
846        key: &str,
847        value: &PropertyValue,
848    ) -> Vec<RelationshipRecord>
849    where
850        Self: Sized,
851    {
852        if PropertyIndexKey::from_value(value).is_none() {
853            return self.scan_relationships_by_property(rel_type, key, value);
854        }
855
856        self.ensure_relationship_property_index(key);
857        if rel_type.is_none() {
858            self.ensure_any_scope_relationship_property_index(key);
859        }
860        let indexes = self.indexes_read();
861
862        match rel_type {
863            Some(rel_type) => {
864                let Some(ids) = indexes
865                    .relationship_properties
866                    .scoped_ids_for(rel_type, key, value)
867                else {
868                    return Vec::new();
869                };
870                ids.iter()
871                    .filter_map(|id| self.rel_at(id).map(|r| r.to_record()))
872                    .collect()
873            }
874            None => indexes
875                .relationship_properties
876                .ids_for(key, value)
877                .into_iter()
878                .flat_map(|ids| ids.iter())
879                .filter_map(|id| self.rel_at(id).map(|r| r.to_record()))
880                .collect(),
881        }
882    }
883
884    fn find_relationship_ids_by_property(
885        &self,
886        rel_type: Option<&str>,
887        key: &str,
888        value: &PropertyValue,
889    ) -> Vec<RelationshipId>
890    where
891        Self: Sized,
892    {
893        if PropertyIndexKey::from_value(value).is_none() {
894            return self.scan_relationship_ids_by_property(rel_type, key, value);
895        }
896
897        self.ensure_relationship_property_index(key);
898        if rel_type.is_none() {
899            self.ensure_any_scope_relationship_property_index(key);
900        }
901        let indexes = self.indexes_read();
902
903        match rel_type {
904            Some(rel_type) => indexes
905                .relationship_properties
906                .scoped_ids_for(rel_type, key, value)
907                .map(|ids| ids.to_vec())
908                .unwrap_or_default(),
909            None => indexes
910                .relationship_properties
911                .ids_for(key, value)
912                .map(|ids| ids.to_vec())
913                .unwrap_or_default(),
914        }
915    }
916
917    fn node_exists_with_label_and_property(
918        &self,
919        label: &str,
920        key: &str,
921        value: &PropertyValue,
922    ) -> bool
923    where
924        Self: Sized,
925    {
926        if PropertyIndexKey::from_value(value).is_none() {
927            return self.any_node_by_property(label, key, value);
928        }
929
930        self.ensure_node_property_index(key);
931        let indexes = self.indexes_read();
932        indexes
933            .node_properties
934            .scoped_ids_for(label, key, value)
935            .map(|ids| !ids.is_empty())
936            .unwrap_or(false)
937    }
938
939    fn relationship_exists_with_type_and_property(
940        &self,
941        rel_type: &str,
942        key: &str,
943        value: &PropertyValue,
944    ) -> bool
945    where
946        Self: Sized,
947    {
948        if PropertyIndexKey::from_value(value).is_none() {
949            return self.any_relationship_by_property(rel_type, key, value);
950        }
951
952        self.ensure_relationship_property_index(key);
953        let indexes = self.indexes_read();
954        indexes
955            .relationship_properties
956            .scoped_ids_for(rel_type, key, value)
957            .map(|ids| !ids.is_empty())
958            .unwrap_or(false)
959    }
960}
961
962impl GraphStorageMut for InMemoryGraph {
963    fn try_create_node(
964        &mut self,
965        labels: Vec<String>,
966        properties: Properties,
967    ) -> Option<NodeRecord> {
968        let (id, idx) = self.try_reserve_next_node_slot()?;
969        let labels = Self::normalize_labels(labels);
970
971        let node = NodeRecord {
972            id,
973            labels,
974            properties,
975        };
976
977        self.put_node_at_slot(idx, &node);
978
979        self.on_node_created(&node);
980
981        // Reserving the slot also grew both adjacency tables to cover
982        // this id.
983
984        self.emit(|| MutationEvent::CreateNode {
985            id,
986            labels: node.labels.to_strings(),
987            properties: node.properties.clone(),
988        });
989
990        Some(node)
991    }
992
993    fn create_relationship(
994        &mut self,
995        src: NodeId,
996        dst: NodeId,
997        rel_type: &str,
998        properties: Properties,
999    ) -> Option<RelationshipRecord> {
1000        if !self.has_node_at(src) || !self.has_node_at(dst) {
1001            return None;
1002        }
1003
1004        let trimmed = rel_type.trim();
1005        if trimmed.is_empty() {
1006            return None;
1007        }
1008
1009        let (id, idx) = self.try_reserve_next_rel_slot()?;
1010        let rel = RelationshipRecord {
1011            id,
1012            src,
1013            dst,
1014            rel_type: trimmed.into(),
1015            properties,
1016        };
1017
1018        self.put_rel_at_slot(idx, &rel);
1019        self.on_relationship_created(&rel);
1020
1021        self.emit(|| MutationEvent::CreateRelationship {
1022            id,
1023            src,
1024            dst,
1025            rel_type: rel.rel_type.to_string(),
1026            properties: rel.properties.clone(),
1027        });
1028
1029        Some(rel)
1030    }
1031
1032    fn set_node_property(&mut self, node_id: NodeId, key: String, value: PropertyValue) -> bool {
1033        let Some(old) = self.edit_node_property(node_id, &key, PropEdit::Set(&value)) else {
1034            return false;
1035        };
1036        self.on_node_property_set(node_id, &key, old.as_ref(), &value);
1037
1038        self.emit(|| MutationEvent::SetNodeProperty {
1039            node_id,
1040            key: key.clone(),
1041            value: value.clone(),
1042        });
1043
1044        true
1045    }
1046
1047    fn remove_node_property(&mut self, node_id: NodeId, key: &str) -> bool {
1048        let Some(removed) = self
1049            .edit_node_property(node_id, key, PropEdit::Remove)
1050            .flatten()
1051        else {
1052            return false;
1053        };
1054
1055        self.on_node_property_removed(node_id, key, &removed);
1056
1057        self.emit(|| MutationEvent::RemoveNodeProperty {
1058            node_id,
1059            key: key.to_string(),
1060        });
1061
1062        true
1063    }
1064
1065    fn add_node_label(&mut self, node_id: NodeId, label: &str) -> bool {
1066        let label = label.trim();
1067        if label.is_empty() {
1068            return false;
1069        }
1070
1071        let applied = match self.node_at(node_id) {
1072            Some(node) if node.has_label(label) => return false,
1073            Some(_) => self
1074                .update_node(node_id, |node| node.labels.push(label))
1075                .is_some(),
1076            None => false,
1077        };
1078        if applied {
1079            self.on_node_label_added(node_id, label);
1080            self.emit(|| MutationEvent::AddNodeLabel {
1081                node_id,
1082                label: label.to_string(),
1083            });
1084        }
1085        applied
1086    }
1087
1088    fn remove_node_label(&mut self, node_id: NodeId, label: &str) -> bool {
1089        let applied = self
1090            .node_at(node_id)
1091            .is_some_and(|node| node.has_label(label))
1092            && self
1093                .update_node(node_id, |node| node.labels.retain(|l| l != label))
1094                .is_some();
1095        if applied {
1096            self.on_node_label_removed(node_id, label);
1097            self.emit(|| MutationEvent::RemoveNodeLabel {
1098                node_id,
1099                label: label.to_string(),
1100            });
1101        }
1102        applied
1103    }
1104
1105    fn set_relationship_property(
1106        &mut self,
1107        rel_id: RelationshipId,
1108        key: String,
1109        value: PropertyValue,
1110    ) -> bool {
1111        let Some(old) = self.edit_rel_property(rel_id, &key, PropEdit::Set(&value)) else {
1112            return false;
1113        };
1114        self.on_relationship_property_set(rel_id, &key, old.as_ref(), &value);
1115
1116        self.emit(|| MutationEvent::SetRelationshipProperty {
1117            rel_id,
1118            key: key.clone(),
1119            value: value.clone(),
1120        });
1121
1122        true
1123    }
1124
1125    fn remove_relationship_property(&mut self, rel_id: RelationshipId, key: &str) -> bool {
1126        let Some(removed) = self
1127            .edit_rel_property(rel_id, key, PropEdit::Remove)
1128            .flatten()
1129        else {
1130            return false;
1131        };
1132
1133        self.on_relationship_property_removed(rel_id, key, &removed);
1134
1135        self.emit(|| MutationEvent::RemoveRelationshipProperty {
1136            rel_id,
1137            key: key.to_string(),
1138        });
1139
1140        true
1141    }
1142
1143    fn delete_relationship(&mut self, rel_id: RelationshipId) -> bool {
1144        let applied = match self.take_rel(rel_id) {
1145            Some(rel) => {
1146                if let Some(sink) = &self.deleted_sink {
1147                    sink.relationship_deleted(&rel);
1148                }
1149                self.on_relationship_deleted(&rel);
1150                true
1151            }
1152            None => false,
1153        };
1154        if applied {
1155            self.emit(|| MutationEvent::DeleteRelationship { rel_id });
1156        }
1157        applied
1158    }
1159
1160    fn delete_node(&mut self, node_id: NodeId) -> bool {
1161        if !self.has_node_at(node_id) {
1162            return false;
1163        }
1164
1165        if self.has_incident_relationships(node_id) {
1166            return false;
1167        }
1168
1169        let node = match self.take_node(node_id) {
1170            Some(node) => node,
1171            None => return false,
1172        };
1173
1174        if let Some(sink) = &self.deleted_sink {
1175            sink.node_deleted(&node);
1176        }
1177        self.on_node_deleted(&node);
1178
1179        // take_node already cleared the node's adjacency lists.
1180
1181        self.emit(|| MutationEvent::DeleteNode { node_id });
1182
1183        true
1184    }
1185
1186    fn detach_delete_node(&mut self, node_id: NodeId) -> bool {
1187        if !self.has_node_at(node_id) {
1188            return false;
1189        }
1190
1191        let rel_ids: Vec<_> = self
1192            .incident_relationship_ids(node_id)
1193            .into_iter()
1194            .collect();
1195
1196        // We deliberately fire per-relationship DeleteRelationship events
1197        // here (via `delete_relationship`) and a DetachDeleteNode event at
1198        // the end. A WAL replayer that sees DetachDeleteNode can ignore the
1199        // preceding DeleteRelationship events — or, equivalently, replay
1200        // them and the DetachDeleteNode becomes a no-op on the remaining
1201        // (now-empty) node. The emit-before-delete choice costs one extra
1202        // event per mutation but keeps the replay contract simple:
1203        // "apply every event in order".
1204        for rel_id in rel_ids {
1205            let _ = self.delete_relationship(rel_id);
1206        }
1207
1208        if self.delete_node(node_id) {
1209            self.emit(|| MutationEvent::DetachDeleteNode { node_id });
1210            true
1211        } else {
1212            false
1213        }
1214    }
1215
1216    fn clear(&mut self) {
1217        // Keep the recorder across clear so observers can see the Clear
1218        // event plus whatever follows. Matches WAL semantics where the log
1219        // is the source of truth across a truncation.
1220        let recorder = self.recorder.take();
1221        let deleted_sink = self.deleted_sink.take();
1222        *self = Self::default();
1223        self.recorder = recorder;
1224        self.deleted_sink = deleted_sink;
1225        self.emit(|| MutationEvent::Clear);
1226    }
1227
1228    fn create_index(
1229        &mut self,
1230        request: IndexRequest,
1231        if_not_exists: bool,
1232    ) -> Result<CreateIndexOutcome, CreateIndexError> {
1233        // `register_index` only needs `&self` (catalog + property-index
1234        // registries are behind RwLock for interior mutability), but we
1235        // keep the trait signature `&mut self` so downstream callers can
1236        // hold a uniquely-owned mutation lock. That mirrors the pattern
1237        // used for property writes elsewhere in this impl.
1238        self.register_index(request, if_not_exists)
1239    }
1240
1241    fn drop_index(
1242        &mut self,
1243        name: &str,
1244        if_exists: bool,
1245    ) -> Result<DropIndexOutcome, DropIndexError> {
1246        self.drop_named_index(name, if_exists)
1247    }
1248
1249    fn create_constraint(
1250        &mut self,
1251        request: ConstraintRequest,
1252        if_not_exists: bool,
1253    ) -> Result<CreateConstraintOutcome, CreateConstraintError> {
1254        self.register_constraint(request, if_not_exists)
1255    }
1256
1257    fn drop_constraint(
1258        &mut self,
1259        name: &str,
1260        if_exists: bool,
1261    ) -> Result<DropConstraintOutcome, DropConstraintError> {
1262        self.drop_named_constraint(name, if_exists)
1263    }
1264}
1265
1266/// Move up to `max` of the ascending `ids` into `out` and leave `cursor`
1267/// just past the last one moved. Returns whether any id is left.
1268#[inline]
1269fn fill_page(
1270    mut ids: impl Iterator<Item = NodeId>,
1271    cursor: &mut u64,
1272    max: usize,
1273    out: &mut Vec<NodeId>,
1274) -> bool {
1275    for id in ids.by_ref().take(max) {
1276        out.push(id);
1277        *cursor = id.saturating_add(1);
1278    }
1279    ids.next().is_some()
1280}