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