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