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