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::{EntityValues, PropertyIndexKey, 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,
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,
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 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, &self.node_values())
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 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, &self.relationship_values())
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 #[inline]
824 pub(super) fn node_values(&self) -> RecordValues<'_> {
825 RecordValues::nodes(&self.nodes, &self.dicts)
826 }
827
828 #[inline]
830 pub(super) fn relationship_values(&self) -> RecordValues<'_> {
831 RecordValues::relationships(&self.relationships, &self.dicts)
832 }
833
834 fn node_index_parts(&mut self) -> (&mut PropertyIndexRegistry, RecordValues<'_>) {
836 let registry = self
837 .indexes
838 .properties
839 .get_mut()
840 .unwrap_or_else(|poisoned| poisoned.into_inner());
841 (registry, RecordValues::nodes(&self.nodes, &self.dicts))
842 }
843
844 fn relationship_index_parts(&mut self) -> (&mut PropertyIndexRegistry, RecordValues<'_>) {
846 let registry = self
847 .indexes
848 .properties
849 .get_mut()
850 .unwrap_or_else(|poisoned| poisoned.into_inner());
851 (
852 registry,
853 RecordValues::relationships(&self.relationships, &self.dicts),
854 )
855 }
856
857 pub(super) fn indexes_mut(&mut self) -> &mut PropertyIndexRegistry {
858 self.indexes
859 .properties
860 .get_mut()
861 .unwrap_or_else(|poisoned| poisoned.into_inner())
862 }
863
864 #[inline]
865 pub(super) fn active_node_property_index_count(&self) -> usize {
866 self.indexes
867 .active_node_property_indexes
868 .load(Ordering::Relaxed)
869 }
870
871 #[inline]
872 pub(super) fn active_relationship_property_index_count(&self) -> usize {
873 self.indexes
874 .active_relationship_property_indexes
875 .load(Ordering::Relaxed)
876 }
877
878 #[inline]
879 pub(super) fn active_constraint_count(&self) -> usize {
880 self.active_constraints.load(Ordering::Relaxed)
881 }
882
883 #[inline]
884 pub(super) fn has_active_constraints(&self) -> bool {
885 self.active_constraint_count() != 0
886 }
887
888 #[inline]
889 pub(super) fn active_fulltext_index_count(&self) -> usize {
890 self.indexes.active_fulltext_indexes.load(Ordering::Relaxed)
891 }
892
893 #[inline]
894 pub(super) fn has_active_fulltext_indexes(&self) -> bool {
895 self.active_fulltext_index_count() != 0
896 }
897
898 pub(super) fn node_property_index_is_active(&mut self, key: &str) -> bool {
899 self.active_node_property_index_count() != 0
900 && self.indexes_mut().node_properties.is_active(key)
901 }
902
903 pub(super) fn relationship_property_index_is_active(&mut self, key: &str) -> bool {
904 self.active_relationship_property_index_count() != 0
905 && self.indexes_mut().relationship_properties.is_active(key)
906 }
907
908 pub(super) fn ensure_any_scope_node_property_index(&self, key: &str) {
911 if self.indexes_read().node_properties.any_scope_is_active(key) {
912 return;
913 }
914 self.indexes_write()
915 .node_properties
916 .activate_any_scope(key, &self.node_values());
917 }
918
919 pub(super) fn ensure_any_scope_relationship_property_index(&self, key: &str) {
920 if self
921 .indexes_read()
922 .relationship_properties
923 .any_scope_is_active(key)
924 {
925 return;
926 }
927 self.indexes_write()
928 .relationship_properties
929 .activate_any_scope(key, &self.relationship_values());
930 }
931
932 pub(super) fn ensure_node_property_index(&self, key: &str) {
933 {
934 let indexes = self.indexes_read();
935 if indexes.node_properties.is_active(key) {
936 return;
937 }
938 }
939
940 let mut indexes = self.indexes_write();
941 if indexes.node_properties.is_active(key) {
942 return;
943 }
944
945 indexes.node_properties.insert_bulk(
946 key,
947 || {
948 self.iter_nodes().filter_map(|(id, node)| {
949 let value = node.properties().get(key)?;
950 Some((id, node.labels().strs(), value))
951 })
952 },
953 &self.node_values(),
954 );
955 if indexes.node_properties.activate(key) {
956 self.indexes
957 .active_node_property_indexes
958 .fetch_add(1, Ordering::Relaxed);
959 }
960 }
961
962 pub(super) fn ensure_relationship_property_index(&self, key: &str) {
963 {
964 let indexes = self.indexes_read();
965 if indexes.relationship_properties.is_active(key) {
966 return;
967 }
968 }
969
970 let mut indexes = self.indexes_write();
971 if indexes.relationship_properties.is_active(key) {
972 return;
973 }
974
975 indexes.relationship_properties.insert_bulk(
976 key,
977 || {
978 self.iter_rels().filter_map(|(id, rel)| {
979 let value = rel.properties().get(key)?;
980 Some((id, [rel.rel_type()], value))
981 })
982 },
983 &self.relationship_values(),
984 );
985 if indexes.relationship_properties.activate(key) {
986 self.indexes
987 .active_relationship_property_indexes
988 .fetch_add(1, Ordering::Relaxed);
989 }
990 }
991
992 pub(super) fn index_catalog_read(&self) -> IndexRead<'_, IndexCatalog> {
993 read_shared(&self.indexes.catalog)
994 }
995
996 pub(super) fn index_catalog_write(&self) -> IndexWrite<'_, IndexCatalog> {
998 write_shared(&self.indexes.catalog)
999 }
1000
1001 pub(super) fn constraint_catalog_read(&self) -> IndexRead<'_, ConstraintCatalog> {
1002 read_shared(&self.constraint_catalog)
1003 }
1004
1005 pub(super) fn constraint_catalog_write(&self) -> IndexWrite<'_, ConstraintCatalog> {
1007 write_shared(&self.constraint_catalog)
1008 }
1009
1010 #[allow(clippy::result_large_err)]
1018 pub(super) fn register_index(
1019 &self,
1020 request: IndexRequest,
1021 if_not_exists: bool,
1022 ) -> Result<CreateIndexOutcome, CreateIndexError> {
1023 self.register_index_with_recording(request, if_not_exists, true)
1024 }
1025
1026 #[allow(clippy::result_large_err)]
1027 fn register_index_with_recording(
1028 &self,
1029 request: IndexRequest,
1030 if_not_exists: bool,
1031 record_event: bool,
1032 ) -> Result<CreateIndexOutcome, CreateIndexError> {
1033 let request_for_event = record_event.then(|| request.clone());
1034 let outcome = {
1035 let mut catalog = self.index_catalog_write();
1036 catalog.try_create(request, if_not_exists)?
1037 };
1038
1039 if let CreateIndexOutcome::Created(def) = &outcome {
1040 self.populate_index_data(def);
1041 }
1042
1043 if matches!(outcome, CreateIndexOutcome::Created(_)) {
1047 if let Some(request_for_event) = request_for_event {
1048 self.emit(|| crate::MutationEvent::CreateIndex {
1049 request: request_for_event,
1050 if_not_exists,
1051 });
1052 }
1053 }
1054
1055 Ok(outcome)
1056 }
1057
1058 #[doc(hidden)]
1062 pub fn replay_create_index(
1063 &mut self,
1064 request: IndexRequest,
1065 if_not_exists: bool,
1066 ) -> Result<(), String> {
1067 if self.recorder.is_some() {
1068 return Err("cannot replay create_index while a mutation recorder is installed".into());
1069 }
1070 self.register_index(request, if_not_exists)
1071 .map(|_| ())
1072 .map_err(|e| e.to_string())
1073 }
1074
1075 #[doc(hidden)]
1077 pub fn replay_drop_index(&mut self, name: &str, if_exists: bool) -> Result<(), String> {
1078 if self.recorder.is_some() {
1079 return Err("cannot replay drop_index while a mutation recorder is installed".into());
1080 }
1081 self.drop_named_index(name, if_exists)
1082 .map(|_| ())
1083 .map_err(|e| e.to_string())
1084 }
1085
1086 pub(super) fn register_constraint(
1092 &self,
1093 request: ConstraintRequest,
1094 if_not_exists: bool,
1095 ) -> Result<CreateConstraintOutcome, CreateConstraintError> {
1096 {
1101 let constraint_catalog = self.constraint_catalog_read();
1102 if let Some(existing) = constraint_catalog.find_equivalent(&request) {
1103 let cloned = existing.clone();
1104 drop(constraint_catalog);
1105 if if_not_exists {
1106 return Ok(CreateConstraintOutcome::NoOpExists(cloned));
1107 }
1108 return Err(CreateConstraintError::EquivalentConstraintExists(
1109 cloned.name,
1110 ));
1111 }
1112 if let Some(existing) = constraint_catalog.get(&request.name) {
1113 let cloned = existing.clone();
1114 drop(constraint_catalog);
1115 if if_not_exists {
1116 return Ok(CreateConstraintOutcome::NoOpExists(cloned));
1117 }
1118 return Err(CreateConstraintError::DuplicateName(cloned.name));
1119 }
1120 if let Some(existing) = constraint_catalog.find_same_schema(&request) {
1121 let cloned = existing.clone();
1122 drop(constraint_catalog);
1123 if super::constraint_catalog::kinds_conflict_for_validation(
1124 &cloned.kind,
1125 &request.kind,
1126 ) {
1127 if if_not_exists {
1128 return Ok(CreateConstraintOutcome::NoOpExists(cloned));
1129 }
1130 return Err(CreateConstraintError::ConflictingConstraint(cloned.name));
1131 }
1132 }
1133 }
1134
1135 if request.kind.requires_backing_index() {
1140 let idx_catalog = self.index_catalog_read();
1141 if idx_catalog.get(&request.name).is_some() {
1142 return Err(CreateConstraintError::DuplicateIndexName(
1143 request.name.clone(),
1144 ));
1145 }
1146 let conflict = idx_catalog.list().into_iter().find(|def| {
1147 def.kind == StoredIndexKind::Range
1148 && def.entity == request.entity
1149 && def.label.as_deref() == Some(request.label.as_str())
1150 && def.properties == request.properties
1151 && def.name != request.name
1152 });
1153 drop(idx_catalog);
1154 if let Some(def) = conflict {
1155 return Err(CreateConstraintError::BackingIndexConflict(format!(
1156 "(:{} {{{}}}) already covered by index `{}`",
1157 request.label,
1158 request.properties.join(", "),
1159 def.name,
1160 )));
1161 }
1162 }
1163
1164 let owns_backing = request.kind.requires_backing_index();
1165 let request_for_event = request.clone();
1166 let outcome = {
1167 let mut catalog = self.constraint_catalog_write();
1168 catalog.try_create(request, if_not_exists)?
1169 };
1170
1171 if let CreateConstraintOutcome::Created(def) = &outcome {
1172 if let Err(violation) = self.validate_existing_data_for_constraint(def) {
1177 let mut catalog = self.constraint_catalog_write();
1178 let _ = catalog.try_drop(&def.name, true);
1179 return Err(CreateConstraintError::DataViolation(violation.to_string()));
1180 }
1181 }
1182
1183 if let CreateConstraintOutcome::Created(def) = &outcome {
1184 if owns_backing {
1185 let idx_request = IndexRequest {
1189 explicit_name: Some(def.name.clone()),
1190 kind: StoredIndexKind::Range,
1191 entity: def.entity,
1192 label: Some(def.label.clone()),
1193 additional_labels: Vec::new(),
1194 properties: def.properties.clone(),
1195 options: Default::default(),
1196 };
1197 if let Err(err) = self.register_index_with_recording(idx_request, true, false) {
1201 let mut catalog = self.constraint_catalog_write();
1202 let _ = catalog.try_drop(&def.name, true);
1203 return Err(CreateConstraintError::BackingIndexConflict(err.to_string()));
1204 }
1205 }
1206 self.emit(|| crate::MutationEvent::CreateConstraint {
1207 request: request_for_event,
1208 if_not_exists,
1209 });
1210 self.active_constraints.fetch_add(1, Ordering::Relaxed);
1211 }
1212
1213 Ok(outcome)
1214 }
1215
1216 #[doc(hidden)]
1218 pub fn replay_create_constraint(
1219 &mut self,
1220 request: ConstraintRequest,
1221 if_not_exists: bool,
1222 ) -> Result<(), String> {
1223 if self.recorder.is_some() {
1224 return Err(
1225 "cannot replay create_constraint while a mutation recorder is installed".into(),
1226 );
1227 }
1228 self.register_constraint(request, if_not_exists)
1229 .map(|_| ())
1230 .map_err(|e| e.to_string())
1231 }
1232
1233 #[doc(hidden)]
1235 pub fn replay_drop_constraint(&mut self, name: &str, if_exists: bool) -> Result<(), String> {
1236 if self.recorder.is_some() {
1237 return Err(
1238 "cannot replay drop_constraint while a mutation recorder is installed".into(),
1239 );
1240 }
1241 self.drop_named_constraint(name, if_exists)
1242 .map(|_| ())
1243 .map_err(|e| e.to_string())
1244 }
1245
1246 pub(super) fn drop_named_constraint(
1249 &self,
1250 name: &str,
1251 if_exists: bool,
1252 ) -> Result<DropConstraintOutcome, DropConstraintError> {
1253 let outcome = {
1254 let mut catalog = self.constraint_catalog_write();
1255 catalog.try_drop(name, if_exists)?
1256 };
1257 if let DropConstraintOutcome::Dropped(def) = &outcome {
1258 if let Some(index_name) = def.owned_index.as_deref() {
1259 let _ = self.drop_named_index_inner(index_name, true, false);
1262 }
1263 self.active_constraints.fetch_sub(1, Ordering::Relaxed);
1264 self.emit(|| crate::MutationEvent::DropConstraint {
1265 name: name.to_string(),
1266 if_exists,
1267 });
1268 }
1269 Ok(outcome)
1270 }
1271
1272 pub(super) fn drop_named_index(
1277 &self,
1278 name: &str,
1279 if_exists: bool,
1280 ) -> Result<DropIndexOutcome, DropIndexError> {
1281 self.drop_named_index_inner(name, if_exists, true)
1282 }
1283
1284 fn drop_named_index_inner(
1285 &self,
1286 name: &str,
1287 if_exists: bool,
1288 emit_event: bool,
1289 ) -> Result<DropIndexOutcome, DropIndexError> {
1290 if let Some(owner) = self
1291 .constraint_catalog_read()
1292 .constraint_owning_index(name)
1293 .cloned()
1294 {
1295 return Err(DropIndexError::ConstraintOwned {
1296 index: name.to_string(),
1297 constraint: owner.name,
1298 });
1299 }
1300
1301 let outcome = {
1302 let mut catalog = self.index_catalog_write();
1303 catalog.try_drop(name, if_exists)?
1304 };
1305 if let DropIndexOutcome::Dropped(def) = &outcome {
1306 match def.kind {
1308 StoredIndexKind::Text => {
1309 if let Some(label) = def.label.as_deref() {
1310 for prop in &def.properties {
1311 self.deactivate_text_scope(def.entity, label, prop);
1312 }
1313 }
1314 }
1315 StoredIndexKind::Range => {
1316 if let Some(label) = def.label.as_deref() {
1317 for prop in &def.properties {
1318 self.deactivate_sorted_scope(def.entity, label, prop);
1319 }
1320 }
1321 let declared = self.declared_property_index_keys();
1324 let mut indexes = self.indexes_write();
1325 for prop in &def.properties {
1326 if declared.contains(&(def.entity, prop.clone())) {
1327 continue;
1328 }
1329 match def.entity {
1330 StoredIndexEntity::Node => indexes.node_properties.deactivate(prop),
1331 StoredIndexEntity::Relationship => {
1332 indexes.relationship_properties.deactivate(prop)
1333 }
1334 };
1335 }
1336 }
1337 StoredIndexKind::Point => {
1338 if let Some(label) = def.label.as_deref() {
1339 for prop in &def.properties {
1340 self.deactivate_point_scope(def.entity, label, prop);
1341 }
1342 }
1343 }
1344 StoredIndexKind::Lookup => {
1345 }
1348 StoredIndexKind::Vector => {
1349 self.deactivate_vector_index(def.entity, &def.name);
1350 }
1351 StoredIndexKind::Fulltext => {
1352 self.deactivate_fulltext_index(def.entity, &def.name);
1353 }
1354 }
1355 if emit_event {
1356 self.emit(|| crate::MutationEvent::DropIndex {
1357 name: name.to_string(),
1358 if_exists,
1359 });
1360 }
1361 }
1362 Ok(outcome)
1363 }
1364
1365 pub(super) fn declared_property_index_keys(
1371 &self,
1372 ) -> std::collections::BTreeSet<(StoredIndexEntity, String)> {
1373 self.index_catalog_read()
1374 .list()
1375 .into_iter()
1376 .filter(|def| def.kind == StoredIndexKind::Range)
1377 .flat_map(|def| {
1378 let entity = def.entity;
1379 def.properties.into_iter().map(move |p| (entity, p))
1380 })
1381 .collect()
1382 }
1383
1384 fn populate_index_data(&self, def: &IndexDefinition) {
1385 match def.kind {
1393 StoredIndexKind::Range => {
1394 for key in &def.properties {
1395 match def.entity {
1396 StoredIndexEntity::Node => self.ensure_node_property_index(key),
1397 StoredIndexEntity::Relationship => {
1398 self.ensure_relationship_property_index(key)
1399 }
1400 }
1401 if let Some(label) = def.label.as_deref() {
1402 self.activate_sorted_scope(def.entity, label, key);
1403 }
1404 }
1405 }
1406 StoredIndexKind::Text => {
1407 let label = match def.label.as_deref() {
1408 Some(l) => l,
1409 None => return,
1410 };
1411 for property in &def.properties {
1412 self.activate_text_scope(def.entity, label, property);
1413 }
1414 }
1415 StoredIndexKind::Point => {
1416 let label = match def.label.as_deref() {
1417 Some(l) => l,
1418 None => return,
1419 };
1420 let cell_size = PointRegistry::cell_size_from_options(&def.options);
1421 for property in &def.properties {
1422 self.activate_point_scope(def.entity, label, property, cell_size);
1423 }
1424 }
1425 StoredIndexKind::Fulltext => {
1426 let labels: Vec<String> = def.all_labels().map(String::from).collect();
1427 if labels.is_empty() {
1428 return;
1429 }
1430 self.activate_fulltext_index(def.entity, &def.name, &labels, &def.properties);
1431 }
1432 StoredIndexKind::Vector => {
1433 let label = match def.label.as_deref() {
1434 Some(l) => l,
1435 None => return,
1436 };
1437 let property = match def.properties.first() {
1438 Some(p) => p.as_str(),
1439 None => return,
1440 };
1441 let similarity = VectorSimilarity::from_options(&def.options)
1442 .unwrap_or(VectorSimilarity::Cosine);
1443 let provider = VectorIndexProvider::from_options(&def.options)
1444 .unwrap_or(VectorIndexProvider::Flat);
1445 let hnsw = HnswParams::from_options(&def.options);
1446 let lazy = matches!(
1447 def.options.get("vector.populate.async"),
1448 Some(super::index_catalog::IndexConfigValue::Bool(true))
1449 );
1450 self.activate_vector_index(
1451 def.entity, &def.name, label, property, similarity, provider, hnsw, lazy,
1452 );
1453 if lazy {
1454 self.index_catalog_write()
1455 .set_state(&def.name, StoredIndexState::Populating);
1456 }
1457 }
1458 StoredIndexKind::Lookup => {}
1460 }
1461 }
1462
1463 pub(super) fn text_indexes_read(
1464 &self,
1465 entity: StoredIndexEntity,
1466 ) -> IndexRead<'_, TrigramRegistry> {
1467 self.indexes.text.read(entity)
1468 }
1469
1470 pub(super) fn text_indexes_write(
1471 &self,
1472 entity: StoredIndexEntity,
1473 ) -> IndexWrite<'_, TrigramRegistry> {
1474 self.indexes.text.write(entity)
1475 }
1476
1477 pub(super) fn fulltext_indexes_read(
1478 &self,
1479 entity: StoredIndexEntity,
1480 ) -> IndexRead<'_, FulltextRegistry> {
1481 self.indexes.fulltext.read(entity)
1482 }
1483
1484 #[allow(dead_code)]
1485 pub(super) fn fulltext_indexes_write(
1486 &self,
1487 entity: StoredIndexEntity,
1488 ) -> IndexWrite<'_, FulltextRegistry> {
1489 self.indexes.fulltext.write(entity)
1490 }
1491
1492 fn activate_text_scope(&self, entity: StoredIndexEntity, label: &str, property: &str) {
1493 if !self.text_indexes_write(entity).add_scope(label, property) {
1494 return;
1495 }
1496
1497 let backfill: Vec<(u64, String)> = match entity {
1498 StoredIndexEntity::Node => self
1499 .iter_nodes()
1500 .filter(|(_, node)| node.labels().strs().any(|l| l == label))
1501 .filter_map(|(id, node)| match node.properties().get(property) {
1502 Some(crate::ValueRef::String(value)) => Some((id, value.to_owned())),
1503 _ => None,
1504 })
1505 .collect(),
1506 StoredIndexEntity::Relationship => self
1507 .iter_rels()
1508 .filter(|(_, rel)| rel.rel_type() == label)
1509 .filter_map(|(id, rel)| match rel.properties().get(property) {
1510 Some(crate::ValueRef::String(value)) => Some((id, value.to_owned())),
1511 _ => None,
1512 })
1513 .collect(),
1514 };
1515
1516 let mut registry = self.text_indexes_write(entity);
1517 for (id, value) in backfill {
1518 registry.insert(label, property, id, &value);
1519 }
1520 }
1521
1522 pub(super) fn deactivate_text_scope(
1524 &self,
1525 entity: StoredIndexEntity,
1526 label: &str,
1527 property: &str,
1528 ) {
1529 self.text_indexes_write(entity)
1530 .remove_scope(label, property);
1531 }
1532
1533 fn activate_fulltext_index(
1534 &self,
1535 entity: StoredIndexEntity,
1536 name: &str,
1537 labels: &[String],
1538 properties: &[String],
1539 ) {
1540 use super::fulltext_index::{term_counts_for_properties, TermCounts};
1541
1542 {
1543 let mut registry = self.fulltext_indexes_write(entity);
1544 registry.register(name.to_string(), labels.to_vec(), properties.to_vec());
1545 }
1546 self.indexes
1547 .active_fulltext_indexes
1548 .fetch_add(1, Ordering::Relaxed);
1549
1550 let backfill: Vec<(u64, TermCounts)> = match entity {
1553 StoredIndexEntity::Node => self
1554 .iter_nodes()
1555 .filter(|(_, node)| {
1556 labels
1557 .iter()
1558 .any(|wanted| node.labels().strs().any(|l| l == wanted))
1559 })
1560 .map(|(id, node)| {
1561 let counts = term_counts_for_properties(node.properties(), properties);
1562 (id, counts)
1563 })
1564 .filter(|(_, c)| !c.is_empty())
1565 .collect(),
1566 StoredIndexEntity::Relationship => self
1567 .iter_rels()
1568 .filter(|(_, rel)| labels.iter().any(|wanted| wanted == rel.rel_type()))
1569 .map(|(id, rel)| {
1570 let counts = term_counts_for_properties(rel.properties(), properties);
1571 (id, counts)
1572 })
1573 .filter(|(_, c)| !c.is_empty())
1574 .collect(),
1575 };
1576
1577 let mut registry = self.fulltext_indexes_write(entity);
1578 if let Some(index) = registry.get_mut(name) {
1579 for (id, counts) in backfill {
1580 index.reindex_entity(id, counts);
1581 }
1582 }
1583 }
1584
1585 pub(super) fn deactivate_fulltext_index(&self, entity: StoredIndexEntity, name: &str) {
1586 self.fulltext_indexes_write(entity).deregister(name);
1587 self.indexes
1588 .active_fulltext_indexes
1589 .fetch_sub(1, Ordering::Relaxed);
1590 }
1591
1592 pub(super) fn sorted_indexes_read(
1593 &self,
1594 entity: StoredIndexEntity,
1595 ) -> IndexRead<'_, SortedPropertyIndex> {
1596 self.indexes.sorted.read(entity)
1597 }
1598
1599 pub(super) fn sorted_indexes_write(
1600 &self,
1601 entity: StoredIndexEntity,
1602 ) -> IndexWrite<'_, SortedPropertyIndex> {
1603 self.indexes.sorted.write(entity)
1604 }
1605
1606 fn activate_sorted_scope(&self, entity: StoredIndexEntity, label: &str, property: &str) {
1607 if !self.sorted_indexes_write(entity).add_scope(label, property) {
1608 return;
1609 }
1610
1611 let mut registry = self.sorted_indexes_write(entity);
1614 match entity {
1615 StoredIndexEntity::Node => {
1616 for (id, node) in self.iter_nodes() {
1617 if !node.labels().strs().any(|l| l == label) {
1618 continue;
1619 }
1620 match node.properties().get(property) {
1621 Some(crate::ValueRef::Other(value)) => {
1623 registry.insert(label, property, id, &value.get())
1624 }
1625 Some(value) => {
1626 if let Some(key) = PropertyIndexKey::from_value_ref(value) {
1627 registry.insert_key(label, property, id, key);
1628 }
1629 }
1630 None => {}
1631 }
1632 }
1633 }
1634 StoredIndexEntity::Relationship => {
1635 for (id, rel) in self.iter_rels() {
1636 if rel.rel_type() != label {
1637 continue;
1638 }
1639 match rel.properties().get(property) {
1640 Some(crate::ValueRef::Other(value)) => {
1642 registry.insert(label, property, id, &value.get())
1643 }
1644 Some(value) => {
1645 if let Some(key) = PropertyIndexKey::from_value_ref(value) {
1646 registry.insert_key(label, property, id, key);
1647 }
1648 }
1649 None => {}
1650 }
1651 }
1652 }
1653 }
1654 }
1655
1656 pub(super) fn deactivate_sorted_scope(
1657 &self,
1658 entity: StoredIndexEntity,
1659 label: &str,
1660 property: &str,
1661 ) {
1662 self.sorted_indexes_write(entity)
1663 .remove_scope(label, property);
1664 }
1665
1666 pub(super) fn point_indexes_read(
1667 &self,
1668 entity: StoredIndexEntity,
1669 ) -> IndexRead<'_, PointRegistry> {
1670 self.indexes.point.read(entity)
1671 }
1672
1673 pub(super) fn point_indexes_write(
1674 &self,
1675 entity: StoredIndexEntity,
1676 ) -> IndexWrite<'_, PointRegistry> {
1677 self.indexes.point.write(entity)
1678 }
1679
1680 fn activate_point_scope(
1681 &self,
1682 entity: StoredIndexEntity,
1683 label: &str,
1684 property: &str,
1685 cell_size: Option<f64>,
1686 ) {
1687 if !self
1688 .point_indexes_write(entity)
1689 .add_scope(label, property, cell_size)
1690 {
1691 return;
1692 }
1693
1694 let backfill: Vec<(u64, LoraPoint)> = match entity {
1695 StoredIndexEntity::Node => self
1696 .iter_nodes()
1697 .filter(|(_, node)| node.labels().strs().any(|l| l == label))
1698 .filter_map(|(id, node)| {
1699 match node.properties().get(property).map(|v| v.to_owned()) {
1700 Some(PropertyValue::Point(point)) => Some((id, point)),
1701 _ => None,
1702 }
1703 })
1704 .collect(),
1705 StoredIndexEntity::Relationship => self
1706 .iter_rels()
1707 .filter(|(_, rel)| rel.rel_type() == label)
1708 .filter_map(|(id, rel)| {
1709 match rel.properties().get(property).map(|v| v.to_owned()) {
1710 Some(PropertyValue::Point(point)) => Some((id, point)),
1711 _ => None,
1712 }
1713 })
1714 .collect(),
1715 };
1716
1717 let mut registry = self.point_indexes_write(entity);
1718 for (id, point) in backfill {
1719 registry.insert(label, property, id, point);
1720 }
1721 }
1722
1723 pub(super) fn deactivate_point_scope(
1724 &self,
1725 entity: StoredIndexEntity,
1726 label: &str,
1727 property: &str,
1728 ) {
1729 self.point_indexes_write(entity)
1730 .remove_scope(label, property);
1731 }
1732
1733 pub(super) fn vector_indexes_read(
1734 &self,
1735 entity: StoredIndexEntity,
1736 ) -> IndexRead<'_, VectorIndexRegistry> {
1737 self.indexes.vector.read(entity)
1738 }
1739
1740 pub(super) fn vector_indexes_write(
1741 &self,
1742 entity: StoredIndexEntity,
1743 ) -> IndexWrite<'_, VectorIndexRegistry> {
1744 self.indexes.vector.write(entity)
1745 }
1746
1747 #[allow(clippy::too_many_arguments)]
1748 fn activate_vector_index(
1749 &self,
1750 entity: StoredIndexEntity,
1751 name: &str,
1752 label: &str,
1753 property: &str,
1754 similarity: VectorSimilarity,
1755 provider: VectorIndexProvider,
1756 hnsw: HnswParams,
1757 lazy: bool,
1758 ) {
1759 {
1760 let mut registry = self.vector_indexes_write(entity);
1761 registry.register(
1762 name.to_string(),
1763 label.to_string(),
1764 property.to_string(),
1765 similarity,
1766 provider,
1767 hnsw,
1768 );
1769 }
1770
1771 if lazy {
1772 return;
1779 }
1780
1781 self.backfill_vector_index(entity, label, property);
1782 }
1783
1784 fn backfill_vector_index(&self, entity: StoredIndexEntity, label: &str, property: &str) {
1788 let backfill: Vec<(u64, crate::LoraVector)> = match entity {
1789 StoredIndexEntity::Node => self
1790 .iter_nodes()
1791 .filter(|(_, node)| node.labels().strs().any(|l| l == label))
1792 .filter_map(|(id, node)| {
1793 match node.properties().get(property).map(|v| v.to_owned()) {
1794 Some(PropertyValue::Vector(v)) => Some((id, v)),
1795 _ => None,
1796 }
1797 })
1798 .collect(),
1799 StoredIndexEntity::Relationship => self
1800 .iter_rels()
1801 .filter(|(_, rel)| rel.rel_type() == label)
1802 .filter_map(|(id, rel)| {
1803 match rel.properties().get(property).map(|v| v.to_owned()) {
1804 Some(PropertyValue::Vector(v)) => Some((id, v)),
1805 _ => None,
1806 }
1807 })
1808 .collect(),
1809 };
1810
1811 let mut registry = self.vector_indexes_write(entity);
1812 for (id, vector) in backfill {
1813 registry.insert_for(label, property, id, &vector);
1814 }
1815 }
1816
1817 pub(super) fn lazy_populate_vector_index(&self, name: &str) {
1823 let def = match self.index_catalog_read().get(name).cloned() {
1824 Some(d) => d,
1825 None => return,
1826 };
1827 if def.state != StoredIndexState::Populating || def.kind != StoredIndexKind::Vector {
1828 return;
1829 }
1830 let label = match def.label.as_deref() {
1831 Some(l) => l,
1832 None => return,
1833 };
1834 let property = match def.properties.first() {
1835 Some(p) => p.as_str(),
1836 None => return,
1837 };
1838 self.backfill_vector_index(def.entity, label, property);
1839 self.index_catalog_write()
1840 .set_state(name, StoredIndexState::Online);
1841 }
1842
1843 pub(super) fn deactivate_vector_index(&self, entity: StoredIndexEntity, name: &str) {
1844 self.vector_indexes_write(entity).deregister(name);
1845 }
1846
1847 pub fn graph_stats(&self) -> GraphStats {
1853 let mut stats = GraphStats {
1854 node_count: self.live_node_count,
1855 relationship_count: self.live_rel_count,
1856 ..Default::default()
1857 };
1858 stats.nodes_by_label = self
1861 .nodes_by_label
1862 .iter()
1863 .map(|(label, ids)| (label.to_string(), ids.len()))
1864 .collect();
1865 stats.relationships_by_type = self
1866 .relationships_by_type
1867 .iter()
1868 .map(|(rel_type, ids)| (rel_type.to_string(), ids.len()))
1869 .collect();
1870 stats.node_distinct_values = self.distinct_stats.nodes.distinct_values();
1877 stats.relationship_distinct_values = self.distinct_stats.relationships.distinct_values();
1878
1879 for def in self.index_catalog_read().list() {
1880 if def.state != StoredIndexState::Online {
1881 continue;
1882 }
1883 let Some(label) = def.label else {
1884 continue;
1885 };
1886 for property in def.properties {
1887 let scope = (label.clone(), property);
1888 match (def.entity, def.kind) {
1889 (StoredIndexEntity::Node, StoredIndexKind::Range) => {
1890 stats.node_range_indexes.insert(scope);
1891 }
1892 (StoredIndexEntity::Node, StoredIndexKind::Text) => {
1893 stats.node_text_indexes.insert(scope);
1894 }
1895 (StoredIndexEntity::Node, StoredIndexKind::Point) => {
1896 stats.node_point_indexes.insert(scope);
1897 }
1898 (StoredIndexEntity::Relationship, StoredIndexKind::Range) => {
1899 stats.relationship_range_indexes.insert(scope);
1900 }
1901 (StoredIndexEntity::Relationship, StoredIndexKind::Text) => {
1902 stats.relationship_text_indexes.insert(scope);
1903 }
1904 (StoredIndexEntity::Relationship, StoredIndexKind::Point) => {
1905 stats.relationship_point_indexes.insert(scope);
1906 }
1907 (StoredIndexEntity::Node, StoredIndexKind::Vector) => {
1908 stats.node_vector_indexes.insert(scope);
1909 }
1910 (StoredIndexEntity::Relationship, StoredIndexKind::Vector) => {
1911 stats.relationship_vector_indexes.insert(scope);
1912 }
1913 (_, StoredIndexKind::Lookup | StoredIndexKind::Fulltext) => {}
1914 }
1915 }
1916 }
1917 for (entity, elements) in [
1918 (StoredIndexEntity::Node, &mut stats.node_list_elements),
1919 (
1920 StoredIndexEntity::Relationship,
1921 &mut stats.relationship_list_elements,
1922 ),
1923 ] {
1924 let sorted = self.sorted_indexes_read(entity);
1925 for (label, property, distinct, postings) in sorted.element_stats() {
1926 elements.insert(
1927 (label.to_string(), property.to_string()),
1928 super::ListElementStats { distinct, postings },
1929 );
1930 }
1931 }
1932 stats
1933 }
1934
1935 pub fn plan_signature(&self) -> u64 {
1940 let mut sig = super::PlanSignature::new();
1941 sig.totals(self.live_node_count, self.live_rel_count);
1942 for (label, ids) in &self.nodes_by_label {
1943 sig.label(label, ids.len());
1944 }
1945 for (rel_type, ids) in &self.relationships_by_type {
1946 sig.relationship_type(rel_type, ids.len());
1947 }
1948 for (label, property, distinct) in self.distinct_stats.nodes.estimates() {
1949 sig.node_distinct(label, property, distinct);
1950 }
1951 for (rel_type, property, distinct) in self.distinct_stats.relationships.estimates() {
1952 sig.relationship_distinct(rel_type, property, distinct);
1953 }
1954 for def in self.index_catalog_read().iter() {
1955 if def.state != StoredIndexState::Online
1956 || matches!(
1957 def.kind,
1958 StoredIndexKind::Lookup | StoredIndexKind::Fulltext
1959 )
1960 {
1961 continue;
1962 }
1963 let Some(label) = &def.label else {
1964 continue;
1965 };
1966 for property in &def.properties {
1967 sig.index(def.entity, def.kind, label, property);
1968 }
1969 }
1970 for entity in [StoredIndexEntity::Node, StoredIndexEntity::Relationship] {
1971 let sorted = self.sorted_indexes_read(entity);
1972 for (label, property, distinct, postings) in sorted.element_stats() {
1973 sig.list_elements(
1974 entity,
1975 label,
1976 property,
1977 super::ListElementStats { distinct, postings },
1978 );
1979 }
1980 }
1981 sig.finish()
1982 }
1983
1984 pub fn memory_estimate(&self) -> super::MemoryReport {
1989 super::mem_report::estimate(self)
1990 }
1991
1992 pub(super) fn count_distinct_values<'a>(
1995 stats: &mut super::distinct_stats::DistinctStats,
1996 scopes: impl IntoIterator<Item = &'a str>,
1997 properties: &Properties,
1998 add: bool,
1999 ) {
2000 if properties.is_empty() {
2001 return;
2002 }
2003 for scope in scopes {
2004 if add {
2005 stats.add_all(scope, properties);
2006 } else {
2007 for (key, value) in properties {
2008 stats.remove(scope, key, value);
2009 }
2010 }
2011 }
2012 }
2013
2014 pub(super) fn on_node_created(&mut self, node: &NodeRecord) {
2015 for label in &node.labels {
2016 self.insert_node_label_index(node.id, label);
2017 }
2018 Self::count_distinct_values(
2019 &mut self.distinct_stats.nodes,
2020 node.labels.strs(),
2021 &node.properties,
2022 true,
2023 );
2024 self.index_node_properties_if_active(node.id, node.labels.strs(), &node.properties);
2025 self.maintain_node_secondary_indexes(node, SecondaryIndexMutation::Insert);
2026 }
2027
2028 pub(super) fn on_node_property_set(
2029 &mut self,
2030 node_id: NodeId,
2031 key: &str,
2032 old: Option<&PropertyValue>,
2033 new: &PropertyValue,
2034 ) {
2035 let Some(labels) = self.node_at(node_id).map(|node| node.labels().to_owned()) else {
2036 return;
2037 };
2038 for label in &labels {
2039 self.distinct_stats.nodes.replace(label, key, old, new);
2040 }
2041
2042 if self.node_property_index_is_active(key) {
2043 if let Some(old) = old {
2044 self.unindex_node_property_if_active(node_id, labels.strs(), key, old);
2045 }
2046 self.index_node_property_if_active(node_id, labels.strs(), key, new);
2047 }
2048
2049 self.update_secondary_property(
2050 StoredIndexEntity::Node,
2051 labels.strs(),
2052 node_id,
2053 key,
2054 old,
2055 Some(new),
2056 );
2057 }
2058
2059 pub(super) fn on_node_property_removed(
2060 &mut self,
2061 node_id: NodeId,
2062 key: &str,
2063 old: &PropertyValue,
2064 ) {
2065 let Some(labels) = self.node_at(node_id).map(|node| node.labels().to_owned()) else {
2066 return;
2067 };
2068 for label in &labels {
2069 self.distinct_stats.nodes.remove(label, key, old);
2070 }
2071 if self.node_property_index_is_active(key) {
2072 self.unindex_node_property_if_active(node_id, labels.strs(), key, old);
2073 }
2074 self.update_secondary_property(
2075 StoredIndexEntity::Node,
2076 labels.strs(),
2077 node_id,
2078 key,
2079 Some(old),
2080 None,
2081 );
2082 }
2083
2084 pub(super) fn on_node_label_added(&mut self, node_id: NodeId, label: &str) {
2085 self.insert_node_label_index(node_id, label);
2086
2087 let Some(properties) = self
2088 .node_at(node_id)
2089 .map(|node| node.properties().to_owned())
2090 else {
2091 return;
2092 };
2093 Self::count_distinct_values(&mut self.distinct_stats.nodes, [label], &properties, true);
2094 if self.active_node_property_index_count() != 0 {
2095 self.index_node_scope_properties_if_active(node_id, label, &properties);
2096 if self.node_at(node_id).is_some_and(|n| n.labels().len() == 1) {
2098 self.unindex_node_scope_properties_if_active(node_id, UNLABELLED, &properties);
2099 }
2100 }
2101 for (key, value) in &properties {
2102 self.update_secondary_property(
2103 StoredIndexEntity::Node,
2104 [label],
2105 node_id,
2106 key,
2107 None,
2108 Some(value),
2109 );
2110 }
2111 }
2112
2113 pub(super) fn on_node_label_removed(&mut self, node_id: NodeId, label: &str) {
2114 self.remove_node_label_index(node_id, label);
2115
2116 let Some(properties) = self
2117 .node_at(node_id)
2118 .map(|node| node.properties().to_owned())
2119 else {
2120 return;
2121 };
2122 Self::count_distinct_values(&mut self.distinct_stats.nodes, [label], &properties, false);
2123 if self.active_node_property_index_count() != 0 {
2124 self.unindex_node_scope_properties_if_active(node_id, label, &properties);
2125 if self.node_at(node_id).is_some_and(|n| n.labels().is_empty()) {
2127 self.index_node_scope_properties_if_active(node_id, UNLABELLED, &properties);
2128 }
2129 }
2130 for (key, value) in &properties {
2131 self.update_secondary_property(
2132 StoredIndexEntity::Node,
2133 [label],
2134 node_id,
2135 key,
2136 Some(value),
2137 None,
2138 );
2139 }
2140 }
2141
2142 pub(super) fn on_node_deleted(&mut self, node: &NodeRecord) {
2143 for label in &node.labels {
2144 self.remove_node_label_index(node.id, label);
2145 }
2146 Self::count_distinct_values(
2147 &mut self.distinct_stats.nodes,
2148 node.labels.strs(),
2149 &node.properties,
2150 false,
2151 );
2152 self.unindex_active_node_properties(node.id, node.labels.strs(), &node.properties);
2153 self.maintain_node_secondary_indexes(node, SecondaryIndexMutation::Remove);
2154 }
2155
2156 pub(super) fn on_relationship_created(&mut self, rel: &RelationshipRecord) {
2157 self.attach_relationship(rel);
2158 Self::count_distinct_values(
2159 &mut self.distinct_stats.relationships,
2160 [rel.rel_type.as_str()],
2161 &rel.properties,
2162 true,
2163 );
2164 self.index_relationship_properties_if_active(
2165 rel.id,
2166 [rel.rel_type.as_str()],
2167 &rel.properties,
2168 );
2169 self.maintain_relationship_secondary_indexes(rel, SecondaryIndexMutation::Insert);
2170 }
2171
2172 pub(super) fn on_relationship_property_set(
2173 &mut self,
2174 rel_id: RelationshipId,
2175 key: &str,
2176 old: Option<&PropertyValue>,
2177 new: &PropertyValue,
2178 ) {
2179 let Some(rel_type) = self.rel_at(rel_id).map(|rel| rel.type_name().clone()) else {
2180 return;
2181 };
2182 self.distinct_stats
2183 .relationships
2184 .replace(&rel_type, key, old, new);
2185
2186 if self.relationship_property_index_is_active(key) {
2187 if let Some(old) = old {
2188 self.unindex_relationship_property_if_active(rel_id, [rel_type.as_str()], key, old);
2189 }
2190 self.index_relationship_property_if_active(rel_id, [rel_type.as_str()], key, new);
2191 }
2192
2193 self.update_secondary_property(
2194 StoredIndexEntity::Relationship,
2195 [rel_type.as_str()],
2196 rel_id,
2197 key,
2198 old,
2199 Some(new),
2200 );
2201 }
2202
2203 pub(super) fn on_relationship_property_removed(
2204 &mut self,
2205 rel_id: RelationshipId,
2206 key: &str,
2207 old: &PropertyValue,
2208 ) {
2209 let Some(rel_type) = self.rel_at(rel_id).map(|rel| rel.type_name().clone()) else {
2210 return;
2211 };
2212 self.distinct_stats
2213 .relationships
2214 .remove(&rel_type, key, old);
2215 if self.relationship_property_index_is_active(key) {
2216 self.unindex_relationship_property_if_active(rel_id, [rel_type.as_str()], key, old);
2217 }
2218 self.update_secondary_property(
2219 StoredIndexEntity::Relationship,
2220 [rel_type.as_str()],
2221 rel_id,
2222 key,
2223 Some(old),
2224 None,
2225 );
2226 }
2227
2228 pub(super) fn on_relationship_deleted(
2231 &mut self,
2232 rel: &RelationshipRecord,
2233 pending: Option<&mut PendingAdjRemovals>,
2234 ) {
2235 self.detach_relationship_indexes(rel, pending);
2236 Self::count_distinct_values(
2237 &mut self.distinct_stats.relationships,
2238 [rel.rel_type.as_str()],
2239 &rel.properties,
2240 false,
2241 );
2242 self.unindex_active_relationship_properties(
2243 rel.id,
2244 [rel.rel_type.as_str()],
2245 &rel.properties,
2246 );
2247 self.maintain_relationship_secondary_indexes(rel, SecondaryIndexMutation::Remove);
2248 }
2249
2250 fn index_node_property_if_active<'a>(
2251 &mut self,
2252 node_id: NodeId,
2253 labels: impl IntoIterator<Item = &'a str>,
2254 key: &str,
2255 value: &PropertyValue,
2256 ) {
2257 if self.active_node_property_index_count() == 0 {
2258 return;
2259 }
2260 let (indexes, records) = self.node_index_parts();
2261 if indexes.node_properties.is_active(key) {
2262 indexes
2263 .node_properties
2264 .insert_with_scopes(node_id, labels, key, value, &records);
2265 }
2266 }
2267
2268 fn index_node_properties_if_active<'a>(
2269 &mut self,
2270 node_id: NodeId,
2271 labels: impl IntoIterator<Item = &'a str> + Clone,
2272 properties: &Properties,
2273 ) {
2274 if self.active_node_property_index_count() == 0 {
2275 return;
2276 }
2277 let (indexes, records) = self.node_index_parts();
2278 for (key, value) in properties {
2279 if indexes.node_properties.is_active(key) {
2280 indexes.node_properties.insert_with_scopes(
2281 node_id,
2282 labels.clone(),
2283 key,
2284 value,
2285 &records,
2286 );
2287 }
2288 }
2289 }
2290
2291 fn unindex_node_property_if_active<'a>(
2292 &mut self,
2293 node_id: NodeId,
2294 labels: impl IntoIterator<Item = &'a str>,
2295 key: &str,
2296 value: &PropertyValue,
2297 ) {
2298 if self.active_node_property_index_count() == 0 {
2299 return;
2300 }
2301 let indexes = self.indexes_mut();
2302 if indexes.node_properties.is_active(key) {
2303 indexes
2304 .node_properties
2305 .remove_with_scopes(node_id, labels, key, value);
2306 }
2307 }
2308
2309 fn index_node_scope_properties_if_active(
2310 &mut self,
2311 node_id: NodeId,
2312 scope: &str,
2313 properties: &Properties,
2314 ) {
2315 if self.active_node_property_index_count() == 0 {
2316 return;
2317 }
2318 let (indexes, records) = self.node_index_parts();
2319 for (key, value) in properties {
2320 if indexes.node_properties.is_active(key) {
2321 indexes
2322 .node_properties
2323 .insert_scoped(node_id, scope, key, value, &records);
2324 }
2325 }
2326 }
2327
2328 fn unindex_node_scope_properties_if_active(
2329 &mut self,
2330 node_id: NodeId,
2331 scope: &str,
2332 properties: &Properties,
2333 ) {
2334 if self.active_node_property_index_count() == 0 {
2335 return;
2336 }
2337 let indexes = self.indexes_mut();
2338 for (key, value) in properties {
2339 if indexes.node_properties.is_active(key) {
2340 indexes
2341 .node_properties
2342 .remove_scoped(node_id, scope, key, value);
2343 }
2344 }
2345 }
2346
2347 fn unindex_active_node_properties<'a>(
2348 &mut self,
2349 node_id: NodeId,
2350 labels: impl IntoIterator<Item = &'a str> + Clone,
2351 properties: &Properties,
2352 ) {
2353 if self.active_node_property_index_count() == 0 {
2354 return;
2355 }
2356 let indexes = self.indexes_mut();
2357 for (key, value) in properties {
2358 if indexes.node_properties.is_active(key) {
2359 indexes
2360 .node_properties
2361 .remove_with_scopes(node_id, labels.clone(), key, value);
2362 }
2363 }
2364 }
2365
2366 fn index_relationship_property_if_active<'a>(
2367 &mut self,
2368 rel_id: RelationshipId,
2369 scopes: impl IntoIterator<Item = &'a str>,
2370 key: &str,
2371 value: &PropertyValue,
2372 ) {
2373 if self.active_relationship_property_index_count() == 0 {
2374 return;
2375 }
2376 let (indexes, records) = self.relationship_index_parts();
2377 if indexes.relationship_properties.is_active(key) {
2378 indexes
2379 .relationship_properties
2380 .insert_with_scopes(rel_id, scopes, key, value, &records);
2381 }
2382 }
2383
2384 fn index_relationship_properties_if_active<'a>(
2385 &mut self,
2386 rel_id: RelationshipId,
2387 scopes: impl IntoIterator<Item = &'a str> + Clone,
2388 properties: &Properties,
2389 ) {
2390 if self.active_relationship_property_index_count() == 0 {
2391 return;
2392 }
2393 let (indexes, records) = self.relationship_index_parts();
2394 for (key, value) in properties {
2395 if indexes.relationship_properties.is_active(key) {
2396 indexes.relationship_properties.insert_with_scopes(
2397 rel_id,
2398 scopes.clone(),
2399 key,
2400 value,
2401 &records,
2402 );
2403 }
2404 }
2405 }
2406
2407 fn unindex_relationship_property_if_active<'a>(
2408 &mut self,
2409 rel_id: RelationshipId,
2410 scopes: impl IntoIterator<Item = &'a str>,
2411 key: &str,
2412 value: &PropertyValue,
2413 ) {
2414 if self.active_relationship_property_index_count() == 0 {
2415 return;
2416 }
2417 let indexes = self.indexes_mut();
2418 if indexes.relationship_properties.is_active(key) {
2419 indexes
2420 .relationship_properties
2421 .remove_with_scopes(rel_id, scopes, key, value);
2422 }
2423 }
2424
2425 fn unindex_active_relationship_properties<'a>(
2426 &mut self,
2427 rel_id: RelationshipId,
2428 scopes: impl IntoIterator<Item = &'a str> + Clone,
2429 properties: &Properties,
2430 ) {
2431 if self.active_relationship_property_index_count() == 0 {
2432 return;
2433 }
2434 let indexes = self.indexes_mut();
2435 for (key, value) in properties {
2436 if indexes.relationship_properties.is_active(key) {
2437 indexes.relationship_properties.remove_with_scopes(
2438 rel_id,
2439 scopes.clone(),
2440 key,
2441 value,
2442 );
2443 }
2444 }
2445 }
2446
2447 pub(super) fn scan_nodes_by_property(
2448 &self,
2449 label: Option<&str>,
2450 key: &str,
2451 value: &PropertyValue,
2452 ) -> Vec<NodeRecord> {
2453 match label {
2454 Some(label) => self
2455 .nodes_by_label
2456 .get(label)
2457 .into_iter()
2458 .flat_map(|ids| ids.iter())
2459 .filter_map(|id| self.node_at(id))
2460 .filter(|node| node.properties().get(key).is_some_and(|v| v == *value))
2461 .map(|node| node.to_record())
2462 .collect(),
2463 None => self
2464 .iter_node_refs()
2465 .filter(|node| node.properties().get(key).is_some_and(|v| v == *value))
2466 .map(|node| node.to_record())
2467 .collect(),
2468 }
2469 }
2470
2471 pub(super) fn scan_node_ids_by_property(
2472 &self,
2473 label: Option<&str>,
2474 key: &str,
2475 value: &PropertyValue,
2476 ) -> Vec<NodeId> {
2477 match label {
2478 Some(label) => self
2479 .nodes_by_label
2480 .get(label)
2481 .into_iter()
2482 .flat_map(|ids| ids.iter())
2483 .filter_map(|id| {
2484 (self
2485 .node_at(id)?
2486 .properties()
2487 .get(key)
2488 .is_some_and(|v| v == *value))
2489 .then_some(id)
2490 })
2491 .collect(),
2492 None => self
2493 .iter_nodes()
2494 .filter_map(|(id, node)| {
2495 (node.properties().get(key).is_some_and(|v| v == *value)).then_some(id)
2496 })
2497 .collect(),
2498 }
2499 }
2500
2501 pub(super) fn any_node_by_property(
2502 &self,
2503 label: &str,
2504 key: &str,
2505 value: &PropertyValue,
2506 ) -> bool {
2507 self.nodes_by_label
2508 .get(label)
2509 .into_iter()
2510 .flat_map(|ids| ids.iter())
2511 .filter_map(|id| self.node_at(id))
2512 .any(|node| node.properties().get(key).is_some_and(|v| v == *value))
2513 }
2514
2515 pub(super) fn scan_relationships_by_property(
2516 &self,
2517 rel_type: Option<&str>,
2518 key: &str,
2519 value: &PropertyValue,
2520 ) -> Vec<RelationshipRecord> {
2521 match rel_type {
2522 Some(rel_type) => self
2523 .relationships_by_type
2524 .get(rel_type)
2525 .into_iter()
2526 .flat_map(|ids| ids.iter())
2527 .filter_map(|id| self.rel_at(id))
2528 .filter(|rel| rel.properties().get(key).is_some_and(|v| v == *value))
2529 .map(|rel| rel.to_record())
2530 .collect(),
2531 None => self
2532 .iter_rel_refs()
2533 .filter(|rel| rel.properties().get(key).is_some_and(|v| v == *value))
2534 .map(|rel| rel.to_record())
2535 .collect(),
2536 }
2537 }
2538
2539 pub(super) fn scan_relationship_ids_by_property(
2540 &self,
2541 rel_type: Option<&str>,
2542 key: &str,
2543 value: &PropertyValue,
2544 ) -> Vec<RelationshipId> {
2545 match rel_type {
2546 Some(rel_type) => self
2547 .relationships_by_type
2548 .get(rel_type)
2549 .into_iter()
2550 .flat_map(|ids| ids.iter())
2551 .filter_map(|id| {
2552 (self
2553 .rel_at(id)?
2554 .properties()
2555 .get(key)
2556 .is_some_and(|v| v == *value))
2557 .then_some(id)
2558 })
2559 .collect(),
2560 None => self
2561 .iter_rels()
2562 .filter_map(|(id, rel)| {
2563 (rel.properties().get(key).is_some_and(|v| v == *value)).then_some(id)
2564 })
2565 .collect(),
2566 }
2567 }
2568
2569 pub(super) fn any_relationship_by_property(
2570 &self,
2571 rel_type: &str,
2572 key: &str,
2573 value: &PropertyValue,
2574 ) -> bool {
2575 self.relationships_by_type
2576 .get(rel_type)
2577 .into_iter()
2578 .flat_map(|ids| ids.iter())
2579 .filter_map(|id| self.rel_at(id))
2580 .any(|rel| rel.properties().get(key).is_some_and(|v| v == *value))
2581 }
2582
2583 pub(super) fn attach_relationship(&mut self, rel: &RelationshipRecord) {
2584 let type_id = self.dicts.types.id_or_insert(&rel.rel_type);
2585 self.adjacency_push(
2586 rel.src,
2587 Side::Out,
2588 AdjEntry {
2589 type_id,
2590 neighbour: rel.dst,
2591 rel: rel.id,
2592 },
2593 );
2594 self.adjacency_push(
2595 rel.dst,
2596 Side::In,
2597 AdjEntry {
2598 type_id,
2599 neighbour: rel.src,
2600 rel: rel.id,
2601 },
2602 );
2603 self.insert_relationship_type_index(rel.id, &rel.rel_type);
2604 }
2605
2606 fn detach_relationship_indexes(
2607 &mut self,
2608 rel: &RelationshipRecord,
2609 mut pending: Option<&mut PendingAdjRemovals>,
2610 ) {
2611 self.adjacency_remove(rel.src, Side::Out, rel.id, pending.as_deref_mut());
2612 self.adjacency_remove(rel.dst, Side::In, rel.id, pending);
2613
2614 self.remove_relationship_type_index(rel.id, &rel.rel_type);
2615 }
2616
2617 pub(super) fn relationship_ids_for_direction(
2618 &self,
2619 node_id: NodeId,
2620 direction: Direction,
2621 ) -> Vec<RelationshipId> {
2622 let mut ids = Vec::new();
2623 let _ = self.try_for_each_adjacent_id_unchecked(node_id, direction, &[], |rel_id, _| {
2624 ids.push(rel_id);
2625 Ok::<(), ()>(())
2626 });
2627 ids
2628 }
2629
2630 pub(super) fn has_incident_relationships(&self, node_id: NodeId) -> bool {
2631 self.outgoing_at(node_id).is_some_and(|adj| !adj.is_empty())
2632 || self.incoming_at(node_id).is_some_and(|adj| !adj.is_empty())
2633 }
2634
2635 pub(super) fn incident_relationship_ids(&self, node_id: NodeId) -> Vec<RelationshipId> {
2636 self.relationship_ids_for_direction(node_id, Direction::Undirected)
2637 }
2638
2639 #[doc(hidden)]
2643 pub fn replay_create_node(
2644 &mut self,
2645 id: NodeId,
2646 labels: Vec<String>,
2647 properties: Properties,
2648 ) -> Result<NodeRecord, String> {
2649 if self.recorder.is_some() {
2650 return Err(
2651 "cannot replay node creation while a mutation recorder is installed".into(),
2652 );
2653 }
2654 if self.has_node_at(id) {
2655 return Err(format!("node id {id} already exists"));
2656 }
2657 let idx = self.ensure_node_slot_checked(id)?;
2658 self.bump_next_node_id_past(id)?;
2659
2660 let labels = Self::normalize_labels(labels);
2661 let node = NodeRecord {
2662 id,
2663 labels,
2664 properties,
2665 };
2666
2667 self.put_node_at_slot(idx, &node);
2668 self.on_node_created(&node);
2674
2675 Ok(node)
2676 }
2677
2678 #[doc(hidden)]
2682 pub fn replay_create_relationship(
2683 &mut self,
2684 id: RelationshipId,
2685 src: NodeId,
2686 dst: NodeId,
2687 rel_type: &str,
2688 properties: Properties,
2689 ) -> Result<RelationshipRecord, String> {
2690 if self.recorder.is_some() {
2691 return Err(
2692 "cannot replay relationship creation while a mutation recorder is installed".into(),
2693 );
2694 }
2695 if self.has_rel_at(id) {
2696 return Err(format!("relationship id {id} already exists"));
2697 }
2698 if !self.has_node_at(src) {
2699 return Err(format!(
2700 "relationship {id} references missing source node {src}"
2701 ));
2702 }
2703 if !self.has_node_at(dst) {
2704 return Err(format!(
2705 "relationship {id} references missing target node {dst}"
2706 ));
2707 }
2708
2709 let trimmed = rel_type.trim();
2710 if trimmed.is_empty() {
2711 return Err(format!("relationship {id} has an empty type"));
2712 }
2713 let idx = self.ensure_rel_slot_checked(id)?;
2714 self.bump_next_rel_id_past(id)?;
2715
2716 let rel = RelationshipRecord {
2717 id,
2718 src,
2719 dst,
2720 rel_type: trimmed.into(),
2721 properties,
2722 };
2723
2724 self.put_rel_at_slot(idx, &rel);
2725 self.on_relationship_created(&rel);
2727
2728 Ok(rel)
2729 }
2730
2731 #[cfg(test)]
2732 pub(super) fn assert_property_indexes_match_scan(&self) {
2733 let indexes = self.indexes_read();
2734 assert_eq!(
2735 indexes.node_properties.active_keys.len(),
2736 self.active_node_property_index_count(),
2737 "node property index counter diverged from active key set"
2738 );
2739 assert_eq!(
2740 indexes.relationship_properties.active_keys.len(),
2741 self.active_relationship_property_index_count(),
2742 "relationship property index counter diverged from active key set"
2743 );
2744
2745 let mut expected_nodes = PropertyIndexState {
2746 active_keys: indexes.node_properties.active_keys.clone(),
2747 any_scope_keys: indexes.node_properties.any_scope_keys.clone(),
2748 ..PropertyIndexState::default()
2749 };
2750 for (id, node) in self.iter_nodes() {
2751 for (key, value) in node.properties() {
2752 if expected_nodes.is_active(key) {
2753 expected_nodes.insert_with_scopes(
2754 id,
2755 node.labels().strs(),
2756 key,
2757 &value.to_owned(),
2758 &self.node_values(),
2759 );
2760 }
2761 }
2762 }
2763 assert_eq!(
2764 indexes.node_properties.scoped_values, expected_nodes.scoped_values,
2765 "node property scoped index values diverged from scan"
2766 );
2767 assert_eq!(
2768 indexes.node_properties.any_scope, expected_nodes.any_scope,
2769 "node property across-scopes index values diverged from scan"
2770 );
2771
2772 let mut expected_relationships = PropertyIndexState {
2773 active_keys: indexes.relationship_properties.active_keys.clone(),
2774 any_scope_keys: indexes.relationship_properties.any_scope_keys.clone(),
2775 ..PropertyIndexState::default()
2776 };
2777 for (id, rel) in self.iter_rels() {
2778 for (key, value) in rel.properties() {
2779 if expected_relationships.is_active(key) {
2780 expected_relationships.insert_with_scopes(
2781 id,
2782 [rel.rel_type()],
2783 key,
2784 &value.to_owned(),
2785 &self.relationship_values(),
2786 );
2787 }
2788 }
2789 }
2790 assert_eq!(
2791 indexes.relationship_properties.scoped_values, expected_relationships.scoped_values,
2792 "relationship property scoped index values diverged from scan"
2793 );
2794 assert_eq!(
2795 indexes.relationship_properties.any_scope, expected_relationships.any_scope,
2796 "relationship property across-scopes index values diverged from scan"
2797 );
2798 }
2799}
2800
2801pub(super) struct RecordValues<'g> {
2804 slab: &'g RecordSlab,
2805 dicts: &'g Dicts,
2806 nodes: bool,
2807}
2808
2809impl<'g> RecordValues<'g> {
2810 pub(super) fn nodes(slab: &'g RecordSlab, dicts: &'g Dicts) -> Self {
2811 Self {
2812 slab,
2813 dicts,
2814 nodes: true,
2815 }
2816 }
2817
2818 pub(super) fn relationships(slab: &'g RecordSlab, dicts: &'g Dicts) -> Self {
2819 Self {
2820 slab,
2821 dicts,
2822 nodes: false,
2823 }
2824 }
2825}
2826
2827impl EntityValues for RecordValues<'_> {
2828 #[inline]
2829 fn get(&self, id: u64, key: &str) -> Option<crate::ValueRef<'_>> {
2830 let bytes = self.slab.get(usize::try_from(id).ok()?)?;
2831 if self.nodes {
2832 StoredNode::new(id, bytes, self.dicts).properties().get(key)
2833 } else {
2834 StoredRel::new(id, bytes, self.dicts).properties().get(key)
2835 }
2836 }
2837}
2838
2839#[inline]
2843fn edit_record_property(
2844 slab: &mut RecordSlab,
2845 idx: usize,
2846 kind: encoded::RecordKind,
2847 dicts: &mut Dicts,
2848 key: &str,
2849 edit: encoded::PropEdit<'_>,
2850) -> Option<Option<PropertyValue>> {
2851 let bytes = slab.get(idx)?;
2852 let mut out = Scratch::take();
2853 let old = encoded::edit_property(bytes, kind, dicts, key, edit, &mut out)?;
2854 slab.set(idx, &out);
2855 Some(old)
2856}