Skip to main content

lora_store/memory/
impls.rs

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