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