1use std::collections::BTreeMap;
6use std::sync::atomic::{AtomicUsize, Ordering};
7use std::sync::{Arc, RwLock, RwLockWriteGuard};
8
9use lora_ast::Direction;
10
11use crate::{
12 DeletedRecordSink, Labels, LoraPoint, MutationEvent, MutationRecorder, NodeId, NodeRecord,
13 Properties, PropertyValue, RelationshipId, RelationshipRecord,
14};
15
16use super::adjacency::{AdjEntry, AdjSide, Adjacency, Side, TypeFilter};
17use super::chunked_vec::ChunkedVec;
18use super::constraint_catalog::{
19 ConstraintCatalog, ConstraintRequest, CreateConstraintError, CreateConstraintOutcome,
20 DropConstraintError, DropConstraintOutcome,
21};
22use super::distinct_stats::DistinctStatsRegistry;
23use super::entity_index_store::{
24 read_shared, share, write_shared, IndexBundle, IndexRead, IndexWrite,
25};
26use super::fulltext_index::FulltextRegistry;
27use super::hnsw::HnswParams;
28use super::id_bitmap::IdBitmap;
29use super::index_catalog::{
30 CreateIndexError, CreateIndexOutcome, DropIndexError, DropIndexOutcome, IndexCatalog,
31 IndexDefinition, IndexRequest, StoredIndexEntity, StoredIndexKind, StoredIndexState,
32};
33use super::point_index::PointRegistry;
34#[cfg(test)]
35use super::property_index::PropertyIndexState;
36use super::property_index::{PropertyIndexRegistry, UNLABELLED};
37use super::record_slab::RecordSlab;
38use super::secondary_index_maintenance::SecondaryIndexMutation;
39use super::sorted_property_index::SortedPropertyIndex;
40use super::stats::GraphStats;
41use super::text_index::TrigramRegistry;
42use super::vector_index::{VectorIndexProvider, VectorIndexRegistry, VectorSimilarity};
43use crate::dict::Dicts;
44use crate::encoded::{self, Scratch, StoredNode, StoredRel};
45use crate::{NodeRef, RelRef};
46
47#[derive(Default)]
48pub struct InMemoryGraph {
49 pub(super) next_node_id: NodeId,
50 pub(super) next_rel_id: RelationshipId,
51
52 pub(super) nodes: RecordSlab,
65 pub(super) relationships: RecordSlab,
66 pub(super) dicts: Dicts,
70 pub(super) live_node_count: usize,
74 pub(super) live_rel_count: usize,
75
76 pub(super) adjacency: ChunkedVec<Adjacency>,
82
83 pub(super) nodes_by_label: BTreeMap<String, IdBitmap>,
89 pub(super) relationships_by_type: BTreeMap<String, IdBitmap>,
90
91 pub(super) indexes: IndexBundle,
99
100 pub(super) distinct_stats: DistinctStatsRegistry,
106
107 pub(super) constraint_catalog: RwLock<Arc<ConstraintCatalog>>,
113 pub(super) active_constraints: AtomicUsize,
117
118 pub(super) recorder: Option<Arc<dyn MutationRecorder>>,
123
124 pub(super) deleted_sink: Option<Arc<dyn DeletedRecordSink>>,
129}
130
131impl std::fmt::Debug for InMemoryGraph {
132 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133 f.debug_struct("InMemoryGraph")
134 .field("next_node_id", &self.next_node_id)
135 .field("next_rel_id", &self.next_rel_id)
136 .field(
137 "nodes",
138 &self.iter_nodes().map(|(_, n)| n).collect::<Vec<_>>(),
139 )
140 .field(
141 "relationships",
142 &self.iter_rels().map(|(_, r)| r).collect::<Vec<_>>(),
143 )
144 .field("adjacency", &self.adjacency)
145 .field("nodes_by_label", &self.nodes_by_label)
146 .field("relationships_by_type", &self.relationships_by_type)
147 .field("indexes", &self.indexes)
148 .field(
149 "active_node_property_indexes",
150 &self.active_node_property_index_count(),
151 )
152 .field(
153 "active_relationship_property_indexes",
154 &self.active_relationship_property_index_count(),
155 )
156 .field(
157 "index_catalog_entries",
158 &self
159 .indexes
160 .catalog
161 .read()
162 .map(|c| c.list().len())
163 .unwrap_or(0),
164 )
165 .field("active_constraints", &self.active_constraint_count())
166 .field(
167 "active_fulltext_indexes",
168 &self.active_fulltext_index_count(),
169 )
170 .field("recorder", &self.recorder.as_ref().map(|_| "installed"))
171 .finish()
172 }
173}
174
175impl Clone for InMemoryGraph {
176 fn clone(&self) -> Self {
177 Self {
180 next_node_id: self.next_node_id,
181 next_rel_id: self.next_rel_id,
182 nodes: self.nodes.clone(),
183 relationships: self.relationships.clone(),
184 live_node_count: self.live_node_count,
185 live_rel_count: self.live_rel_count,
186 adjacency: self.adjacency.clone(),
187 dicts: self.dicts.clone(),
188 nodes_by_label: self.nodes_by_label.clone(),
189 relationships_by_type: self.relationships_by_type.clone(),
190 indexes: self.indexes.clone(),
193 distinct_stats: self.distinct_stats.clone(),
194 constraint_catalog: share(&self.constraint_catalog),
195 active_constraints: AtomicUsize::new(self.active_constraint_count()),
196 recorder: None,
197 deleted_sink: None,
198 }
199 }
200}
201
202impl InMemoryGraph {
203 pub fn new() -> Self {
204 Self::default()
205 }
206
207 pub fn with_capacity_hint(_nodes: usize, _relationships: usize) -> Self {
211 Self::default()
212 }
213
214 pub fn contains_node(&self, node_id: NodeId) -> bool {
215 self.has_node_at(node_id)
216 }
217
218 pub fn contains_relationship(&self, rel_id: RelationshipId) -> bool {
219 self.has_rel_at(rel_id)
220 }
221
222 pub fn set_mutation_recorder(&mut self, recorder: Option<Arc<dyn MutationRecorder>>) {
226 self.recorder = recorder;
227 }
228
229 pub fn set_deleted_record_sink(&mut self, sink: Option<Arc<dyn DeletedRecordSink>>) {
231 self.deleted_sink = sink;
232 }
233
234 pub fn mutation_recorder(&self) -> Option<&Arc<dyn MutationRecorder>> {
236 self.recorder.as_ref()
237 }
238
239 #[inline]
244 pub(super) fn emit<F: FnOnce() -> MutationEvent>(&self, build: F) {
245 if let Some(rec) = &self.recorder {
246 rec.record(build());
247 }
248 }
249
250 fn bump_next_node_id_past(&mut self, id: NodeId) -> Result<(), String> {
251 let next = id
252 .checked_add(1)
253 .ok_or_else(|| format!("node id {id} leaves no valid next node id"))?;
254 self.next_node_id = self.next_node_id.max(next);
255 Ok(())
256 }
257
258 fn bump_next_rel_id_past(&mut self, id: RelationshipId) -> Result<(), String> {
259 let next = id
260 .checked_add(1)
261 .ok_or_else(|| format!("relationship id {id} leaves no valid next relationship id"))?;
262 self.next_rel_id = self.next_rel_id.max(next);
263 Ok(())
264 }
265
266 pub(super) fn try_reserve_next_node_slot(&mut self) -> Option<(NodeId, usize)> {
267 let id = self.next_node_id;
268 let idx = self.ensure_node_slot_checked(id).ok()?;
269 self.bump_next_node_id_past(id).ok()?;
270 Some((id, idx))
271 }
272
273 pub(super) fn try_reserve_next_rel_slot(&mut self) -> Option<(RelationshipId, usize)> {
274 let id = self.next_rel_id;
275 let idx = self.ensure_rel_slot_checked(id).ok()?;
276 self.bump_next_rel_id_past(id).ok()?;
277 Some((id, idx))
278 }
279
280 #[inline]
286 pub(super) fn node_at(&self, id: NodeId) -> Option<NodeRef<'_>> {
287 let bytes = self.nodes.get(Self::slot_index(id)?)?;
288 Some(NodeRef::stored(StoredNode::new(id, bytes, &self.dicts)))
289 }
290
291 #[inline]
292 pub(super) fn has_node_at(&self, id: NodeId) -> bool {
293 Self::slot_index(id).is_some_and(|idx| self.nodes.contains(idx))
294 }
295
296 #[inline]
297 pub(super) fn rel_at(&self, id: RelationshipId) -> Option<RelRef<'_>> {
298 let bytes = self.relationships.get(Self::slot_index(id)?)?;
299 Some(RelRef::stored(StoredRel::new(id, bytes, &self.dicts)))
300 }
301
302 #[inline]
303 pub(super) fn has_rel_at(&self, id: RelationshipId) -> bool {
304 Self::slot_index(id).is_some_and(|idx| self.relationships.contains(idx))
305 }
306
307 #[inline]
310 pub(super) fn rel_endpoints_at(&self, id: RelationshipId) -> Option<(NodeId, NodeId)> {
311 let bytes = self.relationships.get(Self::slot_index(id)?)?;
312 Some(encoded::rel_endpoints(bytes))
313 }
314
315 pub(super) fn update_node<R>(
319 &mut self,
320 id: NodeId,
321 change: impl FnOnce(&mut NodeRecord) -> R,
322 ) -> Option<R> {
323 let idx = Self::slot_index(id)?;
324 let mut record = self.node_at(id)?.to_record();
325 let result = change(&mut record);
326 self.store_node(idx, &record);
327 Some(result)
328 }
329
330 pub(super) fn edit_node_property(
334 &mut self,
335 id: NodeId,
336 key: &str,
337 edit: encoded::PropEdit<'_>,
338 ) -> Option<Option<PropertyValue>> {
339 let idx = Self::slot_index(id)?;
340 edit_record_property(
341 &mut self.nodes,
342 idx,
343 encoded::RecordKind::Node,
344 &mut self.dicts.keys,
345 key,
346 edit,
347 )
348 }
349
350 pub(super) fn edit_rel_property(
351 &mut self,
352 id: RelationshipId,
353 key: &str,
354 edit: encoded::PropEdit<'_>,
355 ) -> Option<Option<PropertyValue>> {
356 let idx = Self::slot_index(id)?;
357 edit_record_property(
358 &mut self.relationships,
359 idx,
360 encoded::RecordKind::Relationship,
361 &mut self.dicts.keys,
362 key,
363 edit,
364 )
365 }
366
367 fn slot_len_for_id(id: u64, kind: &str) -> Result<usize, String> {
371 let idx = usize::try_from(id)
372 .map_err(|_| format!("{kind} id {id} does not fit in usize on this platform"))?;
373 idx.checked_add(1)
374 .ok_or_else(|| format!("{kind} id {id} leaves no valid slab slot"))
375 }
376
377 #[inline]
378 fn slot_index(id: u64) -> Option<usize> {
379 usize::try_from(id).ok()
380 }
381
382 fn ensure_node_slot_checked(&mut self, id: NodeId) -> Result<usize, String> {
383 let target = Self::slot_len_for_id(id, "node")?;
384 if self.nodes.len() < target {
385 let additional = target - self.nodes.len();
386 self.nodes.try_reserve(additional).map_err(|e| {
387 format!("node id {id} requires {target} slots, but allocation failed: {e}")
388 })?;
389 self.adjacency.try_reserve_exact(additional).map_err(|e| {
390 format!(
391 "node id {id} requires {target} adjacency slots, but allocation failed: {e}"
392 )
393 })?;
394 self.nodes.grow_to(target);
395 self.adjacency.resize_with(target, Adjacency::default);
396 }
397 Ok(target - 1)
398 }
399
400 fn ensure_rel_slot_checked(&mut self, id: RelationshipId) -> Result<usize, String> {
401 let target = Self::slot_len_for_id(id, "relationship")?;
402 if self.relationships.len() < target {
403 self.relationships
404 .try_reserve(target - self.relationships.len())
405 .map_err(|e| {
406 format!(
407 "relationship id {id} requires {target} slots, but allocation failed: {e}"
408 )
409 })?;
410 self.relationships.grow_to(target);
411 }
412 Ok(target - 1)
413 }
414
415 pub(super) fn put_node_checked(&mut self, id: NodeId, node: &NodeRecord) -> Result<(), String> {
416 let idx = self.ensure_node_slot_checked(id)?;
417 self.put_node_at_slot(idx, node);
418 Ok(())
419 }
420
421 pub(super) fn put_rel_checked(
422 &mut self,
423 id: RelationshipId,
424 rel: &RelationshipRecord,
425 ) -> Result<(), String> {
426 let idx = self.ensure_rel_slot_checked(id)?;
427 self.put_rel_at_slot(idx, rel);
428 Ok(())
429 }
430
431 pub(super) fn put_node_at_slot(&mut self, idx: usize, node: &NodeRecord) {
432 if self.store_node(idx, node) {
433 self.live_node_count += 1;
434 }
435 }
436
437 pub(super) fn put_rel_at_slot(&mut self, idx: usize, rel: &RelationshipRecord) {
438 let mut out = Scratch::take();
439 encoded::encode_rel(rel, &mut self.dicts, &mut out);
440 if self.relationships.set(idx, &out) {
441 self.live_rel_count += 1;
442 }
443 }
444
445 #[inline]
447 fn store_node(&mut self, idx: usize, node: &NodeRecord) -> bool {
448 let mut out = Scratch::take();
449 encoded::encode_node(node, &mut self.dicts, &mut out);
450 self.nodes.set(idx, &out)
451 }
452
453 pub(super) fn take_node(&mut self, id: NodeId) -> Option<NodeRecord> {
454 let idx = Self::slot_index(id)?;
455 let removed = StoredNode::new(id, self.nodes.get(idx)?, &self.dicts).to_record();
456 self.nodes.remove(idx);
457 self.live_node_count -= 1;
458 if let Some(adj) = self.adjacency.get_mut(idx) {
464 adj.clear();
465 }
466 Some(removed)
467 }
468
469 pub(super) fn take_rel(&mut self, id: RelationshipId) -> Option<RelationshipRecord> {
470 let idx = Self::slot_index(id)?;
471 let removed = StoredRel::new(id, self.relationships.get(idx)?, &self.dicts).to_record();
472 self.relationships.remove(idx);
473 self.live_rel_count -= 1;
474 Some(removed)
475 }
476
477 #[inline]
478 pub(super) fn outgoing_at(&self, id: NodeId) -> Option<AdjSide<'_>> {
479 Some(self.adjacency.get(Self::slot_index(id)?)?.outgoing())
480 }
481
482 #[inline]
483 pub(super) fn incoming_at(&self, id: NodeId) -> Option<AdjSide<'_>> {
484 Some(self.adjacency.get(Self::slot_index(id)?)?.incoming())
485 }
486
487 #[inline]
491 fn try_for_each_adjacent_entry<F, E>(
492 node_id: NodeId,
493 filter: &TypeFilter,
494 adj: AdjSide<'_>,
495 skip_self_loops: bool,
496 visit: &mut F,
497 ) -> Result<(), E>
498 where
499 F: FnMut(RelationshipId, NodeId) -> Result<(), E>,
500 {
501 for entry in adj.iter() {
502 if skip_self_loops && entry.neighbour == node_id {
503 continue;
504 }
505 if !filter.matches(entry.type_id) {
506 continue;
507 }
508 visit(entry.rel, entry.neighbour)?;
509 }
510 Ok(())
511 }
512
513 #[inline]
514 pub(super) fn try_for_each_adjacent_id_unchecked<F, E>(
515 &self,
516 node_id: NodeId,
517 direction: Direction,
518 types: &[String],
519 mut visit: F,
520 ) -> Result<(), E>
521 where
522 F: FnMut(RelationshipId, NodeId) -> Result<(), E>,
523 {
524 let filter = TypeFilter::resolve(&self.dicts.types, types);
525 if matches!(filter, TypeFilter::Nothing) {
526 return Ok(());
527 }
528 if !matches!(direction, Direction::Left) {
529 if let Some(adj) = self.outgoing_at(node_id) {
530 Self::try_for_each_adjacent_entry(node_id, &filter, adj, false, &mut visit)?;
531 }
532 }
533 if !matches!(direction, Direction::Right) {
534 if let Some(adj) = self.incoming_at(node_id) {
535 let skip_self_loops = matches!(direction, Direction::Undirected);
536 Self::try_for_each_adjacent_entry(
537 node_id,
538 &filter,
539 adj,
540 skip_self_loops,
541 &mut visit,
542 )?;
543 }
544 }
545
546 Ok(())
547 }
548
549 #[inline]
550 pub(super) fn try_for_each_adjacent_id<F, E>(
551 &self,
552 node_id: NodeId,
553 direction: Direction,
554 types: &[String],
555 visit: F,
556 ) -> Result<(), E>
557 where
558 F: FnMut(RelationshipId, NodeId) -> Result<(), E>,
559 {
560 if !self.has_node_at(node_id) {
561 return Ok(());
562 }
563 self.try_for_each_adjacent_id_unchecked(node_id, direction, types, visit)
564 }
565
566 pub(super) fn iter_node_ids(&self) -> impl Iterator<Item = NodeId> + '_ {
567 self.nodes.slots_from(0).map(|i| i as NodeId)
568 }
569
570 pub(super) fn iter_node_refs(&self) -> impl Iterator<Item = NodeRef<'_>> + '_ {
571 self.iter_nodes().map(|(_, node)| node)
572 }
573
574 pub(super) fn iter_rel_ids(&self) -> impl Iterator<Item = RelationshipId> + '_ {
575 self.relationships
576 .slots_from(0)
577 .map(|i| i as RelationshipId)
578 }
579
580 pub(super) fn iter_rel_refs(&self) -> impl Iterator<Item = RelRef<'_>> + '_ {
581 self.iter_rels().map(|(_, rel)| rel)
582 }
583
584 pub(super) fn iter_nodes(&self) -> impl Iterator<Item = (NodeId, NodeRef<'_>)> + '_ {
585 let dicts = &self.dicts;
586 self.nodes.iter().map(move |(i, bytes)| {
587 let id = i as NodeId;
588 (id, NodeRef::stored(StoredNode::new(id, bytes, dicts)))
589 })
590 }
591
592 pub(super) fn iter_rels(&self) -> impl Iterator<Item = (RelationshipId, RelRef<'_>)> + '_ {
593 let dicts = &self.dicts;
594 self.relationships.iter().map(move |(i, bytes)| {
595 let id = i as RelationshipId;
596 (id, RelRef::stored(StoredRel::new(id, bytes, dicts)))
597 })
598 }
599
600 fn adjacency_push(&mut self, node_id: NodeId, side: Side, entry: AdjEntry) {
604 if let Ok(idx) = self.ensure_node_slot_checked(node_id) {
605 self.adjacency[idx].push(side, entry);
606 }
607 }
608
609 fn adjacency_remove(&mut self, node_id: NodeId, side: Side, rel_id: RelationshipId) {
610 if let Some(adj) = Self::slot_index(node_id).and_then(|idx| self.adjacency.get_mut(idx)) {
611 adj.remove(side, rel_id);
612 }
613 }
614
615 pub(super) fn normalize_labels(labels: Vec<String>) -> Labels {
616 let mut out = Labels::new();
617 for label in &labels {
618 let label = label.trim();
619 if !label.is_empty() && !out.has(label) {
620 out.push(label);
621 }
622 }
623 out
624 }
625
626 pub(super) fn insert_node_label_index(&mut self, node_id: NodeId, label: &str) {
627 if let Some(bucket) = self.nodes_by_label.get_mut(label) {
632 bucket.insert(node_id);
633 } else {
634 self.nodes_by_label
635 .insert(label.to_string(), std::iter::once(node_id).collect());
636 }
637 }
638
639 fn remove_node_label_index(&mut self, node_id: NodeId, label: &str) {
640 if let Some(ids) = self.nodes_by_label.get_mut(label) {
641 ids.remove(node_id);
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.insert(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 ids.remove(rel_id);
661 if ids.is_empty() {
662 self.relationships_by_type.remove(rel_type);
663 }
664 }
665 }
666
667 pub(super) fn indexed_node_ids(
671 &self,
672 label: &str,
673 key: &str,
674 value: &PropertyValue,
675 ) -> Option<Vec<NodeId>> {
676 super::property_index::PropertyIndexKey::from_value(value)?;
677 let indexes = self.indexes_read();
678 if !indexes.node_properties.is_active(key) {
679 return None;
680 }
681 Some(
682 indexes
683 .node_properties
684 .scoped_ids_for(label, key, value)
685 .map(|ids| ids.to_vec())
686 .unwrap_or_default(),
687 )
688 }
689
690 pub(super) fn indexed_rel_ids(
692 &self,
693 rel_type: &str,
694 key: &str,
695 value: &PropertyValue,
696 ) -> Option<Vec<RelationshipId>> {
697 super::property_index::PropertyIndexKey::from_value(value)?;
698 let indexes = self.indexes_read();
699 if !indexes.relationship_properties.is_active(key) {
700 return None;
701 }
702 Some(
703 indexes
704 .relationship_properties
705 .scoped_ids_for(rel_type, key, value)
706 .map(|ids| ids.to_vec())
707 .unwrap_or_default(),
708 )
709 }
710
711 pub(super) fn indexes_read(&self) -> std::sync::RwLockReadGuard<'_, PropertyIndexRegistry> {
712 self.indexes
713 .properties
714 .read()
715 .unwrap_or_else(|poisoned| poisoned.into_inner())
716 }
717
718 pub(super) fn indexes_write(&self) -> RwLockWriteGuard<'_, PropertyIndexRegistry> {
719 self.indexes
720 .properties
721 .write()
722 .unwrap_or_else(|poisoned| poisoned.into_inner())
723 }
724
725 pub(super) fn indexes_mut(&mut self) -> &mut PropertyIndexRegistry {
726 self.indexes
727 .properties
728 .get_mut()
729 .unwrap_or_else(|poisoned| poisoned.into_inner())
730 }
731
732 #[inline]
733 pub(super) fn active_node_property_index_count(&self) -> usize {
734 self.indexes
735 .active_node_property_indexes
736 .load(Ordering::Relaxed)
737 }
738
739 #[inline]
740 pub(super) fn active_relationship_property_index_count(&self) -> usize {
741 self.indexes
742 .active_relationship_property_indexes
743 .load(Ordering::Relaxed)
744 }
745
746 #[inline]
747 pub(super) fn active_constraint_count(&self) -> usize {
748 self.active_constraints.load(Ordering::Relaxed)
749 }
750
751 #[inline]
752 pub(super) fn has_active_constraints(&self) -> bool {
753 self.active_constraint_count() != 0
754 }
755
756 #[inline]
757 pub(super) fn active_fulltext_index_count(&self) -> usize {
758 self.indexes.active_fulltext_indexes.load(Ordering::Relaxed)
759 }
760
761 #[inline]
762 pub(super) fn has_active_fulltext_indexes(&self) -> bool {
763 self.active_fulltext_index_count() != 0
764 }
765
766 pub(super) fn node_property_index_is_active(&mut self, key: &str) -> bool {
767 self.active_node_property_index_count() != 0
768 && self.indexes_mut().node_properties.is_active(key)
769 }
770
771 pub(super) fn relationship_property_index_is_active(&mut self, key: &str) -> bool {
772 self.active_relationship_property_index_count() != 0
773 && self.indexes_mut().relationship_properties.is_active(key)
774 }
775
776 pub(super) fn ensure_any_scope_node_property_index(&self, key: &str) {
779 if self.indexes_read().node_properties.any_scope_is_active(key) {
780 return;
781 }
782 self.indexes_write().node_properties.activate_any_scope(key);
783 }
784
785 pub(super) fn ensure_any_scope_relationship_property_index(&self, key: &str) {
786 if self
787 .indexes_read()
788 .relationship_properties
789 .any_scope_is_active(key)
790 {
791 return;
792 }
793 self.indexes_write()
794 .relationship_properties
795 .activate_any_scope(key);
796 }
797
798 pub(super) fn ensure_node_property_index(&self, key: &str) {
799 {
800 let indexes = self.indexes_read();
801 if indexes.node_properties.is_active(key) {
802 return;
803 }
804 }
805
806 let mut indexes = self.indexes_write();
807 if indexes.node_properties.is_active(key) {
808 return;
809 }
810
811 indexes.node_properties.insert_bulk(key, || {
812 self.iter_nodes().filter_map(|(id, node)| {
813 let value = node.properties().get(key)?;
814 Some((id, node.labels().strs(), value))
815 })
816 });
817 if indexes.node_properties.activate(key) {
818 self.indexes
819 .active_node_property_indexes
820 .fetch_add(1, Ordering::Relaxed);
821 }
822 }
823
824 pub(super) fn ensure_relationship_property_index(&self, key: &str) {
825 {
826 let indexes = self.indexes_read();
827 if indexes.relationship_properties.is_active(key) {
828 return;
829 }
830 }
831
832 let mut indexes = self.indexes_write();
833 if indexes.relationship_properties.is_active(key) {
834 return;
835 }
836
837 indexes.relationship_properties.insert_bulk(key, || {
838 self.iter_rels().filter_map(|(id, rel)| {
839 let value = rel.properties().get(key)?;
840 Some((id, [rel.rel_type()], value))
841 })
842 });
843 if indexes.relationship_properties.activate(key) {
844 self.indexes
845 .active_relationship_property_indexes
846 .fetch_add(1, Ordering::Relaxed);
847 }
848 }
849
850 pub(super) fn index_catalog_read(&self) -> IndexRead<'_, IndexCatalog> {
851 read_shared(&self.indexes.catalog)
852 }
853
854 pub(super) fn index_catalog_write(&self) -> IndexWrite<'_, IndexCatalog> {
856 write_shared(&self.indexes.catalog)
857 }
858
859 pub(super) fn constraint_catalog_read(&self) -> IndexRead<'_, ConstraintCatalog> {
860 read_shared(&self.constraint_catalog)
861 }
862
863 pub(super) fn constraint_catalog_write(&self) -> IndexWrite<'_, ConstraintCatalog> {
865 write_shared(&self.constraint_catalog)
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 pub(super) fn declared_property_index_keys(
1214 &self,
1215 ) -> std::collections::BTreeSet<(StoredIndexEntity, String)> {
1216 self.index_catalog_read()
1217 .list()
1218 .into_iter()
1219 .filter(|def| def.kind == StoredIndexKind::Range)
1220 .flat_map(|def| {
1221 let entity = def.entity;
1222 def.properties.into_iter().map(move |p| (entity, p))
1223 })
1224 .collect()
1225 }
1226
1227 fn populate_index_data(&self, def: &IndexDefinition) {
1228 match def.kind {
1236 StoredIndexKind::Range => {
1237 for key in &def.properties {
1238 match def.entity {
1239 StoredIndexEntity::Node => self.ensure_node_property_index(key),
1240 StoredIndexEntity::Relationship => {
1241 self.ensure_relationship_property_index(key)
1242 }
1243 }
1244 if let Some(label) = def.label.as_deref() {
1245 self.activate_sorted_scope(def.entity, label, key);
1246 }
1247 }
1248 }
1249 StoredIndexKind::Text => {
1250 let label = match def.label.as_deref() {
1251 Some(l) => l,
1252 None => return,
1253 };
1254 for property in &def.properties {
1255 self.activate_text_scope(def.entity, label, property);
1256 }
1257 }
1258 StoredIndexKind::Point => {
1259 let label = match def.label.as_deref() {
1260 Some(l) => l,
1261 None => return,
1262 };
1263 let cell_size = PointRegistry::cell_size_from_options(&def.options);
1264 for property in &def.properties {
1265 self.activate_point_scope(def.entity, label, property, cell_size);
1266 }
1267 }
1268 StoredIndexKind::Fulltext => {
1269 let labels: Vec<String> = def.all_labels().map(String::from).collect();
1270 if labels.is_empty() {
1271 return;
1272 }
1273 self.activate_fulltext_index(def.entity, &def.name, &labels, &def.properties);
1274 }
1275 StoredIndexKind::Vector => {
1276 let label = match def.label.as_deref() {
1277 Some(l) => l,
1278 None => return,
1279 };
1280 let property = match def.properties.first() {
1281 Some(p) => p.as_str(),
1282 None => return,
1283 };
1284 let similarity = VectorSimilarity::from_options(&def.options)
1285 .unwrap_or(VectorSimilarity::Cosine);
1286 let provider = VectorIndexProvider::from_options(&def.options)
1287 .unwrap_or(VectorIndexProvider::Flat);
1288 let hnsw = HnswParams::from_options(&def.options);
1289 let lazy = matches!(
1290 def.options.get("vector.populate.async"),
1291 Some(super::index_catalog::IndexConfigValue::Bool(true))
1292 );
1293 self.activate_vector_index(
1294 def.entity, &def.name, label, property, similarity, provider, hnsw, lazy,
1295 );
1296 if lazy {
1297 self.index_catalog_write()
1298 .set_state(&def.name, StoredIndexState::Populating);
1299 }
1300 }
1301 StoredIndexKind::Lookup => {}
1303 }
1304 }
1305
1306 pub(super) fn text_indexes_read(
1307 &self,
1308 entity: StoredIndexEntity,
1309 ) -> IndexRead<'_, TrigramRegistry> {
1310 self.indexes.text.read(entity)
1311 }
1312
1313 pub(super) fn text_indexes_write(
1314 &self,
1315 entity: StoredIndexEntity,
1316 ) -> IndexWrite<'_, TrigramRegistry> {
1317 self.indexes.text.write(entity)
1318 }
1319
1320 pub(super) fn fulltext_indexes_read(
1321 &self,
1322 entity: StoredIndexEntity,
1323 ) -> IndexRead<'_, FulltextRegistry> {
1324 self.indexes.fulltext.read(entity)
1325 }
1326
1327 #[allow(dead_code)]
1328 pub(super) fn fulltext_indexes_write(
1329 &self,
1330 entity: StoredIndexEntity,
1331 ) -> IndexWrite<'_, FulltextRegistry> {
1332 self.indexes.fulltext.write(entity)
1333 }
1334
1335 fn activate_text_scope(&self, entity: StoredIndexEntity, label: &str, property: &str) {
1336 if !self.text_indexes_write(entity).add_scope(label, property) {
1337 return;
1338 }
1339
1340 let backfill: Vec<(u64, String)> = match entity {
1341 StoredIndexEntity::Node => self
1342 .iter_nodes()
1343 .filter(|(_, node)| node.labels().strs().any(|l| l == label))
1344 .filter_map(|(id, node)| match node.properties().get(property) {
1345 Some(crate::ValueRef::String(value)) => Some((id, value.to_owned())),
1346 _ => None,
1347 })
1348 .collect(),
1349 StoredIndexEntity::Relationship => self
1350 .iter_rels()
1351 .filter(|(_, rel)| rel.rel_type() == label)
1352 .filter_map(|(id, rel)| match rel.properties().get(property) {
1353 Some(crate::ValueRef::String(value)) => Some((id, value.to_owned())),
1354 _ => None,
1355 })
1356 .collect(),
1357 };
1358
1359 let mut registry = self.text_indexes_write(entity);
1360 for (id, value) in backfill {
1361 registry.insert(label, property, id, &value);
1362 }
1363 }
1364
1365 pub(super) fn deactivate_text_scope(
1367 &self,
1368 entity: StoredIndexEntity,
1369 label: &str,
1370 property: &str,
1371 ) {
1372 self.text_indexes_write(entity)
1373 .remove_scope(label, property);
1374 }
1375
1376 fn activate_fulltext_index(
1377 &self,
1378 entity: StoredIndexEntity,
1379 name: &str,
1380 labels: &[String],
1381 properties: &[String],
1382 ) {
1383 use super::fulltext_index::{term_counts_for_properties, TermCounts};
1384
1385 {
1386 let mut registry = self.fulltext_indexes_write(entity);
1387 registry.register(name.to_string(), labels.to_vec(), properties.to_vec());
1388 }
1389 self.indexes
1390 .active_fulltext_indexes
1391 .fetch_add(1, Ordering::Relaxed);
1392
1393 let backfill: Vec<(u64, TermCounts)> = match entity {
1396 StoredIndexEntity::Node => self
1397 .iter_nodes()
1398 .filter(|(_, node)| {
1399 labels
1400 .iter()
1401 .any(|wanted| node.labels().strs().any(|l| l == wanted))
1402 })
1403 .map(|(id, node)| {
1404 let counts = term_counts_for_properties(node.properties(), properties);
1405 (id, counts)
1406 })
1407 .filter(|(_, c)| !c.is_empty())
1408 .collect(),
1409 StoredIndexEntity::Relationship => self
1410 .iter_rels()
1411 .filter(|(_, rel)| labels.iter().any(|wanted| wanted == rel.rel_type()))
1412 .map(|(id, rel)| {
1413 let counts = term_counts_for_properties(rel.properties(), properties);
1414 (id, counts)
1415 })
1416 .filter(|(_, c)| !c.is_empty())
1417 .collect(),
1418 };
1419
1420 let mut registry = self.fulltext_indexes_write(entity);
1421 if let Some(index) = registry.get_mut(name) {
1422 for (id, counts) in backfill {
1423 index.reindex_entity(id, counts);
1424 }
1425 }
1426 }
1427
1428 pub(super) fn deactivate_fulltext_index(&self, entity: StoredIndexEntity, name: &str) {
1429 self.fulltext_indexes_write(entity).deregister(name);
1430 self.indexes
1431 .active_fulltext_indexes
1432 .fetch_sub(1, Ordering::Relaxed);
1433 }
1434
1435 pub(super) fn sorted_indexes_read(
1436 &self,
1437 entity: StoredIndexEntity,
1438 ) -> IndexRead<'_, SortedPropertyIndex> {
1439 self.indexes.sorted.read(entity)
1440 }
1441
1442 pub(super) fn sorted_indexes_write(
1443 &self,
1444 entity: StoredIndexEntity,
1445 ) -> IndexWrite<'_, SortedPropertyIndex> {
1446 self.indexes.sorted.write(entity)
1447 }
1448
1449 fn activate_sorted_scope(&self, entity: StoredIndexEntity, label: &str, property: &str) {
1450 if !self.sorted_indexes_write(entity).add_scope(label, property) {
1451 return;
1452 }
1453
1454 let mut registry = self.sorted_indexes_write(entity);
1457 match entity {
1458 StoredIndexEntity::Node => {
1459 for (id, node) in self.iter_nodes() {
1460 if !node.labels().strs().any(|l| l == label) {
1461 continue;
1462 }
1463 if let Some(value) = node.properties().get(property) {
1464 registry.insert(label, property, id, &value.to_owned());
1465 }
1466 }
1467 }
1468 StoredIndexEntity::Relationship => {
1469 for (id, rel) in self.iter_rels() {
1470 if rel.rel_type() != label {
1471 continue;
1472 }
1473 if let Some(value) = rel.properties().get(property) {
1474 registry.insert(label, property, id, &value.to_owned());
1475 }
1476 }
1477 }
1478 }
1479 }
1480
1481 pub(super) fn deactivate_sorted_scope(
1482 &self,
1483 entity: StoredIndexEntity,
1484 label: &str,
1485 property: &str,
1486 ) {
1487 self.sorted_indexes_write(entity)
1488 .remove_scope(label, property);
1489 }
1490
1491 pub(super) fn point_indexes_read(
1492 &self,
1493 entity: StoredIndexEntity,
1494 ) -> IndexRead<'_, PointRegistry> {
1495 self.indexes.point.read(entity)
1496 }
1497
1498 pub(super) fn point_indexes_write(
1499 &self,
1500 entity: StoredIndexEntity,
1501 ) -> IndexWrite<'_, PointRegistry> {
1502 self.indexes.point.write(entity)
1503 }
1504
1505 fn activate_point_scope(
1506 &self,
1507 entity: StoredIndexEntity,
1508 label: &str,
1509 property: &str,
1510 cell_size: Option<f64>,
1511 ) {
1512 if !self
1513 .point_indexes_write(entity)
1514 .add_scope(label, property, cell_size)
1515 {
1516 return;
1517 }
1518
1519 let backfill: Vec<(u64, LoraPoint)> = match entity {
1520 StoredIndexEntity::Node => self
1521 .iter_nodes()
1522 .filter(|(_, node)| node.labels().strs().any(|l| l == label))
1523 .filter_map(|(id, node)| {
1524 match node.properties().get(property).map(|v| v.to_owned()) {
1525 Some(PropertyValue::Point(point)) => Some((id, point)),
1526 _ => None,
1527 }
1528 })
1529 .collect(),
1530 StoredIndexEntity::Relationship => self
1531 .iter_rels()
1532 .filter(|(_, rel)| rel.rel_type() == label)
1533 .filter_map(|(id, rel)| {
1534 match rel.properties().get(property).map(|v| v.to_owned()) {
1535 Some(PropertyValue::Point(point)) => Some((id, point)),
1536 _ => None,
1537 }
1538 })
1539 .collect(),
1540 };
1541
1542 let mut registry = self.point_indexes_write(entity);
1543 for (id, point) in backfill {
1544 registry.insert(label, property, id, point);
1545 }
1546 }
1547
1548 pub(super) fn deactivate_point_scope(
1549 &self,
1550 entity: StoredIndexEntity,
1551 label: &str,
1552 property: &str,
1553 ) {
1554 self.point_indexes_write(entity)
1555 .remove_scope(label, property);
1556 }
1557
1558 pub(super) fn vector_indexes_read(
1559 &self,
1560 entity: StoredIndexEntity,
1561 ) -> IndexRead<'_, VectorIndexRegistry> {
1562 self.indexes.vector.read(entity)
1563 }
1564
1565 pub(super) fn vector_indexes_write(
1566 &self,
1567 entity: StoredIndexEntity,
1568 ) -> IndexWrite<'_, VectorIndexRegistry> {
1569 self.indexes.vector.write(entity)
1570 }
1571
1572 #[allow(clippy::too_many_arguments)]
1573 fn activate_vector_index(
1574 &self,
1575 entity: StoredIndexEntity,
1576 name: &str,
1577 label: &str,
1578 property: &str,
1579 similarity: VectorSimilarity,
1580 provider: VectorIndexProvider,
1581 hnsw: HnswParams,
1582 lazy: bool,
1583 ) {
1584 {
1585 let mut registry = self.vector_indexes_write(entity);
1586 registry.register(
1587 name.to_string(),
1588 label.to_string(),
1589 property.to_string(),
1590 similarity,
1591 provider,
1592 hnsw,
1593 );
1594 }
1595
1596 if lazy {
1597 return;
1604 }
1605
1606 self.backfill_vector_index(entity, label, property);
1607 }
1608
1609 fn backfill_vector_index(&self, entity: StoredIndexEntity, label: &str, property: &str) {
1613 let backfill: Vec<(u64, crate::LoraVector)> = match entity {
1614 StoredIndexEntity::Node => self
1615 .iter_nodes()
1616 .filter(|(_, node)| node.labels().strs().any(|l| l == label))
1617 .filter_map(|(id, node)| {
1618 match node.properties().get(property).map(|v| v.to_owned()) {
1619 Some(PropertyValue::Vector(v)) => Some((id, v)),
1620 _ => None,
1621 }
1622 })
1623 .collect(),
1624 StoredIndexEntity::Relationship => self
1625 .iter_rels()
1626 .filter(|(_, rel)| rel.rel_type() == label)
1627 .filter_map(|(id, rel)| {
1628 match rel.properties().get(property).map(|v| v.to_owned()) {
1629 Some(PropertyValue::Vector(v)) => Some((id, v)),
1630 _ => None,
1631 }
1632 })
1633 .collect(),
1634 };
1635
1636 let mut registry = self.vector_indexes_write(entity);
1637 for (id, vector) in backfill {
1638 registry.insert_for(label, property, id, &vector);
1639 }
1640 }
1641
1642 pub(super) fn lazy_populate_vector_index(&self, name: &str) {
1648 let def = match self.index_catalog_read().get(name).cloned() {
1649 Some(d) => d,
1650 None => return,
1651 };
1652 if def.state != StoredIndexState::Populating || def.kind != StoredIndexKind::Vector {
1653 return;
1654 }
1655 let label = match def.label.as_deref() {
1656 Some(l) => l,
1657 None => return,
1658 };
1659 let property = match def.properties.first() {
1660 Some(p) => p.as_str(),
1661 None => return,
1662 };
1663 self.backfill_vector_index(def.entity, label, property);
1664 self.index_catalog_write()
1665 .set_state(name, StoredIndexState::Online);
1666 }
1667
1668 pub(super) fn deactivate_vector_index(&self, entity: StoredIndexEntity, name: &str) {
1669 self.vector_indexes_write(entity).deregister(name);
1670 }
1671
1672 pub fn graph_stats(&self) -> GraphStats {
1678 let mut stats = GraphStats {
1679 node_count: self.live_node_count,
1680 relationship_count: self.live_rel_count,
1681 ..Default::default()
1682 };
1683 for (label, ids) in &self.nodes_by_label {
1684 stats.nodes_by_label.insert(label.clone(), ids.len());
1685 }
1686 for (rel_type, ids) in &self.relationships_by_type {
1687 stats
1688 .relationships_by_type
1689 .insert(rel_type.clone(), ids.len());
1690 }
1691 stats.node_distinct_values = self.distinct_stats.nodes.distinct_values();
1698 stats.relationship_distinct_values = self.distinct_stats.relationships.distinct_values();
1699
1700 for def in self.index_catalog_read().list() {
1701 if def.state != StoredIndexState::Online {
1702 continue;
1703 }
1704 let Some(label) = def.label else {
1705 continue;
1706 };
1707 for property in def.properties {
1708 let scope = (label.clone(), property);
1709 match (def.entity, def.kind) {
1710 (StoredIndexEntity::Node, StoredIndexKind::Range) => {
1711 stats.node_range_indexes.insert(scope);
1712 }
1713 (StoredIndexEntity::Node, StoredIndexKind::Text) => {
1714 stats.node_text_indexes.insert(scope);
1715 }
1716 (StoredIndexEntity::Node, StoredIndexKind::Point) => {
1717 stats.node_point_indexes.insert(scope);
1718 }
1719 (StoredIndexEntity::Relationship, StoredIndexKind::Range) => {
1720 stats.relationship_range_indexes.insert(scope);
1721 }
1722 (StoredIndexEntity::Relationship, StoredIndexKind::Text) => {
1723 stats.relationship_text_indexes.insert(scope);
1724 }
1725 (StoredIndexEntity::Relationship, StoredIndexKind::Point) => {
1726 stats.relationship_point_indexes.insert(scope);
1727 }
1728 (StoredIndexEntity::Node, StoredIndexKind::Vector) => {
1729 stats.node_vector_indexes.insert(scope);
1730 }
1731 (StoredIndexEntity::Relationship, StoredIndexKind::Vector) => {
1732 stats.relationship_vector_indexes.insert(scope);
1733 }
1734 (_, StoredIndexKind::Lookup | StoredIndexKind::Fulltext) => {}
1735 }
1736 }
1737 }
1738 stats
1739 }
1740
1741 pub fn memory_estimate(&self) -> super::MemoryReport {
1746 super::mem_report::estimate(self)
1747 }
1748
1749 pub(super) fn count_distinct_values<'a>(
1752 stats: &mut super::distinct_stats::DistinctStats,
1753 scopes: impl IntoIterator<Item = &'a str>,
1754 properties: &Properties,
1755 add: bool,
1756 ) {
1757 if properties.is_empty() {
1758 return;
1759 }
1760 for scope in scopes {
1761 if add {
1762 stats.add_all(scope, properties);
1763 } else {
1764 for (key, value) in properties {
1765 stats.remove(scope, key, value);
1766 }
1767 }
1768 }
1769 }
1770
1771 pub(super) fn on_node_created(&mut self, node: &NodeRecord) {
1772 for label in &node.labels {
1773 self.insert_node_label_index(node.id, label);
1774 }
1775 Self::count_distinct_values(
1776 &mut self.distinct_stats.nodes,
1777 node.labels.strs(),
1778 &node.properties,
1779 true,
1780 );
1781 self.index_node_properties_if_active(node.id, node.labels.strs(), &node.properties);
1782 self.maintain_node_secondary_indexes(node, SecondaryIndexMutation::Insert);
1783 }
1784
1785 pub(super) fn on_node_property_set(
1786 &mut self,
1787 node_id: NodeId,
1788 key: &str,
1789 old: Option<&PropertyValue>,
1790 new: &PropertyValue,
1791 ) {
1792 let Some(labels) = self.node_at(node_id).map(|node| node.labels().to_owned()) else {
1793 return;
1794 };
1795 for label in &labels {
1796 self.distinct_stats.nodes.replace(label, key, old, new);
1797 }
1798
1799 if self.node_property_index_is_active(key) {
1800 if let Some(old) = old {
1801 self.unindex_node_property_if_active(node_id, labels.strs(), key, old);
1802 }
1803 self.index_node_property_if_active(node_id, labels.strs(), key, new);
1804 }
1805
1806 self.update_secondary_property(
1807 StoredIndexEntity::Node,
1808 labels.strs(),
1809 node_id,
1810 key,
1811 old,
1812 Some(new),
1813 );
1814 }
1815
1816 pub(super) fn on_node_property_removed(
1817 &mut self,
1818 node_id: NodeId,
1819 key: &str,
1820 old: &PropertyValue,
1821 ) {
1822 let Some(labels) = self.node_at(node_id).map(|node| node.labels().to_owned()) else {
1823 return;
1824 };
1825 for label in &labels {
1826 self.distinct_stats.nodes.remove(label, key, old);
1827 }
1828 if self.node_property_index_is_active(key) {
1829 self.unindex_node_property_if_active(node_id, labels.strs(), key, old);
1830 }
1831 self.update_secondary_property(
1832 StoredIndexEntity::Node,
1833 labels.strs(),
1834 node_id,
1835 key,
1836 Some(old),
1837 None,
1838 );
1839 }
1840
1841 pub(super) fn on_node_label_added(&mut self, node_id: NodeId, label: &str) {
1842 self.insert_node_label_index(node_id, label);
1843
1844 let Some(properties) = self
1845 .node_at(node_id)
1846 .map(|node| node.properties().to_owned())
1847 else {
1848 return;
1849 };
1850 Self::count_distinct_values(&mut self.distinct_stats.nodes, [label], &properties, true);
1851 if self.active_node_property_index_count() != 0 {
1852 self.index_node_scope_properties_if_active(node_id, label, &properties);
1853 if self.node_at(node_id).is_some_and(|n| n.labels().len() == 1) {
1855 self.unindex_node_scope_properties_if_active(node_id, UNLABELLED, &properties);
1856 }
1857 }
1858 for (key, value) in &properties {
1859 self.update_secondary_property(
1860 StoredIndexEntity::Node,
1861 [label],
1862 node_id,
1863 key,
1864 None,
1865 Some(value),
1866 );
1867 }
1868 }
1869
1870 pub(super) fn on_node_label_removed(&mut self, node_id: NodeId, label: &str) {
1871 self.remove_node_label_index(node_id, label);
1872
1873 let Some(properties) = self
1874 .node_at(node_id)
1875 .map(|node| node.properties().to_owned())
1876 else {
1877 return;
1878 };
1879 Self::count_distinct_values(&mut self.distinct_stats.nodes, [label], &properties, false);
1880 if self.active_node_property_index_count() != 0 {
1881 self.unindex_node_scope_properties_if_active(node_id, label, &properties);
1882 if self.node_at(node_id).is_some_and(|n| n.labels().is_empty()) {
1884 self.index_node_scope_properties_if_active(node_id, UNLABELLED, &properties);
1885 }
1886 }
1887 for (key, value) in &properties {
1888 self.update_secondary_property(
1889 StoredIndexEntity::Node,
1890 [label],
1891 node_id,
1892 key,
1893 Some(value),
1894 None,
1895 );
1896 }
1897 }
1898
1899 pub(super) fn on_node_deleted(&mut self, node: &NodeRecord) {
1900 for label in &node.labels {
1901 self.remove_node_label_index(node.id, label);
1902 }
1903 Self::count_distinct_values(
1904 &mut self.distinct_stats.nodes,
1905 node.labels.strs(),
1906 &node.properties,
1907 false,
1908 );
1909 self.unindex_active_node_properties(node.id, node.labels.strs(), &node.properties);
1910 self.maintain_node_secondary_indexes(node, SecondaryIndexMutation::Remove);
1911 }
1912
1913 pub(super) fn on_relationship_created(&mut self, rel: &RelationshipRecord) {
1914 self.attach_relationship(rel);
1915 Self::count_distinct_values(
1916 &mut self.distinct_stats.relationships,
1917 [rel.rel_type.as_str()],
1918 &rel.properties,
1919 true,
1920 );
1921 self.index_relationship_properties_if_active(
1922 rel.id,
1923 [rel.rel_type.as_str()],
1924 &rel.properties,
1925 );
1926 self.maintain_relationship_secondary_indexes(rel, SecondaryIndexMutation::Insert);
1927 }
1928
1929 pub(super) fn on_relationship_property_set(
1930 &mut self,
1931 rel_id: RelationshipId,
1932 key: &str,
1933 old: Option<&PropertyValue>,
1934 new: &PropertyValue,
1935 ) {
1936 let Some(rel_type) = self.rel_at(rel_id).map(|rel| rel.type_name().clone()) else {
1937 return;
1938 };
1939 self.distinct_stats
1940 .relationships
1941 .replace(&rel_type, key, old, new);
1942
1943 if self.relationship_property_index_is_active(key) {
1944 if let Some(old) = old {
1945 self.unindex_relationship_property_if_active(rel_id, [rel_type.as_str()], key, old);
1946 }
1947 self.index_relationship_property_if_active(rel_id, [rel_type.as_str()], key, new);
1948 }
1949
1950 self.update_secondary_property(
1951 StoredIndexEntity::Relationship,
1952 [rel_type.as_str()],
1953 rel_id,
1954 key,
1955 old,
1956 Some(new),
1957 );
1958 }
1959
1960 pub(super) fn on_relationship_property_removed(
1961 &mut self,
1962 rel_id: RelationshipId,
1963 key: &str,
1964 old: &PropertyValue,
1965 ) {
1966 let Some(rel_type) = self.rel_at(rel_id).map(|rel| rel.type_name().clone()) else {
1967 return;
1968 };
1969 self.distinct_stats
1970 .relationships
1971 .remove(&rel_type, key, old);
1972 if self.relationship_property_index_is_active(key) {
1973 self.unindex_relationship_property_if_active(rel_id, [rel_type.as_str()], key, old);
1974 }
1975 self.update_secondary_property(
1976 StoredIndexEntity::Relationship,
1977 [rel_type.as_str()],
1978 rel_id,
1979 key,
1980 Some(old),
1981 None,
1982 );
1983 }
1984
1985 pub(super) fn on_relationship_deleted(&mut self, rel: &RelationshipRecord) {
1986 self.detach_relationship_indexes(rel);
1987 Self::count_distinct_values(
1988 &mut self.distinct_stats.relationships,
1989 [rel.rel_type.as_str()],
1990 &rel.properties,
1991 false,
1992 );
1993 self.unindex_active_relationship_properties(
1994 rel.id,
1995 [rel.rel_type.as_str()],
1996 &rel.properties,
1997 );
1998 self.maintain_relationship_secondary_indexes(rel, SecondaryIndexMutation::Remove);
1999 }
2000
2001 fn index_node_property_if_active<'a>(
2002 &mut self,
2003 node_id: NodeId,
2004 labels: impl IntoIterator<Item = &'a str>,
2005 key: &str,
2006 value: &PropertyValue,
2007 ) {
2008 if self.active_node_property_index_count() == 0 {
2009 return;
2010 }
2011 let indexes = self.indexes_mut();
2012 if indexes.node_properties.is_active(key) {
2013 indexes
2014 .node_properties
2015 .insert_with_scopes(node_id, labels, key, value);
2016 }
2017 }
2018
2019 fn index_node_properties_if_active<'a>(
2020 &mut self,
2021 node_id: NodeId,
2022 labels: impl IntoIterator<Item = &'a str> + Clone,
2023 properties: &Properties,
2024 ) {
2025 if self.active_node_property_index_count() == 0 {
2026 return;
2027 }
2028 let indexes = self.indexes_mut();
2029 for (key, value) in properties {
2030 if indexes.node_properties.is_active(key) {
2031 indexes
2032 .node_properties
2033 .insert_with_scopes(node_id, labels.clone(), key, value);
2034 }
2035 }
2036 }
2037
2038 fn unindex_node_property_if_active<'a>(
2039 &mut self,
2040 node_id: NodeId,
2041 labels: impl IntoIterator<Item = &'a str>,
2042 key: &str,
2043 value: &PropertyValue,
2044 ) {
2045 if self.active_node_property_index_count() == 0 {
2046 return;
2047 }
2048 let indexes = self.indexes_mut();
2049 if indexes.node_properties.is_active(key) {
2050 indexes
2051 .node_properties
2052 .remove_with_scopes(node_id, labels, key, value);
2053 }
2054 }
2055
2056 fn index_node_scope_properties_if_active(
2057 &mut self,
2058 node_id: NodeId,
2059 scope: &str,
2060 properties: &Properties,
2061 ) {
2062 if self.active_node_property_index_count() == 0 {
2063 return;
2064 }
2065 let indexes = self.indexes_mut();
2066 for (key, value) in properties {
2067 if indexes.node_properties.is_active(key) {
2068 indexes
2069 .node_properties
2070 .insert_scoped(node_id, scope, key, value);
2071 }
2072 }
2073 }
2074
2075 fn unindex_node_scope_properties_if_active(
2076 &mut self,
2077 node_id: NodeId,
2078 scope: &str,
2079 properties: &Properties,
2080 ) {
2081 if self.active_node_property_index_count() == 0 {
2082 return;
2083 }
2084 let indexes = self.indexes_mut();
2085 for (key, value) in properties {
2086 if indexes.node_properties.is_active(key) {
2087 indexes
2088 .node_properties
2089 .remove_scoped(node_id, scope, key, value);
2090 }
2091 }
2092 }
2093
2094 fn unindex_active_node_properties<'a>(
2095 &mut self,
2096 node_id: NodeId,
2097 labels: impl IntoIterator<Item = &'a str> + Clone,
2098 properties: &Properties,
2099 ) {
2100 if self.active_node_property_index_count() == 0 {
2101 return;
2102 }
2103 let indexes = self.indexes_mut();
2104 for (key, value) in properties {
2105 if indexes.node_properties.is_active(key) {
2106 indexes
2107 .node_properties
2108 .remove_with_scopes(node_id, labels.clone(), key, value);
2109 }
2110 }
2111 }
2112
2113 fn index_relationship_property_if_active<'a>(
2114 &mut self,
2115 rel_id: RelationshipId,
2116 scopes: impl IntoIterator<Item = &'a str>,
2117 key: &str,
2118 value: &PropertyValue,
2119 ) {
2120 if self.active_relationship_property_index_count() == 0 {
2121 return;
2122 }
2123 let indexes = self.indexes_mut();
2124 if indexes.relationship_properties.is_active(key) {
2125 indexes
2126 .relationship_properties
2127 .insert_with_scopes(rel_id, scopes, key, value);
2128 }
2129 }
2130
2131 fn index_relationship_properties_if_active<'a>(
2132 &mut self,
2133 rel_id: RelationshipId,
2134 scopes: impl IntoIterator<Item = &'a str> + Clone,
2135 properties: &Properties,
2136 ) {
2137 if self.active_relationship_property_index_count() == 0 {
2138 return;
2139 }
2140 let indexes = self.indexes_mut();
2141 for (key, value) in properties {
2142 if indexes.relationship_properties.is_active(key) {
2143 indexes.relationship_properties.insert_with_scopes(
2144 rel_id,
2145 scopes.clone(),
2146 key,
2147 value,
2148 );
2149 }
2150 }
2151 }
2152
2153 fn unindex_relationship_property_if_active<'a>(
2154 &mut self,
2155 rel_id: RelationshipId,
2156 scopes: impl IntoIterator<Item = &'a str>,
2157 key: &str,
2158 value: &PropertyValue,
2159 ) {
2160 if self.active_relationship_property_index_count() == 0 {
2161 return;
2162 }
2163 let indexes = self.indexes_mut();
2164 if indexes.relationship_properties.is_active(key) {
2165 indexes
2166 .relationship_properties
2167 .remove_with_scopes(rel_id, scopes, key, value);
2168 }
2169 }
2170
2171 fn unindex_active_relationship_properties<'a>(
2172 &mut self,
2173 rel_id: RelationshipId,
2174 scopes: impl IntoIterator<Item = &'a str> + Clone,
2175 properties: &Properties,
2176 ) {
2177 if self.active_relationship_property_index_count() == 0 {
2178 return;
2179 }
2180 let indexes = self.indexes_mut();
2181 for (key, value) in properties {
2182 if indexes.relationship_properties.is_active(key) {
2183 indexes.relationship_properties.remove_with_scopes(
2184 rel_id,
2185 scopes.clone(),
2186 key,
2187 value,
2188 );
2189 }
2190 }
2191 }
2192
2193 pub(super) fn scan_nodes_by_property(
2194 &self,
2195 label: Option<&str>,
2196 key: &str,
2197 value: &PropertyValue,
2198 ) -> Vec<NodeRecord> {
2199 match label {
2200 Some(label) => self
2201 .nodes_by_label
2202 .get(label)
2203 .into_iter()
2204 .flat_map(|ids| ids.iter())
2205 .filter_map(|id| self.node_at(id))
2206 .filter(|node| node.properties().get(key).is_some_and(|v| v == *value))
2207 .map(|node| node.to_record())
2208 .collect(),
2209 None => self
2210 .iter_node_refs()
2211 .filter(|node| node.properties().get(key).is_some_and(|v| v == *value))
2212 .map(|node| node.to_record())
2213 .collect(),
2214 }
2215 }
2216
2217 pub(super) fn scan_node_ids_by_property(
2218 &self,
2219 label: Option<&str>,
2220 key: &str,
2221 value: &PropertyValue,
2222 ) -> Vec<NodeId> {
2223 match label {
2224 Some(label) => self
2225 .nodes_by_label
2226 .get(label)
2227 .into_iter()
2228 .flat_map(|ids| ids.iter())
2229 .filter_map(|id| {
2230 (self
2231 .node_at(id)?
2232 .properties()
2233 .get(key)
2234 .is_some_and(|v| v == *value))
2235 .then_some(id)
2236 })
2237 .collect(),
2238 None => self
2239 .iter_nodes()
2240 .filter_map(|(id, node)| {
2241 (node.properties().get(key).is_some_and(|v| v == *value)).then_some(id)
2242 })
2243 .collect(),
2244 }
2245 }
2246
2247 pub(super) fn any_node_by_property(
2248 &self,
2249 label: &str,
2250 key: &str,
2251 value: &PropertyValue,
2252 ) -> bool {
2253 self.nodes_by_label
2254 .get(label)
2255 .into_iter()
2256 .flat_map(|ids| ids.iter())
2257 .filter_map(|id| self.node_at(id))
2258 .any(|node| node.properties().get(key).is_some_and(|v| v == *value))
2259 }
2260
2261 pub(super) fn scan_relationships_by_property(
2262 &self,
2263 rel_type: Option<&str>,
2264 key: &str,
2265 value: &PropertyValue,
2266 ) -> Vec<RelationshipRecord> {
2267 match rel_type {
2268 Some(rel_type) => self
2269 .relationships_by_type
2270 .get(rel_type)
2271 .into_iter()
2272 .flat_map(|ids| ids.iter())
2273 .filter_map(|id| self.rel_at(id))
2274 .filter(|rel| rel.properties().get(key).is_some_and(|v| v == *value))
2275 .map(|rel| rel.to_record())
2276 .collect(),
2277 None => self
2278 .iter_rel_refs()
2279 .filter(|rel| rel.properties().get(key).is_some_and(|v| v == *value))
2280 .map(|rel| rel.to_record())
2281 .collect(),
2282 }
2283 }
2284
2285 pub(super) fn scan_relationship_ids_by_property(
2286 &self,
2287 rel_type: Option<&str>,
2288 key: &str,
2289 value: &PropertyValue,
2290 ) -> Vec<RelationshipId> {
2291 match rel_type {
2292 Some(rel_type) => self
2293 .relationships_by_type
2294 .get(rel_type)
2295 .into_iter()
2296 .flat_map(|ids| ids.iter())
2297 .filter_map(|id| {
2298 (self
2299 .rel_at(id)?
2300 .properties()
2301 .get(key)
2302 .is_some_and(|v| v == *value))
2303 .then_some(id)
2304 })
2305 .collect(),
2306 None => self
2307 .iter_rels()
2308 .filter_map(|(id, rel)| {
2309 (rel.properties().get(key).is_some_and(|v| v == *value)).then_some(id)
2310 })
2311 .collect(),
2312 }
2313 }
2314
2315 pub(super) fn any_relationship_by_property(
2316 &self,
2317 rel_type: &str,
2318 key: &str,
2319 value: &PropertyValue,
2320 ) -> bool {
2321 self.relationships_by_type
2322 .get(rel_type)
2323 .into_iter()
2324 .flat_map(|ids| ids.iter())
2325 .filter_map(|id| self.rel_at(id))
2326 .any(|rel| rel.properties().get(key).is_some_and(|v| v == *value))
2327 }
2328
2329 pub(super) fn attach_relationship(&mut self, rel: &RelationshipRecord) {
2330 let type_id = self.dicts.types.id_or_insert(&rel.rel_type);
2331 self.adjacency_push(
2332 rel.src,
2333 Side::Out,
2334 AdjEntry {
2335 type_id,
2336 neighbour: rel.dst,
2337 rel: rel.id,
2338 },
2339 );
2340 self.adjacency_push(
2341 rel.dst,
2342 Side::In,
2343 AdjEntry {
2344 type_id,
2345 neighbour: rel.src,
2346 rel: rel.id,
2347 },
2348 );
2349 self.insert_relationship_type_index(rel.id, &rel.rel_type);
2350 }
2351
2352 fn detach_relationship_indexes(&mut self, rel: &RelationshipRecord) {
2353 self.adjacency_remove(rel.src, Side::Out, rel.id);
2354 self.adjacency_remove(rel.dst, Side::In, rel.id);
2355
2356 self.remove_relationship_type_index(rel.id, &rel.rel_type);
2357 }
2358
2359 pub(super) fn relationship_ids_for_direction(
2360 &self,
2361 node_id: NodeId,
2362 direction: Direction,
2363 ) -> Vec<RelationshipId> {
2364 let mut ids = Vec::new();
2365 let _ = self.try_for_each_adjacent_id_unchecked(node_id, direction, &[], |rel_id, _| {
2366 ids.push(rel_id);
2367 Ok::<(), ()>(())
2368 });
2369 ids
2370 }
2371
2372 pub(super) fn has_incident_relationships(&self, node_id: NodeId) -> bool {
2373 self.outgoing_at(node_id).is_some_and(|adj| !adj.is_empty())
2374 || self.incoming_at(node_id).is_some_and(|adj| !adj.is_empty())
2375 }
2376
2377 pub(super) fn incident_relationship_ids(&self, node_id: NodeId) -> Vec<RelationshipId> {
2378 self.relationship_ids_for_direction(node_id, Direction::Undirected)
2379 }
2380
2381 #[doc(hidden)]
2385 pub fn replay_create_node(
2386 &mut self,
2387 id: NodeId,
2388 labels: Vec<String>,
2389 properties: Properties,
2390 ) -> Result<NodeRecord, String> {
2391 if self.recorder.is_some() {
2392 return Err(
2393 "cannot replay node creation while a mutation recorder is installed".into(),
2394 );
2395 }
2396 if self.has_node_at(id) {
2397 return Err(format!("node id {id} already exists"));
2398 }
2399 let idx = self.ensure_node_slot_checked(id)?;
2400 self.bump_next_node_id_past(id)?;
2401
2402 let labels = Self::normalize_labels(labels);
2403 let node = NodeRecord {
2404 id,
2405 labels,
2406 properties,
2407 };
2408
2409 self.put_node_at_slot(idx, &node);
2410 self.on_node_created(&node);
2416
2417 Ok(node)
2418 }
2419
2420 #[doc(hidden)]
2424 pub fn replay_create_relationship(
2425 &mut self,
2426 id: RelationshipId,
2427 src: NodeId,
2428 dst: NodeId,
2429 rel_type: &str,
2430 properties: Properties,
2431 ) -> Result<RelationshipRecord, String> {
2432 if self.recorder.is_some() {
2433 return Err(
2434 "cannot replay relationship creation while a mutation recorder is installed".into(),
2435 );
2436 }
2437 if self.has_rel_at(id) {
2438 return Err(format!("relationship id {id} already exists"));
2439 }
2440 if !self.has_node_at(src) {
2441 return Err(format!(
2442 "relationship {id} references missing source node {src}"
2443 ));
2444 }
2445 if !self.has_node_at(dst) {
2446 return Err(format!(
2447 "relationship {id} references missing target node {dst}"
2448 ));
2449 }
2450
2451 let trimmed = rel_type.trim();
2452 if trimmed.is_empty() {
2453 return Err(format!("relationship {id} has an empty type"));
2454 }
2455 let idx = self.ensure_rel_slot_checked(id)?;
2456 self.bump_next_rel_id_past(id)?;
2457
2458 let rel = RelationshipRecord {
2459 id,
2460 src,
2461 dst,
2462 rel_type: trimmed.into(),
2463 properties,
2464 };
2465
2466 self.put_rel_at_slot(idx, &rel);
2467 self.on_relationship_created(&rel);
2469
2470 Ok(rel)
2471 }
2472
2473 #[cfg(test)]
2474 pub(super) fn assert_property_indexes_match_scan(&self) {
2475 let indexes = self.indexes_read();
2476 assert_eq!(
2477 indexes.node_properties.active_keys.len(),
2478 self.active_node_property_index_count(),
2479 "node property index counter diverged from active key set"
2480 );
2481 assert_eq!(
2482 indexes.relationship_properties.active_keys.len(),
2483 self.active_relationship_property_index_count(),
2484 "relationship property index counter diverged from active key set"
2485 );
2486
2487 let mut expected_nodes = PropertyIndexState {
2488 active_keys: indexes.node_properties.active_keys.clone(),
2489 any_scope_keys: indexes.node_properties.any_scope_keys.clone(),
2490 ..PropertyIndexState::default()
2491 };
2492 for (id, node) in self.iter_nodes() {
2493 for (key, value) in node.properties() {
2494 if expected_nodes.is_active(key) {
2495 expected_nodes.insert_with_scopes(
2496 id,
2497 node.labels().strs(),
2498 key,
2499 &value.to_owned(),
2500 );
2501 }
2502 }
2503 }
2504 assert_eq!(
2505 indexes.node_properties.scoped_values, expected_nodes.scoped_values,
2506 "node property scoped index values diverged from scan"
2507 );
2508 assert_eq!(
2509 indexes.node_properties.any_scope, expected_nodes.any_scope,
2510 "node property across-scopes index values diverged from scan"
2511 );
2512
2513 let mut expected_relationships = PropertyIndexState {
2514 active_keys: indexes.relationship_properties.active_keys.clone(),
2515 any_scope_keys: indexes.relationship_properties.any_scope_keys.clone(),
2516 ..PropertyIndexState::default()
2517 };
2518 for (id, rel) in self.iter_rels() {
2519 for (key, value) in rel.properties() {
2520 if expected_relationships.is_active(key) {
2521 expected_relationships.insert_with_scopes(
2522 id,
2523 [rel.rel_type()],
2524 key,
2525 &value.to_owned(),
2526 );
2527 }
2528 }
2529 }
2530 assert_eq!(
2531 indexes.relationship_properties.scoped_values, expected_relationships.scoped_values,
2532 "relationship property scoped index values diverged from scan"
2533 );
2534 assert_eq!(
2535 indexes.relationship_properties.any_scope, expected_relationships.any_scope,
2536 "relationship property across-scopes index values diverged from scan"
2537 );
2538 }
2539}
2540
2541#[inline]
2545fn edit_record_property(
2546 slab: &mut RecordSlab,
2547 idx: usize,
2548 kind: encoded::RecordKind,
2549 keys: &mut crate::dict::NameDict,
2550 key: &str,
2551 edit: encoded::PropEdit<'_>,
2552) -> Option<Option<PropertyValue>> {
2553 let bytes = slab.get(idx)?;
2554 let mut out = Scratch::take();
2555 let old = encoded::edit_property(bytes, kind, keys, key, edit, &mut out)?;
2556 slab.set(idx, &out);
2557 Some(old)
2558}