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