1use std::collections::{BTreeMap, BTreeSet};
6use std::sync::atomic::{AtomicUsize, Ordering};
7use std::sync::{Arc, RwLock, RwLockWriteGuard};
8
9use lora_ast::Direction;
10
11use crate::{
12 DeletedRecordSink, LoraPoint, MutationEvent, MutationRecorder, NodeId, NodeRecord, Properties,
13 PropertyValue, RelationshipId, RelationshipRecord,
14};
15
16use super::chunked_vec::ChunkedVec;
17use super::constraint_catalog::{
18 ConstraintCatalog, ConstraintRequest, CreateConstraintError, CreateConstraintOutcome,
19 DropConstraintError, DropConstraintOutcome,
20};
21use super::entity_index_store::{IndexBundle, IndexRead, IndexWrite};
22use super::fulltext_index::FulltextRegistry;
23use super::hnsw::HnswParams;
24use super::index_catalog::{
25 CreateIndexError, CreateIndexOutcome, DropIndexError, DropIndexOutcome, IndexCatalog,
26 IndexDefinition, IndexRequest, StoredIndexEntity, StoredIndexKind, StoredIndexState,
27};
28use super::point_index::PointRegistry;
29#[cfg(test)]
30use super::property_index::PropertyIndexState;
31use super::property_index::{PropertyIndexKey, PropertyIndexRegistry};
32use super::secondary_index_maintenance::SecondaryIndexMutation;
33use super::sorted_property_index::SortedPropertyIndex;
34use super::stats::GraphStats;
35use super::text_index::TrigramRegistry;
36use super::vector_index::{VectorIndexProvider, VectorIndexRegistry, VectorSimilarity};
37
38pub(super) type AdjList = smallvec::SmallVec<RelationshipId, 2>;
45
46#[derive(Default)]
47pub struct InMemoryGraph {
48 pub(super) next_node_id: NodeId,
49 pub(super) next_rel_id: RelationshipId,
50
51 pub(super) nodes: ChunkedVec<Option<Arc<NodeRecord>>>,
67 pub(super) relationships: ChunkedVec<Option<Arc<RelationshipRecord>>>,
68 pub(super) live_node_count: usize,
72 pub(super) live_rel_count: usize,
73
74 pub(super) outgoing: ChunkedVec<AdjList>,
80 pub(super) incoming: ChunkedVec<AdjList>,
81
82 pub(super) nodes_by_label: BTreeMap<String, ChunkedVec<NodeId>>,
89 pub(super) relationships_by_type: BTreeMap<String, ChunkedVec<RelationshipId>>,
90
91 pub(super) indexes: IndexBundle,
99
100 pub(super) constraint_catalog: RwLock<ConstraintCatalog>,
106 pub(super) active_constraints: AtomicUsize,
110
111 pub(super) recorder: Option<Arc<dyn MutationRecorder>>,
116
117 pub(super) deleted_sink: Option<Arc<dyn DeletedRecordSink>>,
122}
123
124impl std::fmt::Debug for InMemoryGraph {
125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 f.debug_struct("InMemoryGraph")
127 .field("next_node_id", &self.next_node_id)
128 .field("next_rel_id", &self.next_rel_id)
129 .field("nodes", &self.nodes)
130 .field("relationships", &self.relationships)
131 .field("outgoing", &self.outgoing)
132 .field("incoming", &self.incoming)
133 .field("nodes_by_label", &self.nodes_by_label)
134 .field("relationships_by_type", &self.relationships_by_type)
135 .field("indexes", &self.indexes)
136 .field(
137 "active_node_property_indexes",
138 &self.active_node_property_index_count(),
139 )
140 .field(
141 "active_relationship_property_indexes",
142 &self.active_relationship_property_index_count(),
143 )
144 .field(
145 "index_catalog_entries",
146 &self
147 .indexes
148 .catalog
149 .read()
150 .map(|c| c.list().len())
151 .unwrap_or(0),
152 )
153 .field("active_constraints", &self.active_constraint_count())
154 .field(
155 "active_fulltext_indexes",
156 &self.active_fulltext_index_count(),
157 )
158 .field("recorder", &self.recorder.as_ref().map(|_| "installed"))
159 .finish()
160 }
161}
162
163impl Clone for InMemoryGraph {
164 fn clone(&self) -> Self {
165 Self {
168 next_node_id: self.next_node_id,
169 next_rel_id: self.next_rel_id,
170 nodes: self.nodes.clone(),
171 relationships: self.relationships.clone(),
172 live_node_count: self.live_node_count,
173 live_rel_count: self.live_rel_count,
174 outgoing: self.outgoing.clone(),
175 incoming: self.incoming.clone(),
176 nodes_by_label: self.nodes_by_label.clone(),
177 relationships_by_type: self.relationships_by_type.clone(),
178 indexes: self.indexes.clone(),
184 constraint_catalog: RwLock::new(self.constraint_catalog_read().clone()),
185 active_constraints: AtomicUsize::new(self.active_constraint_count()),
186 recorder: None,
187 deleted_sink: None,
188 }
189 }
190}
191
192impl InMemoryGraph {
193 pub fn new() -> Self {
194 Self::default()
195 }
196
197 pub fn with_capacity_hint(_nodes: usize, _relationships: usize) -> Self {
201 Self::default()
202 }
203
204 pub fn contains_node(&self, node_id: NodeId) -> bool {
205 self.node_at(node_id).is_some()
206 }
207
208 pub fn contains_relationship(&self, rel_id: RelationshipId) -> bool {
209 self.rel_at(rel_id).is_some()
210 }
211
212 pub fn set_mutation_recorder(&mut self, recorder: Option<Arc<dyn MutationRecorder>>) {
216 self.recorder = recorder;
217 }
218
219 pub fn set_deleted_record_sink(&mut self, sink: Option<Arc<dyn DeletedRecordSink>>) {
221 self.deleted_sink = sink;
222 }
223
224 pub fn mutation_recorder(&self) -> Option<&Arc<dyn MutationRecorder>> {
226 self.recorder.as_ref()
227 }
228
229 #[inline]
234 pub(super) fn emit<F: FnOnce() -> MutationEvent>(&self, build: F) {
235 if let Some(rec) = &self.recorder {
236 rec.record(build());
237 }
238 }
239
240 fn bump_next_node_id_past(&mut self, id: NodeId) -> Result<(), String> {
241 let next = id
242 .checked_add(1)
243 .ok_or_else(|| format!("node id {id} leaves no valid next node id"))?;
244 self.next_node_id = self.next_node_id.max(next);
245 Ok(())
246 }
247
248 fn bump_next_rel_id_past(&mut self, id: RelationshipId) -> Result<(), String> {
249 let next = id
250 .checked_add(1)
251 .ok_or_else(|| format!("relationship id {id} leaves no valid next relationship id"))?;
252 self.next_rel_id = self.next_rel_id.max(next);
253 Ok(())
254 }
255
256 pub(super) fn try_reserve_next_node_slot(&mut self) -> Option<(NodeId, usize)> {
257 let id = self.next_node_id;
258 let idx = self.ensure_node_slot_checked(id).ok()?;
259 self.bump_next_node_id_past(id).ok()?;
260 Some((id, idx))
261 }
262
263 pub(super) fn try_reserve_next_rel_slot(&mut self) -> Option<(RelationshipId, usize)> {
264 let id = self.next_rel_id;
265 let idx = self.ensure_rel_slot_checked(id).ok()?;
266 self.bump_next_rel_id_past(id).ok()?;
267 Some((id, idx))
268 }
269
270 #[inline]
276 pub(super) fn node_at(&self, id: NodeId) -> Option<&NodeRecord> {
277 self.nodes
278 .get(Self::slot_index(id)?)
279 .and_then(|s| s.as_ref())
280 .map(|arc| arc.as_ref())
281 }
282
283 #[inline]
288 pub(super) fn node_at_mut(&mut self, id: NodeId) -> Option<&mut NodeRecord> {
289 self.nodes
290 .get_mut(Self::slot_index(id)?)
291 .and_then(|s| s.as_mut())
292 .map(Arc::make_mut)
293 }
294
295 #[inline]
296 pub(super) fn rel_at(&self, id: RelationshipId) -> Option<&RelationshipRecord> {
297 self.relationships
298 .get(Self::slot_index(id)?)
299 .and_then(|s| s.as_ref())
300 .map(|arc| arc.as_ref())
301 }
302
303 #[inline]
304 pub(super) fn rel_at_mut(&mut self, id: RelationshipId) -> Option<&mut RelationshipRecord> {
305 self.relationships
306 .get_mut(Self::slot_index(id)?)
307 .and_then(|s| s.as_mut())
308 .map(Arc::make_mut)
309 }
310
311 fn slot_len_for_id(id: u64, kind: &str) -> Result<usize, String> {
315 let idx = usize::try_from(id)
316 .map_err(|_| format!("{kind} id {id} does not fit in usize on this platform"))?;
317 idx.checked_add(1)
318 .ok_or_else(|| format!("{kind} id {id} leaves no valid slab slot"))
319 }
320
321 #[inline]
322 fn slot_index(id: u64) -> Option<usize> {
323 usize::try_from(id).ok()
324 }
325
326 fn ensure_node_slot_checked(&mut self, id: NodeId) -> Result<usize, String> {
327 let target = Self::slot_len_for_id(id, "node")?;
328 if self.nodes.len() < target {
329 let additional = target - self.nodes.len();
330 self.nodes.try_reserve_exact(additional).map_err(|e| {
331 format!("node id {id} requires {target} slots, but allocation failed: {e}")
332 })?;
333 self.outgoing.try_reserve_exact(additional).map_err(|e| {
334 format!(
335 "node id {id} requires {target} adjacency slots, but allocation failed: {e}"
336 )
337 })?;
338 self.incoming.try_reserve_exact(additional).map_err(|e| {
339 format!(
340 "node id {id} requires {target} adjacency slots, but allocation failed: {e}"
341 )
342 })?;
343 self.nodes.resize_with(target, || None);
344 self.outgoing.resize_with(target, AdjList::new);
345 self.incoming.resize_with(target, AdjList::new);
346 }
347 Ok(target - 1)
348 }
349
350 fn ensure_rel_slot_checked(&mut self, id: RelationshipId) -> Result<usize, String> {
351 let target = Self::slot_len_for_id(id, "relationship")?;
352 if self.relationships.len() < target {
353 self.relationships
354 .try_reserve_exact(target - self.relationships.len())
355 .map_err(|e| {
356 format!(
357 "relationship id {id} requires {target} slots, but allocation failed: {e}"
358 )
359 })?;
360 self.relationships.resize_with(target, || None);
361 }
362 Ok(target - 1)
363 }
364
365 pub(super) fn put_node_checked(&mut self, id: NodeId, node: NodeRecord) -> Result<(), String> {
366 let idx = self.ensure_node_slot_checked(id)?;
367 self.put_node_at_slot(idx, node);
368 Ok(())
369 }
370
371 pub(super) fn put_rel_checked(
372 &mut self,
373 id: RelationshipId,
374 rel: RelationshipRecord,
375 ) -> Result<(), String> {
376 let idx = self.ensure_rel_slot_checked(id)?;
377 self.put_rel_at_slot(idx, rel);
378 Ok(())
379 }
380
381 pub(super) fn put_node_at_slot(&mut self, idx: usize, node: NodeRecord) {
382 let was_present = self.nodes[idx].is_some();
383 self.nodes[idx] = Some(Arc::new(node));
384 if !was_present {
385 self.live_node_count += 1;
386 }
387 }
388
389 pub(super) fn put_rel_at_slot(&mut self, idx: usize, rel: RelationshipRecord) {
390 let was_present = self.relationships[idx].is_some();
391 self.relationships[idx] = Some(Arc::new(rel));
392 if !was_present {
393 self.live_rel_count += 1;
394 }
395 }
396
397 pub(super) fn take_node(&mut self, id: NodeId) -> Option<NodeRecord> {
398 let idx = Self::slot_index(id)?;
399 let removed = self.nodes.get_mut(idx).and_then(|s| s.take());
400 if removed.is_some() {
401 self.live_node_count -= 1;
402 if let Some(out) = self.outgoing.get_mut(idx) {
408 out.clear();
409 }
410 if let Some(inc) = self.incoming.get_mut(idx) {
411 inc.clear();
412 }
413 }
414 removed.map(|arc| Arc::try_unwrap(arc).unwrap_or_else(|arc| (*arc).clone()))
419 }
420
421 pub(super) fn take_rel(&mut self, id: RelationshipId) -> Option<RelationshipRecord> {
422 let idx = Self::slot_index(id)?;
423 let removed = self.relationships.get_mut(idx).and_then(|s| s.take());
424 if removed.is_some() {
425 self.live_rel_count -= 1;
426 }
427 removed.map(|arc| Arc::try_unwrap(arc).unwrap_or_else(|arc| (*arc).clone()))
428 }
429
430 #[inline]
431 pub(super) fn outgoing_at(&self, id: NodeId) -> Option<&[RelationshipId]> {
432 self.outgoing
433 .get(Self::slot_index(id)?)
434 .map(|adj| adj.as_slice())
435 }
436
437 #[inline]
438 pub(super) fn incoming_at(&self, id: NodeId) -> Option<&[RelationshipId]> {
439 self.incoming
440 .get(Self::slot_index(id)?)
441 .map(|adj| adj.as_slice())
442 }
443
444 #[inline]
445 fn try_for_each_adjacent_slice<F, E>(
446 &self,
447 node_id: NodeId,
448 types: &[String],
449 adj: &[RelationshipId],
450 skip_self_loops: bool,
451 visit: &mut F,
452 ) -> Result<(), E>
453 where
454 F: FnMut(RelationshipId, NodeId) -> Result<(), E>,
455 {
456 let single_type = match types {
457 [single] => Some(single.as_str()),
458 _ => None,
459 };
460 let has_type_filter = !types.is_empty();
461
462 for &rel_id in adj {
463 let Some(rel) = self.rel_at(rel_id) else {
464 continue;
465 };
466 if skip_self_loops && rel.src == node_id && rel.dst == node_id {
467 continue;
468 }
469 if let Some(single) = single_type {
470 if rel.rel_type != single {
471 continue;
472 }
473 } else if has_type_filter && !types.iter().any(|t| t == &rel.rel_type) {
474 continue;
475 }
476 let Some(other_id) = Self::other_endpoint(rel, node_id) else {
477 continue;
478 };
479 visit(rel_id, other_id)?;
480 }
481 Ok(())
482 }
483
484 #[inline]
485 pub(super) fn try_for_each_adjacent_id_unchecked<F, E>(
486 &self,
487 node_id: NodeId,
488 direction: Direction,
489 types: &[String],
490 mut visit: F,
491 ) -> Result<(), E>
492 where
493 F: FnMut(RelationshipId, NodeId) -> Result<(), E>,
494 {
495 match direction {
496 Direction::Right => {
497 if let Some(adj) = self.outgoing_at(node_id) {
498 self.try_for_each_adjacent_slice(node_id, types, adj, false, &mut visit)?;
499 }
500 }
501 Direction::Left => {
502 if let Some(adj) = self.incoming_at(node_id) {
503 self.try_for_each_adjacent_slice(node_id, types, adj, false, &mut visit)?;
504 }
505 }
506 Direction::Undirected => {
507 if let Some(adj) = self.outgoing_at(node_id) {
508 self.try_for_each_adjacent_slice(node_id, types, adj, false, &mut visit)?;
509 }
510 if let Some(adj) = self.incoming_at(node_id) {
511 self.try_for_each_adjacent_slice(node_id, types, adj, true, &mut visit)?;
512 }
513 }
514 }
515
516 Ok(())
517 }
518
519 #[inline]
520 pub(super) fn try_for_each_adjacent_id<F, E>(
521 &self,
522 node_id: NodeId,
523 direction: Direction,
524 types: &[String],
525 visit: F,
526 ) -> Result<(), E>
527 where
528 F: FnMut(RelationshipId, NodeId) -> Result<(), E>,
529 {
530 if self.node_at(node_id).is_none() {
531 return Ok(());
532 }
533 self.try_for_each_adjacent_id_unchecked(node_id, direction, types, visit)
534 }
535
536 pub(super) fn iter_node_ids(&self) -> impl Iterator<Item = NodeId> + '_ {
537 self.nodes
538 .iter()
539 .enumerate()
540 .filter_map(|(i, slot)| slot.as_ref().map(|_| i as NodeId))
541 }
542
543 pub(super) fn iter_node_records(&self) -> impl Iterator<Item = &NodeRecord> + '_ {
544 self.nodes
545 .iter()
546 .filter_map(|s| s.as_ref())
547 .map(|arc| arc.as_ref())
548 }
549
550 pub(super) fn iter_rel_ids(&self) -> impl Iterator<Item = RelationshipId> + '_ {
551 self.relationships
552 .iter()
553 .enumerate()
554 .filter_map(|(i, slot)| slot.as_ref().map(|_| i as RelationshipId))
555 }
556
557 pub(super) fn iter_rel_records(&self) -> impl Iterator<Item = &RelationshipRecord> + '_ {
558 self.relationships
559 .iter()
560 .filter_map(|s| s.as_ref())
561 .map(|arc| arc.as_ref())
562 }
563
564 pub(super) fn iter_nodes(&self) -> impl Iterator<Item = (NodeId, &NodeRecord)> + '_ {
565 self.nodes
566 .iter()
567 .enumerate()
568 .filter_map(|(i, slot)| slot.as_ref().map(|n| (i as NodeId, n.as_ref())))
569 }
570
571 pub(super) fn iter_rels(
572 &self,
573 ) -> impl Iterator<Item = (RelationshipId, &RelationshipRecord)> + '_ {
574 self.relationships
575 .iter()
576 .enumerate()
577 .filter_map(|(i, slot)| slot.as_ref().map(|r| (i as RelationshipId, r.as_ref())))
578 }
579
580 fn outgoing_push(&mut self, node_id: NodeId, rel_id: RelationshipId) {
584 if let Ok(idx) = self.ensure_node_slot_checked(node_id) {
585 self.outgoing[idx].push(rel_id);
586 }
587 }
588
589 fn incoming_push(&mut self, node_id: NodeId, rel_id: RelationshipId) {
590 if let Ok(idx) = self.ensure_node_slot_checked(node_id) {
591 self.incoming[idx].push(rel_id);
592 }
593 }
594
595 fn outgoing_remove(&mut self, node_id: NodeId, rel_id: RelationshipId) {
598 if let Some(v) = Self::slot_index(node_id).and_then(|idx| self.outgoing.get_mut(idx)) {
599 if let Some(pos) = v.iter().position(|&id| id == rel_id) {
600 v.swap_remove(pos);
601 }
602 }
603 }
604
605 fn incoming_remove(&mut self, node_id: NodeId, rel_id: RelationshipId) {
606 if let Some(v) = Self::slot_index(node_id).and_then(|idx| self.incoming.get_mut(idx)) {
607 if let Some(pos) = v.iter().position(|&id| id == rel_id) {
608 v.swap_remove(pos);
609 }
610 }
611 }
612
613 pub(super) fn normalize_labels(labels: Vec<String>) -> Vec<String> {
614 let mut seen = BTreeSet::new();
615
616 labels
617 .into_iter()
618 .map(|s| s.trim().to_string())
619 .filter(|s| !s.is_empty())
620 .filter(|s| seen.insert(s.clone()))
621 .collect()
622 }
623
624 pub(super) fn insert_node_label_index(&mut self, node_id: NodeId, label: &str) {
625 if let Some(bucket) = self.nodes_by_label.get_mut(label) {
630 bucket.push(node_id);
631 } else {
632 self.nodes_by_label
633 .insert(label.to_string(), std::iter::once(node_id).collect());
634 }
635 }
636
637 fn remove_node_label_index(&mut self, node_id: NodeId, label: &str) {
638 if let Some(ids) = self.nodes_by_label.get_mut(label) {
639 let pos = ids.iter().position(|&id| id == node_id);
640 if let Some(pos) = pos {
641 ids.swap_remove(pos);
642 }
643 if ids.is_empty() {
644 self.nodes_by_label.remove(label);
645 }
646 }
647 }
648
649 fn insert_relationship_type_index(&mut self, rel_id: RelationshipId, rel_type: &str) {
650 if let Some(bucket) = self.relationships_by_type.get_mut(rel_type) {
652 bucket.push(rel_id);
653 } else {
654 self.relationships_by_type
655 .insert(rel_type.to_string(), std::iter::once(rel_id).collect());
656 }
657 }
658
659 fn remove_relationship_type_index(&mut self, rel_id: RelationshipId, rel_type: &str) {
660 if let Some(ids) = self.relationships_by_type.get_mut(rel_type) {
661 let pos = ids.iter().position(|&id| id == rel_id);
662 if let Some(pos) = pos {
663 ids.swap_remove(pos);
664 }
665 if ids.is_empty() {
666 self.relationships_by_type.remove(rel_type);
667 }
668 }
669 }
670
671 pub(super) fn indexed_node_ids(
675 &self,
676 label: &str,
677 key: &str,
678 value: &PropertyValue,
679 ) -> Option<Vec<NodeId>> {
680 super::property_index::PropertyIndexKey::from_value(value)?;
681 let indexes = self.indexes_read();
682 if !indexes.node_properties.is_active(key) {
683 return None;
684 }
685 Some(
686 indexes
687 .node_properties
688 .scoped_ids_for(label, key, value)
689 .map(|ids| ids.to_vec())
690 .unwrap_or_default(),
691 )
692 }
693
694 pub(super) fn indexed_rel_ids(
696 &self,
697 rel_type: &str,
698 key: &str,
699 value: &PropertyValue,
700 ) -> Option<Vec<RelationshipId>> {
701 super::property_index::PropertyIndexKey::from_value(value)?;
702 let indexes = self.indexes_read();
703 if !indexes.relationship_properties.is_active(key) {
704 return None;
705 }
706 Some(
707 indexes
708 .relationship_properties
709 .scoped_ids_for(rel_type, key, value)
710 .map(|ids| ids.to_vec())
711 .unwrap_or_default(),
712 )
713 }
714
715 pub(super) fn indexes_read(&self) -> std::sync::RwLockReadGuard<'_, PropertyIndexRegistry> {
716 self.indexes
717 .properties
718 .read()
719 .unwrap_or_else(|poisoned| poisoned.into_inner())
720 }
721
722 pub(super) fn indexes_write(&self) -> RwLockWriteGuard<'_, PropertyIndexRegistry> {
723 self.indexes
724 .properties
725 .write()
726 .unwrap_or_else(|poisoned| poisoned.into_inner())
727 }
728
729 pub(super) fn indexes_mut(&mut self) -> &mut PropertyIndexRegistry {
730 self.indexes
731 .properties
732 .get_mut()
733 .unwrap_or_else(|poisoned| poisoned.into_inner())
734 }
735
736 #[inline]
737 pub(super) fn active_node_property_index_count(&self) -> usize {
738 self.indexes
739 .active_node_property_indexes
740 .load(Ordering::Relaxed)
741 }
742
743 #[inline]
744 pub(super) fn active_relationship_property_index_count(&self) -> usize {
745 self.indexes
746 .active_relationship_property_indexes
747 .load(Ordering::Relaxed)
748 }
749
750 #[inline]
751 pub(super) fn active_constraint_count(&self) -> usize {
752 self.active_constraints.load(Ordering::Relaxed)
753 }
754
755 #[inline]
756 pub(super) fn has_active_constraints(&self) -> bool {
757 self.active_constraint_count() != 0
758 }
759
760 #[inline]
761 pub(super) fn active_fulltext_index_count(&self) -> usize {
762 self.indexes.active_fulltext_indexes.load(Ordering::Relaxed)
763 }
764
765 #[inline]
766 pub(super) fn has_active_fulltext_indexes(&self) -> bool {
767 self.active_fulltext_index_count() != 0
768 }
769
770 pub(super) fn node_property_index_is_active(&mut self, key: &str) -> bool {
771 self.active_node_property_index_count() != 0
772 && self.indexes_mut().node_properties.is_active(key)
773 }
774
775 pub(super) fn relationship_property_index_is_active(&mut self, key: &str) -> bool {
776 self.active_relationship_property_index_count() != 0
777 && self.indexes_mut().relationship_properties.is_active(key)
778 }
779
780 pub(super) fn ensure_node_property_index(&self, key: &str) {
781 {
782 let indexes = self.indexes_read();
783 if indexes.node_properties.is_active(key) {
784 return;
785 }
786 }
787
788 let mut indexes = self.indexes_write();
789 if indexes.node_properties.is_active(key) {
790 return;
791 }
792
793 for (id, node) in self.iter_nodes() {
794 if let Some(value) = node.properties.get(key) {
795 indexes.node_properties.insert_with_scopes(
796 id,
797 node.labels.iter().map(String::as_str),
798 key,
799 value,
800 );
801 }
802 }
803 if indexes.node_properties.activate(key) {
804 self.indexes
805 .active_node_property_indexes
806 .fetch_add(1, Ordering::Relaxed);
807 }
808 }
809
810 pub(super) fn ensure_relationship_property_index(&self, key: &str) {
811 {
812 let indexes = self.indexes_read();
813 if indexes.relationship_properties.is_active(key) {
814 return;
815 }
816 }
817
818 let mut indexes = self.indexes_write();
819 if indexes.relationship_properties.is_active(key) {
820 return;
821 }
822
823 for (id, rel) in self.iter_rels() {
824 if let Some(value) = rel.properties.get(key) {
825 indexes.relationship_properties.insert_with_scopes(
826 id,
827 [rel.rel_type.as_str()],
828 key,
829 value,
830 );
831 }
832 }
833 if indexes.relationship_properties.activate(key) {
834 self.indexes
835 .active_relationship_property_indexes
836 .fetch_add(1, Ordering::Relaxed);
837 }
838 }
839
840 pub(super) fn index_catalog_read(&self) -> std::sync::RwLockReadGuard<'_, IndexCatalog> {
841 self.indexes
842 .catalog
843 .read()
844 .unwrap_or_else(|poisoned| poisoned.into_inner())
845 }
846
847 pub(super) fn index_catalog_write(&self) -> RwLockWriteGuard<'_, IndexCatalog> {
848 self.indexes
849 .catalog
850 .write()
851 .unwrap_or_else(|poisoned| poisoned.into_inner())
852 }
853
854 pub(super) fn constraint_catalog_read(
855 &self,
856 ) -> std::sync::RwLockReadGuard<'_, ConstraintCatalog> {
857 self.constraint_catalog
858 .read()
859 .unwrap_or_else(|poisoned| poisoned.into_inner())
860 }
861
862 pub(super) fn constraint_catalog_write(&self) -> RwLockWriteGuard<'_, ConstraintCatalog> {
863 self.constraint_catalog
864 .write()
865 .unwrap_or_else(|poisoned| poisoned.into_inner())
866 }
867
868 #[allow(clippy::result_large_err)]
876 pub(super) fn register_index(
877 &self,
878 request: IndexRequest,
879 if_not_exists: bool,
880 ) -> Result<CreateIndexOutcome, CreateIndexError> {
881 self.register_index_with_recording(request, if_not_exists, true)
882 }
883
884 #[allow(clippy::result_large_err)]
885 fn register_index_with_recording(
886 &self,
887 request: IndexRequest,
888 if_not_exists: bool,
889 record_event: bool,
890 ) -> Result<CreateIndexOutcome, CreateIndexError> {
891 let request_for_event = record_event.then(|| request.clone());
892 let outcome = {
893 let mut catalog = self.index_catalog_write();
894 catalog.try_create(request, if_not_exists)?
895 };
896
897 if let CreateIndexOutcome::Created(def) = &outcome {
898 self.populate_index_data(def);
899 }
900
901 if matches!(outcome, CreateIndexOutcome::Created(_)) {
905 if let Some(request_for_event) = request_for_event {
906 self.emit(|| crate::MutationEvent::CreateIndex {
907 request: request_for_event,
908 if_not_exists,
909 });
910 }
911 }
912
913 Ok(outcome)
914 }
915
916 #[doc(hidden)]
920 pub fn replay_create_index(
921 &mut self,
922 request: IndexRequest,
923 if_not_exists: bool,
924 ) -> Result<(), String> {
925 if self.recorder.is_some() {
926 return Err("cannot replay create_index while a mutation recorder is installed".into());
927 }
928 self.register_index(request, if_not_exists)
929 .map(|_| ())
930 .map_err(|e| e.to_string())
931 }
932
933 #[doc(hidden)]
935 pub fn replay_drop_index(&mut self, name: &str, if_exists: bool) -> Result<(), String> {
936 if self.recorder.is_some() {
937 return Err("cannot replay drop_index while a mutation recorder is installed".into());
938 }
939 self.drop_named_index(name, if_exists)
940 .map(|_| ())
941 .map_err(|e| e.to_string())
942 }
943
944 pub(super) fn register_constraint(
950 &self,
951 request: ConstraintRequest,
952 if_not_exists: bool,
953 ) -> Result<CreateConstraintOutcome, CreateConstraintError> {
954 {
959 let constraint_catalog = self.constraint_catalog_read();
960 if let Some(existing) = constraint_catalog.find_equivalent(&request) {
961 let cloned = existing.clone();
962 drop(constraint_catalog);
963 if if_not_exists {
964 return Ok(CreateConstraintOutcome::NoOpExists(cloned));
965 }
966 return Err(CreateConstraintError::EquivalentConstraintExists(
967 cloned.name,
968 ));
969 }
970 if let Some(existing) = constraint_catalog.get(&request.name) {
971 let cloned = existing.clone();
972 drop(constraint_catalog);
973 if if_not_exists {
974 return Ok(CreateConstraintOutcome::NoOpExists(cloned));
975 }
976 return Err(CreateConstraintError::DuplicateName(cloned.name));
977 }
978 if let Some(existing) = constraint_catalog.find_same_schema(&request) {
979 let cloned = existing.clone();
980 drop(constraint_catalog);
981 if super::constraint_catalog::kinds_conflict_for_validation(
982 &cloned.kind,
983 &request.kind,
984 ) {
985 if if_not_exists {
986 return Ok(CreateConstraintOutcome::NoOpExists(cloned));
987 }
988 return Err(CreateConstraintError::ConflictingConstraint(cloned.name));
989 }
990 }
991 }
992
993 if request.kind.requires_backing_index() {
998 let idx_catalog = self.index_catalog_read();
999 if idx_catalog.get(&request.name).is_some() {
1000 return Err(CreateConstraintError::DuplicateIndexName(
1001 request.name.clone(),
1002 ));
1003 }
1004 let conflict = idx_catalog.list().into_iter().find(|def| {
1005 def.kind == StoredIndexKind::Range
1006 && def.entity == request.entity
1007 && def.label.as_deref() == Some(request.label.as_str())
1008 && def.properties == request.properties
1009 && def.name != request.name
1010 });
1011 drop(idx_catalog);
1012 if let Some(def) = conflict {
1013 return Err(CreateConstraintError::BackingIndexConflict(format!(
1014 "(:{} {{{}}}) already covered by index `{}`",
1015 request.label,
1016 request.properties.join(", "),
1017 def.name,
1018 )));
1019 }
1020 }
1021
1022 let owns_backing = request.kind.requires_backing_index();
1023 let request_for_event = request.clone();
1024 let outcome = {
1025 let mut catalog = self.constraint_catalog_write();
1026 catalog.try_create(request, if_not_exists)?
1027 };
1028
1029 if let CreateConstraintOutcome::Created(def) = &outcome {
1030 if let Err(violation) = self.validate_existing_data_for_constraint(def) {
1035 let mut catalog = self.constraint_catalog_write();
1036 let _ = catalog.try_drop(&def.name, true);
1037 return Err(CreateConstraintError::DataViolation(violation.to_string()));
1038 }
1039 }
1040
1041 if let CreateConstraintOutcome::Created(def) = &outcome {
1042 if owns_backing {
1043 let idx_request = IndexRequest {
1047 explicit_name: Some(def.name.clone()),
1048 kind: StoredIndexKind::Range,
1049 entity: def.entity,
1050 label: Some(def.label.clone()),
1051 additional_labels: Vec::new(),
1052 properties: def.properties.clone(),
1053 options: Default::default(),
1054 };
1055 if let Err(err) = self.register_index_with_recording(idx_request, true, false) {
1059 let mut catalog = self.constraint_catalog_write();
1060 let _ = catalog.try_drop(&def.name, true);
1061 return Err(CreateConstraintError::BackingIndexConflict(err.to_string()));
1062 }
1063 }
1064 self.emit(|| crate::MutationEvent::CreateConstraint {
1065 request: request_for_event,
1066 if_not_exists,
1067 });
1068 self.active_constraints.fetch_add(1, Ordering::Relaxed);
1069 }
1070
1071 Ok(outcome)
1072 }
1073
1074 #[doc(hidden)]
1076 pub fn replay_create_constraint(
1077 &mut self,
1078 request: ConstraintRequest,
1079 if_not_exists: bool,
1080 ) -> Result<(), String> {
1081 if self.recorder.is_some() {
1082 return Err(
1083 "cannot replay create_constraint while a mutation recorder is installed".into(),
1084 );
1085 }
1086 self.register_constraint(request, if_not_exists)
1087 .map(|_| ())
1088 .map_err(|e| e.to_string())
1089 }
1090
1091 #[doc(hidden)]
1093 pub fn replay_drop_constraint(&mut self, name: &str, if_exists: bool) -> Result<(), String> {
1094 if self.recorder.is_some() {
1095 return Err(
1096 "cannot replay drop_constraint while a mutation recorder is installed".into(),
1097 );
1098 }
1099 self.drop_named_constraint(name, if_exists)
1100 .map(|_| ())
1101 .map_err(|e| e.to_string())
1102 }
1103
1104 pub(super) fn drop_named_constraint(
1107 &self,
1108 name: &str,
1109 if_exists: bool,
1110 ) -> Result<DropConstraintOutcome, DropConstraintError> {
1111 let outcome = {
1112 let mut catalog = self.constraint_catalog_write();
1113 catalog.try_drop(name, if_exists)?
1114 };
1115 if let DropConstraintOutcome::Dropped(def) = &outcome {
1116 if let Some(index_name) = def.owned_index.as_deref() {
1117 let _ = self.drop_named_index_inner(index_name, true, false);
1120 }
1121 self.active_constraints.fetch_sub(1, Ordering::Relaxed);
1122 self.emit(|| crate::MutationEvent::DropConstraint {
1123 name: name.to_string(),
1124 if_exists,
1125 });
1126 }
1127 Ok(outcome)
1128 }
1129
1130 pub(super) fn drop_named_index(
1135 &self,
1136 name: &str,
1137 if_exists: bool,
1138 ) -> Result<DropIndexOutcome, DropIndexError> {
1139 self.drop_named_index_inner(name, if_exists, true)
1140 }
1141
1142 fn drop_named_index_inner(
1143 &self,
1144 name: &str,
1145 if_exists: bool,
1146 emit_event: bool,
1147 ) -> Result<DropIndexOutcome, DropIndexError> {
1148 if let Some(owner) = self
1149 .constraint_catalog_read()
1150 .constraint_owning_index(name)
1151 .cloned()
1152 {
1153 return Err(DropIndexError::ConstraintOwned {
1154 index: name.to_string(),
1155 constraint: owner.name,
1156 });
1157 }
1158
1159 let outcome = {
1160 let mut catalog = self.index_catalog_write();
1161 catalog.try_drop(name, if_exists)?
1162 };
1163 if let DropIndexOutcome::Dropped(def) = &outcome {
1164 match def.kind {
1166 StoredIndexKind::Text => {
1167 if let Some(label) = def.label.as_deref() {
1168 for prop in &def.properties {
1169 self.deactivate_text_scope(def.entity, label, prop);
1170 }
1171 }
1172 }
1173 StoredIndexKind::Range => {
1174 if let Some(label) = def.label.as_deref() {
1175 for prop in &def.properties {
1176 self.deactivate_sorted_scope(def.entity, label, prop);
1177 }
1178 }
1179 }
1180 StoredIndexKind::Point => {
1181 if let Some(label) = def.label.as_deref() {
1182 for prop in &def.properties {
1183 self.deactivate_point_scope(def.entity, label, prop);
1184 }
1185 }
1186 }
1187 StoredIndexKind::Lookup => {
1188 }
1191 StoredIndexKind::Vector => {
1192 self.deactivate_vector_index(def.entity, &def.name);
1193 }
1194 StoredIndexKind::Fulltext => {
1195 self.deactivate_fulltext_index(def.entity, &def.name);
1196 }
1197 }
1198 if emit_event {
1199 self.emit(|| crate::MutationEvent::DropIndex {
1200 name: name.to_string(),
1201 if_exists,
1202 });
1203 }
1204 }
1205 Ok(outcome)
1206 }
1207
1208 fn populate_index_data(&self, def: &IndexDefinition) {
1209 match def.kind {
1217 StoredIndexKind::Range => {
1218 for key in &def.properties {
1219 match def.entity {
1220 StoredIndexEntity::Node => self.ensure_node_property_index(key),
1221 StoredIndexEntity::Relationship => {
1222 self.ensure_relationship_property_index(key)
1223 }
1224 }
1225 if let Some(label) = def.label.as_deref() {
1226 self.activate_sorted_scope(def.entity, label, key);
1227 }
1228 }
1229 }
1230 StoredIndexKind::Text => {
1231 let label = match def.label.as_deref() {
1232 Some(l) => l,
1233 None => return,
1234 };
1235 for property in &def.properties {
1236 self.activate_text_scope(def.entity, label, property);
1237 }
1238 }
1239 StoredIndexKind::Point => {
1240 let label = match def.label.as_deref() {
1241 Some(l) => l,
1242 None => return,
1243 };
1244 let cell_size = PointRegistry::cell_size_from_options(&def.options);
1245 for property in &def.properties {
1246 self.activate_point_scope(def.entity, label, property, cell_size);
1247 }
1248 }
1249 StoredIndexKind::Fulltext => {
1250 let labels: Vec<String> = def.all_labels().map(String::from).collect();
1251 if labels.is_empty() {
1252 return;
1253 }
1254 self.activate_fulltext_index(def.entity, &def.name, &labels, &def.properties);
1255 }
1256 StoredIndexKind::Vector => {
1257 let label = match def.label.as_deref() {
1258 Some(l) => l,
1259 None => return,
1260 };
1261 let property = match def.properties.first() {
1262 Some(p) => p.as_str(),
1263 None => return,
1264 };
1265 let similarity = VectorSimilarity::from_options(&def.options)
1266 .unwrap_or(VectorSimilarity::Cosine);
1267 let provider = VectorIndexProvider::from_options(&def.options)
1268 .unwrap_or(VectorIndexProvider::Flat);
1269 let hnsw = HnswParams::from_options(&def.options);
1270 let lazy = matches!(
1271 def.options.get("vector.populate.async"),
1272 Some(super::index_catalog::IndexConfigValue::Bool(true))
1273 );
1274 self.activate_vector_index(
1275 def.entity, &def.name, label, property, similarity, provider, hnsw, lazy,
1276 );
1277 if lazy {
1278 self.index_catalog_write()
1279 .set_state(&def.name, StoredIndexState::Populating);
1280 }
1281 }
1282 StoredIndexKind::Lookup => {}
1284 }
1285 }
1286
1287 pub(super) fn text_indexes_read(
1288 &self,
1289 entity: StoredIndexEntity,
1290 ) -> IndexRead<'_, TrigramRegistry> {
1291 self.indexes.text.read(entity)
1292 }
1293
1294 pub(super) fn text_indexes_write(
1295 &self,
1296 entity: StoredIndexEntity,
1297 ) -> IndexWrite<'_, TrigramRegistry> {
1298 self.indexes.text.write(entity)
1299 }
1300
1301 pub(super) fn fulltext_indexes_read(
1302 &self,
1303 entity: StoredIndexEntity,
1304 ) -> IndexRead<'_, FulltextRegistry> {
1305 self.indexes.fulltext.read(entity)
1306 }
1307
1308 #[allow(dead_code)]
1309 pub(super) fn fulltext_indexes_write(
1310 &self,
1311 entity: StoredIndexEntity,
1312 ) -> IndexWrite<'_, FulltextRegistry> {
1313 self.indexes.fulltext.write(entity)
1314 }
1315
1316 fn activate_text_scope(&self, entity: StoredIndexEntity, label: &str, property: &str) {
1317 if !self.text_indexes_write(entity).add_scope(label, property) {
1318 return;
1319 }
1320
1321 let backfill: Vec<(u64, String)> = match entity {
1322 StoredIndexEntity::Node => self
1323 .iter_nodes()
1324 .filter(|(_, node)| node.labels.iter().any(|l| l == label))
1325 .filter_map(|(id, node)| match node.properties.get(property) {
1326 Some(PropertyValue::String(value)) => Some((id, value.clone())),
1327 _ => None,
1328 })
1329 .collect(),
1330 StoredIndexEntity::Relationship => self
1331 .iter_rels()
1332 .filter(|(_, rel)| rel.rel_type == label)
1333 .filter_map(|(id, rel)| match rel.properties.get(property) {
1334 Some(PropertyValue::String(value)) => Some((id, value.clone())),
1335 _ => None,
1336 })
1337 .collect(),
1338 };
1339
1340 let mut registry = self.text_indexes_write(entity);
1341 for (id, value) in backfill {
1342 registry.insert(label, property, id, &value);
1343 }
1344 }
1345
1346 pub(super) fn deactivate_text_scope(
1348 &self,
1349 entity: StoredIndexEntity,
1350 label: &str,
1351 property: &str,
1352 ) {
1353 self.text_indexes_write(entity)
1354 .remove_scope(label, property);
1355 }
1356
1357 fn activate_fulltext_index(
1358 &self,
1359 entity: StoredIndexEntity,
1360 name: &str,
1361 labels: &[String],
1362 properties: &[String],
1363 ) {
1364 use super::fulltext_index::{term_counts_for_properties, TermCounts};
1365
1366 {
1367 let mut registry = self.fulltext_indexes_write(entity);
1368 registry.register(name.to_string(), labels.to_vec(), properties.to_vec());
1369 }
1370 self.indexes
1371 .active_fulltext_indexes
1372 .fetch_add(1, Ordering::Relaxed);
1373
1374 let backfill: Vec<(u64, TermCounts)> = match entity {
1377 StoredIndexEntity::Node => self
1378 .iter_nodes()
1379 .filter(|(_, node)| {
1380 labels
1381 .iter()
1382 .any(|wanted| node.labels.iter().any(|l| l == wanted))
1383 })
1384 .map(|(id, node)| {
1385 let counts = term_counts_for_properties(&node.properties, properties);
1386 (id, counts)
1387 })
1388 .filter(|(_, c)| !c.is_empty())
1389 .collect(),
1390 StoredIndexEntity::Relationship => self
1391 .iter_rels()
1392 .filter(|(_, rel)| labels.iter().any(|wanted| wanted == &rel.rel_type))
1393 .map(|(id, rel)| {
1394 let counts = term_counts_for_properties(&rel.properties, properties);
1395 (id, counts)
1396 })
1397 .filter(|(_, c)| !c.is_empty())
1398 .collect(),
1399 };
1400
1401 let mut registry = self.fulltext_indexes_write(entity);
1402 if let Some(index) = registry.get_mut(name) {
1403 for (id, counts) in backfill {
1404 index.reindex_entity(id, counts);
1405 }
1406 }
1407 }
1408
1409 pub(super) fn deactivate_fulltext_index(&self, entity: StoredIndexEntity, name: &str) {
1410 self.fulltext_indexes_write(entity).deregister(name);
1411 self.indexes
1412 .active_fulltext_indexes
1413 .fetch_sub(1, Ordering::Relaxed);
1414 }
1415
1416 pub(super) fn sorted_indexes_read(
1417 &self,
1418 entity: StoredIndexEntity,
1419 ) -> IndexRead<'_, SortedPropertyIndex> {
1420 self.indexes.sorted.read(entity)
1421 }
1422
1423 pub(super) fn sorted_indexes_write(
1424 &self,
1425 entity: StoredIndexEntity,
1426 ) -> IndexWrite<'_, SortedPropertyIndex> {
1427 self.indexes.sorted.write(entity)
1428 }
1429
1430 fn activate_sorted_scope(&self, entity: StoredIndexEntity, label: &str, property: &str) {
1431 if !self.sorted_indexes_write(entity).add_scope(label, property) {
1432 return;
1433 }
1434
1435 let backfill: Vec<(u64, PropertyValue)> = match entity {
1436 StoredIndexEntity::Node => self
1437 .iter_nodes()
1438 .filter(|(_, node)| node.labels.iter().any(|l| l == label))
1439 .filter_map(|(id, node)| {
1440 node.properties
1441 .get(property)
1442 .map(|value| (id, value.clone()))
1443 })
1444 .collect(),
1445 StoredIndexEntity::Relationship => self
1446 .iter_rels()
1447 .filter(|(_, rel)| rel.rel_type == label)
1448 .filter_map(|(id, rel)| {
1449 rel.properties
1450 .get(property)
1451 .map(|value| (id, value.clone()))
1452 })
1453 .collect(),
1454 };
1455
1456 let mut registry = self.sorted_indexes_write(entity);
1457 for (id, value) in backfill {
1458 registry.insert(label, property, id, &value);
1459 }
1460 }
1461
1462 pub(super) fn deactivate_sorted_scope(
1463 &self,
1464 entity: StoredIndexEntity,
1465 label: &str,
1466 property: &str,
1467 ) {
1468 self.sorted_indexes_write(entity)
1469 .remove_scope(label, property);
1470 }
1471
1472 pub(super) fn point_indexes_read(
1473 &self,
1474 entity: StoredIndexEntity,
1475 ) -> IndexRead<'_, PointRegistry> {
1476 self.indexes.point.read(entity)
1477 }
1478
1479 pub(super) fn point_indexes_write(
1480 &self,
1481 entity: StoredIndexEntity,
1482 ) -> IndexWrite<'_, PointRegistry> {
1483 self.indexes.point.write(entity)
1484 }
1485
1486 fn activate_point_scope(
1487 &self,
1488 entity: StoredIndexEntity,
1489 label: &str,
1490 property: &str,
1491 cell_size: Option<f64>,
1492 ) {
1493 if !self
1494 .point_indexes_write(entity)
1495 .add_scope(label, property, cell_size)
1496 {
1497 return;
1498 }
1499
1500 let backfill: Vec<(u64, LoraPoint)> = match entity {
1501 StoredIndexEntity::Node => self
1502 .iter_nodes()
1503 .filter(|(_, node)| node.labels.iter().any(|l| l == label))
1504 .filter_map(|(id, node)| match node.properties.get(property) {
1505 Some(PropertyValue::Point(point)) => Some((id, point.clone())),
1506 _ => None,
1507 })
1508 .collect(),
1509 StoredIndexEntity::Relationship => self
1510 .iter_rels()
1511 .filter(|(_, rel)| rel.rel_type == label)
1512 .filter_map(|(id, rel)| match rel.properties.get(property) {
1513 Some(PropertyValue::Point(point)) => Some((id, point.clone())),
1514 _ => None,
1515 })
1516 .collect(),
1517 };
1518
1519 let mut registry = self.point_indexes_write(entity);
1520 for (id, point) in backfill {
1521 registry.insert(label, property, id, point);
1522 }
1523 }
1524
1525 pub(super) fn deactivate_point_scope(
1526 &self,
1527 entity: StoredIndexEntity,
1528 label: &str,
1529 property: &str,
1530 ) {
1531 self.point_indexes_write(entity)
1532 .remove_scope(label, property);
1533 }
1534
1535 pub(super) fn vector_indexes_read(
1536 &self,
1537 entity: StoredIndexEntity,
1538 ) -> IndexRead<'_, VectorIndexRegistry> {
1539 self.indexes.vector.read(entity)
1540 }
1541
1542 pub(super) fn vector_indexes_write(
1543 &self,
1544 entity: StoredIndexEntity,
1545 ) -> IndexWrite<'_, VectorIndexRegistry> {
1546 self.indexes.vector.write(entity)
1547 }
1548
1549 #[allow(clippy::too_many_arguments)]
1550 fn activate_vector_index(
1551 &self,
1552 entity: StoredIndexEntity,
1553 name: &str,
1554 label: &str,
1555 property: &str,
1556 similarity: VectorSimilarity,
1557 provider: VectorIndexProvider,
1558 hnsw: HnswParams,
1559 lazy: bool,
1560 ) {
1561 {
1562 let mut registry = self.vector_indexes_write(entity);
1563 registry.register(
1564 name.to_string(),
1565 label.to_string(),
1566 property.to_string(),
1567 similarity,
1568 provider,
1569 hnsw,
1570 );
1571 }
1572
1573 if lazy {
1574 return;
1581 }
1582
1583 self.backfill_vector_index(entity, label, property);
1584 }
1585
1586 fn backfill_vector_index(&self, entity: StoredIndexEntity, label: &str, property: &str) {
1590 let backfill: Vec<(u64, crate::LoraVector)> = match entity {
1591 StoredIndexEntity::Node => self
1592 .iter_nodes()
1593 .filter(|(_, node)| node.labels.iter().any(|l| l == label))
1594 .filter_map(|(id, node)| match node.properties.get(property) {
1595 Some(PropertyValue::Vector(v)) => Some((id, v.clone())),
1596 _ => None,
1597 })
1598 .collect(),
1599 StoredIndexEntity::Relationship => self
1600 .iter_rels()
1601 .filter(|(_, rel)| rel.rel_type == label)
1602 .filter_map(|(id, rel)| match rel.properties.get(property) {
1603 Some(PropertyValue::Vector(v)) => Some((id, v.clone())),
1604 _ => None,
1605 })
1606 .collect(),
1607 };
1608
1609 let mut registry = self.vector_indexes_write(entity);
1610 for (id, vector) in backfill {
1611 registry.insert_for(label, property, id, &vector);
1612 }
1613 }
1614
1615 pub(super) fn lazy_populate_vector_index(&self, name: &str) {
1621 let def = match self.index_catalog_read().get(name).cloned() {
1622 Some(d) => d,
1623 None => return,
1624 };
1625 if def.state != StoredIndexState::Populating || def.kind != StoredIndexKind::Vector {
1626 return;
1627 }
1628 let label = match def.label.as_deref() {
1629 Some(l) => l,
1630 None => return,
1631 };
1632 let property = match def.properties.first() {
1633 Some(p) => p.as_str(),
1634 None => return,
1635 };
1636 self.backfill_vector_index(def.entity, label, property);
1637 self.index_catalog_write()
1638 .set_state(name, StoredIndexState::Online);
1639 }
1640
1641 pub(super) fn deactivate_vector_index(&self, entity: StoredIndexEntity, name: &str) {
1642 self.vector_indexes_write(entity).deregister(name);
1643 }
1644
1645 pub fn graph_stats(&self) -> GraphStats {
1650 let mut stats = GraphStats {
1651 node_count: self.live_node_count,
1652 relationship_count: self.live_rel_count,
1653 ..Default::default()
1654 };
1655 for (label, ids) in &self.nodes_by_label {
1656 stats.nodes_by_label.insert(label.clone(), ids.len());
1657 }
1658 for (rel_type, ids) in &self.relationships_by_type {
1659 stats
1660 .relationships_by_type
1661 .insert(rel_type.clone(), ids.len());
1662 }
1663 let prop_indexes = self.indexes_read();
1669 for (scope, props) in &prop_indexes.node_properties.scoped_values {
1670 for (key, values) in props {
1671 stats
1672 .node_distinct_values
1673 .insert((scope.clone(), key.clone()), values.len());
1674 }
1675 }
1676 for (scope, props) in &prop_indexes.relationship_properties.scoped_values {
1677 for (key, values) in props {
1678 stats
1679 .relationship_distinct_values
1680 .insert((scope.clone(), key.clone()), values.len());
1681 }
1682 }
1683
1684 for def in self.index_catalog_read().list() {
1685 if def.state != StoredIndexState::Online {
1686 continue;
1687 }
1688 let Some(label) = def.label else {
1689 continue;
1690 };
1691 for property in def.properties {
1692 let scope = (label.clone(), property);
1693 match (def.entity, def.kind) {
1694 (StoredIndexEntity::Node, StoredIndexKind::Range) => {
1695 stats.node_range_indexes.insert(scope);
1696 }
1697 (StoredIndexEntity::Node, StoredIndexKind::Text) => {
1698 stats.node_text_indexes.insert(scope);
1699 }
1700 (StoredIndexEntity::Node, StoredIndexKind::Point) => {
1701 stats.node_point_indexes.insert(scope);
1702 }
1703 (StoredIndexEntity::Relationship, StoredIndexKind::Range) => {
1704 stats.relationship_range_indexes.insert(scope);
1705 }
1706 (StoredIndexEntity::Relationship, StoredIndexKind::Text) => {
1707 stats.relationship_text_indexes.insert(scope);
1708 }
1709 (StoredIndexEntity::Relationship, StoredIndexKind::Point) => {
1710 stats.relationship_point_indexes.insert(scope);
1711 }
1712 (StoredIndexEntity::Node, StoredIndexKind::Vector) => {
1713 stats.node_vector_indexes.insert(scope);
1714 }
1715 (StoredIndexEntity::Relationship, StoredIndexKind::Vector) => {
1716 stats.relationship_vector_indexes.insert(scope);
1717 }
1718 (_, StoredIndexKind::Lookup | StoredIndexKind::Fulltext) => {}
1719 }
1720 }
1721 }
1722 stats
1723 }
1724
1725 pub fn memory_estimate(&self) -> super::MemoryReport {
1730 super::mem_report::estimate(self)
1731 }
1732
1733 pub(super) fn rebuild_property_indexes(&mut self) {
1734 let mut indexes = PropertyIndexRegistry::default();
1735
1736 for (id, node) in self.iter_nodes() {
1737 for (key, value) in &node.properties {
1738 if PropertyIndexKey::from_value(value).is_some() {
1739 indexes.node_properties.activate(key);
1740 indexes.node_properties.insert_with_scopes(
1741 id,
1742 node.labels.iter().map(String::as_str),
1743 key,
1744 value,
1745 );
1746 }
1747 }
1748 }
1749
1750 for (id, rel) in self.iter_rels() {
1751 for (key, value) in &rel.properties {
1752 if PropertyIndexKey::from_value(value).is_some() {
1753 indexes.relationship_properties.activate(key);
1754 indexes.relationship_properties.insert_with_scopes(
1755 id,
1756 [rel.rel_type.as_str()],
1757 key,
1758 value,
1759 );
1760 }
1761 }
1762 }
1763
1764 let node_index_count = indexes.node_properties.active_keys.len();
1765 let relationship_index_count = indexes.relationship_properties.active_keys.len();
1766 *self.indexes_mut() = indexes;
1767 self.indexes
1768 .active_node_property_indexes
1769 .store(node_index_count, Ordering::Relaxed);
1770 self.indexes
1771 .active_relationship_property_indexes
1772 .store(relationship_index_count, Ordering::Relaxed);
1773 }
1774
1775 pub(super) fn on_node_created(&mut self, node: &NodeRecord) {
1776 for label in &node.labels {
1777 self.insert_node_label_index(node.id, label);
1778 }
1779 self.index_node_properties_if_active(
1780 node.id,
1781 node.labels.iter().map(String::as_str),
1782 &node.properties,
1783 );
1784 self.maintain_node_secondary_indexes(node, SecondaryIndexMutation::Insert);
1785 }
1786
1787 pub(super) fn on_node_replayed(&mut self, node: &NodeRecord) {
1788 for label in &node.labels {
1789 self.insert_node_label_index(node.id, label);
1790 }
1791 self.index_node_properties_eager(
1792 node.id,
1793 node.labels.iter().map(String::as_str),
1794 &node.properties,
1795 );
1796 self.maintain_node_secondary_indexes(node, SecondaryIndexMutation::Insert);
1797 }
1798
1799 pub(super) fn on_node_property_set(
1800 &mut self,
1801 node_id: NodeId,
1802 key: &str,
1803 old: Option<&PropertyValue>,
1804 new: &PropertyValue,
1805 ) {
1806 let Some(labels) = self.node_at(node_id).map(|node| node.labels.clone()) else {
1807 return;
1808 };
1809
1810 if self.node_property_index_is_active(key) {
1811 if let Some(old) = old {
1812 self.unindex_node_property_if_active(
1813 node_id,
1814 labels.iter().map(String::as_str),
1815 key,
1816 old,
1817 );
1818 }
1819 self.index_node_property_if_active(
1820 node_id,
1821 labels.iter().map(String::as_str),
1822 key,
1823 new,
1824 );
1825 }
1826
1827 self.update_secondary_property(
1828 StoredIndexEntity::Node,
1829 labels.iter().map(String::as_str),
1830 node_id,
1831 key,
1832 old,
1833 Some(new),
1834 );
1835 }
1836
1837 pub(super) fn on_node_property_removed(
1838 &mut self,
1839 node_id: NodeId,
1840 key: &str,
1841 old: &PropertyValue,
1842 ) {
1843 let Some(labels) = self.node_at(node_id).map(|node| node.labels.clone()) else {
1844 return;
1845 };
1846 if self.node_property_index_is_active(key) {
1847 self.unindex_node_property_if_active(
1848 node_id,
1849 labels.iter().map(String::as_str),
1850 key,
1851 old,
1852 );
1853 }
1854 self.update_secondary_property(
1855 StoredIndexEntity::Node,
1856 labels.iter().map(String::as_str),
1857 node_id,
1858 key,
1859 Some(old),
1860 None,
1861 );
1862 }
1863
1864 pub(super) fn on_node_label_added(&mut self, node_id: NodeId, label: &str) {
1865 self.insert_node_label_index(node_id, label);
1866
1867 let Some(properties) = self.node_at(node_id).map(|node| node.properties.clone()) else {
1868 return;
1869 };
1870 if self.active_node_property_index_count() != 0 {
1871 self.index_node_scope_properties_if_active(node_id, label, &properties);
1872 }
1873 for (key, value) in &properties {
1874 self.update_secondary_property(
1875 StoredIndexEntity::Node,
1876 [label],
1877 node_id,
1878 key,
1879 None,
1880 Some(value),
1881 );
1882 }
1883 }
1884
1885 pub(super) fn on_node_label_removed(&mut self, node_id: NodeId, label: &str) {
1886 self.remove_node_label_index(node_id, label);
1887
1888 let Some(properties) = self.node_at(node_id).map(|node| node.properties.clone()) else {
1889 return;
1890 };
1891 if self.active_node_property_index_count() != 0 {
1892 self.unindex_node_scope_properties_if_active(node_id, label, &properties);
1893 }
1894 for (key, value) in &properties {
1895 self.update_secondary_property(
1896 StoredIndexEntity::Node,
1897 [label],
1898 node_id,
1899 key,
1900 Some(value),
1901 None,
1902 );
1903 }
1904 }
1905
1906 pub(super) fn on_node_deleted(&mut self, node: &NodeRecord) {
1907 for label in &node.labels {
1908 self.remove_node_label_index(node.id, label);
1909 }
1910 self.unindex_active_node_properties(
1911 node.id,
1912 node.labels.iter().map(String::as_str),
1913 &node.properties,
1914 );
1915 self.maintain_node_secondary_indexes(node, SecondaryIndexMutation::Remove);
1916 }
1917
1918 pub(super) fn on_relationship_created(&mut self, rel: &RelationshipRecord) {
1919 self.attach_relationship(rel);
1920 self.index_relationship_properties_if_active(
1921 rel.id,
1922 [rel.rel_type.as_str()],
1923 &rel.properties,
1924 );
1925 self.maintain_relationship_secondary_indexes(rel, SecondaryIndexMutation::Insert);
1926 }
1927
1928 pub(super) fn on_relationship_replayed(&mut self, rel: &RelationshipRecord) {
1929 self.attach_relationship(rel);
1930 self.index_relationship_properties_eager(rel.id, [rel.rel_type.as_str()], &rel.properties);
1931 self.maintain_relationship_secondary_indexes(rel, SecondaryIndexMutation::Insert);
1932 }
1933
1934 pub(super) fn on_relationship_property_set(
1935 &mut self,
1936 rel_id: RelationshipId,
1937 key: &str,
1938 old: Option<&PropertyValue>,
1939 new: &PropertyValue,
1940 ) {
1941 let Some(rel_type) = self.rel_at(rel_id).map(|rel| rel.rel_type.clone()) else {
1942 return;
1943 };
1944
1945 if self.relationship_property_index_is_active(key) {
1946 if let Some(old) = old {
1947 self.unindex_relationship_property_if_active(rel_id, [rel_type.as_str()], key, old);
1948 }
1949 self.index_relationship_property_if_active(rel_id, [rel_type.as_str()], key, new);
1950 }
1951
1952 self.update_secondary_property(
1953 StoredIndexEntity::Relationship,
1954 [rel_type.as_str()],
1955 rel_id,
1956 key,
1957 old,
1958 Some(new),
1959 );
1960 }
1961
1962 pub(super) fn on_relationship_property_removed(
1963 &mut self,
1964 rel_id: RelationshipId,
1965 key: &str,
1966 old: &PropertyValue,
1967 ) {
1968 let Some(rel_type) = self.rel_at(rel_id).map(|rel| rel.rel_type.clone()) else {
1969 return;
1970 };
1971 if self.relationship_property_index_is_active(key) {
1972 self.unindex_relationship_property_if_active(rel_id, [rel_type.as_str()], key, old);
1973 }
1974 self.update_secondary_property(
1975 StoredIndexEntity::Relationship,
1976 [rel_type.as_str()],
1977 rel_id,
1978 key,
1979 Some(old),
1980 None,
1981 );
1982 }
1983
1984 pub(super) fn on_relationship_deleted(&mut self, rel: &RelationshipRecord) {
1985 self.detach_relationship_indexes(rel);
1986 self.unindex_active_relationship_properties(
1987 rel.id,
1988 [rel.rel_type.as_str()],
1989 &rel.properties,
1990 );
1991 self.maintain_relationship_secondary_indexes(rel, SecondaryIndexMutation::Remove);
1992 }
1993
1994 fn index_node_property_eager<'a>(
1995 &mut self,
1996 node_id: NodeId,
1997 labels: impl IntoIterator<Item = &'a str>,
1998 key: &str,
1999 value: &PropertyValue,
2000 ) {
2001 if PropertyIndexKey::from_value(value).is_none() {
2002 return;
2003 }
2004
2005 let activated = {
2006 let indexes = self.indexes_mut();
2007 let activated = indexes.node_properties.activate(key);
2008 indexes
2009 .node_properties
2010 .insert_with_scopes(node_id, labels, key, value);
2011 activated
2012 };
2013 if activated {
2014 self.indexes
2015 .active_node_property_indexes
2016 .fetch_add(1, Ordering::Relaxed);
2017 }
2018 }
2019
2020 fn index_relationship_property_eager<'a>(
2021 &mut self,
2022 rel_id: RelationshipId,
2023 scopes: impl IntoIterator<Item = &'a str>,
2024 key: &str,
2025 value: &PropertyValue,
2026 ) {
2027 if PropertyIndexKey::from_value(value).is_none() {
2028 return;
2029 }
2030
2031 let activated = {
2032 let indexes = self.indexes_mut();
2033 let activated = indexes.relationship_properties.activate(key);
2034 indexes
2035 .relationship_properties
2036 .insert_with_scopes(rel_id, scopes, key, value);
2037 activated
2038 };
2039 if activated {
2040 self.indexes
2041 .active_relationship_property_indexes
2042 .fetch_add(1, Ordering::Relaxed);
2043 }
2044 }
2045
2046 fn index_node_properties_eager<'a>(
2047 &mut self,
2048 node_id: NodeId,
2049 labels: impl IntoIterator<Item = &'a str> + Clone,
2050 properties: &Properties,
2051 ) {
2052 for (key, value) in properties {
2053 self.index_node_property_eager(node_id, labels.clone(), key, value);
2054 }
2055 }
2056
2057 fn index_relationship_properties_eager<'a>(
2058 &mut self,
2059 rel_id: RelationshipId,
2060 scopes: impl IntoIterator<Item = &'a str> + Clone,
2061 properties: &Properties,
2062 ) {
2063 for (key, value) in properties {
2064 self.index_relationship_property_eager(rel_id, scopes.clone(), key, value);
2065 }
2066 }
2067
2068 fn index_node_property_if_active<'a>(
2069 &mut self,
2070 node_id: NodeId,
2071 labels: impl IntoIterator<Item = &'a str>,
2072 key: &str,
2073 value: &PropertyValue,
2074 ) {
2075 if self.active_node_property_index_count() == 0 {
2076 return;
2077 }
2078 let indexes = self.indexes_mut();
2079 if indexes.node_properties.is_active(key) {
2080 indexes
2081 .node_properties
2082 .insert_with_scopes(node_id, labels, key, value);
2083 }
2084 }
2085
2086 fn index_node_properties_if_active<'a>(
2087 &mut self,
2088 node_id: NodeId,
2089 labels: impl IntoIterator<Item = &'a str> + Clone,
2090 properties: &Properties,
2091 ) {
2092 if self.active_node_property_index_count() == 0 {
2093 return;
2094 }
2095 let indexes = self.indexes_mut();
2096 for (key, value) in properties {
2097 if indexes.node_properties.is_active(key) {
2098 indexes
2099 .node_properties
2100 .insert_with_scopes(node_id, labels.clone(), key, value);
2101 }
2102 }
2103 }
2104
2105 fn unindex_node_property_if_active<'a>(
2106 &mut self,
2107 node_id: NodeId,
2108 labels: impl IntoIterator<Item = &'a str>,
2109 key: &str,
2110 value: &PropertyValue,
2111 ) {
2112 if self.active_node_property_index_count() == 0 {
2113 return;
2114 }
2115 let indexes = self.indexes_mut();
2116 if indexes.node_properties.is_active(key) {
2117 indexes
2118 .node_properties
2119 .remove_with_scopes(node_id, labels, key, value);
2120 }
2121 }
2122
2123 fn index_node_scope_properties_if_active(
2124 &mut self,
2125 node_id: NodeId,
2126 scope: &str,
2127 properties: &Properties,
2128 ) {
2129 if self.active_node_property_index_count() == 0 {
2130 return;
2131 }
2132 let indexes = self.indexes_mut();
2133 for (key, value) in properties {
2134 if indexes.node_properties.is_active(key) {
2135 indexes
2136 .node_properties
2137 .insert_scoped(node_id, scope, key, value);
2138 }
2139 }
2140 }
2141
2142 fn unindex_node_scope_properties_if_active(
2143 &mut self,
2144 node_id: NodeId,
2145 scope: &str,
2146 properties: &Properties,
2147 ) {
2148 if self.active_node_property_index_count() == 0 {
2149 return;
2150 }
2151 let indexes = self.indexes_mut();
2152 for (key, value) in properties {
2153 if indexes.node_properties.is_active(key) {
2154 indexes
2155 .node_properties
2156 .remove_scoped(node_id, scope, key, value);
2157 }
2158 }
2159 }
2160
2161 fn unindex_active_node_properties<'a>(
2162 &mut self,
2163 node_id: NodeId,
2164 labels: impl IntoIterator<Item = &'a str> + Clone,
2165 properties: &Properties,
2166 ) {
2167 if self.active_node_property_index_count() == 0 {
2168 return;
2169 }
2170 let indexes = self.indexes_mut();
2171 for (key, value) in properties {
2172 if indexes.node_properties.is_active(key) {
2173 indexes
2174 .node_properties
2175 .remove_with_scopes(node_id, labels.clone(), key, value);
2176 }
2177 }
2178 }
2179
2180 fn index_relationship_property_if_active<'a>(
2181 &mut self,
2182 rel_id: RelationshipId,
2183 scopes: impl IntoIterator<Item = &'a str>,
2184 key: &str,
2185 value: &PropertyValue,
2186 ) {
2187 if self.active_relationship_property_index_count() == 0 {
2188 return;
2189 }
2190 let indexes = self.indexes_mut();
2191 if indexes.relationship_properties.is_active(key) {
2192 indexes
2193 .relationship_properties
2194 .insert_with_scopes(rel_id, scopes, key, value);
2195 }
2196 }
2197
2198 fn index_relationship_properties_if_active<'a>(
2199 &mut self,
2200 rel_id: RelationshipId,
2201 scopes: impl IntoIterator<Item = &'a str> + Clone,
2202 properties: &Properties,
2203 ) {
2204 if self.active_relationship_property_index_count() == 0 {
2205 return;
2206 }
2207 let indexes = self.indexes_mut();
2208 for (key, value) in properties {
2209 if indexes.relationship_properties.is_active(key) {
2210 indexes.relationship_properties.insert_with_scopes(
2211 rel_id,
2212 scopes.clone(),
2213 key,
2214 value,
2215 );
2216 }
2217 }
2218 }
2219
2220 fn unindex_relationship_property_if_active<'a>(
2221 &mut self,
2222 rel_id: RelationshipId,
2223 scopes: impl IntoIterator<Item = &'a str>,
2224 key: &str,
2225 value: &PropertyValue,
2226 ) {
2227 if self.active_relationship_property_index_count() == 0 {
2228 return;
2229 }
2230 let indexes = self.indexes_mut();
2231 if indexes.relationship_properties.is_active(key) {
2232 indexes
2233 .relationship_properties
2234 .remove_with_scopes(rel_id, scopes, key, value);
2235 }
2236 }
2237
2238 fn unindex_active_relationship_properties<'a>(
2239 &mut self,
2240 rel_id: RelationshipId,
2241 scopes: impl IntoIterator<Item = &'a str> + Clone,
2242 properties: &Properties,
2243 ) {
2244 if self.active_relationship_property_index_count() == 0 {
2245 return;
2246 }
2247 let indexes = self.indexes_mut();
2248 for (key, value) in properties {
2249 if indexes.relationship_properties.is_active(key) {
2250 indexes.relationship_properties.remove_with_scopes(
2251 rel_id,
2252 scopes.clone(),
2253 key,
2254 value,
2255 );
2256 }
2257 }
2258 }
2259
2260 pub(super) fn scan_nodes_by_property(
2261 &self,
2262 label: Option<&str>,
2263 key: &str,
2264 value: &PropertyValue,
2265 ) -> Vec<NodeRecord> {
2266 match label {
2267 Some(label) => self
2268 .nodes_by_label
2269 .get(label)
2270 .into_iter()
2271 .flat_map(|ids| ids.iter())
2272 .filter_map(|&id| self.node_at(id))
2273 .filter(|node| node.properties.get(key) == Some(value))
2274 .cloned()
2275 .collect(),
2276 None => self
2277 .iter_node_records()
2278 .filter(|node| node.properties.get(key) == Some(value))
2279 .cloned()
2280 .collect(),
2281 }
2282 }
2283
2284 pub(super) fn scan_node_ids_by_property(
2285 &self,
2286 label: Option<&str>,
2287 key: &str,
2288 value: &PropertyValue,
2289 ) -> Vec<NodeId> {
2290 match label {
2291 Some(label) => self
2292 .nodes_by_label
2293 .get(label)
2294 .into_iter()
2295 .flat_map(|ids| ids.iter())
2296 .filter_map(|&id| {
2297 (self.node_at(id)?.properties.get(key) == Some(value)).then_some(id)
2298 })
2299 .collect(),
2300 None => self
2301 .iter_nodes()
2302 .filter_map(|(id, node)| (node.properties.get(key) == Some(value)).then_some(id))
2303 .collect(),
2304 }
2305 }
2306
2307 pub(super) fn any_node_by_property(
2308 &self,
2309 label: &str,
2310 key: &str,
2311 value: &PropertyValue,
2312 ) -> bool {
2313 self.nodes_by_label
2314 .get(label)
2315 .into_iter()
2316 .flat_map(|ids| ids.iter())
2317 .filter_map(|&id| self.node_at(id))
2318 .any(|node| node.properties.get(key) == Some(value))
2319 }
2320
2321 pub(super) fn scan_relationships_by_property(
2322 &self,
2323 rel_type: Option<&str>,
2324 key: &str,
2325 value: &PropertyValue,
2326 ) -> Vec<RelationshipRecord> {
2327 match rel_type {
2328 Some(rel_type) => self
2329 .relationships_by_type
2330 .get(rel_type)
2331 .into_iter()
2332 .flat_map(|ids| ids.iter())
2333 .filter_map(|&id| self.rel_at(id))
2334 .filter(|rel| rel.properties.get(key) == Some(value))
2335 .cloned()
2336 .collect(),
2337 None => self
2338 .iter_rel_records()
2339 .filter(|rel| rel.properties.get(key) == Some(value))
2340 .cloned()
2341 .collect(),
2342 }
2343 }
2344
2345 pub(super) fn scan_relationship_ids_by_property(
2346 &self,
2347 rel_type: Option<&str>,
2348 key: &str,
2349 value: &PropertyValue,
2350 ) -> Vec<RelationshipId> {
2351 match rel_type {
2352 Some(rel_type) => self
2353 .relationships_by_type
2354 .get(rel_type)
2355 .into_iter()
2356 .flat_map(|ids| ids.iter())
2357 .filter_map(|&id| {
2358 (self.rel_at(id)?.properties.get(key) == Some(value)).then_some(id)
2359 })
2360 .collect(),
2361 None => self
2362 .iter_rels()
2363 .filter_map(|(id, rel)| (rel.properties.get(key) == Some(value)).then_some(id))
2364 .collect(),
2365 }
2366 }
2367
2368 pub(super) fn any_relationship_by_property(
2369 &self,
2370 rel_type: &str,
2371 key: &str,
2372 value: &PropertyValue,
2373 ) -> bool {
2374 self.relationships_by_type
2375 .get(rel_type)
2376 .into_iter()
2377 .flat_map(|ids| ids.iter())
2378 .filter_map(|&id| self.rel_at(id))
2379 .any(|rel| rel.properties.get(key) == Some(value))
2380 }
2381
2382 pub(super) fn attach_relationship(&mut self, rel: &RelationshipRecord) {
2383 self.outgoing_push(rel.src, rel.id);
2384 self.incoming_push(rel.dst, rel.id);
2385 self.insert_relationship_type_index(rel.id, &rel.rel_type);
2386 }
2387
2388 fn detach_relationship_indexes(&mut self, rel: &RelationshipRecord) {
2389 self.outgoing_remove(rel.src, rel.id);
2393 self.incoming_remove(rel.dst, rel.id);
2394
2395 self.remove_relationship_type_index(rel.id, &rel.rel_type);
2396 }
2397
2398 pub(super) fn relationship_ids_for_direction(
2399 &self,
2400 node_id: NodeId,
2401 direction: Direction,
2402 ) -> Vec<RelationshipId> {
2403 match direction {
2404 Direction::Left => self
2405 .incoming_at(node_id)
2406 .map(<[_]>::to_vec)
2407 .unwrap_or_default(),
2408
2409 Direction::Right => self
2410 .outgoing_at(node_id)
2411 .map(<[_]>::to_vec)
2412 .unwrap_or_default(),
2413
2414 Direction::Undirected => {
2415 let out = self.outgoing_at(node_id);
2416 let inc = self.incoming_at(node_id);
2417 let mut ids = Vec::with_capacity(
2418 out.map(<[_]>::len).unwrap_or(0) + inc.map(<[_]>::len).unwrap_or(0),
2419 );
2420
2421 if let Some(out) = out {
2422 ids.extend(out.iter().copied());
2423 }
2424 if let Some(inc) = inc {
2425 for &rel_id in inc {
2426 let Some(rel) = self.rel_at(rel_id) else {
2427 continue;
2428 };
2429 if rel.src == node_id && rel.dst == node_id {
2430 continue;
2431 }
2432 ids.push(rel_id);
2433 }
2434 }
2435
2436 ids
2437 }
2438 }
2439 }
2440
2441 pub(super) fn other_endpoint(rel: &RelationshipRecord, node_id: NodeId) -> Option<NodeId> {
2442 if rel.src == node_id {
2443 Some(rel.dst)
2444 } else if rel.dst == node_id {
2445 Some(rel.src)
2446 } else {
2447 None
2448 }
2449 }
2450
2451 pub(super) fn has_incident_relationships(&self, node_id: NodeId) -> bool {
2452 self.outgoing_at(node_id)
2453 .map(|ids| !ids.is_empty())
2454 .unwrap_or(false)
2455 || self
2456 .incoming_at(node_id)
2457 .map(|ids| !ids.is_empty())
2458 .unwrap_or(false)
2459 }
2460
2461 pub(super) fn incident_relationship_ids(&self, node_id: NodeId) -> Vec<RelationshipId> {
2462 let out = self.outgoing_at(node_id);
2463 let inc = self.incoming_at(node_id);
2464 let mut rel_ids =
2465 Vec::with_capacity(out.map(<[_]>::len).unwrap_or(0) + inc.map(<[_]>::len).unwrap_or(0));
2466
2467 if let Some(ids) = out {
2468 rel_ids.extend(ids.iter().copied());
2469 }
2470 if let Some(ids) = inc {
2471 for &rel_id in ids {
2472 let Some(rel) = self.rel_at(rel_id) else {
2473 continue;
2474 };
2475 if rel.src == node_id && rel.dst == node_id {
2476 continue;
2477 }
2478 rel_ids.push(rel_id);
2479 }
2480 }
2481
2482 rel_ids
2483 }
2484
2485 #[doc(hidden)]
2489 pub fn replay_create_node(
2490 &mut self,
2491 id: NodeId,
2492 labels: Vec<String>,
2493 properties: Properties,
2494 ) -> Result<NodeRecord, String> {
2495 if self.recorder.is_some() {
2496 return Err(
2497 "cannot replay node creation while a mutation recorder is installed".into(),
2498 );
2499 }
2500 if self.node_at(id).is_some() {
2501 return Err(format!("node id {id} already exists"));
2502 }
2503 let idx = self.ensure_node_slot_checked(id)?;
2504 self.bump_next_node_id_past(id)?;
2505
2506 let labels = Self::normalize_labels(labels);
2507 let node = NodeRecord {
2508 id,
2509 labels: labels.clone(),
2510 properties,
2511 };
2512
2513 self.put_node_at_slot(idx, node.clone());
2514 self.on_node_replayed(&node);
2515
2516 Ok(node)
2517 }
2518
2519 #[doc(hidden)]
2523 pub fn replay_create_relationship(
2524 &mut self,
2525 id: RelationshipId,
2526 src: NodeId,
2527 dst: NodeId,
2528 rel_type: &str,
2529 properties: Properties,
2530 ) -> Result<RelationshipRecord, String> {
2531 if self.recorder.is_some() {
2532 return Err(
2533 "cannot replay relationship creation while a mutation recorder is installed".into(),
2534 );
2535 }
2536 if self.rel_at(id).is_some() {
2537 return Err(format!("relationship id {id} already exists"));
2538 }
2539 if self.node_at(src).is_none() {
2540 return Err(format!(
2541 "relationship {id} references missing source node {src}"
2542 ));
2543 }
2544 if self.node_at(dst).is_none() {
2545 return Err(format!(
2546 "relationship {id} references missing target node {dst}"
2547 ));
2548 }
2549
2550 let trimmed = rel_type.trim();
2551 if trimmed.is_empty() {
2552 return Err(format!("relationship {id} has an empty type"));
2553 }
2554 let idx = self.ensure_rel_slot_checked(id)?;
2555 self.bump_next_rel_id_past(id)?;
2556
2557 let rel = RelationshipRecord {
2558 id,
2559 src,
2560 dst,
2561 rel_type: trimmed.to_string(),
2562 properties,
2563 };
2564
2565 self.put_rel_at_slot(idx, rel.clone());
2566 self.on_relationship_replayed(&rel);
2567
2568 Ok(rel)
2569 }
2570
2571 #[cfg(test)]
2572 pub(super) fn assert_property_indexes_match_scan(&self) {
2573 let indexes = self.indexes_read();
2574 assert_eq!(
2575 indexes.node_properties.active_keys.len(),
2576 self.active_node_property_index_count(),
2577 "node property index counter diverged from active key set"
2578 );
2579 assert_eq!(
2580 indexes.relationship_properties.active_keys.len(),
2581 self.active_relationship_property_index_count(),
2582 "relationship property index counter diverged from active key set"
2583 );
2584
2585 let mut expected_nodes = PropertyIndexState {
2586 active_keys: indexes.node_properties.active_keys.clone(),
2587 ..PropertyIndexState::default()
2588 };
2589 for (id, node) in self.iter_nodes() {
2590 for (key, value) in &node.properties {
2591 if expected_nodes.is_active(key) {
2592 expected_nodes.insert_with_scopes(
2593 id,
2594 node.labels.iter().map(String::as_str),
2595 key,
2596 value,
2597 );
2598 }
2599 }
2600 }
2601 assert_eq!(
2602 indexes.node_properties.values, expected_nodes.values,
2603 "node property index values diverged from scan"
2604 );
2605 assert_eq!(
2606 indexes.node_properties.scoped_values, expected_nodes.scoped_values,
2607 "node property scoped index values diverged from scan"
2608 );
2609
2610 let mut expected_relationships = PropertyIndexState {
2611 active_keys: indexes.relationship_properties.active_keys.clone(),
2612 ..PropertyIndexState::default()
2613 };
2614 for (id, rel) in self.iter_rels() {
2615 for (key, value) in &rel.properties {
2616 if expected_relationships.is_active(key) {
2617 expected_relationships.insert_with_scopes(
2618 id,
2619 [rel.rel_type.as_str()],
2620 key,
2621 value,
2622 );
2623 }
2624 }
2625 }
2626 assert_eq!(
2627 indexes.relationship_properties.values, expected_relationships.values,
2628 "relationship property index values diverged from scan"
2629 );
2630 assert_eq!(
2631 indexes.relationship_properties.scoped_values, expected_relationships.scoped_values,
2632 "relationship property scoped index values diverged from scan"
2633 );
2634 }
2635}