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::{
22 read_shared, share, write_shared, IndexBundle, IndexRead, IndexWrite,
23};
24use super::fulltext_index::FulltextRegistry;
25use super::hnsw::HnswParams;
26use super::index_catalog::{
27 CreateIndexError, CreateIndexOutcome, DropIndexError, DropIndexOutcome, IndexCatalog,
28 IndexDefinition, IndexRequest, StoredIndexEntity, StoredIndexKind, StoredIndexState,
29};
30use super::point_index::PointRegistry;
31use super::property_index::PropertyIndexRegistry;
32#[cfg(test)]
33use super::property_index::PropertyIndexState;
34use super::secondary_index_maintenance::SecondaryIndexMutation;
35use super::sorted_property_index::SortedPropertyIndex;
36use super::stats::GraphStats;
37use super::text_index::TrigramRegistry;
38use super::vector_index::{VectorIndexProvider, VectorIndexRegistry, VectorSimilarity};
39
40pub(super) type AdjList = smallvec::SmallVec<RelationshipId, 2>;
47
48#[derive(Default)]
49pub struct InMemoryGraph {
50 pub(super) next_node_id: NodeId,
51 pub(super) next_rel_id: RelationshipId,
52
53 pub(super) nodes: ChunkedVec<Option<Arc<NodeRecord>>>,
69 pub(super) relationships: ChunkedVec<Option<Arc<RelationshipRecord>>>,
70 pub(super) live_node_count: usize,
74 pub(super) live_rel_count: usize,
75
76 pub(super) outgoing: ChunkedVec<AdjList>,
82 pub(super) incoming: ChunkedVec<AdjList>,
83
84 pub(super) nodes_by_label: BTreeMap<String, ChunkedVec<NodeId>>,
91 pub(super) relationships_by_type: BTreeMap<String, ChunkedVec<RelationshipId>>,
92
93 pub(super) indexes: IndexBundle,
101
102 pub(super) constraint_catalog: RwLock<Arc<ConstraintCatalog>>,
108 pub(super) active_constraints: AtomicUsize,
112
113 pub(super) recorder: Option<Arc<dyn MutationRecorder>>,
118
119 pub(super) deleted_sink: Option<Arc<dyn DeletedRecordSink>>,
124}
125
126impl std::fmt::Debug for InMemoryGraph {
127 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128 f.debug_struct("InMemoryGraph")
129 .field("next_node_id", &self.next_node_id)
130 .field("next_rel_id", &self.next_rel_id)
131 .field("nodes", &self.nodes)
132 .field("relationships", &self.relationships)
133 .field("outgoing", &self.outgoing)
134 .field("incoming", &self.incoming)
135 .field("nodes_by_label", &self.nodes_by_label)
136 .field("relationships_by_type", &self.relationships_by_type)
137 .field("indexes", &self.indexes)
138 .field(
139 "active_node_property_indexes",
140 &self.active_node_property_index_count(),
141 )
142 .field(
143 "active_relationship_property_indexes",
144 &self.active_relationship_property_index_count(),
145 )
146 .field(
147 "index_catalog_entries",
148 &self
149 .indexes
150 .catalog
151 .read()
152 .map(|c| c.list().len())
153 .unwrap_or(0),
154 )
155 .field("active_constraints", &self.active_constraint_count())
156 .field(
157 "active_fulltext_indexes",
158 &self.active_fulltext_index_count(),
159 )
160 .field("recorder", &self.recorder.as_ref().map(|_| "installed"))
161 .finish()
162 }
163}
164
165impl Clone for InMemoryGraph {
166 fn clone(&self) -> Self {
167 Self {
170 next_node_id: self.next_node_id,
171 next_rel_id: self.next_rel_id,
172 nodes: self.nodes.clone(),
173 relationships: self.relationships.clone(),
174 live_node_count: self.live_node_count,
175 live_rel_count: self.live_rel_count,
176 outgoing: self.outgoing.clone(),
177 incoming: self.incoming.clone(),
178 nodes_by_label: self.nodes_by_label.clone(),
179 relationships_by_type: self.relationships_by_type.clone(),
180 indexes: self.indexes.clone(),
183 constraint_catalog: share(&self.constraint_catalog),
184 active_constraints: AtomicUsize::new(self.active_constraint_count()),
185 recorder: None,
186 deleted_sink: None,
187 }
188 }
189}
190
191impl InMemoryGraph {
192 pub fn new() -> Self {
193 Self::default()
194 }
195
196 pub fn with_capacity_hint(_nodes: usize, _relationships: usize) -> Self {
200 Self::default()
201 }
202
203 pub fn contains_node(&self, node_id: NodeId) -> bool {
204 self.node_at(node_id).is_some()
205 }
206
207 pub fn contains_relationship(&self, rel_id: RelationshipId) -> bool {
208 self.rel_at(rel_id).is_some()
209 }
210
211 pub fn set_mutation_recorder(&mut self, recorder: Option<Arc<dyn MutationRecorder>>) {
215 self.recorder = recorder;
216 }
217
218 pub fn set_deleted_record_sink(&mut self, sink: Option<Arc<dyn DeletedRecordSink>>) {
220 self.deleted_sink = sink;
221 }
222
223 pub fn mutation_recorder(&self) -> Option<&Arc<dyn MutationRecorder>> {
225 self.recorder.as_ref()
226 }
227
228 #[inline]
233 pub(super) fn emit<F: FnOnce() -> MutationEvent>(&self, build: F) {
234 if let Some(rec) = &self.recorder {
235 rec.record(build());
236 }
237 }
238
239 fn bump_next_node_id_past(&mut self, id: NodeId) -> Result<(), String> {
240 let next = id
241 .checked_add(1)
242 .ok_or_else(|| format!("node id {id} leaves no valid next node id"))?;
243 self.next_node_id = self.next_node_id.max(next);
244 Ok(())
245 }
246
247 fn bump_next_rel_id_past(&mut self, id: RelationshipId) -> Result<(), String> {
248 let next = id
249 .checked_add(1)
250 .ok_or_else(|| format!("relationship id {id} leaves no valid next relationship id"))?;
251 self.next_rel_id = self.next_rel_id.max(next);
252 Ok(())
253 }
254
255 pub(super) fn try_reserve_next_node_slot(&mut self) -> Option<(NodeId, usize)> {
256 let id = self.next_node_id;
257 let idx = self.ensure_node_slot_checked(id).ok()?;
258 self.bump_next_node_id_past(id).ok()?;
259 Some((id, idx))
260 }
261
262 pub(super) fn try_reserve_next_rel_slot(&mut self) -> Option<(RelationshipId, usize)> {
263 let id = self.next_rel_id;
264 let idx = self.ensure_rel_slot_checked(id).ok()?;
265 self.bump_next_rel_id_past(id).ok()?;
266 Some((id, idx))
267 }
268
269 #[inline]
275 pub(super) fn node_at(&self, id: NodeId) -> Option<&NodeRecord> {
276 self.nodes
277 .get(Self::slot_index(id)?)
278 .and_then(|s| s.as_ref())
279 .map(|arc| arc.as_ref())
280 }
281
282 #[inline]
287 pub(super) fn node_at_mut(&mut self, id: NodeId) -> Option<&mut NodeRecord> {
288 self.nodes
289 .get_mut(Self::slot_index(id)?)
290 .and_then(|s| s.as_mut())
291 .map(Arc::make_mut)
292 }
293
294 #[inline]
295 pub(super) fn rel_at(&self, id: RelationshipId) -> Option<&RelationshipRecord> {
296 self.relationships
297 .get(Self::slot_index(id)?)
298 .and_then(|s| s.as_ref())
299 .map(|arc| arc.as_ref())
300 }
301
302 #[inline]
303 pub(super) fn rel_at_mut(&mut self, id: RelationshipId) -> Option<&mut RelationshipRecord> {
304 self.relationships
305 .get_mut(Self::slot_index(id)?)
306 .and_then(|s| s.as_mut())
307 .map(Arc::make_mut)
308 }
309
310 fn slot_len_for_id(id: u64, kind: &str) -> Result<usize, String> {
314 let idx = usize::try_from(id)
315 .map_err(|_| format!("{kind} id {id} does not fit in usize on this platform"))?;
316 idx.checked_add(1)
317 .ok_or_else(|| format!("{kind} id {id} leaves no valid slab slot"))
318 }
319
320 #[inline]
321 fn slot_index(id: u64) -> Option<usize> {
322 usize::try_from(id).ok()
323 }
324
325 fn ensure_node_slot_checked(&mut self, id: NodeId) -> Result<usize, String> {
326 let target = Self::slot_len_for_id(id, "node")?;
327 if self.nodes.len() < target {
328 let additional = target - self.nodes.len();
329 self.nodes.try_reserve_exact(additional).map_err(|e| {
330 format!("node id {id} requires {target} slots, but allocation failed: {e}")
331 })?;
332 self.outgoing.try_reserve_exact(additional).map_err(|e| {
333 format!(
334 "node id {id} requires {target} adjacency slots, but allocation failed: {e}"
335 )
336 })?;
337 self.incoming.try_reserve_exact(additional).map_err(|e| {
338 format!(
339 "node id {id} requires {target} adjacency slots, but allocation failed: {e}"
340 )
341 })?;
342 self.nodes.resize_with(target, || None);
343 self.outgoing.resize_with(target, AdjList::new);
344 self.incoming.resize_with(target, AdjList::new);
345 }
346 Ok(target - 1)
347 }
348
349 fn ensure_rel_slot_checked(&mut self, id: RelationshipId) -> Result<usize, String> {
350 let target = Self::slot_len_for_id(id, "relationship")?;
351 if self.relationships.len() < target {
352 self.relationships
353 .try_reserve_exact(target - self.relationships.len())
354 .map_err(|e| {
355 format!(
356 "relationship id {id} requires {target} slots, but allocation failed: {e}"
357 )
358 })?;
359 self.relationships.resize_with(target, || None);
360 }
361 Ok(target - 1)
362 }
363
364 pub(super) fn put_node_checked(&mut self, id: NodeId, node: NodeRecord) -> Result<(), String> {
365 let idx = self.ensure_node_slot_checked(id)?;
366 self.put_node_at_slot(idx, node);
367 Ok(())
368 }
369
370 pub(super) fn put_rel_checked(
371 &mut self,
372 id: RelationshipId,
373 rel: RelationshipRecord,
374 ) -> Result<(), String> {
375 let idx = self.ensure_rel_slot_checked(id)?;
376 self.put_rel_at_slot(idx, rel);
377 Ok(())
378 }
379
380 pub(super) fn put_node_at_slot(&mut self, idx: usize, node: NodeRecord) {
381 let was_present = self.nodes[idx].is_some();
382 self.nodes[idx] = Some(Arc::new(node));
383 if !was_present {
384 self.live_node_count += 1;
385 }
386 }
387
388 pub(super) fn put_rel_at_slot(&mut self, idx: usize, rel: RelationshipRecord) {
389 let was_present = self.relationships[idx].is_some();
390 self.relationships[idx] = Some(Arc::new(rel));
391 if !was_present {
392 self.live_rel_count += 1;
393 }
394 }
395
396 pub(super) fn take_node(&mut self, id: NodeId) -> Option<NodeRecord> {
397 let idx = Self::slot_index(id)?;
398 let removed = self.nodes.get_mut(idx).and_then(|s| s.take());
399 if removed.is_some() {
400 self.live_node_count -= 1;
401 if let Some(out) = self.outgoing.get_mut(idx) {
407 out.clear();
408 }
409 if let Some(inc) = self.incoming.get_mut(idx) {
410 inc.clear();
411 }
412 }
413 removed.map(|arc| Arc::try_unwrap(arc).unwrap_or_else(|arc| (*arc).clone()))
418 }
419
420 pub(super) fn take_rel(&mut self, id: RelationshipId) -> Option<RelationshipRecord> {
421 let idx = Self::slot_index(id)?;
422 let removed = self.relationships.get_mut(idx).and_then(|s| s.take());
423 if removed.is_some() {
424 self.live_rel_count -= 1;
425 }
426 removed.map(|arc| Arc::try_unwrap(arc).unwrap_or_else(|arc| (*arc).clone()))
427 }
428
429 #[inline]
430 pub(super) fn outgoing_at(&self, id: NodeId) -> Option<&[RelationshipId]> {
431 self.outgoing
432 .get(Self::slot_index(id)?)
433 .map(|adj| adj.as_slice())
434 }
435
436 #[inline]
437 pub(super) fn incoming_at(&self, id: NodeId) -> Option<&[RelationshipId]> {
438 self.incoming
439 .get(Self::slot_index(id)?)
440 .map(|adj| adj.as_slice())
441 }
442
443 #[inline]
444 fn try_for_each_adjacent_slice<F, E>(
445 &self,
446 node_id: NodeId,
447 types: &[String],
448 adj: &[RelationshipId],
449 skip_self_loops: bool,
450 visit: &mut F,
451 ) -> Result<(), E>
452 where
453 F: FnMut(RelationshipId, NodeId) -> Result<(), E>,
454 {
455 let single_type = match types {
456 [single] => Some(single.as_str()),
457 _ => None,
458 };
459 let has_type_filter = !types.is_empty();
460
461 for &rel_id in adj {
462 let Some(rel) = self.rel_at(rel_id) else {
463 continue;
464 };
465 if skip_self_loops && rel.src == node_id && rel.dst == node_id {
466 continue;
467 }
468 if let Some(single) = single_type {
469 if rel.rel_type != single {
470 continue;
471 }
472 } else if has_type_filter && !types.iter().any(|t| t == &rel.rel_type) {
473 continue;
474 }
475 let Some(other_id) = Self::other_endpoint(rel, node_id) else {
476 continue;
477 };
478 visit(rel_id, other_id)?;
479 }
480 Ok(())
481 }
482
483 #[inline]
484 pub(super) fn try_for_each_adjacent_id_unchecked<F, E>(
485 &self,
486 node_id: NodeId,
487 direction: Direction,
488 types: &[String],
489 mut visit: F,
490 ) -> Result<(), E>
491 where
492 F: FnMut(RelationshipId, NodeId) -> Result<(), E>,
493 {
494 match direction {
495 Direction::Right => {
496 if let Some(adj) = self.outgoing_at(node_id) {
497 self.try_for_each_adjacent_slice(node_id, types, adj, false, &mut visit)?;
498 }
499 }
500 Direction::Left => {
501 if let Some(adj) = self.incoming_at(node_id) {
502 self.try_for_each_adjacent_slice(node_id, types, adj, false, &mut visit)?;
503 }
504 }
505 Direction::Undirected => {
506 if let Some(adj) = self.outgoing_at(node_id) {
507 self.try_for_each_adjacent_slice(node_id, types, adj, false, &mut visit)?;
508 }
509 if let Some(adj) = self.incoming_at(node_id) {
510 self.try_for_each_adjacent_slice(node_id, types, adj, true, &mut visit)?;
511 }
512 }
513 }
514
515 Ok(())
516 }
517
518 #[inline]
519 pub(super) fn try_for_each_adjacent_id<F, E>(
520 &self,
521 node_id: NodeId,
522 direction: Direction,
523 types: &[String],
524 visit: F,
525 ) -> Result<(), E>
526 where
527 F: FnMut(RelationshipId, NodeId) -> Result<(), E>,
528 {
529 if self.node_at(node_id).is_none() {
530 return Ok(());
531 }
532 self.try_for_each_adjacent_id_unchecked(node_id, direction, types, visit)
533 }
534
535 pub(super) fn iter_node_ids(&self) -> impl Iterator<Item = NodeId> + '_ {
536 self.nodes
537 .iter()
538 .enumerate()
539 .filter_map(|(i, slot)| slot.as_ref().map(|_| i as NodeId))
540 }
541
542 pub(super) fn iter_node_records(&self) -> impl Iterator<Item = &NodeRecord> + '_ {
543 self.nodes
544 .iter()
545 .filter_map(|s| s.as_ref())
546 .map(|arc| arc.as_ref())
547 }
548
549 pub(super) fn iter_rel_ids(&self) -> impl Iterator<Item = RelationshipId> + '_ {
550 self.relationships
551 .iter()
552 .enumerate()
553 .filter_map(|(i, slot)| slot.as_ref().map(|_| i as RelationshipId))
554 }
555
556 pub(super) fn iter_rel_records(&self) -> impl Iterator<Item = &RelationshipRecord> + '_ {
557 self.relationships
558 .iter()
559 .filter_map(|s| s.as_ref())
560 .map(|arc| arc.as_ref())
561 }
562
563 pub(super) fn iter_nodes(&self) -> impl Iterator<Item = (NodeId, &NodeRecord)> + '_ {
564 self.nodes
565 .iter()
566 .enumerate()
567 .filter_map(|(i, slot)| slot.as_ref().map(|n| (i as NodeId, n.as_ref())))
568 }
569
570 pub(super) fn iter_rels(
571 &self,
572 ) -> impl Iterator<Item = (RelationshipId, &RelationshipRecord)> + '_ {
573 self.relationships
574 .iter()
575 .enumerate()
576 .filter_map(|(i, slot)| slot.as_ref().map(|r| (i as RelationshipId, r.as_ref())))
577 }
578
579 fn outgoing_push(&mut self, node_id: NodeId, rel_id: RelationshipId) {
583 if let Ok(idx) = self.ensure_node_slot_checked(node_id) {
584 self.outgoing[idx].push(rel_id);
585 }
586 }
587
588 fn incoming_push(&mut self, node_id: NodeId, rel_id: RelationshipId) {
589 if let Ok(idx) = self.ensure_node_slot_checked(node_id) {
590 self.incoming[idx].push(rel_id);
591 }
592 }
593
594 fn outgoing_remove(&mut self, node_id: NodeId, rel_id: RelationshipId) {
597 if let Some(v) = Self::slot_index(node_id).and_then(|idx| self.outgoing.get_mut(idx)) {
598 if let Some(pos) = v.iter().position(|&id| id == rel_id) {
599 v.swap_remove(pos);
600 }
601 }
602 }
603
604 fn incoming_remove(&mut self, node_id: NodeId, rel_id: RelationshipId) {
605 if let Some(v) = Self::slot_index(node_id).and_then(|idx| self.incoming.get_mut(idx)) {
606 if let Some(pos) = v.iter().position(|&id| id == rel_id) {
607 v.swap_remove(pos);
608 }
609 }
610 }
611
612 pub(super) fn normalize_labels(labels: Vec<String>) -> Vec<String> {
613 let mut seen = BTreeSet::new();
614
615 labels
616 .into_iter()
617 .map(|s| s.trim().to_string())
618 .filter(|s| !s.is_empty())
619 .filter(|s| seen.insert(s.clone()))
620 .collect()
621 }
622
623 pub(super) fn insert_node_label_index(&mut self, node_id: NodeId, label: &str) {
624 if let Some(bucket) = self.nodes_by_label.get_mut(label) {
629 bucket.push(node_id);
630 } else {
631 self.nodes_by_label
632 .insert(label.to_string(), std::iter::once(node_id).collect());
633 }
634 }
635
636 fn remove_node_label_index(&mut self, node_id: NodeId, label: &str) {
637 if let Some(ids) = self.nodes_by_label.get_mut(label) {
638 let pos = ids.iter().position(|&id| id == node_id);
639 if let Some(pos) = pos {
640 ids.swap_remove(pos);
641 }
642 if ids.is_empty() {
643 self.nodes_by_label.remove(label);
644 }
645 }
646 }
647
648 fn insert_relationship_type_index(&mut self, rel_id: RelationshipId, rel_type: &str) {
649 if let Some(bucket) = self.relationships_by_type.get_mut(rel_type) {
651 bucket.push(rel_id);
652 } else {
653 self.relationships_by_type
654 .insert(rel_type.to_string(), std::iter::once(rel_id).collect());
655 }
656 }
657
658 fn remove_relationship_type_index(&mut self, rel_id: RelationshipId, rel_type: &str) {
659 if let Some(ids) = self.relationships_by_type.get_mut(rel_type) {
660 let pos = ids.iter().position(|&id| id == rel_id);
661 if let Some(pos) = pos {
662 ids.swap_remove(pos);
663 }
664 if ids.is_empty() {
665 self.relationships_by_type.remove(rel_type);
666 }
667 }
668 }
669
670 pub(super) fn indexed_node_ids(
674 &self,
675 label: &str,
676 key: &str,
677 value: &PropertyValue,
678 ) -> Option<Vec<NodeId>> {
679 super::property_index::PropertyIndexKey::from_value(value)?;
680 let indexes = self.indexes_read();
681 if !indexes.node_properties.is_active(key) {
682 return None;
683 }
684 Some(
685 indexes
686 .node_properties
687 .scoped_ids_for(label, key, value)
688 .map(|ids| ids.to_vec())
689 .unwrap_or_default(),
690 )
691 }
692
693 pub(super) fn indexed_rel_ids(
695 &self,
696 rel_type: &str,
697 key: &str,
698 value: &PropertyValue,
699 ) -> Option<Vec<RelationshipId>> {
700 super::property_index::PropertyIndexKey::from_value(value)?;
701 let indexes = self.indexes_read();
702 if !indexes.relationship_properties.is_active(key) {
703 return None;
704 }
705 Some(
706 indexes
707 .relationship_properties
708 .scoped_ids_for(rel_type, key, value)
709 .map(|ids| ids.to_vec())
710 .unwrap_or_default(),
711 )
712 }
713
714 pub(super) fn indexes_read(&self) -> std::sync::RwLockReadGuard<'_, PropertyIndexRegistry> {
715 self.indexes
716 .properties
717 .read()
718 .unwrap_or_else(|poisoned| poisoned.into_inner())
719 }
720
721 pub(super) fn indexes_write(&self) -> RwLockWriteGuard<'_, PropertyIndexRegistry> {
722 self.indexes
723 .properties
724 .write()
725 .unwrap_or_else(|poisoned| poisoned.into_inner())
726 }
727
728 pub(super) fn indexes_mut(&mut self) -> &mut PropertyIndexRegistry {
729 self.indexes
730 .properties
731 .get_mut()
732 .unwrap_or_else(|poisoned| poisoned.into_inner())
733 }
734
735 #[inline]
736 pub(super) fn active_node_property_index_count(&self) -> usize {
737 self.indexes
738 .active_node_property_indexes
739 .load(Ordering::Relaxed)
740 }
741
742 #[inline]
743 pub(super) fn active_relationship_property_index_count(&self) -> usize {
744 self.indexes
745 .active_relationship_property_indexes
746 .load(Ordering::Relaxed)
747 }
748
749 #[inline]
750 pub(super) fn active_constraint_count(&self) -> usize {
751 self.active_constraints.load(Ordering::Relaxed)
752 }
753
754 #[inline]
755 pub(super) fn has_active_constraints(&self) -> bool {
756 self.active_constraint_count() != 0
757 }
758
759 #[inline]
760 pub(super) fn active_fulltext_index_count(&self) -> usize {
761 self.indexes.active_fulltext_indexes.load(Ordering::Relaxed)
762 }
763
764 #[inline]
765 pub(super) fn has_active_fulltext_indexes(&self) -> bool {
766 self.active_fulltext_index_count() != 0
767 }
768
769 pub(super) fn node_property_index_is_active(&mut self, key: &str) -> bool {
770 self.active_node_property_index_count() != 0
771 && self.indexes_mut().node_properties.is_active(key)
772 }
773
774 pub(super) fn relationship_property_index_is_active(&mut self, key: &str) -> bool {
775 self.active_relationship_property_index_count() != 0
776 && self.indexes_mut().relationship_properties.is_active(key)
777 }
778
779 pub(super) fn ensure_node_property_index(&self, key: &str) {
780 {
781 let indexes = self.indexes_read();
782 if indexes.node_properties.is_active(key) {
783 return;
784 }
785 }
786
787 let mut indexes = self.indexes_write();
788 if indexes.node_properties.is_active(key) {
789 return;
790 }
791
792 indexes.node_properties.insert_bulk(key, || {
793 self.iter_nodes().filter_map(|(id, node)| {
794 let value = node.properties.get(key)?;
795 Some((id, node.labels.iter().map(String::as_str), value))
796 })
797 });
798 if indexes.node_properties.activate(key) {
799 self.indexes
800 .active_node_property_indexes
801 .fetch_add(1, Ordering::Relaxed);
802 }
803 }
804
805 pub(super) fn ensure_relationship_property_index(&self, key: &str) {
806 {
807 let indexes = self.indexes_read();
808 if indexes.relationship_properties.is_active(key) {
809 return;
810 }
811 }
812
813 let mut indexes = self.indexes_write();
814 if indexes.relationship_properties.is_active(key) {
815 return;
816 }
817
818 indexes.relationship_properties.insert_bulk(key, || {
819 self.iter_rels().filter_map(|(id, rel)| {
820 let value = rel.properties.get(key)?;
821 Some((id, [rel.rel_type.as_str()], value))
822 })
823 });
824 if indexes.relationship_properties.activate(key) {
825 self.indexes
826 .active_relationship_property_indexes
827 .fetch_add(1, Ordering::Relaxed);
828 }
829 }
830
831 pub(super) fn index_catalog_read(&self) -> IndexRead<'_, IndexCatalog> {
832 read_shared(&self.indexes.catalog)
833 }
834
835 pub(super) fn index_catalog_write(&self) -> IndexWrite<'_, IndexCatalog> {
837 write_shared(&self.indexes.catalog)
838 }
839
840 pub(super) fn constraint_catalog_read(&self) -> IndexRead<'_, ConstraintCatalog> {
841 read_shared(&self.constraint_catalog)
842 }
843
844 pub(super) fn constraint_catalog_write(&self) -> IndexWrite<'_, ConstraintCatalog> {
846 write_shared(&self.constraint_catalog)
847 }
848
849 #[allow(clippy::result_large_err)]
857 pub(super) fn register_index(
858 &self,
859 request: IndexRequest,
860 if_not_exists: bool,
861 ) -> Result<CreateIndexOutcome, CreateIndexError> {
862 self.register_index_with_recording(request, if_not_exists, true)
863 }
864
865 #[allow(clippy::result_large_err)]
866 fn register_index_with_recording(
867 &self,
868 request: IndexRequest,
869 if_not_exists: bool,
870 record_event: bool,
871 ) -> Result<CreateIndexOutcome, CreateIndexError> {
872 let request_for_event = record_event.then(|| request.clone());
873 let outcome = {
874 let mut catalog = self.index_catalog_write();
875 catalog.try_create(request, if_not_exists)?
876 };
877
878 if let CreateIndexOutcome::Created(def) = &outcome {
879 self.populate_index_data(def);
880 }
881
882 if matches!(outcome, CreateIndexOutcome::Created(_)) {
886 if let Some(request_for_event) = request_for_event {
887 self.emit(|| crate::MutationEvent::CreateIndex {
888 request: request_for_event,
889 if_not_exists,
890 });
891 }
892 }
893
894 Ok(outcome)
895 }
896
897 #[doc(hidden)]
901 pub fn replay_create_index(
902 &mut self,
903 request: IndexRequest,
904 if_not_exists: bool,
905 ) -> Result<(), String> {
906 if self.recorder.is_some() {
907 return Err("cannot replay create_index while a mutation recorder is installed".into());
908 }
909 self.register_index(request, if_not_exists)
910 .map(|_| ())
911 .map_err(|e| e.to_string())
912 }
913
914 #[doc(hidden)]
916 pub fn replay_drop_index(&mut self, name: &str, if_exists: bool) -> Result<(), String> {
917 if self.recorder.is_some() {
918 return Err("cannot replay drop_index while a mutation recorder is installed".into());
919 }
920 self.drop_named_index(name, if_exists)
921 .map(|_| ())
922 .map_err(|e| e.to_string())
923 }
924
925 pub(super) fn register_constraint(
931 &self,
932 request: ConstraintRequest,
933 if_not_exists: bool,
934 ) -> Result<CreateConstraintOutcome, CreateConstraintError> {
935 {
940 let constraint_catalog = self.constraint_catalog_read();
941 if let Some(existing) = constraint_catalog.find_equivalent(&request) {
942 let cloned = existing.clone();
943 drop(constraint_catalog);
944 if if_not_exists {
945 return Ok(CreateConstraintOutcome::NoOpExists(cloned));
946 }
947 return Err(CreateConstraintError::EquivalentConstraintExists(
948 cloned.name,
949 ));
950 }
951 if let Some(existing) = constraint_catalog.get(&request.name) {
952 let cloned = existing.clone();
953 drop(constraint_catalog);
954 if if_not_exists {
955 return Ok(CreateConstraintOutcome::NoOpExists(cloned));
956 }
957 return Err(CreateConstraintError::DuplicateName(cloned.name));
958 }
959 if let Some(existing) = constraint_catalog.find_same_schema(&request) {
960 let cloned = existing.clone();
961 drop(constraint_catalog);
962 if super::constraint_catalog::kinds_conflict_for_validation(
963 &cloned.kind,
964 &request.kind,
965 ) {
966 if if_not_exists {
967 return Ok(CreateConstraintOutcome::NoOpExists(cloned));
968 }
969 return Err(CreateConstraintError::ConflictingConstraint(cloned.name));
970 }
971 }
972 }
973
974 if request.kind.requires_backing_index() {
979 let idx_catalog = self.index_catalog_read();
980 if idx_catalog.get(&request.name).is_some() {
981 return Err(CreateConstraintError::DuplicateIndexName(
982 request.name.clone(),
983 ));
984 }
985 let conflict = idx_catalog.list().into_iter().find(|def| {
986 def.kind == StoredIndexKind::Range
987 && def.entity == request.entity
988 && def.label.as_deref() == Some(request.label.as_str())
989 && def.properties == request.properties
990 && def.name != request.name
991 });
992 drop(idx_catalog);
993 if let Some(def) = conflict {
994 return Err(CreateConstraintError::BackingIndexConflict(format!(
995 "(:{} {{{}}}) already covered by index `{}`",
996 request.label,
997 request.properties.join(", "),
998 def.name,
999 )));
1000 }
1001 }
1002
1003 let owns_backing = request.kind.requires_backing_index();
1004 let request_for_event = request.clone();
1005 let outcome = {
1006 let mut catalog = self.constraint_catalog_write();
1007 catalog.try_create(request, if_not_exists)?
1008 };
1009
1010 if let CreateConstraintOutcome::Created(def) = &outcome {
1011 if let Err(violation) = self.validate_existing_data_for_constraint(def) {
1016 let mut catalog = self.constraint_catalog_write();
1017 let _ = catalog.try_drop(&def.name, true);
1018 return Err(CreateConstraintError::DataViolation(violation.to_string()));
1019 }
1020 }
1021
1022 if let CreateConstraintOutcome::Created(def) = &outcome {
1023 if owns_backing {
1024 let idx_request = IndexRequest {
1028 explicit_name: Some(def.name.clone()),
1029 kind: StoredIndexKind::Range,
1030 entity: def.entity,
1031 label: Some(def.label.clone()),
1032 additional_labels: Vec::new(),
1033 properties: def.properties.clone(),
1034 options: Default::default(),
1035 };
1036 if let Err(err) = self.register_index_with_recording(idx_request, true, false) {
1040 let mut catalog = self.constraint_catalog_write();
1041 let _ = catalog.try_drop(&def.name, true);
1042 return Err(CreateConstraintError::BackingIndexConflict(err.to_string()));
1043 }
1044 }
1045 self.emit(|| crate::MutationEvent::CreateConstraint {
1046 request: request_for_event,
1047 if_not_exists,
1048 });
1049 self.active_constraints.fetch_add(1, Ordering::Relaxed);
1050 }
1051
1052 Ok(outcome)
1053 }
1054
1055 #[doc(hidden)]
1057 pub fn replay_create_constraint(
1058 &mut self,
1059 request: ConstraintRequest,
1060 if_not_exists: bool,
1061 ) -> Result<(), String> {
1062 if self.recorder.is_some() {
1063 return Err(
1064 "cannot replay create_constraint while a mutation recorder is installed".into(),
1065 );
1066 }
1067 self.register_constraint(request, if_not_exists)
1068 .map(|_| ())
1069 .map_err(|e| e.to_string())
1070 }
1071
1072 #[doc(hidden)]
1074 pub fn replay_drop_constraint(&mut self, name: &str, if_exists: bool) -> Result<(), String> {
1075 if self.recorder.is_some() {
1076 return Err(
1077 "cannot replay drop_constraint while a mutation recorder is installed".into(),
1078 );
1079 }
1080 self.drop_named_constraint(name, if_exists)
1081 .map(|_| ())
1082 .map_err(|e| e.to_string())
1083 }
1084
1085 pub(super) fn drop_named_constraint(
1088 &self,
1089 name: &str,
1090 if_exists: bool,
1091 ) -> Result<DropConstraintOutcome, DropConstraintError> {
1092 let outcome = {
1093 let mut catalog = self.constraint_catalog_write();
1094 catalog.try_drop(name, if_exists)?
1095 };
1096 if let DropConstraintOutcome::Dropped(def) = &outcome {
1097 if let Some(index_name) = def.owned_index.as_deref() {
1098 let _ = self.drop_named_index_inner(index_name, true, false);
1101 }
1102 self.active_constraints.fetch_sub(1, Ordering::Relaxed);
1103 self.emit(|| crate::MutationEvent::DropConstraint {
1104 name: name.to_string(),
1105 if_exists,
1106 });
1107 }
1108 Ok(outcome)
1109 }
1110
1111 pub(super) fn drop_named_index(
1116 &self,
1117 name: &str,
1118 if_exists: bool,
1119 ) -> Result<DropIndexOutcome, DropIndexError> {
1120 self.drop_named_index_inner(name, if_exists, true)
1121 }
1122
1123 fn drop_named_index_inner(
1124 &self,
1125 name: &str,
1126 if_exists: bool,
1127 emit_event: bool,
1128 ) -> Result<DropIndexOutcome, DropIndexError> {
1129 if let Some(owner) = self
1130 .constraint_catalog_read()
1131 .constraint_owning_index(name)
1132 .cloned()
1133 {
1134 return Err(DropIndexError::ConstraintOwned {
1135 index: name.to_string(),
1136 constraint: owner.name,
1137 });
1138 }
1139
1140 let outcome = {
1141 let mut catalog = self.index_catalog_write();
1142 catalog.try_drop(name, if_exists)?
1143 };
1144 if let DropIndexOutcome::Dropped(def) = &outcome {
1145 match def.kind {
1147 StoredIndexKind::Text => {
1148 if let Some(label) = def.label.as_deref() {
1149 for prop in &def.properties {
1150 self.deactivate_text_scope(def.entity, label, prop);
1151 }
1152 }
1153 }
1154 StoredIndexKind::Range => {
1155 if let Some(label) = def.label.as_deref() {
1156 for prop in &def.properties {
1157 self.deactivate_sorted_scope(def.entity, label, prop);
1158 }
1159 }
1160 }
1161 StoredIndexKind::Point => {
1162 if let Some(label) = def.label.as_deref() {
1163 for prop in &def.properties {
1164 self.deactivate_point_scope(def.entity, label, prop);
1165 }
1166 }
1167 }
1168 StoredIndexKind::Lookup => {
1169 }
1172 StoredIndexKind::Vector => {
1173 self.deactivate_vector_index(def.entity, &def.name);
1174 }
1175 StoredIndexKind::Fulltext => {
1176 self.deactivate_fulltext_index(def.entity, &def.name);
1177 }
1178 }
1179 if emit_event {
1180 self.emit(|| crate::MutationEvent::DropIndex {
1181 name: name.to_string(),
1182 if_exists,
1183 });
1184 }
1185 }
1186 Ok(outcome)
1187 }
1188
1189 pub(super) fn declared_property_index_keys(
1195 &self,
1196 ) -> std::collections::BTreeSet<(StoredIndexEntity, String)> {
1197 self.index_catalog_read()
1198 .list()
1199 .into_iter()
1200 .filter(|def| def.kind == StoredIndexKind::Range)
1201 .flat_map(|def| {
1202 let entity = def.entity;
1203 def.properties.into_iter().map(move |p| (entity, p))
1204 })
1205 .collect()
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.iter() {
1670 for (key, values) in props.iter() {
1671 stats
1672 .node_distinct_values
1673 .insert((scope.to_string(), key.to_string()), values.len());
1674 }
1675 }
1676 for (scope, props) in prop_indexes.relationship_properties.scoped_values.iter() {
1677 for (key, values) in props.iter() {
1678 stats
1679 .relationship_distinct_values
1680 .insert((scope.to_string(), key.to_string()), 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 on_node_created(&mut self, node: &NodeRecord) {
1734 for label in &node.labels {
1735 self.insert_node_label_index(node.id, label);
1736 }
1737 self.index_node_properties_if_active(
1738 node.id,
1739 node.labels.iter().map(String::as_str),
1740 &node.properties,
1741 );
1742 self.maintain_node_secondary_indexes(node, SecondaryIndexMutation::Insert);
1743 }
1744
1745 pub(super) fn on_node_property_set(
1746 &mut self,
1747 node_id: NodeId,
1748 key: &str,
1749 old: Option<&PropertyValue>,
1750 new: &PropertyValue,
1751 ) {
1752 let Some(labels) = self.node_at(node_id).map(|node| node.labels.clone()) else {
1753 return;
1754 };
1755
1756 if self.node_property_index_is_active(key) {
1757 if let Some(old) = old {
1758 self.unindex_node_property_if_active(
1759 node_id,
1760 labels.iter().map(String::as_str),
1761 key,
1762 old,
1763 );
1764 }
1765 self.index_node_property_if_active(
1766 node_id,
1767 labels.iter().map(String::as_str),
1768 key,
1769 new,
1770 );
1771 }
1772
1773 self.update_secondary_property(
1774 StoredIndexEntity::Node,
1775 labels.iter().map(String::as_str),
1776 node_id,
1777 key,
1778 old,
1779 Some(new),
1780 );
1781 }
1782
1783 pub(super) fn on_node_property_removed(
1784 &mut self,
1785 node_id: NodeId,
1786 key: &str,
1787 old: &PropertyValue,
1788 ) {
1789 let Some(labels) = self.node_at(node_id).map(|node| node.labels.clone()) else {
1790 return;
1791 };
1792 if self.node_property_index_is_active(key) {
1793 self.unindex_node_property_if_active(
1794 node_id,
1795 labels.iter().map(String::as_str),
1796 key,
1797 old,
1798 );
1799 }
1800 self.update_secondary_property(
1801 StoredIndexEntity::Node,
1802 labels.iter().map(String::as_str),
1803 node_id,
1804 key,
1805 Some(old),
1806 None,
1807 );
1808 }
1809
1810 pub(super) fn on_node_label_added(&mut self, node_id: NodeId, label: &str) {
1811 self.insert_node_label_index(node_id, label);
1812
1813 let Some(properties) = self.node_at(node_id).map(|node| node.properties.clone()) else {
1814 return;
1815 };
1816 if self.active_node_property_index_count() != 0 {
1817 self.index_node_scope_properties_if_active(node_id, label, &properties);
1818 }
1819 for (key, value) in &properties {
1820 self.update_secondary_property(
1821 StoredIndexEntity::Node,
1822 [label],
1823 node_id,
1824 key,
1825 None,
1826 Some(value),
1827 );
1828 }
1829 }
1830
1831 pub(super) fn on_node_label_removed(&mut self, node_id: NodeId, label: &str) {
1832 self.remove_node_label_index(node_id, label);
1833
1834 let Some(properties) = self.node_at(node_id).map(|node| node.properties.clone()) else {
1835 return;
1836 };
1837 if self.active_node_property_index_count() != 0 {
1838 self.unindex_node_scope_properties_if_active(node_id, label, &properties);
1839 }
1840 for (key, value) in &properties {
1841 self.update_secondary_property(
1842 StoredIndexEntity::Node,
1843 [label],
1844 node_id,
1845 key,
1846 Some(value),
1847 None,
1848 );
1849 }
1850 }
1851
1852 pub(super) fn on_node_deleted(&mut self, node: &NodeRecord) {
1853 for label in &node.labels {
1854 self.remove_node_label_index(node.id, label);
1855 }
1856 self.unindex_active_node_properties(
1857 node.id,
1858 node.labels.iter().map(String::as_str),
1859 &node.properties,
1860 );
1861 self.maintain_node_secondary_indexes(node, SecondaryIndexMutation::Remove);
1862 }
1863
1864 pub(super) fn on_relationship_created(&mut self, rel: &RelationshipRecord) {
1865 self.attach_relationship(rel);
1866 self.index_relationship_properties_if_active(
1867 rel.id,
1868 [rel.rel_type.as_str()],
1869 &rel.properties,
1870 );
1871 self.maintain_relationship_secondary_indexes(rel, SecondaryIndexMutation::Insert);
1872 }
1873
1874 pub(super) fn on_relationship_property_set(
1875 &mut self,
1876 rel_id: RelationshipId,
1877 key: &str,
1878 old: Option<&PropertyValue>,
1879 new: &PropertyValue,
1880 ) {
1881 let Some(rel_type) = self.rel_at(rel_id).map(|rel| rel.rel_type.clone()) else {
1882 return;
1883 };
1884
1885 if self.relationship_property_index_is_active(key) {
1886 if let Some(old) = old {
1887 self.unindex_relationship_property_if_active(rel_id, [rel_type.as_str()], key, old);
1888 }
1889 self.index_relationship_property_if_active(rel_id, [rel_type.as_str()], key, new);
1890 }
1891
1892 self.update_secondary_property(
1893 StoredIndexEntity::Relationship,
1894 [rel_type.as_str()],
1895 rel_id,
1896 key,
1897 old,
1898 Some(new),
1899 );
1900 }
1901
1902 pub(super) fn on_relationship_property_removed(
1903 &mut self,
1904 rel_id: RelationshipId,
1905 key: &str,
1906 old: &PropertyValue,
1907 ) {
1908 let Some(rel_type) = self.rel_at(rel_id).map(|rel| rel.rel_type.clone()) else {
1909 return;
1910 };
1911 if self.relationship_property_index_is_active(key) {
1912 self.unindex_relationship_property_if_active(rel_id, [rel_type.as_str()], key, old);
1913 }
1914 self.update_secondary_property(
1915 StoredIndexEntity::Relationship,
1916 [rel_type.as_str()],
1917 rel_id,
1918 key,
1919 Some(old),
1920 None,
1921 );
1922 }
1923
1924 pub(super) fn on_relationship_deleted(&mut self, rel: &RelationshipRecord) {
1925 self.detach_relationship_indexes(rel);
1926 self.unindex_active_relationship_properties(
1927 rel.id,
1928 [rel.rel_type.as_str()],
1929 &rel.properties,
1930 );
1931 self.maintain_relationship_secondary_indexes(rel, SecondaryIndexMutation::Remove);
1932 }
1933
1934 fn index_node_property_if_active<'a>(
1935 &mut self,
1936 node_id: NodeId,
1937 labels: impl IntoIterator<Item = &'a str>,
1938 key: &str,
1939 value: &PropertyValue,
1940 ) {
1941 if self.active_node_property_index_count() == 0 {
1942 return;
1943 }
1944 let indexes = self.indexes_mut();
1945 if indexes.node_properties.is_active(key) {
1946 indexes
1947 .node_properties
1948 .insert_with_scopes(node_id, labels, key, value);
1949 }
1950 }
1951
1952 fn index_node_properties_if_active<'a>(
1953 &mut self,
1954 node_id: NodeId,
1955 labels: impl IntoIterator<Item = &'a str> + Clone,
1956 properties: &Properties,
1957 ) {
1958 if self.active_node_property_index_count() == 0 {
1959 return;
1960 }
1961 let indexes = self.indexes_mut();
1962 for (key, value) in properties {
1963 if indexes.node_properties.is_active(key) {
1964 indexes
1965 .node_properties
1966 .insert_with_scopes(node_id, labels.clone(), key, value);
1967 }
1968 }
1969 }
1970
1971 fn unindex_node_property_if_active<'a>(
1972 &mut self,
1973 node_id: NodeId,
1974 labels: impl IntoIterator<Item = &'a str>,
1975 key: &str,
1976 value: &PropertyValue,
1977 ) {
1978 if self.active_node_property_index_count() == 0 {
1979 return;
1980 }
1981 let indexes = self.indexes_mut();
1982 if indexes.node_properties.is_active(key) {
1983 indexes
1984 .node_properties
1985 .remove_with_scopes(node_id, labels, key, value);
1986 }
1987 }
1988
1989 fn index_node_scope_properties_if_active(
1990 &mut self,
1991 node_id: NodeId,
1992 scope: &str,
1993 properties: &Properties,
1994 ) {
1995 if self.active_node_property_index_count() == 0 {
1996 return;
1997 }
1998 let indexes = self.indexes_mut();
1999 for (key, value) in properties {
2000 if indexes.node_properties.is_active(key) {
2001 indexes
2002 .node_properties
2003 .insert_scoped(node_id, scope, key, value);
2004 }
2005 }
2006 }
2007
2008 fn unindex_node_scope_properties_if_active(
2009 &mut self,
2010 node_id: NodeId,
2011 scope: &str,
2012 properties: &Properties,
2013 ) {
2014 if self.active_node_property_index_count() == 0 {
2015 return;
2016 }
2017 let indexes = self.indexes_mut();
2018 for (key, value) in properties {
2019 if indexes.node_properties.is_active(key) {
2020 indexes
2021 .node_properties
2022 .remove_scoped(node_id, scope, key, value);
2023 }
2024 }
2025 }
2026
2027 fn unindex_active_node_properties<'a>(
2028 &mut self,
2029 node_id: NodeId,
2030 labels: impl IntoIterator<Item = &'a str> + Clone,
2031 properties: &Properties,
2032 ) {
2033 if self.active_node_property_index_count() == 0 {
2034 return;
2035 }
2036 let indexes = self.indexes_mut();
2037 for (key, value) in properties {
2038 if indexes.node_properties.is_active(key) {
2039 indexes
2040 .node_properties
2041 .remove_with_scopes(node_id, labels.clone(), key, value);
2042 }
2043 }
2044 }
2045
2046 fn index_relationship_property_if_active<'a>(
2047 &mut self,
2048 rel_id: RelationshipId,
2049 scopes: impl IntoIterator<Item = &'a str>,
2050 key: &str,
2051 value: &PropertyValue,
2052 ) {
2053 if self.active_relationship_property_index_count() == 0 {
2054 return;
2055 }
2056 let indexes = self.indexes_mut();
2057 if indexes.relationship_properties.is_active(key) {
2058 indexes
2059 .relationship_properties
2060 .insert_with_scopes(rel_id, scopes, key, value);
2061 }
2062 }
2063
2064 fn index_relationship_properties_if_active<'a>(
2065 &mut self,
2066 rel_id: RelationshipId,
2067 scopes: impl IntoIterator<Item = &'a str> + Clone,
2068 properties: &Properties,
2069 ) {
2070 if self.active_relationship_property_index_count() == 0 {
2071 return;
2072 }
2073 let indexes = self.indexes_mut();
2074 for (key, value) in properties {
2075 if indexes.relationship_properties.is_active(key) {
2076 indexes.relationship_properties.insert_with_scopes(
2077 rel_id,
2078 scopes.clone(),
2079 key,
2080 value,
2081 );
2082 }
2083 }
2084 }
2085
2086 fn unindex_relationship_property_if_active<'a>(
2087 &mut self,
2088 rel_id: RelationshipId,
2089 scopes: impl IntoIterator<Item = &'a str>,
2090 key: &str,
2091 value: &PropertyValue,
2092 ) {
2093 if self.active_relationship_property_index_count() == 0 {
2094 return;
2095 }
2096 let indexes = self.indexes_mut();
2097 if indexes.relationship_properties.is_active(key) {
2098 indexes
2099 .relationship_properties
2100 .remove_with_scopes(rel_id, scopes, key, value);
2101 }
2102 }
2103
2104 fn unindex_active_relationship_properties<'a>(
2105 &mut self,
2106 rel_id: RelationshipId,
2107 scopes: impl IntoIterator<Item = &'a str> + Clone,
2108 properties: &Properties,
2109 ) {
2110 if self.active_relationship_property_index_count() == 0 {
2111 return;
2112 }
2113 let indexes = self.indexes_mut();
2114 for (key, value) in properties {
2115 if indexes.relationship_properties.is_active(key) {
2116 indexes.relationship_properties.remove_with_scopes(
2117 rel_id,
2118 scopes.clone(),
2119 key,
2120 value,
2121 );
2122 }
2123 }
2124 }
2125
2126 pub(super) fn scan_nodes_by_property(
2127 &self,
2128 label: Option<&str>,
2129 key: &str,
2130 value: &PropertyValue,
2131 ) -> Vec<NodeRecord> {
2132 match label {
2133 Some(label) => self
2134 .nodes_by_label
2135 .get(label)
2136 .into_iter()
2137 .flat_map(|ids| ids.iter())
2138 .filter_map(|&id| self.node_at(id))
2139 .filter(|node| node.properties.get(key) == Some(value))
2140 .cloned()
2141 .collect(),
2142 None => self
2143 .iter_node_records()
2144 .filter(|node| node.properties.get(key) == Some(value))
2145 .cloned()
2146 .collect(),
2147 }
2148 }
2149
2150 pub(super) fn scan_node_ids_by_property(
2151 &self,
2152 label: Option<&str>,
2153 key: &str,
2154 value: &PropertyValue,
2155 ) -> Vec<NodeId> {
2156 match label {
2157 Some(label) => self
2158 .nodes_by_label
2159 .get(label)
2160 .into_iter()
2161 .flat_map(|ids| ids.iter())
2162 .filter_map(|&id| {
2163 (self.node_at(id)?.properties.get(key) == Some(value)).then_some(id)
2164 })
2165 .collect(),
2166 None => self
2167 .iter_nodes()
2168 .filter_map(|(id, node)| (node.properties.get(key) == Some(value)).then_some(id))
2169 .collect(),
2170 }
2171 }
2172
2173 pub(super) fn any_node_by_property(
2174 &self,
2175 label: &str,
2176 key: &str,
2177 value: &PropertyValue,
2178 ) -> bool {
2179 self.nodes_by_label
2180 .get(label)
2181 .into_iter()
2182 .flat_map(|ids| ids.iter())
2183 .filter_map(|&id| self.node_at(id))
2184 .any(|node| node.properties.get(key) == Some(value))
2185 }
2186
2187 pub(super) fn scan_relationships_by_property(
2188 &self,
2189 rel_type: Option<&str>,
2190 key: &str,
2191 value: &PropertyValue,
2192 ) -> Vec<RelationshipRecord> {
2193 match rel_type {
2194 Some(rel_type) => self
2195 .relationships_by_type
2196 .get(rel_type)
2197 .into_iter()
2198 .flat_map(|ids| ids.iter())
2199 .filter_map(|&id| self.rel_at(id))
2200 .filter(|rel| rel.properties.get(key) == Some(value))
2201 .cloned()
2202 .collect(),
2203 None => self
2204 .iter_rel_records()
2205 .filter(|rel| rel.properties.get(key) == Some(value))
2206 .cloned()
2207 .collect(),
2208 }
2209 }
2210
2211 pub(super) fn scan_relationship_ids_by_property(
2212 &self,
2213 rel_type: Option<&str>,
2214 key: &str,
2215 value: &PropertyValue,
2216 ) -> Vec<RelationshipId> {
2217 match rel_type {
2218 Some(rel_type) => self
2219 .relationships_by_type
2220 .get(rel_type)
2221 .into_iter()
2222 .flat_map(|ids| ids.iter())
2223 .filter_map(|&id| {
2224 (self.rel_at(id)?.properties.get(key) == Some(value)).then_some(id)
2225 })
2226 .collect(),
2227 None => self
2228 .iter_rels()
2229 .filter_map(|(id, rel)| (rel.properties.get(key) == Some(value)).then_some(id))
2230 .collect(),
2231 }
2232 }
2233
2234 pub(super) fn any_relationship_by_property(
2235 &self,
2236 rel_type: &str,
2237 key: &str,
2238 value: &PropertyValue,
2239 ) -> bool {
2240 self.relationships_by_type
2241 .get(rel_type)
2242 .into_iter()
2243 .flat_map(|ids| ids.iter())
2244 .filter_map(|&id| self.rel_at(id))
2245 .any(|rel| rel.properties.get(key) == Some(value))
2246 }
2247
2248 pub(super) fn attach_relationship(&mut self, rel: &RelationshipRecord) {
2249 self.outgoing_push(rel.src, rel.id);
2250 self.incoming_push(rel.dst, rel.id);
2251 self.insert_relationship_type_index(rel.id, &rel.rel_type);
2252 }
2253
2254 fn detach_relationship_indexes(&mut self, rel: &RelationshipRecord) {
2255 self.outgoing_remove(rel.src, rel.id);
2259 self.incoming_remove(rel.dst, rel.id);
2260
2261 self.remove_relationship_type_index(rel.id, &rel.rel_type);
2262 }
2263
2264 pub(super) fn relationship_ids_for_direction(
2265 &self,
2266 node_id: NodeId,
2267 direction: Direction,
2268 ) -> Vec<RelationshipId> {
2269 match direction {
2270 Direction::Left => self
2271 .incoming_at(node_id)
2272 .map(<[_]>::to_vec)
2273 .unwrap_or_default(),
2274
2275 Direction::Right => self
2276 .outgoing_at(node_id)
2277 .map(<[_]>::to_vec)
2278 .unwrap_or_default(),
2279
2280 Direction::Undirected => {
2281 let out = self.outgoing_at(node_id);
2282 let inc = self.incoming_at(node_id);
2283 let mut ids = Vec::with_capacity(
2284 out.map(<[_]>::len).unwrap_or(0) + inc.map(<[_]>::len).unwrap_or(0),
2285 );
2286
2287 if let Some(out) = out {
2288 ids.extend(out.iter().copied());
2289 }
2290 if let Some(inc) = inc {
2291 for &rel_id in inc {
2292 let Some(rel) = self.rel_at(rel_id) else {
2293 continue;
2294 };
2295 if rel.src == node_id && rel.dst == node_id {
2296 continue;
2297 }
2298 ids.push(rel_id);
2299 }
2300 }
2301
2302 ids
2303 }
2304 }
2305 }
2306
2307 pub(super) fn other_endpoint(rel: &RelationshipRecord, node_id: NodeId) -> Option<NodeId> {
2308 if rel.src == node_id {
2309 Some(rel.dst)
2310 } else if rel.dst == node_id {
2311 Some(rel.src)
2312 } else {
2313 None
2314 }
2315 }
2316
2317 pub(super) fn has_incident_relationships(&self, node_id: NodeId) -> bool {
2318 self.outgoing_at(node_id)
2319 .map(|ids| !ids.is_empty())
2320 .unwrap_or(false)
2321 || self
2322 .incoming_at(node_id)
2323 .map(|ids| !ids.is_empty())
2324 .unwrap_or(false)
2325 }
2326
2327 pub(super) fn incident_relationship_ids(&self, node_id: NodeId) -> Vec<RelationshipId> {
2328 let out = self.outgoing_at(node_id);
2329 let inc = self.incoming_at(node_id);
2330 let mut rel_ids =
2331 Vec::with_capacity(out.map(<[_]>::len).unwrap_or(0) + inc.map(<[_]>::len).unwrap_or(0));
2332
2333 if let Some(ids) = out {
2334 rel_ids.extend(ids.iter().copied());
2335 }
2336 if let Some(ids) = inc {
2337 for &rel_id in ids {
2338 let Some(rel) = self.rel_at(rel_id) else {
2339 continue;
2340 };
2341 if rel.src == node_id && rel.dst == node_id {
2342 continue;
2343 }
2344 rel_ids.push(rel_id);
2345 }
2346 }
2347
2348 rel_ids
2349 }
2350
2351 #[doc(hidden)]
2355 pub fn replay_create_node(
2356 &mut self,
2357 id: NodeId,
2358 labels: Vec<String>,
2359 properties: Properties,
2360 ) -> Result<NodeRecord, String> {
2361 if self.recorder.is_some() {
2362 return Err(
2363 "cannot replay node creation while a mutation recorder is installed".into(),
2364 );
2365 }
2366 if self.node_at(id).is_some() {
2367 return Err(format!("node id {id} already exists"));
2368 }
2369 let idx = self.ensure_node_slot_checked(id)?;
2370 self.bump_next_node_id_past(id)?;
2371
2372 let labels = Self::normalize_labels(labels);
2373 let node = NodeRecord {
2374 id,
2375 labels: labels.clone(),
2376 properties,
2377 };
2378
2379 self.put_node_at_slot(idx, node.clone());
2380 self.on_node_created(&node);
2386
2387 Ok(node)
2388 }
2389
2390 #[doc(hidden)]
2394 pub fn replay_create_relationship(
2395 &mut self,
2396 id: RelationshipId,
2397 src: NodeId,
2398 dst: NodeId,
2399 rel_type: &str,
2400 properties: Properties,
2401 ) -> Result<RelationshipRecord, String> {
2402 if self.recorder.is_some() {
2403 return Err(
2404 "cannot replay relationship creation while a mutation recorder is installed".into(),
2405 );
2406 }
2407 if self.rel_at(id).is_some() {
2408 return Err(format!("relationship id {id} already exists"));
2409 }
2410 if self.node_at(src).is_none() {
2411 return Err(format!(
2412 "relationship {id} references missing source node {src}"
2413 ));
2414 }
2415 if self.node_at(dst).is_none() {
2416 return Err(format!(
2417 "relationship {id} references missing target node {dst}"
2418 ));
2419 }
2420
2421 let trimmed = rel_type.trim();
2422 if trimmed.is_empty() {
2423 return Err(format!("relationship {id} has an empty type"));
2424 }
2425 let idx = self.ensure_rel_slot_checked(id)?;
2426 self.bump_next_rel_id_past(id)?;
2427
2428 let rel = RelationshipRecord {
2429 id,
2430 src,
2431 dst,
2432 rel_type: trimmed.to_string(),
2433 properties,
2434 };
2435
2436 self.put_rel_at_slot(idx, rel.clone());
2437 self.on_relationship_created(&rel);
2439
2440 Ok(rel)
2441 }
2442
2443 #[cfg(test)]
2444 pub(super) fn assert_property_indexes_match_scan(&self) {
2445 let indexes = self.indexes_read();
2446 assert_eq!(
2447 indexes.node_properties.active_keys.len(),
2448 self.active_node_property_index_count(),
2449 "node property index counter diverged from active key set"
2450 );
2451 assert_eq!(
2452 indexes.relationship_properties.active_keys.len(),
2453 self.active_relationship_property_index_count(),
2454 "relationship property index counter diverged from active key set"
2455 );
2456
2457 let mut expected_nodes = PropertyIndexState {
2458 active_keys: indexes.node_properties.active_keys.clone(),
2459 ..PropertyIndexState::default()
2460 };
2461 for (id, node) in self.iter_nodes() {
2462 for (key, value) in &node.properties {
2463 if expected_nodes.is_active(key) {
2464 expected_nodes.insert_with_scopes(
2465 id,
2466 node.labels.iter().map(String::as_str),
2467 key,
2468 value,
2469 );
2470 }
2471 }
2472 }
2473 assert_eq!(
2474 indexes.node_properties.values, expected_nodes.values,
2475 "node property index values diverged from scan"
2476 );
2477 assert_eq!(
2478 indexes.node_properties.scoped_values, expected_nodes.scoped_values,
2479 "node property scoped index values diverged from scan"
2480 );
2481
2482 let mut expected_relationships = PropertyIndexState {
2483 active_keys: indexes.relationship_properties.active_keys.clone(),
2484 ..PropertyIndexState::default()
2485 };
2486 for (id, rel) in self.iter_rels() {
2487 for (key, value) in &rel.properties {
2488 if expected_relationships.is_active(key) {
2489 expected_relationships.insert_with_scopes(
2490 id,
2491 [rel.rel_type.as_str()],
2492 key,
2493 value,
2494 );
2495 }
2496 }
2497 }
2498 assert_eq!(
2499 indexes.relationship_properties.values, expected_relationships.values,
2500 "relationship property index values diverged from scan"
2501 );
2502 assert_eq!(
2503 indexes.relationship_properties.scoped_values, expected_relationships.scoped_values,
2504 "relationship property scoped index values diverged from scan"
2505 );
2506 }
2507}