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