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        self.ensure_node_property_index(key);
813        if label.is_none() {
814            self.ensure_any_scope_node_property_index(key);
815        }
816        let indexes = self.indexes_read();
817
818        match label {
819            Some(label) => {
820                let Some(ids) =
821                    indexes
822                        .node_properties
823                        .scoped_ids_for(label, key, value, &self.node_values())
824                else {
825                    return Vec::new();
826                };
827                ids.iter()
828                    .filter_map(|id| self.node_at(id).map(|r| r.to_record()))
829                    .collect()
830            }
831            None => indexes
832                .node_properties
833                .ids_for(key, value, &self.node_values())
834                .into_iter()
835                .flat_map(|ids| ids.iter())
836                .filter_map(|id| self.node_at(id).map(|r| r.to_record()))
837                .collect(),
838        }
839    }
840
841    fn find_node_ids_by_property(
842        &self,
843        label: Option<&str>,
844        key: &str,
845        value: &PropertyValue,
846    ) -> Vec<NodeId>
847    where
848        Self: Sized,
849    {
850        if PropertyIndexKey::from_value(value).is_none() {
851            return self.scan_node_ids_by_property(label, key, value);
852        }
853
854        self.ensure_node_property_index(key);
855        if label.is_none() {
856            self.ensure_any_scope_node_property_index(key);
857        }
858        let indexes = self.indexes_read();
859
860        match label {
861            Some(label) => indexes
862                .node_properties
863                .scoped_ids_for(label, key, value, &self.node_values())
864                .map(|ids| ids.to_vec())
865                .unwrap_or_default(),
866            None => indexes
867                .node_properties
868                .ids_for(key, value, &self.node_values())
869                .map(|ids| ids.to_vec())
870                .unwrap_or_default(),
871        }
872    }
873    // ---------- Overrides: schema introspection ----------
874
875    fn find_relationships_by_property(
876        &self,
877        rel_type: Option<&str>,
878        key: &str,
879        value: &PropertyValue,
880    ) -> Vec<RelationshipRecord>
881    where
882        Self: Sized,
883    {
884        if PropertyIndexKey::from_value(value).is_none() {
885            return self.scan_relationships_by_property(rel_type, key, value);
886        }
887
888        self.ensure_relationship_property_index(key);
889        if rel_type.is_none() {
890            self.ensure_any_scope_relationship_property_index(key);
891        }
892        let indexes = self.indexes_read();
893
894        match rel_type {
895            Some(rel_type) => {
896                let Some(ids) = indexes.relationship_properties.scoped_ids_for(
897                    rel_type,
898                    key,
899                    value,
900                    &self.relationship_values(),
901                ) else {
902                    return Vec::new();
903                };
904                ids.iter()
905                    .filter_map(|id| self.rel_at(id).map(|r| r.to_record()))
906                    .collect()
907            }
908            None => indexes
909                .relationship_properties
910                .ids_for(key, value, &self.relationship_values())
911                .into_iter()
912                .flat_map(|ids| ids.iter())
913                .filter_map(|id| self.rel_at(id).map(|r| r.to_record()))
914                .collect(),
915        }
916    }
917
918    fn find_relationship_ids_by_property(
919        &self,
920        rel_type: Option<&str>,
921        key: &str,
922        value: &PropertyValue,
923    ) -> Vec<RelationshipId>
924    where
925        Self: Sized,
926    {
927        if PropertyIndexKey::from_value(value).is_none() {
928            return self.scan_relationship_ids_by_property(rel_type, key, value);
929        }
930
931        self.ensure_relationship_property_index(key);
932        if rel_type.is_none() {
933            self.ensure_any_scope_relationship_property_index(key);
934        }
935        let indexes = self.indexes_read();
936
937        match rel_type {
938            Some(rel_type) => indexes
939                .relationship_properties
940                .scoped_ids_for(rel_type, key, value, &self.relationship_values())
941                .map(|ids| ids.to_vec())
942                .unwrap_or_default(),
943            None => indexes
944                .relationship_properties
945                .ids_for(key, value, &self.relationship_values())
946                .map(|ids| ids.to_vec())
947                .unwrap_or_default(),
948        }
949    }
950
951    fn node_exists_with_label_and_property(
952        &self,
953        label: &str,
954        key: &str,
955        value: &PropertyValue,
956    ) -> bool
957    where
958        Self: Sized,
959    {
960        if PropertyIndexKey::from_value(value).is_none() {
961            return self.any_node_by_property(label, key, value);
962        }
963
964        self.ensure_node_property_index(key);
965        let indexes = self.indexes_read();
966        indexes
967            .node_properties
968            .scoped_ids_for(label, key, value, &self.node_values())
969            .map(|ids| !ids.is_empty())
970            .unwrap_or(false)
971    }
972
973    fn relationship_exists_with_type_and_property(
974        &self,
975        rel_type: &str,
976        key: &str,
977        value: &PropertyValue,
978    ) -> bool
979    where
980        Self: Sized,
981    {
982        if PropertyIndexKey::from_value(value).is_none() {
983            return self.any_relationship_by_property(rel_type, key, value);
984        }
985
986        self.ensure_relationship_property_index(key);
987        let indexes = self.indexes_read();
988        indexes
989            .relationship_properties
990            .scoped_ids_for(rel_type, key, value, &self.relationship_values())
991            .map(|ids| !ids.is_empty())
992            .unwrap_or(false)
993    }
994}
995
996impl InMemoryGraph {
997    /// [`GraphStorageMut::delete_relationship`], leaving the edits of long
998    /// adjacency lists in `pending` when it is given. The caller flushes
999    /// them before anything reads adjacency again.
1000    fn delete_relationship_deferring(
1001        &mut self,
1002        rel_id: RelationshipId,
1003        pending: Option<&mut PendingAdjRemovals>,
1004    ) -> bool {
1005        let applied = match self.take_rel(rel_id) {
1006            Some(rel) => {
1007                if let Some(sink) = &self.deleted_sink {
1008                    sink.relationship_deleted(&rel);
1009                }
1010                self.on_relationship_deleted(&rel, pending);
1011                true
1012            }
1013            None => false,
1014        };
1015        if applied {
1016            self.emit(|| MutationEvent::DeleteRelationship { rel_id });
1017        }
1018        applied
1019    }
1020
1021    /// [`GraphStorageMut::detach_delete_node`] with the far ends' long
1022    /// lists left in `pending`. The node's own lists are read and cleared
1023    /// first, so an entry still pending there (from an earlier node of the
1024    /// same batch) only names a relationship that is gone, which the
1025    /// delete skips, as it would have found the entry removed.
1026    fn detach_delete_deferring(
1027        &mut self,
1028        node_id: NodeId,
1029        pending: &mut PendingAdjRemovals,
1030    ) -> bool {
1031        if !self.has_node_at(node_id) {
1032            return false;
1033        }
1034
1035        let rel_ids = self.incident_relationship_ids(node_id);
1036
1037        // We deliberately fire per-relationship DeleteRelationship events
1038        // here and a DetachDeleteNode event at the end. A WAL replayer that
1039        // sees DetachDeleteNode can ignore the preceding DeleteRelationship
1040        // events, or replay them, after which the DetachDeleteNode is a
1041        // no-op on the remaining (now-empty) node. The
1042        // emit-before-delete choice costs one extra event per mutation but
1043        // keeps the replay contract simple: "apply every event in order".
1044        //
1045        // The node's own lists go first: each removal below then edits only
1046        // the other endpoint's list (a self-loop finds nothing on either side).
1047        self.clear_adjacency(node_id);
1048        for rel_id in rel_ids {
1049            let _ = self.delete_relationship_deferring(rel_id, Some(pending));
1050        }
1051
1052        if self.delete_node(node_id) {
1053            self.emit(|| MutationEvent::DetachDeleteNode { node_id });
1054            true
1055        } else {
1056            false
1057        }
1058    }
1059}
1060
1061impl GraphStorageMut for InMemoryGraph {
1062    fn try_create_node(
1063        &mut self,
1064        labels: Vec<String>,
1065        properties: Properties,
1066    ) -> Option<NodeRecord> {
1067        let (id, idx) = self.try_reserve_next_node_slot()?;
1068        let labels = Self::normalize_labels(labels);
1069
1070        let node = NodeRecord {
1071            id,
1072            labels,
1073            properties,
1074        };
1075
1076        self.put_node_at_slot(idx, &node);
1077
1078        self.on_node_created(&node);
1079
1080        // Reserving the slot also grew both adjacency tables to cover
1081        // this id.
1082
1083        self.emit(|| MutationEvent::CreateNode {
1084            id,
1085            labels: node.labels.to_strings(),
1086            properties: node.properties.clone(),
1087        });
1088
1089        Some(node)
1090    }
1091
1092    fn create_relationship(
1093        &mut self,
1094        src: NodeId,
1095        dst: NodeId,
1096        rel_type: &str,
1097        properties: Properties,
1098    ) -> Option<RelationshipRecord> {
1099        if !self.has_node_at(src) || !self.has_node_at(dst) {
1100            return None;
1101        }
1102
1103        let trimmed = rel_type.trim();
1104        if trimmed.is_empty() {
1105            return None;
1106        }
1107
1108        let (id, idx) = self.try_reserve_next_rel_slot()?;
1109        let rel = RelationshipRecord {
1110            id,
1111            src,
1112            dst,
1113            rel_type: trimmed.into(),
1114            properties,
1115        };
1116
1117        self.put_rel_at_slot(idx, &rel);
1118        self.on_relationship_created(&rel);
1119
1120        self.emit(|| MutationEvent::CreateRelationship {
1121            id,
1122            src,
1123            dst,
1124            rel_type: rel.rel_type.to_string(),
1125            properties: rel.properties.clone(),
1126        });
1127
1128        Some(rel)
1129    }
1130
1131    fn set_node_property(&mut self, node_id: NodeId, key: String, value: PropertyValue) -> bool {
1132        let Some(old) = self.edit_node_property(node_id, &key, PropEdit::Set(&value)) else {
1133            return false;
1134        };
1135        self.on_node_property_set(node_id, &key, old.as_ref(), &value);
1136
1137        self.emit(|| MutationEvent::SetNodeProperty {
1138            node_id,
1139            key: key.clone(),
1140            value: value.clone(),
1141        });
1142
1143        true
1144    }
1145
1146    fn remove_node_property(&mut self, node_id: NodeId, key: &str) -> bool {
1147        let Some(removed) = self
1148            .edit_node_property(node_id, key, PropEdit::Remove)
1149            .flatten()
1150        else {
1151            return false;
1152        };
1153
1154        self.on_node_property_removed(node_id, key, &removed);
1155
1156        self.emit(|| MutationEvent::RemoveNodeProperty {
1157            node_id,
1158            key: key.to_string(),
1159        });
1160
1161        true
1162    }
1163
1164    fn add_node_label(&mut self, node_id: NodeId, label: &str) -> bool {
1165        let label = label.trim();
1166        if label.is_empty() {
1167            return false;
1168        }
1169
1170        let applied = match self.node_at(node_id) {
1171            Some(node) if node.has_label(label) => return false,
1172            Some(_) => self
1173                .update_node(node_id, |node| node.labels.push(label))
1174                .is_some(),
1175            None => false,
1176        };
1177        if applied {
1178            self.on_node_label_added(node_id, label);
1179            self.emit(|| MutationEvent::AddNodeLabel {
1180                node_id,
1181                label: label.to_string(),
1182            });
1183        }
1184        applied
1185    }
1186
1187    fn remove_node_label(&mut self, node_id: NodeId, label: &str) -> bool {
1188        let applied = self
1189            .node_at(node_id)
1190            .is_some_and(|node| node.has_label(label))
1191            && self
1192                .update_node(node_id, |node| node.labels.retain(|l| l != label))
1193                .is_some();
1194        if applied {
1195            self.on_node_label_removed(node_id, label);
1196            self.emit(|| MutationEvent::RemoveNodeLabel {
1197                node_id,
1198                label: label.to_string(),
1199            });
1200        }
1201        applied
1202    }
1203
1204    fn set_relationship_property(
1205        &mut self,
1206        rel_id: RelationshipId,
1207        key: String,
1208        value: PropertyValue,
1209    ) -> bool {
1210        let Some(old) = self.edit_rel_property(rel_id, &key, PropEdit::Set(&value)) else {
1211            return false;
1212        };
1213        self.on_relationship_property_set(rel_id, &key, old.as_ref(), &value);
1214
1215        self.emit(|| MutationEvent::SetRelationshipProperty {
1216            rel_id,
1217            key: key.clone(),
1218            value: value.clone(),
1219        });
1220
1221        true
1222    }
1223
1224    fn remove_relationship_property(&mut self, rel_id: RelationshipId, key: &str) -> bool {
1225        let Some(removed) = self
1226            .edit_rel_property(rel_id, key, PropEdit::Remove)
1227            .flatten()
1228        else {
1229            return false;
1230        };
1231
1232        self.on_relationship_property_removed(rel_id, key, &removed);
1233
1234        self.emit(|| MutationEvent::RemoveRelationshipProperty {
1235            rel_id,
1236            key: key.to_string(),
1237        });
1238
1239        true
1240    }
1241
1242    fn delete_relationship(&mut self, rel_id: RelationshipId) -> bool {
1243        self.delete_relationship_deferring(rel_id, None)
1244    }
1245
1246    fn delete_relationships(&mut self, rel_ids: &[RelationshipId]) -> usize {
1247        let mut pending = PendingAdjRemovals::new();
1248        let deleted = rel_ids
1249            .iter()
1250            .filter(|&&rel_id| self.delete_relationship_deferring(rel_id, Some(&mut pending)))
1251            .count();
1252        self.flush_adjacency_removals(&mut pending);
1253        deleted
1254    }
1255
1256    fn delete_node(&mut self, node_id: NodeId) -> bool {
1257        if !self.has_node_at(node_id) {
1258            return false;
1259        }
1260
1261        if self.has_incident_relationships(node_id) {
1262            return false;
1263        }
1264
1265        let node = match self.take_node(node_id) {
1266            Some(node) => node,
1267            None => return false,
1268        };
1269
1270        if let Some(sink) = &self.deleted_sink {
1271            sink.node_deleted(&node);
1272        }
1273        self.on_node_deleted(&node);
1274
1275        // take_node already cleared the node's adjacency lists.
1276
1277        self.emit(|| MutationEvent::DeleteNode { node_id });
1278
1279        true
1280    }
1281
1282    fn detach_delete_node(&mut self, node_id: NodeId) -> bool {
1283        self.detach_delete_nodes(&[node_id]) == 1
1284    }
1285
1286    fn detach_delete_nodes(&mut self, node_ids: &[NodeId]) -> usize {
1287        let mut pending = PendingAdjRemovals::new();
1288        let deleted = node_ids
1289            .iter()
1290            .filter(|&&node_id| self.detach_delete_deferring(node_id, &mut pending))
1291            .count();
1292        self.flush_adjacency_removals(&mut pending);
1293        deleted
1294    }
1295
1296    fn clear(&mut self) {
1297        // Keep the recorder across clear so observers can see the Clear
1298        // event plus whatever follows. Matches WAL semantics where the log
1299        // is the source of truth across a truncation.
1300        let recorder = self.recorder.take();
1301        let deleted_sink = self.deleted_sink.take();
1302        *self = Self::default();
1303        self.recorder = recorder;
1304        self.deleted_sink = deleted_sink;
1305        self.emit(|| MutationEvent::Clear);
1306    }
1307
1308    fn create_index(
1309        &mut self,
1310        request: IndexRequest,
1311        if_not_exists: bool,
1312    ) -> Result<CreateIndexOutcome, CreateIndexError> {
1313        // `register_index` only needs `&self` (catalog + property-index
1314        // registries are behind RwLock for interior mutability), but we
1315        // keep the trait signature `&mut self` so downstream callers can
1316        // hold a uniquely-owned mutation lock. That mirrors the pattern
1317        // used for property writes elsewhere in this impl.
1318        self.register_index(request, if_not_exists)
1319    }
1320
1321    fn drop_index(
1322        &mut self,
1323        name: &str,
1324        if_exists: bool,
1325    ) -> Result<DropIndexOutcome, DropIndexError> {
1326        self.drop_named_index(name, if_exists)
1327    }
1328
1329    fn create_constraint(
1330        &mut self,
1331        request: ConstraintRequest,
1332        if_not_exists: bool,
1333    ) -> Result<CreateConstraintOutcome, CreateConstraintError> {
1334        self.register_constraint(request, if_not_exists)
1335    }
1336
1337    fn drop_constraint(
1338        &mut self,
1339        name: &str,
1340        if_exists: bool,
1341    ) -> Result<DropConstraintOutcome, DropConstraintError> {
1342        self.drop_named_constraint(name, if_exists)
1343    }
1344}
1345
1346/// Move up to `max` of the ascending `ids` into `out` and leave `cursor`
1347/// just past the last one moved. Returns whether any id is left.
1348#[inline]
1349fn fill_page(
1350    mut ids: impl Iterator<Item = NodeId>,
1351    cursor: &mut u64,
1352    max: usize,
1353    out: &mut Vec<NodeId>,
1354) -> bool {
1355    for id in ids.by_ref().take(max) {
1356        out.push(id);
1357        *cursor = id.saturating_add(1);
1358    }
1359    ids.next().is_some()
1360}