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