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