Skip to main content

lora_store/memory/
impls.rs

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