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;
35use super::property_index::{
36 EntityValues, PropertyIndexKey, PropertyIndexRegistry, PropertyIndexState, UNLABELLED,
37};
38use super::record_slab::{BuiltLeaves, 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_encoded_node_leaves(&mut self, built: BuiltLeaves) -> Result<(), String> {
459 let Some(last) = built.last_slot() else {
460 return Ok(());
461 };
462 self.ensure_node_slot_checked(last as NodeId)?;
463 self.live_node_count += self
464 .nodes
465 .put_leaves(built)
466 .map_err(|slot| format!("duplicate node id {slot} in snapshot payload"))?;
467 Ok(())
468 }
469
470 pub(super) fn put_encoded_relationship_leaves(
475 &mut self,
476 built: BuiltLeaves,
477 links: &[(RelationshipId, NodeId, NodeId, u32)],
478 ) -> Result<(), String> {
479 let Some(last) = built.last_slot() else {
480 return Ok(());
481 };
482 for &(id, src, dst, _) in links {
483 if !self.has_node_at(src) {
484 return Err(format!(
485 "relationship {id} references missing source node {src}"
486 ));
487 }
488 if !self.has_node_at(dst) {
489 return Err(format!(
490 "relationship {id} references missing target node {dst}"
491 ));
492 }
493 }
494 self.ensure_rel_slot_checked(last as RelationshipId)?;
495 self.live_rel_count += self
496 .relationships
497 .put_leaves(built)
498 .map_err(|slot| format!("duplicate relationship id {slot} in snapshot payload"))?;
499 Ok(())
500 }
501
502 pub(super) fn link_relationships(&mut self, links: &[(RelationshipId, NodeId, NodeId, u32)]) {
509 #[cfg(all(feature = "parallel", not(target_arch = "wasm32")))]
510 {
511 use rayon::prelude::*;
512 let mut ends: Vec<(NodeId, Side, AdjEntry)> = Vec::with_capacity(2 * links.len());
513 for &(id, src, dst, type_id) in links {
514 let out = AdjEntry {
515 type_id,
516 neighbour: dst,
517 rel: id,
518 };
519 ends.push((src, Side::Out, out));
520 let into = AdjEntry {
521 type_id,
522 neighbour: src,
523 rel: id,
524 };
525 ends.push((dst, Side::In, into));
526 }
527 const LEAF_SHIFT: u32 = super::chunked_vec::CHUNK.trailing_zeros();
528 const SLOT_MASK: usize = super::chunked_vec::CHUNK - 1;
529 let leaf_of = |node: NodeId| (node as usize) >> LEAF_SHIFT;
530 ends.par_sort_by_key(|(node, _, _)| leaf_of(*node));
531 let groups: Vec<&[(NodeId, Side, AdjEntry)]> =
532 ends.chunk_by(|a, b| leaf_of(a.0) == leaf_of(b.0)).collect();
533 let touched: Vec<usize> = groups.iter().map(|group| leaf_of(group[0].0)).collect();
534 self.adjacency
535 .leaves_mut(&touched)
536 .into_par_iter()
537 .zip(groups)
538 .for_each(|((_, leaf), group)| {
539 for &(node, side, entry) in group {
540 leaf[node as usize & SLOT_MASK].push(side, entry);
541 }
542 });
543 }
544 #[cfg(not(all(feature = "parallel", not(target_arch = "wasm32"))))]
545 for &(id, src, dst, type_id) in links {
546 let out = AdjEntry {
547 type_id,
548 neighbour: dst,
549 rel: id,
550 };
551 self.adjacency_push(src, Side::Out, out);
552 let into = AdjEntry {
553 type_id,
554 neighbour: src,
555 rel: id,
556 };
557 self.adjacency_push(dst, Side::In, into);
558 }
559 }
560
561 pub(super) fn extend_node_label_index(&mut self, label_id: u32, ids: &[NodeId]) {
564 let label = self.dicts.labels.name(label_id).as_arc();
565 let bucket = match self.nodes_by_label.get_mut(&**label) {
566 Some(bucket) => bucket,
567 None => self.nodes_by_label.entry(Arc::clone(label)).or_default(),
568 };
569 for &id in ids {
570 bucket.insert(id);
571 }
572 }
573
574 pub(super) fn extend_relationship_type_index(&mut self, type_id: u32, ids: &[RelationshipId]) {
576 let rel_type = self.dicts.types.name(type_id).as_arc();
577 let bucket = match self.relationships_by_type.get_mut(&**rel_type) {
578 Some(bucket) => bucket,
579 None => self
580 .relationships_by_type
581 .entry(Arc::clone(rel_type))
582 .or_default(),
583 };
584 for &id in ids {
585 bucket.insert(id);
586 }
587 }
588
589 pub(super) fn put_node_at_slot(&mut self, idx: usize, node: &NodeRecord) {
590 if self.store_node(idx, node) {
591 self.live_node_count += 1;
592 }
593 }
594
595 pub(super) fn put_rel_at_slot(&mut self, idx: usize, rel: &RelationshipRecord) {
596 let mut out = Scratch::take();
597 encoded::encode_rel(rel, &mut self.dicts, &mut out);
598 if self.relationships.set(idx, &out) {
599 self.live_rel_count += 1;
600 }
601 }
602
603 #[inline]
605 fn store_node(&mut self, idx: usize, node: &NodeRecord) -> bool {
606 let mut out = Scratch::take();
607 encoded::encode_node(node, &mut self.dicts, &mut out);
608 self.nodes.set(idx, &out)
609 }
610
611 pub(super) fn take_node(&mut self, id: NodeId) -> Option<NodeRecord> {
612 let idx = Self::slot_index(id)?;
613 let removed = StoredNode::new(id, self.nodes.get(idx)?, &self.dicts).to_record();
614 self.nodes.remove(idx);
615 self.live_node_count -= 1;
616 self.clear_adjacency(id);
622 Some(removed)
623 }
624
625 pub(super) fn take_rel(&mut self, id: RelationshipId) -> Option<RelationshipRecord> {
626 let idx = Self::slot_index(id)?;
627 let removed = StoredRel::new(id, self.relationships.get(idx)?, &self.dicts).to_record();
628 self.relationships.remove(idx);
629 self.live_rel_count -= 1;
630 Some(removed)
631 }
632
633 #[inline]
634 pub(super) fn outgoing_at(&self, id: NodeId) -> Option<AdjSide<'_>> {
635 Some(self.adjacency.get(Self::slot_index(id)?)?.outgoing())
636 }
637
638 #[inline]
639 pub(super) fn incoming_at(&self, id: NodeId) -> Option<AdjSide<'_>> {
640 Some(self.adjacency.get(Self::slot_index(id)?)?.incoming())
641 }
642
643 #[inline]
647 fn try_for_each_adjacent_entry<F, E>(
648 node_id: NodeId,
649 filter: &TypeFilter,
650 adj: AdjSide<'_>,
651 skip_self_loops: bool,
652 visit: &mut F,
653 ) -> Result<(), E>
654 where
655 F: FnMut(RelationshipId, NodeId) -> Result<(), E>,
656 {
657 for entry in adj.iter() {
658 if skip_self_loops && entry.neighbour == node_id {
659 continue;
660 }
661 if !filter.matches(entry.type_id) {
662 continue;
663 }
664 visit(entry.rel, entry.neighbour)?;
665 }
666 Ok(())
667 }
668
669 #[inline]
670 pub(super) fn try_for_each_adjacent_id_unchecked<F, E>(
671 &self,
672 node_id: NodeId,
673 direction: Direction,
674 types: &[String],
675 mut visit: F,
676 ) -> Result<(), E>
677 where
678 F: FnMut(RelationshipId, NodeId) -> Result<(), E>,
679 {
680 let filter = TypeFilter::resolve(&self.dicts.types, types);
681 if matches!(filter, TypeFilter::Nothing) {
682 return Ok(());
683 }
684 if !matches!(direction, Direction::Left) {
685 if let Some(adj) = self.outgoing_at(node_id) {
686 Self::try_for_each_adjacent_entry(node_id, &filter, adj, false, &mut visit)?;
687 }
688 }
689 if !matches!(direction, Direction::Right) {
690 if let Some(adj) = self.incoming_at(node_id) {
691 let skip_self_loops = matches!(direction, Direction::Undirected);
692 Self::try_for_each_adjacent_entry(
693 node_id,
694 &filter,
695 adj,
696 skip_self_loops,
697 &mut visit,
698 )?;
699 }
700 }
701
702 Ok(())
703 }
704
705 #[inline]
706 pub(super) fn try_for_each_adjacent_id<F, E>(
707 &self,
708 node_id: NodeId,
709 direction: Direction,
710 types: &[String],
711 visit: F,
712 ) -> Result<(), E>
713 where
714 F: FnMut(RelationshipId, NodeId) -> Result<(), E>,
715 {
716 if !self.has_node_at(node_id) {
717 return Ok(());
718 }
719 self.try_for_each_adjacent_id_unchecked(node_id, direction, types, visit)
720 }
721
722 pub(super) fn iter_node_ids(&self) -> impl Iterator<Item = NodeId> + '_ {
723 self.nodes.slots_from(0).map(|i| i as NodeId)
724 }
725
726 pub(super) fn iter_node_refs(&self) -> impl Iterator<Item = NodeRef<'_>> + '_ {
727 self.iter_nodes().map(|(_, node)| node)
728 }
729
730 pub(super) fn iter_rel_ids(&self) -> impl Iterator<Item = RelationshipId> + '_ {
731 self.relationships
732 .slots_from(0)
733 .map(|i| i as RelationshipId)
734 }
735
736 pub(super) fn iter_rel_refs(&self) -> impl Iterator<Item = RelRef<'_>> + '_ {
737 self.iter_rels().map(|(_, rel)| rel)
738 }
739
740 pub(super) fn iter_nodes(&self) -> impl Iterator<Item = (NodeId, NodeRef<'_>)> + '_ {
741 let dicts = &self.dicts;
742 self.nodes.iter().map(move |(i, bytes)| {
743 let id = i as NodeId;
744 (id, NodeRef::stored(StoredNode::new(id, bytes, dicts)))
745 })
746 }
747
748 pub(super) fn iter_rels(&self) -> impl Iterator<Item = (RelationshipId, RelRef<'_>)> + '_ {
749 let dicts = &self.dicts;
750 self.relationships.iter().map(move |(i, bytes)| {
751 let id = i as RelationshipId;
752 (id, RelRef::stored(StoredRel::new(id, bytes, dicts)))
753 })
754 }
755
756 fn adjacency_push(&mut self, node_id: NodeId, side: Side, entry: AdjEntry) {
760 if let Ok(idx) = self.ensure_node_slot_checked(node_id) {
761 self.adjacency[idx].push(side, entry);
762 }
763 }
764
765 pub(super) fn clear_adjacency(&mut self, node_id: NodeId) {
770 let Some(idx) = Self::slot_index(node_id) else {
771 return;
772 };
773 if self.adjacency.get(idx).is_some_and(|adj| !adj.is_empty()) {
776 if let Some(adj) = self.adjacency.get_mut(idx) {
777 adj.clear();
778 }
779 }
780 }
781
782 fn adjacency_remove(
789 &mut self,
790 node_id: NodeId,
791 side: Side,
792 rel_id: RelationshipId,
793 pending: Option<&mut PendingAdjRemovals>,
794 ) {
795 let Some(idx) = Self::slot_index(node_id) else {
796 return;
797 };
798 let len = self
799 .adjacency
800 .get(idx)
801 .map_or(0, |adj| adj.side_bytes(side));
802 if len == 0 {
805 return;
806 }
807 match pending {
808 Some(pending) if len > DEFER_REMOVAL_MIN_BYTES => {
809 pending.push((node_id, side, rel_id));
810 }
811 _ => {
812 if let Some(adj) = self.adjacency.get_mut(idx) {
813 adj.remove(side, rel_id);
814 }
815 }
816 }
817 }
818
819 pub(super) fn flush_adjacency_removals(&mut self, pending: &mut PendingAdjRemovals) {
822 pending.sort_unstable_by_key(|&(node, side, rel)| (node, side == Side::In, rel));
823 let mut rels = Vec::new();
824 for group in pending.chunk_by(|a, b| a.0 == b.0 && a.1 == b.1) {
825 let (node_id, side, _) = group[0];
826 let Some(idx) = Self::slot_index(node_id) else {
827 continue;
828 };
829 if self
830 .adjacency
831 .get(idx)
832 .is_none_or(|adj| adj.side_bytes(side) == 0)
833 {
834 continue;
835 }
836 rels.clear();
837 rels.extend(group.iter().map(|&(_, _, rel)| rel));
838 if let Some(adj) = self.adjacency.get_mut(idx) {
839 adj.remove_all(side, &rels);
840 }
841 }
842 pending.clear();
843 }
844
845 pub(super) fn normalize_labels(labels: Vec<String>) -> Labels {
846 let mut out = Labels::new();
847 for label in &labels {
848 let label = label.trim();
849 if !label.is_empty() && !out.has(label) {
850 out.push(label);
851 }
852 }
853 out
854 }
855
856 pub(super) fn insert_node_label_index(&mut self, node_id: NodeId, label: &str) {
857 if let Some(bucket) = self.nodes_by_label.get_mut(label) {
862 bucket.insert(node_id);
863 } else {
864 self.nodes_by_label
865 .insert(Arc::from(label), std::iter::once(node_id).collect());
866 }
867 }
868
869 fn remove_node_label_index(&mut self, node_id: NodeId, label: &str) {
870 if let Some(ids) = self.nodes_by_label.get_mut(label) {
871 ids.remove(node_id);
872 if ids.is_empty() {
873 self.nodes_by_label.remove(label);
874 }
875 }
876 }
877
878 fn insert_relationship_type_index(&mut self, rel_id: RelationshipId, rel_type: &str) {
879 if let Some(bucket) = self.relationships_by_type.get_mut(rel_type) {
881 bucket.insert(rel_id);
882 } else {
883 self.relationships_by_type
884 .insert(Arc::from(rel_type), std::iter::once(rel_id).collect());
885 }
886 }
887
888 fn remove_relationship_type_index(&mut self, rel_id: RelationshipId, rel_type: &str) {
889 if let Some(ids) = self.relationships_by_type.get_mut(rel_type) {
890 ids.remove(rel_id);
891 if ids.is_empty() {
892 self.relationships_by_type.remove(rel_type);
893 }
894 }
895 }
896
897 pub(super) fn indexed_node_ids(
901 &self,
902 label: &str,
903 key: &str,
904 value: &PropertyValue,
905 ) -> Option<Vec<NodeId>> {
906 PropertyIndexKey::from_value(value)?;
907 let indexes = self.indexes_read();
908 if !indexes.node_properties.is_active(key) {
909 return None;
910 }
911 Some(
912 indexes
913 .node_properties
914 .scoped_ids_for(label, key, value, &self.node_values())
915 .map(|ids| ids.to_vec())
916 .unwrap_or_default(),
917 )
918 }
919
920 pub(super) fn indexed_rel_ids(
922 &self,
923 rel_type: &str,
924 key: &str,
925 value: &PropertyValue,
926 ) -> Option<Vec<RelationshipId>> {
927 PropertyIndexKey::from_value(value)?;
928 let indexes = self.indexes_read();
929 if !indexes.relationship_properties.is_active(key) {
930 return None;
931 }
932 Some(
933 indexes
934 .relationship_properties
935 .scoped_ids_for(rel_type, key, value, &self.relationship_values())
936 .map(|ids| ids.to_vec())
937 .unwrap_or_default(),
938 )
939 }
940
941 pub(super) fn indexes_read(&self) -> std::sync::RwLockReadGuard<'_, PropertyIndexRegistry> {
942 self.indexes
943 .properties
944 .read()
945 .unwrap_or_else(|poisoned| poisoned.into_inner())
946 }
947
948 pub(super) fn indexes_write(&self) -> RwLockWriteGuard<'_, PropertyIndexRegistry> {
949 self.indexes
950 .properties
951 .write()
952 .unwrap_or_else(|poisoned| poisoned.into_inner())
953 }
954
955 #[inline]
958 pub(super) fn node_values(&self) -> RecordValues<'_> {
959 RecordValues::nodes(&self.nodes, &self.dicts)
960 }
961
962 #[inline]
964 pub(super) fn relationship_values(&self) -> RecordValues<'_> {
965 RecordValues::relationships(&self.relationships, &self.dicts)
966 }
967
968 fn node_index_parts(&mut self) -> (&mut PropertyIndexRegistry, RecordValues<'_>) {
970 let registry = self
971 .indexes
972 .properties
973 .get_mut()
974 .unwrap_or_else(|poisoned| poisoned.into_inner());
975 (registry, RecordValues::nodes(&self.nodes, &self.dicts))
976 }
977
978 fn relationship_index_parts(&mut self) -> (&mut PropertyIndexRegistry, RecordValues<'_>) {
980 let registry = self
981 .indexes
982 .properties
983 .get_mut()
984 .unwrap_or_else(|poisoned| poisoned.into_inner());
985 (
986 registry,
987 RecordValues::relationships(&self.relationships, &self.dicts),
988 )
989 }
990
991 pub(super) fn indexes_mut(&mut self) -> &mut PropertyIndexRegistry {
992 self.indexes
993 .properties
994 .get_mut()
995 .unwrap_or_else(|poisoned| poisoned.into_inner())
996 }
997
998 #[inline]
999 pub(super) fn active_node_property_index_count(&self) -> usize {
1000 self.indexes
1001 .active_node_property_indexes
1002 .load(Ordering::Relaxed)
1003 }
1004
1005 #[inline]
1006 pub(super) fn active_relationship_property_index_count(&self) -> usize {
1007 self.indexes
1008 .active_relationship_property_indexes
1009 .load(Ordering::Relaxed)
1010 }
1011
1012 #[inline]
1013 pub(super) fn active_constraint_count(&self) -> usize {
1014 self.active_constraints.load(Ordering::Relaxed)
1015 }
1016
1017 #[inline]
1018 pub(super) fn has_active_constraints(&self) -> bool {
1019 self.active_constraint_count() != 0
1020 }
1021
1022 #[inline]
1023 pub(super) fn active_fulltext_index_count(&self) -> usize {
1024 self.indexes.active_fulltext_indexes.load(Ordering::Relaxed)
1025 }
1026
1027 #[inline]
1028 pub(super) fn has_active_fulltext_indexes(&self) -> bool {
1029 self.active_fulltext_index_count() != 0
1030 }
1031
1032 pub(super) fn node_property_index_is_active(&mut self, key: &str) -> bool {
1033 self.active_node_property_index_count() != 0
1034 && self.indexes_mut().node_properties.is_active(key)
1035 }
1036
1037 pub(super) fn relationship_property_index_is_active(&mut self, key: &str) -> bool {
1038 self.active_relationship_property_index_count() != 0
1039 && self.indexes_mut().relationship_properties.is_active(key)
1040 }
1041
1042 pub(super) fn ensure_any_scope_node_property_index(&self, key: &str) {
1045 if self.indexes_read().node_properties.any_scope_is_active(key) {
1046 return;
1047 }
1048 self.indexes_write()
1049 .node_properties
1050 .activate_any_scope(key, &self.node_values());
1051 }
1052
1053 pub(super) fn ensure_any_scope_relationship_property_index(&self, key: &str) {
1054 if self
1055 .indexes_read()
1056 .relationship_properties
1057 .any_scope_is_active(key)
1058 {
1059 return;
1060 }
1061 self.indexes_write()
1062 .relationship_properties
1063 .activate_any_scope(key, &self.relationship_values());
1064 }
1065
1066 pub(super) fn try_ensure_node_property_index(&self, key: &str) -> bool {
1070 if self.indexes_read().node_properties.is_active(key) {
1071 return true;
1072 }
1073 let Some(_build) = self.indexes.property_builds.claim(true, key) else {
1074 return false;
1075 };
1076 self.ensure_node_property_index(key);
1077 true
1078 }
1079
1080 pub(super) fn try_ensure_relationship_property_index(&self, key: &str) -> bool {
1082 if self.indexes_read().relationship_properties.is_active(key) {
1083 return true;
1084 }
1085 let Some(_build) = self.indexes.property_builds.claim(false, key) else {
1086 return false;
1087 };
1088 self.ensure_relationship_property_index(key);
1089 true
1090 }
1091
1092 pub(super) fn ensure_node_property_index(&self, key: &str) {
1098 if self.indexes_read().node_properties.is_active(key) {
1099 return;
1100 }
1101
1102 let built = if self.live_node_count >= PARALLEL_INDEX_MIN {
1103 self.build_node_property_index_parallel(key)
1104 } else {
1105 let mut built = PropertyIndexState::default();
1106 built.insert_bulk(
1107 key,
1108 || {
1109 self.iter_nodes().filter_map(|(id, node)| {
1110 let value = node.properties().get(key)?;
1111 Some((id, node.labels().strs(), value))
1112 })
1113 },
1114 &self.node_values(),
1115 );
1116 built
1117 };
1118
1119 let mut indexes = self.indexes_write();
1120 if indexes.node_properties.is_active(key) {
1121 return;
1122 }
1123 indexes.node_properties.absorb(key, built);
1124 if indexes.node_properties.activate(key) {
1125 self.indexes
1126 .active_node_property_indexes
1127 .fetch_add(1, Ordering::Relaxed);
1128 }
1129 }
1130
1131 #[cfg(all(feature = "parallel", not(target_arch = "wasm32")))]
1133 pub(super) fn build_node_property_index_parallel(&self, key: &str) -> PropertyIndexState {
1134 let Some(key_id) = self.dicts.keys.id_of(key) else {
1135 return PropertyIndexState::default();
1136 };
1137 PropertyIndexState::build_parallel(
1138 key,
1139 self.nodes.len() as u64,
1140 |ids| self.node_slots_in(ids),
1141 |id| {
1142 let node = StoredNode::new(id, self.nodes.get(id as usize)?, &self.dicts);
1143 let value = node.properties().get_id(key_id)?;
1144 Some((node.labels().iter().map(crate::Name::as_str), value))
1145 },
1146 &self.node_values(),
1147 )
1148 }
1149
1150 #[cfg(not(all(feature = "parallel", not(target_arch = "wasm32"))))]
1151 pub(super) fn build_node_property_index_parallel(&self, key: &str) -> PropertyIndexState {
1152 let mut built = PropertyIndexState::default();
1153 built.insert_bulk(
1154 key,
1155 || {
1156 self.iter_nodes().filter_map(|(id, node)| {
1157 let value = node.properties().get(key)?;
1158 Some((id, node.labels().strs(), value))
1159 })
1160 },
1161 &self.node_values(),
1162 );
1163 built
1164 }
1165
1166 #[cfg(all(feature = "parallel", not(target_arch = "wasm32")))]
1167 fn build_relationship_property_index_parallel(&self, key: &str) -> PropertyIndexState {
1168 let Some(key_id) = self.dicts.keys.id_of(key) else {
1169 return PropertyIndexState::default();
1170 };
1171 PropertyIndexState::build_parallel(
1172 key,
1173 self.relationships.len() as u64,
1174 |ids| self.rel_slots_in(ids),
1175 |id| {
1176 let rel = StoredRel::new(id, self.relationships.get(id as usize)?, &self.dicts);
1177 let value = rel.properties().get_id(key_id)?;
1178 Some(([rel.rel_type().as_str()], value))
1179 },
1180 &self.relationship_values(),
1181 )
1182 }
1183
1184 #[cfg(not(all(feature = "parallel", not(target_arch = "wasm32"))))]
1185 fn build_relationship_property_index_parallel(&self, key: &str) -> PropertyIndexState {
1186 let mut built = PropertyIndexState::default();
1187 built.insert_bulk(
1188 key,
1189 || {
1190 self.iter_rels().filter_map(|(id, rel)| {
1191 let value = rel.properties().get(key)?;
1192 Some((id, [rel.rel_type()], value))
1193 })
1194 },
1195 &self.relationship_values(),
1196 );
1197 built
1198 }
1199
1200 #[cfg(all(feature = "parallel", not(target_arch = "wasm32")))]
1202 fn node_slots_in(&self, ids: std::ops::Range<u64>) -> impl Iterator<Item = NodeId> + '_ {
1203 let end = ids.end;
1204 self.nodes
1205 .slots_from(usize::try_from(ids.start).unwrap_or(usize::MAX))
1206 .map(|i| i as NodeId)
1207 .take_while(move |id| *id < end)
1208 }
1209
1210 #[cfg(all(feature = "parallel", not(target_arch = "wasm32")))]
1212 fn rel_slots_in(&self, ids: std::ops::Range<u64>) -> impl Iterator<Item = RelationshipId> + '_ {
1213 let end = ids.end;
1214 self.relationships
1215 .slots_from(usize::try_from(ids.start).unwrap_or(usize::MAX))
1216 .map(|i| i as RelationshipId)
1217 .take_while(move |id| *id < end)
1218 }
1219
1220 pub(super) fn ensure_relationship_property_index(&self, key: &str) {
1222 if self.indexes_read().relationship_properties.is_active(key) {
1223 return;
1224 }
1225
1226 let built = if self.live_rel_count >= PARALLEL_INDEX_MIN {
1227 self.build_relationship_property_index_parallel(key)
1228 } else {
1229 let mut built = PropertyIndexState::default();
1230 built.insert_bulk(
1231 key,
1232 || {
1233 self.iter_rels().filter_map(|(id, rel)| {
1234 let value = rel.properties().get(key)?;
1235 Some((id, [rel.rel_type()], value))
1236 })
1237 },
1238 &self.relationship_values(),
1239 );
1240 built
1241 };
1242
1243 let mut indexes = self.indexes_write();
1244 if indexes.relationship_properties.is_active(key) {
1245 return;
1246 }
1247 indexes.relationship_properties.absorb(key, built);
1248 if indexes.relationship_properties.activate(key) {
1249 self.indexes
1250 .active_relationship_property_indexes
1251 .fetch_add(1, Ordering::Relaxed);
1252 }
1253 }
1254
1255 pub(super) fn index_catalog_read(&self) -> IndexRead<'_, IndexCatalog> {
1256 read_shared(&self.indexes.catalog)
1257 }
1258
1259 pub(super) fn index_catalog_write(&self) -> IndexWrite<'_, IndexCatalog> {
1261 write_shared(&self.indexes.catalog)
1262 }
1263
1264 pub(super) fn constraint_catalog_read(&self) -> IndexRead<'_, ConstraintCatalog> {
1265 read_shared(&self.constraint_catalog)
1266 }
1267
1268 pub(super) fn constraint_catalog_write(&self) -> IndexWrite<'_, ConstraintCatalog> {
1270 write_shared(&self.constraint_catalog)
1271 }
1272
1273 #[allow(clippy::result_large_err)]
1281 pub(super) fn register_index(
1282 &self,
1283 request: IndexRequest,
1284 if_not_exists: bool,
1285 ) -> Result<CreateIndexOutcome, CreateIndexError> {
1286 self.register_index_with_recording(request, if_not_exists, true)
1287 }
1288
1289 #[allow(clippy::result_large_err)]
1290 fn register_index_with_recording(
1291 &self,
1292 request: IndexRequest,
1293 if_not_exists: bool,
1294 record_event: bool,
1295 ) -> Result<CreateIndexOutcome, CreateIndexError> {
1296 self.register_index_deferring(request, if_not_exists, record_event, None)
1297 }
1298
1299 #[allow(clippy::result_large_err)]
1304 pub(super) fn register_index_deferring(
1305 &self,
1306 request: IndexRequest,
1307 if_not_exists: bool,
1308 record_event: bool,
1309 deferred: Option<&mut DeferredData>,
1310 ) -> Result<CreateIndexOutcome, CreateIndexError> {
1311 let request_for_event = record_event.then(|| request.clone());
1312 let outcome = {
1313 let mut catalog = self.index_catalog_write();
1314 catalog.try_create(request, if_not_exists)?
1315 };
1316
1317 if let CreateIndexOutcome::Created(def) = &outcome {
1318 match deferred {
1319 Some(deferred) if def.kind != StoredIndexKind::Vector => {
1320 deferred.populate.push(def.clone());
1321 }
1322 _ => self.populate_index_data(def),
1323 }
1324 }
1325
1326 if matches!(outcome, CreateIndexOutcome::Created(_)) {
1330 if let Some(request_for_event) = request_for_event {
1331 self.emit(|| crate::MutationEvent::CreateIndex {
1332 request: request_for_event,
1333 if_not_exists,
1334 });
1335 }
1336 }
1337
1338 Ok(outcome)
1339 }
1340
1341 #[doc(hidden)]
1345 pub fn replay_create_index(
1346 &mut self,
1347 request: IndexRequest,
1348 if_not_exists: bool,
1349 ) -> Result<(), String> {
1350 if self.recorder.is_some() {
1351 return Err("cannot replay create_index while a mutation recorder is installed".into());
1352 }
1353 self.register_index(request, if_not_exists)
1354 .map(|_| ())
1355 .map_err(|e| e.to_string())
1356 }
1357
1358 #[doc(hidden)]
1360 pub fn replay_drop_index(&mut self, name: &str, if_exists: bool) -> Result<(), String> {
1361 if self.recorder.is_some() {
1362 return Err("cannot replay drop_index while a mutation recorder is installed".into());
1363 }
1364 self.drop_named_index(name, if_exists)
1365 .map(|_| ())
1366 .map_err(|e| e.to_string())
1367 }
1368
1369 pub(super) fn register_constraint(
1375 &self,
1376 request: ConstraintRequest,
1377 if_not_exists: bool,
1378 ) -> Result<CreateConstraintOutcome, CreateConstraintError> {
1379 self.register_constraint_deferring(request, if_not_exists, None)
1380 }
1381
1382 pub(super) fn register_constraint_deferring(
1386 &self,
1387 request: ConstraintRequest,
1388 if_not_exists: bool,
1389 mut deferred: Option<&mut DeferredData>,
1390 ) -> Result<CreateConstraintOutcome, CreateConstraintError> {
1391 {
1396 let constraint_catalog = self.constraint_catalog_read();
1397 if let Some(existing) = constraint_catalog.find_equivalent(&request) {
1398 let cloned = existing.clone();
1399 drop(constraint_catalog);
1400 if if_not_exists {
1401 return Ok(CreateConstraintOutcome::NoOpExists(cloned));
1402 }
1403 return Err(CreateConstraintError::EquivalentConstraintExists(
1404 cloned.name,
1405 ));
1406 }
1407 if let Some(existing) = constraint_catalog.get(&request.name) {
1408 let cloned = existing.clone();
1409 drop(constraint_catalog);
1410 if if_not_exists {
1411 return Ok(CreateConstraintOutcome::NoOpExists(cloned));
1412 }
1413 return Err(CreateConstraintError::DuplicateName(cloned.name));
1414 }
1415 if let Some(existing) = constraint_catalog.find_same_schema(&request) {
1416 let cloned = existing.clone();
1417 drop(constraint_catalog);
1418 if super::constraint_catalog::kinds_conflict_for_validation(
1419 &cloned.kind,
1420 &request.kind,
1421 ) {
1422 if if_not_exists {
1423 return Ok(CreateConstraintOutcome::NoOpExists(cloned));
1424 }
1425 return Err(CreateConstraintError::ConflictingConstraint(cloned.name));
1426 }
1427 }
1428 }
1429
1430 if request.kind.requires_backing_index() {
1435 let idx_catalog = self.index_catalog_read();
1436 if idx_catalog.get(&request.name).is_some() {
1437 return Err(CreateConstraintError::DuplicateIndexName(
1438 request.name.clone(),
1439 ));
1440 }
1441 let conflict = idx_catalog.list().into_iter().find(|def| {
1442 def.kind == StoredIndexKind::Range
1443 && def.entity == request.entity
1444 && def.label.as_deref() == Some(request.label.as_str())
1445 && def.properties == request.properties
1446 && def.name != request.name
1447 });
1448 drop(idx_catalog);
1449 if let Some(def) = conflict {
1450 return Err(CreateConstraintError::BackingIndexConflict(format!(
1451 "(:{} {{{}}}) already covered by index `{}`",
1452 request.label,
1453 request.properties.join(", "),
1454 def.name,
1455 )));
1456 }
1457 }
1458
1459 let owns_backing = request.kind.requires_backing_index();
1460 let request_for_event = request.clone();
1461 let outcome = {
1462 let mut catalog = self.constraint_catalog_write();
1463 catalog.try_create(request, if_not_exists)?
1464 };
1465
1466 if let CreateConstraintOutcome::Created(def) = &outcome {
1467 if let Some(deferred) = deferred.as_deref_mut() {
1472 deferred.validate.push(def.clone());
1473 } else if let Err(violation) = self.validate_existing_data_for_constraint(def) {
1474 let mut catalog = self.constraint_catalog_write();
1475 let _ = catalog.try_drop(&def.name, true);
1476 return Err(CreateConstraintError::DataViolation(violation.to_string()));
1477 }
1478 }
1479
1480 if let CreateConstraintOutcome::Created(def) = &outcome {
1481 if owns_backing {
1482 let mut options = BTreeMap::new();
1487 if def.backing == ConstraintBacking::Hash {
1488 options.insert(SORTED_OPTION.to_string(), IndexConfigValue::Bool(false));
1489 }
1490 let idx_request = IndexRequest {
1491 explicit_name: Some(def.name.clone()),
1492 kind: StoredIndexKind::Range,
1493 entity: def.entity,
1494 label: Some(def.label.clone()),
1495 additional_labels: Vec::new(),
1496 properties: def.properties.clone(),
1497 options,
1498 };
1499 if let Err(err) = self.register_index_deferring(idx_request, true, false, deferred)
1503 {
1504 let mut catalog = self.constraint_catalog_write();
1505 let _ = catalog.try_drop(&def.name, true);
1506 return Err(CreateConstraintError::BackingIndexConflict(err.to_string()));
1507 }
1508 }
1509 self.emit(|| crate::MutationEvent::CreateConstraint {
1510 request: request_for_event,
1511 if_not_exists,
1512 });
1513 self.active_constraints.fetch_add(1, Ordering::Relaxed);
1514 }
1515
1516 Ok(outcome)
1517 }
1518
1519 pub(super) fn run_deferred(&self, deferred: DeferredData) -> Result<(), String> {
1526 enum Work {
1527 Populate(IndexDefinition),
1528 Hash(StoredIndexEntity, String),
1531 Sorted(StoredIndexEntity, String, String),
1532 Validate(super::ConstraintDefinition),
1533 }
1534 let mut work: Vec<Work> = deferred.validate.into_iter().map(Work::Validate).collect();
1537 let mut hashed = std::collections::BTreeSet::new();
1538 for def in deferred.populate {
1539 if def.kind != StoredIndexKind::Range {
1540 work.push(Work::Populate(def));
1541 continue;
1542 }
1543 for key in &def.properties {
1544 if hashed.insert((def.entity == StoredIndexEntity::Node, key.clone())) {
1546 work.push(Work::Hash(def.entity, key.clone()));
1547 }
1548 if let Some(label) = def.label.as_deref().filter(|_| is_sorted(&def)) {
1549 work.push(Work::Sorted(def.entity, label.to_owned(), key.clone()));
1550 }
1551 }
1552 }
1553 let run = |work: Work| match work {
1554 Work::Populate(def) => {
1555 self.populate_index_data(&def);
1556 Ok(())
1557 }
1558 Work::Hash(entity, key) => {
1559 match entity {
1560 StoredIndexEntity::Node => self.ensure_node_property_index(&key),
1561 StoredIndexEntity::Relationship => {
1562 self.ensure_relationship_property_index(&key)
1563 }
1564 }
1565 Ok(())
1566 }
1567 Work::Sorted(entity, label, key) => {
1568 self.activate_sorted_scope(entity, &label, &key);
1569 Ok(())
1570 }
1571 Work::Validate(def) => {
1572 self.validate_existing_data_for_constraint(&def)
1573 .map_err(|violation| {
1574 let err = CreateConstraintError::DataViolation(violation.to_string());
1575 format!("constraint `{}`: {err}", def.name)
1576 })
1577 }
1578 };
1579 #[cfg(all(feature = "parallel", not(target_arch = "wasm32")))]
1580 let results: Vec<Result<(), String>> = {
1581 use rayon::prelude::*;
1582 work.into_par_iter().with_max_len(1).map(run).collect()
1583 };
1584 #[cfg(not(all(feature = "parallel", not(target_arch = "wasm32"))))]
1585 let results: Vec<Result<(), String>> = work.into_iter().map(run).collect();
1586 results.into_iter().collect()
1587 }
1588
1589 #[doc(hidden)]
1591 pub fn replay_create_constraint(
1592 &mut self,
1593 request: ConstraintRequest,
1594 if_not_exists: bool,
1595 ) -> Result<(), String> {
1596 if self.recorder.is_some() {
1597 return Err(
1598 "cannot replay create_constraint while a mutation recorder is installed".into(),
1599 );
1600 }
1601 self.register_constraint(request, if_not_exists)
1602 .map(|_| ())
1603 .map_err(|e| e.to_string())
1604 }
1605
1606 #[doc(hidden)]
1608 pub fn replay_drop_constraint(&mut self, name: &str, if_exists: bool) -> Result<(), String> {
1609 if self.recorder.is_some() {
1610 return Err(
1611 "cannot replay drop_constraint while a mutation recorder is installed".into(),
1612 );
1613 }
1614 self.drop_named_constraint(name, if_exists)
1615 .map(|_| ())
1616 .map_err(|e| e.to_string())
1617 }
1618
1619 pub(super) fn drop_named_constraint(
1622 &self,
1623 name: &str,
1624 if_exists: bool,
1625 ) -> Result<DropConstraintOutcome, DropConstraintError> {
1626 let outcome = {
1627 let mut catalog = self.constraint_catalog_write();
1628 catalog.try_drop(name, if_exists)?
1629 };
1630 if let DropConstraintOutcome::Dropped(def) = &outcome {
1631 if let Some(index_name) = def.owned_index.as_deref() {
1632 let _ = self.drop_named_index_inner(index_name, true, false);
1635 }
1636 self.active_constraints.fetch_sub(1, Ordering::Relaxed);
1637 self.emit(|| crate::MutationEvent::DropConstraint {
1638 name: name.to_string(),
1639 if_exists,
1640 });
1641 }
1642 Ok(outcome)
1643 }
1644
1645 pub(super) fn drop_named_index(
1650 &self,
1651 name: &str,
1652 if_exists: bool,
1653 ) -> Result<DropIndexOutcome, DropIndexError> {
1654 self.drop_named_index_inner(name, if_exists, true)
1655 }
1656
1657 fn drop_named_index_inner(
1658 &self,
1659 name: &str,
1660 if_exists: bool,
1661 emit_event: bool,
1662 ) -> Result<DropIndexOutcome, DropIndexError> {
1663 if let Some(owner) = self
1664 .constraint_catalog_read()
1665 .constraint_owning_index(name)
1666 .cloned()
1667 {
1668 return Err(DropIndexError::ConstraintOwned {
1669 index: name.to_string(),
1670 constraint: owner.name,
1671 });
1672 }
1673
1674 let outcome = {
1675 let mut catalog = self.index_catalog_write();
1676 catalog.try_drop(name, if_exists)?
1677 };
1678 if let DropIndexOutcome::Dropped(def) = &outcome {
1679 match def.kind {
1681 StoredIndexKind::Text => {
1682 if let Some(label) = def.label.as_deref() {
1683 for prop in &def.properties {
1684 self.deactivate_text_scope(def.entity, label, prop);
1685 }
1686 }
1687 }
1688 StoredIndexKind::Range => {
1689 if let Some(label) = def.label.as_deref().filter(|_| is_sorted(def)) {
1690 for prop in &def.properties {
1691 self.deactivate_sorted_scope(def.entity, label, prop);
1692 }
1693 }
1694 let declared = self.declared_property_index_keys();
1697 let mut indexes = self.indexes_write();
1698 for prop in &def.properties {
1699 if declared.contains(&(def.entity, prop.clone())) {
1700 continue;
1701 }
1702 match def.entity {
1703 StoredIndexEntity::Node => indexes.node_properties.deactivate(prop),
1704 StoredIndexEntity::Relationship => {
1705 indexes.relationship_properties.deactivate(prop)
1706 }
1707 };
1708 }
1709 }
1710 StoredIndexKind::Point => {
1711 if let Some(label) = def.label.as_deref() {
1712 for prop in &def.properties {
1713 self.deactivate_point_scope(def.entity, label, prop);
1714 }
1715 }
1716 }
1717 StoredIndexKind::Lookup => {
1718 }
1721 StoredIndexKind::Vector => {
1722 self.deactivate_vector_index(def.entity, &def.name);
1723 }
1724 StoredIndexKind::Fulltext => {
1725 self.deactivate_fulltext_index(def.entity, &def.name);
1726 }
1727 }
1728 if emit_event {
1729 self.emit(|| crate::MutationEvent::DropIndex {
1730 name: name.to_string(),
1731 if_exists,
1732 });
1733 }
1734 }
1735 Ok(outcome)
1736 }
1737
1738 pub(super) fn declared_property_index_keys(
1744 &self,
1745 ) -> std::collections::BTreeSet<(StoredIndexEntity, String)> {
1746 self.index_catalog_read()
1747 .list()
1748 .into_iter()
1749 .filter(|def| def.kind == StoredIndexKind::Range)
1750 .flat_map(|def| {
1751 let entity = def.entity;
1752 def.properties.into_iter().map(move |p| (entity, p))
1753 })
1754 .collect()
1755 }
1756
1757 fn populate_index_data(&self, def: &IndexDefinition) {
1758 match def.kind {
1766 StoredIndexKind::Range => {
1767 for key in &def.properties {
1768 match def.entity {
1769 StoredIndexEntity::Node => self.ensure_node_property_index(key),
1770 StoredIndexEntity::Relationship => {
1771 self.ensure_relationship_property_index(key)
1772 }
1773 }
1774 if let Some(label) = def.label.as_deref().filter(|_| is_sorted(def)) {
1775 self.activate_sorted_scope(def.entity, label, key);
1776 }
1777 }
1778 }
1779 StoredIndexKind::Text => {
1780 let label = match def.label.as_deref() {
1781 Some(l) => l,
1782 None => return,
1783 };
1784 for property in &def.properties {
1785 self.activate_text_scope(def.entity, label, property);
1786 }
1787 }
1788 StoredIndexKind::Point => {
1789 let label = match def.label.as_deref() {
1790 Some(l) => l,
1791 None => return,
1792 };
1793 let cell_size = PointRegistry::cell_size_from_options(&def.options);
1794 for property in &def.properties {
1795 self.activate_point_scope(def.entity, label, property, cell_size);
1796 }
1797 }
1798 StoredIndexKind::Fulltext => {
1799 let labels: Vec<String> = def.all_labels().map(String::from).collect();
1800 if labels.is_empty() {
1801 return;
1802 }
1803 self.activate_fulltext_index(def.entity, &def.name, &labels, &def.properties);
1804 }
1805 StoredIndexKind::Vector => {
1806 let label = match def.label.as_deref() {
1807 Some(l) => l,
1808 None => return,
1809 };
1810 let property = match def.properties.first() {
1811 Some(p) => p.as_str(),
1812 None => return,
1813 };
1814 let similarity = VectorSimilarity::from_options(&def.options)
1815 .unwrap_or(VectorSimilarity::Cosine);
1816 let provider = VectorIndexProvider::from_options(&def.options)
1817 .unwrap_or(VectorIndexProvider::Flat);
1818 let hnsw = HnswParams::from_options(&def.options);
1819 let lazy = matches!(
1820 def.options.get("vector.populate.async"),
1821 Some(super::index_catalog::IndexConfigValue::Bool(true))
1822 );
1823 self.activate_vector_index(
1824 def.entity, &def.name, label, property, similarity, provider, hnsw, lazy,
1825 );
1826 if lazy {
1827 self.index_catalog_write()
1828 .set_state(&def.name, StoredIndexState::Populating);
1829 }
1830 }
1831 StoredIndexKind::Lookup => {}
1833 }
1834 }
1835
1836 pub(super) fn text_indexes_read(
1837 &self,
1838 entity: StoredIndexEntity,
1839 ) -> IndexRead<'_, TrigramRegistry> {
1840 self.indexes.text.read(entity)
1841 }
1842
1843 pub(super) fn text_indexes_write(
1844 &self,
1845 entity: StoredIndexEntity,
1846 ) -> IndexWrite<'_, TrigramRegistry> {
1847 self.indexes.text.write(entity)
1848 }
1849
1850 pub(super) fn fulltext_indexes_read(
1851 &self,
1852 entity: StoredIndexEntity,
1853 ) -> IndexRead<'_, FulltextRegistry> {
1854 self.indexes.fulltext.read(entity)
1855 }
1856
1857 #[allow(dead_code)]
1858 pub(super) fn fulltext_indexes_write(
1859 &self,
1860 entity: StoredIndexEntity,
1861 ) -> IndexWrite<'_, FulltextRegistry> {
1862 self.indexes.fulltext.write(entity)
1863 }
1864
1865 fn activate_text_scope(&self, entity: StoredIndexEntity, label: &str, property: &str) {
1866 if !self.text_indexes_write(entity).add_scope(label, property) {
1867 return;
1868 }
1869
1870 let backfill: Vec<(u64, String)> = match entity {
1871 StoredIndexEntity::Node => self
1872 .iter_nodes()
1873 .filter(|(_, node)| node.labels().strs().any(|l| l == label))
1874 .filter_map(|(id, node)| match node.properties().get(property) {
1875 Some(crate::ValueRef::String(value)) => Some((id, value.to_owned())),
1876 _ => None,
1877 })
1878 .collect(),
1879 StoredIndexEntity::Relationship => self
1880 .iter_rels()
1881 .filter(|(_, rel)| rel.rel_type() == label)
1882 .filter_map(|(id, rel)| match rel.properties().get(property) {
1883 Some(crate::ValueRef::String(value)) => Some((id, value.to_owned())),
1884 _ => None,
1885 })
1886 .collect(),
1887 };
1888
1889 let mut registry = self.text_indexes_write(entity);
1890 for (id, value) in backfill {
1891 registry.insert(label, property, id, &value);
1892 }
1893 }
1894
1895 pub(super) fn deactivate_text_scope(
1897 &self,
1898 entity: StoredIndexEntity,
1899 label: &str,
1900 property: &str,
1901 ) {
1902 self.text_indexes_write(entity)
1903 .remove_scope(label, property);
1904 }
1905
1906 fn activate_fulltext_index(
1907 &self,
1908 entity: StoredIndexEntity,
1909 name: &str,
1910 labels: &[String],
1911 properties: &[String],
1912 ) {
1913 use super::fulltext_index::{term_counts_for_properties, TermCounts};
1914
1915 {
1916 let mut registry = self.fulltext_indexes_write(entity);
1917 registry.register(name.to_string(), labels.to_vec(), properties.to_vec());
1918 }
1919 self.indexes
1920 .active_fulltext_indexes
1921 .fetch_add(1, Ordering::Relaxed);
1922
1923 let backfill: Vec<(u64, TermCounts)> = match entity {
1926 StoredIndexEntity::Node => self
1927 .iter_nodes()
1928 .filter(|(_, node)| {
1929 labels
1930 .iter()
1931 .any(|wanted| node.labels().strs().any(|l| l == wanted))
1932 })
1933 .map(|(id, node)| {
1934 let counts = term_counts_for_properties(node.properties(), properties);
1935 (id, counts)
1936 })
1937 .filter(|(_, c)| !c.is_empty())
1938 .collect(),
1939 StoredIndexEntity::Relationship => self
1940 .iter_rels()
1941 .filter(|(_, rel)| labels.iter().any(|wanted| wanted == rel.rel_type()))
1942 .map(|(id, rel)| {
1943 let counts = term_counts_for_properties(rel.properties(), properties);
1944 (id, counts)
1945 })
1946 .filter(|(_, c)| !c.is_empty())
1947 .collect(),
1948 };
1949
1950 let mut registry = self.fulltext_indexes_write(entity);
1951 if let Some(index) = registry.get_mut(name) {
1952 for (id, counts) in backfill {
1953 index.reindex_entity(id, counts);
1954 }
1955 }
1956 }
1957
1958 pub(super) fn deactivate_fulltext_index(&self, entity: StoredIndexEntity, name: &str) {
1959 self.fulltext_indexes_write(entity).deregister(name);
1960 self.indexes
1961 .active_fulltext_indexes
1962 .fetch_sub(1, Ordering::Relaxed);
1963 }
1964
1965 pub(super) fn sorted_indexes_read(
1966 &self,
1967 entity: StoredIndexEntity,
1968 ) -> IndexRead<'_, SortedPropertyIndex> {
1969 self.indexes.sorted.read(entity)
1970 }
1971
1972 pub(super) fn sorted_indexes_write(
1973 &self,
1974 entity: StoredIndexEntity,
1975 ) -> IndexWrite<'_, SortedPropertyIndex> {
1976 self.indexes.sorted.write(entity)
1977 }
1978
1979 pub(super) fn activate_sorted_scope(
1980 &self,
1981 entity: StoredIndexEntity,
1982 label: &str,
1983 property: &str,
1984 ) {
1985 let members = match entity {
1986 StoredIndexEntity::Node => self.nodes_by_label.get(label),
1987 StoredIndexEntity::Relationship => self.relationships_by_type.get(label),
1988 }
1989 .map_or(0, |ids| ids.len());
1990 if cfg!(all(feature = "parallel", not(target_arch = "wasm32")))
1991 && members >= PARALLEL_INDEX_MIN
1992 {
1993 return self.activate_sorted_scope_parallel(entity, label, property);
1994 }
1995 if !self.sorted_indexes_write(entity).add_scope(label, property) {
1996 return;
1997 }
1998
1999 let mut registry = self.sorted_indexes_write(entity);
2002 match entity {
2003 StoredIndexEntity::Node => {
2004 for (id, node) in self.iter_nodes() {
2005 if !node.labels().strs().any(|l| l == label) {
2006 continue;
2007 }
2008 match node.properties().get(property) {
2009 Some(crate::ValueRef::Other(value)) => {
2011 registry.insert(label, property, id, &value.get())
2012 }
2013 Some(value) => {
2014 if let Some(key) = PropertyIndexKey::from_value_ref(value) {
2015 registry.insert_key(label, property, id, key);
2016 }
2017 }
2018 None => {}
2019 }
2020 }
2021 }
2022 StoredIndexEntity::Relationship => {
2023 for (id, rel) in self.iter_rels() {
2024 if rel.rel_type() != label {
2025 continue;
2026 }
2027 match rel.properties().get(property) {
2028 Some(crate::ValueRef::Other(value)) => {
2030 registry.insert(label, property, id, &value.get())
2031 }
2032 Some(value) => {
2033 if let Some(key) = PropertyIndexKey::from_value_ref(value) {
2034 registry.insert_key(label, property, id, key);
2035 }
2036 }
2037 None => {}
2038 }
2039 }
2040 }
2041 }
2042 }
2043
2044 fn activate_sorted_scope_parallel(
2048 &self,
2049 entity: StoredIndexEntity,
2050 label: &str,
2051 property: &str,
2052 ) {
2053 use super::sorted_property_index::{build_scope, element_keys};
2054 if self.sorted_indexes_read(entity).has_scope(label, property) {
2055 self.sorted_indexes_write(entity).add_scope(label, property);
2056 return;
2057 }
2058 let (ids, slots) = match entity {
2059 StoredIndexEntity::Node => (self.nodes_by_label.get(label), self.nodes.len()),
2060 StoredIndexEntity::Relationship => (
2061 self.relationships_by_type.get(label),
2062 self.relationships.len(),
2063 ),
2064 };
2065 let Some(ids) = ids else {
2066 self.sorted_indexes_write(entity).add_scope(label, property);
2067 return;
2068 };
2069 let Some(key_id) = self.dicts.keys.id_of(property) else {
2070 self.sorted_indexes_write(entity).add_scope(label, property);
2071 return;
2072 };
2073 const RANGE: u64 = 1 << 16;
2074 let gather = |start: u64| {
2075 let mut values = Vec::new();
2076 let mut elements = Vec::new();
2077 for id in ids.iter_from(start).take_while(|&id| id < start + RANGE) {
2078 let value = match entity {
2079 StoredIndexEntity::Node => self.nodes.get(id as usize).and_then(|bytes| {
2080 StoredNode::new(id, bytes, &self.dicts)
2081 .properties()
2082 .get_id(key_id)
2083 }),
2084 StoredIndexEntity::Relationship => {
2085 self.relationships.get(id as usize).and_then(|bytes| {
2086 StoredRel::new(id, bytes, &self.dicts)
2087 .properties()
2088 .get_id(key_id)
2089 })
2090 }
2091 };
2092 match value {
2093 Some(crate::ValueRef::Other(value)) => {
2095 let value = value.get();
2096 if let Some(key) = PropertyIndexKey::from_value(&value) {
2097 values.push((key, id));
2098 }
2099 elements.extend(element_keys(&value).into_iter().map(|key| (key, id)));
2100 }
2101 Some(value) => {
2102 if let Some(key) = PropertyIndexKey::from_value_ref(value) {
2103 values.push((key, id));
2104 }
2105 }
2106 None => {}
2107 }
2108 }
2109 (values, elements)
2110 };
2111 let starts: Vec<u64> = (0..(slots as u64).div_ceil(RANGE))
2112 .map(|i| i * RANGE)
2113 .collect();
2114 #[cfg(all(feature = "parallel", not(target_arch = "wasm32")))]
2115 let gathered: Vec<_> = {
2116 use rayon::prelude::*;
2117 starts.into_par_iter().map(gather).collect()
2118 };
2119 #[cfg(not(all(feature = "parallel", not(target_arch = "wasm32"))))]
2120 let gathered: Vec<_> = starts.into_iter().map(gather).collect();
2121 let mut values = Vec::new();
2122 let mut elements = Vec::new();
2123 for (v, e) in gathered {
2124 values.extend(v);
2125 elements.extend(e);
2126 }
2127 let built = build_scope(values, elements);
2128 self.sorted_indexes_write(entity)
2129 .add_built_scope(label, property, built);
2130 }
2131
2132 pub(super) fn deactivate_sorted_scope(
2133 &self,
2134 entity: StoredIndexEntity,
2135 label: &str,
2136 property: &str,
2137 ) {
2138 self.sorted_indexes_write(entity)
2139 .remove_scope(label, property);
2140 }
2141
2142 pub(super) fn point_indexes_read(
2143 &self,
2144 entity: StoredIndexEntity,
2145 ) -> IndexRead<'_, PointRegistry> {
2146 self.indexes.point.read(entity)
2147 }
2148
2149 pub(super) fn point_indexes_write(
2150 &self,
2151 entity: StoredIndexEntity,
2152 ) -> IndexWrite<'_, PointRegistry> {
2153 self.indexes.point.write(entity)
2154 }
2155
2156 fn activate_point_scope(
2157 &self,
2158 entity: StoredIndexEntity,
2159 label: &str,
2160 property: &str,
2161 cell_size: Option<f64>,
2162 ) {
2163 if !self
2164 .point_indexes_write(entity)
2165 .add_scope(label, property, cell_size)
2166 {
2167 return;
2168 }
2169
2170 let backfill: Vec<(u64, LoraPoint)> = match entity {
2171 StoredIndexEntity::Node => self
2172 .iter_nodes()
2173 .filter(|(_, node)| node.labels().strs().any(|l| l == label))
2174 .filter_map(|(id, node)| {
2175 match node.properties().get(property).map(|v| v.to_owned()) {
2176 Some(PropertyValue::Point(point)) => Some((id, point)),
2177 _ => None,
2178 }
2179 })
2180 .collect(),
2181 StoredIndexEntity::Relationship => self
2182 .iter_rels()
2183 .filter(|(_, rel)| rel.rel_type() == label)
2184 .filter_map(|(id, rel)| {
2185 match rel.properties().get(property).map(|v| v.to_owned()) {
2186 Some(PropertyValue::Point(point)) => Some((id, point)),
2187 _ => None,
2188 }
2189 })
2190 .collect(),
2191 };
2192
2193 let mut registry = self.point_indexes_write(entity);
2194 for (id, point) in backfill {
2195 registry.insert(label, property, id, point);
2196 }
2197 }
2198
2199 pub(super) fn deactivate_point_scope(
2200 &self,
2201 entity: StoredIndexEntity,
2202 label: &str,
2203 property: &str,
2204 ) {
2205 self.point_indexes_write(entity)
2206 .remove_scope(label, property);
2207 }
2208
2209 pub(super) fn vector_indexes_read(
2210 &self,
2211 entity: StoredIndexEntity,
2212 ) -> IndexRead<'_, VectorIndexRegistry> {
2213 self.indexes.vector.read(entity)
2214 }
2215
2216 pub(super) fn vector_indexes_write(
2217 &self,
2218 entity: StoredIndexEntity,
2219 ) -> IndexWrite<'_, VectorIndexRegistry> {
2220 self.indexes.vector.write(entity)
2221 }
2222
2223 #[allow(clippy::too_many_arguments)]
2224 fn activate_vector_index(
2225 &self,
2226 entity: StoredIndexEntity,
2227 name: &str,
2228 label: &str,
2229 property: &str,
2230 similarity: VectorSimilarity,
2231 provider: VectorIndexProvider,
2232 hnsw: HnswParams,
2233 lazy: bool,
2234 ) {
2235 {
2236 let mut registry = self.vector_indexes_write(entity);
2237 registry.register(
2238 name.to_string(),
2239 label.to_string(),
2240 property.to_string(),
2241 similarity,
2242 provider,
2243 hnsw,
2244 );
2245 }
2246
2247 if lazy {
2248 return;
2255 }
2256
2257 self.backfill_vector_index(entity, label, property);
2258 }
2259
2260 fn backfill_vector_index(&self, entity: StoredIndexEntity, label: &str, property: &str) {
2264 let backfill: Vec<(u64, crate::LoraVector)> = match entity {
2265 StoredIndexEntity::Node => self
2266 .iter_nodes()
2267 .filter(|(_, node)| node.labels().strs().any(|l| l == label))
2268 .filter_map(|(id, node)| {
2269 match node.properties().get(property).map(|v| v.to_owned()) {
2270 Some(PropertyValue::Vector(v)) => Some((id, v)),
2271 _ => None,
2272 }
2273 })
2274 .collect(),
2275 StoredIndexEntity::Relationship => self
2276 .iter_rels()
2277 .filter(|(_, rel)| rel.rel_type() == label)
2278 .filter_map(|(id, rel)| {
2279 match rel.properties().get(property).map(|v| v.to_owned()) {
2280 Some(PropertyValue::Vector(v)) => Some((id, v)),
2281 _ => None,
2282 }
2283 })
2284 .collect(),
2285 };
2286
2287 let mut registry = self.vector_indexes_write(entity);
2288 for (id, vector) in backfill {
2289 registry.insert_for(label, property, id, &vector);
2290 }
2291 }
2292
2293 pub(super) fn lazy_populate_vector_index(&self, name: &str) {
2299 let def = match self.index_catalog_read().get(name).cloned() {
2300 Some(d) => d,
2301 None => return,
2302 };
2303 if def.state != StoredIndexState::Populating || def.kind != StoredIndexKind::Vector {
2304 return;
2305 }
2306 let label = match def.label.as_deref() {
2307 Some(l) => l,
2308 None => return,
2309 };
2310 let property = match def.properties.first() {
2311 Some(p) => p.as_str(),
2312 None => return,
2313 };
2314 self.backfill_vector_index(def.entity, label, property);
2315 self.index_catalog_write()
2316 .set_state(name, StoredIndexState::Online);
2317 }
2318
2319 pub(super) fn deactivate_vector_index(&self, entity: StoredIndexEntity, name: &str) {
2320 self.vector_indexes_write(entity).deregister(name);
2321 }
2322
2323 pub fn graph_stats(&self) -> GraphStats {
2329 let mut stats = GraphStats {
2330 node_count: self.live_node_count,
2331 relationship_count: self.live_rel_count,
2332 ..Default::default()
2333 };
2334 stats.nodes_by_label = self
2337 .nodes_by_label
2338 .iter()
2339 .map(|(label, ids)| (label.to_string(), ids.len()))
2340 .collect();
2341 stats.relationships_by_type = self
2342 .relationships_by_type
2343 .iter()
2344 .map(|(rel_type, ids)| (rel_type.to_string(), ids.len()))
2345 .collect();
2346 stats.node_distinct_values = self.distinct_stats.nodes.distinct_values();
2353 stats.relationship_distinct_values = self.distinct_stats.relationships.distinct_values();
2354
2355 for def in self.index_catalog_read().list() {
2356 if def.state != StoredIndexState::Online {
2357 continue;
2358 }
2359 if def.kind == StoredIndexKind::Range && !is_sorted(&def) {
2362 continue;
2363 }
2364 let Some(label) = def.label else {
2365 continue;
2366 };
2367 for property in def.properties {
2368 let scope = (label.clone(), property);
2369 match (def.entity, def.kind) {
2370 (StoredIndexEntity::Node, StoredIndexKind::Range) => {
2371 stats.node_range_indexes.insert(scope);
2372 }
2373 (StoredIndexEntity::Node, StoredIndexKind::Text) => {
2374 stats.node_text_indexes.insert(scope);
2375 }
2376 (StoredIndexEntity::Node, StoredIndexKind::Point) => {
2377 stats.node_point_indexes.insert(scope);
2378 }
2379 (StoredIndexEntity::Relationship, StoredIndexKind::Range) => {
2380 stats.relationship_range_indexes.insert(scope);
2381 }
2382 (StoredIndexEntity::Relationship, StoredIndexKind::Text) => {
2383 stats.relationship_text_indexes.insert(scope);
2384 }
2385 (StoredIndexEntity::Relationship, StoredIndexKind::Point) => {
2386 stats.relationship_point_indexes.insert(scope);
2387 }
2388 (StoredIndexEntity::Node, StoredIndexKind::Vector) => {
2389 stats.node_vector_indexes.insert(scope);
2390 }
2391 (StoredIndexEntity::Relationship, StoredIndexKind::Vector) => {
2392 stats.relationship_vector_indexes.insert(scope);
2393 }
2394 (_, StoredIndexKind::Lookup | StoredIndexKind::Fulltext) => {}
2395 }
2396 }
2397 }
2398 for (entity, elements) in [
2399 (StoredIndexEntity::Node, &mut stats.node_list_elements),
2400 (
2401 StoredIndexEntity::Relationship,
2402 &mut stats.relationship_list_elements,
2403 ),
2404 ] {
2405 let sorted = self.sorted_indexes_read(entity);
2406 for (label, property, distinct, postings) in sorted.element_stats() {
2407 elements.insert(
2408 (label.to_string(), property.to_string()),
2409 super::ListElementStats { distinct, postings },
2410 );
2411 }
2412 }
2413 stats
2414 }
2415
2416 pub fn plan_signature(&self) -> u64 {
2421 let mut sig = super::PlanSignature::new();
2422 sig.totals(self.live_node_count, self.live_rel_count);
2423 for (label, ids) in &self.nodes_by_label {
2424 sig.label(label, ids.len());
2425 }
2426 for (rel_type, ids) in &self.relationships_by_type {
2427 sig.relationship_type(rel_type, ids.len());
2428 }
2429 for (label, property, distinct) in self.distinct_stats.nodes.estimates() {
2430 sig.node_distinct(label, property, distinct);
2431 }
2432 for (rel_type, property, distinct) in self.distinct_stats.relationships.estimates() {
2433 sig.relationship_distinct(rel_type, property, distinct);
2434 }
2435 for def in self.index_catalog_read().iter() {
2436 if def.state != StoredIndexState::Online
2437 || matches!(
2438 def.kind,
2439 StoredIndexKind::Lookup | StoredIndexKind::Fulltext
2440 )
2441 {
2442 continue;
2443 }
2444 let Some(label) = &def.label else {
2445 continue;
2446 };
2447 for property in &def.properties {
2448 sig.index(def.entity, def.kind, label, property);
2449 }
2450 }
2451 for entity in [StoredIndexEntity::Node, StoredIndexEntity::Relationship] {
2452 let sorted = self.sorted_indexes_read(entity);
2453 for (label, property, distinct, postings) in sorted.element_stats() {
2454 sig.list_elements(
2455 entity,
2456 label,
2457 property,
2458 super::ListElementStats { distinct, postings },
2459 );
2460 }
2461 }
2462 sig.finish()
2463 }
2464
2465 pub fn memory_estimate(&self) -> super::MemoryReport {
2470 super::mem_report::estimate(self)
2471 }
2472
2473 pub(super) fn count_distinct_values<'a>(
2476 stats: &mut super::distinct_stats::DistinctStats,
2477 scopes: impl IntoIterator<Item = &'a str>,
2478 properties: &Properties,
2479 add: bool,
2480 ) {
2481 if properties.is_empty() {
2482 return;
2483 }
2484 for scope in scopes {
2485 if add {
2486 stats.add_all(scope, properties);
2487 } else {
2488 for (key, value) in properties {
2489 stats.remove(scope, key, value);
2490 }
2491 }
2492 }
2493 }
2494
2495 pub(super) fn on_node_created(&mut self, node: &NodeRecord) {
2496 for label in &node.labels {
2497 self.insert_node_label_index(node.id, label);
2498 }
2499 Self::count_distinct_values(
2500 &mut self.distinct_stats.nodes,
2501 node.labels.strs(),
2502 &node.properties,
2503 true,
2504 );
2505 self.index_node_properties_if_active(node.id, node.labels.strs(), &node.properties);
2506 self.maintain_node_secondary_indexes(node, SecondaryIndexMutation::Insert);
2507 }
2508
2509 pub(super) fn on_node_property_set(
2510 &mut self,
2511 node_id: NodeId,
2512 key: &str,
2513 old: Option<&PropertyValue>,
2514 new: &PropertyValue,
2515 ) {
2516 let Some(labels) = self.node_at(node_id).map(|node| node.labels().to_owned()) else {
2517 return;
2518 };
2519 for label in &labels {
2520 self.distinct_stats.nodes.replace(label, key, old, new);
2521 }
2522
2523 if self.node_property_index_is_active(key) {
2524 if let Some(old) = old {
2525 self.unindex_node_property_if_active(node_id, labels.strs(), key, old);
2526 }
2527 self.index_node_property_if_active(node_id, labels.strs(), key, new);
2528 }
2529
2530 self.update_secondary_property(
2531 StoredIndexEntity::Node,
2532 labels.strs(),
2533 node_id,
2534 key,
2535 old,
2536 Some(new),
2537 );
2538 }
2539
2540 pub(super) fn on_node_property_removed(
2541 &mut self,
2542 node_id: NodeId,
2543 key: &str,
2544 old: &PropertyValue,
2545 ) {
2546 let Some(labels) = self.node_at(node_id).map(|node| node.labels().to_owned()) else {
2547 return;
2548 };
2549 for label in &labels {
2550 self.distinct_stats.nodes.remove(label, key, old);
2551 }
2552 if self.node_property_index_is_active(key) {
2553 self.unindex_node_property_if_active(node_id, labels.strs(), key, old);
2554 }
2555 self.update_secondary_property(
2556 StoredIndexEntity::Node,
2557 labels.strs(),
2558 node_id,
2559 key,
2560 Some(old),
2561 None,
2562 );
2563 }
2564
2565 pub(super) fn on_node_label_added(&mut self, node_id: NodeId, label: &str) {
2566 self.insert_node_label_index(node_id, label);
2567
2568 let Some(properties) = self
2569 .node_at(node_id)
2570 .map(|node| node.properties().to_owned())
2571 else {
2572 return;
2573 };
2574 Self::count_distinct_values(&mut self.distinct_stats.nodes, [label], &properties, true);
2575 if self.active_node_property_index_count() != 0 {
2576 self.index_node_scope_properties_if_active(node_id, label, &properties);
2577 if self.node_at(node_id).is_some_and(|n| n.labels().len() == 1) {
2579 self.unindex_node_scope_properties_if_active(node_id, UNLABELLED, &properties);
2580 }
2581 }
2582 for (key, value) in &properties {
2583 self.update_secondary_property(
2584 StoredIndexEntity::Node,
2585 [label],
2586 node_id,
2587 key,
2588 None,
2589 Some(value),
2590 );
2591 }
2592 }
2593
2594 pub(super) fn on_node_label_removed(&mut self, node_id: NodeId, label: &str) {
2595 self.remove_node_label_index(node_id, label);
2596
2597 let Some(properties) = self
2598 .node_at(node_id)
2599 .map(|node| node.properties().to_owned())
2600 else {
2601 return;
2602 };
2603 Self::count_distinct_values(&mut self.distinct_stats.nodes, [label], &properties, false);
2604 if self.active_node_property_index_count() != 0 {
2605 self.unindex_node_scope_properties_if_active(node_id, label, &properties);
2606 if self.node_at(node_id).is_some_and(|n| n.labels().is_empty()) {
2608 self.index_node_scope_properties_if_active(node_id, UNLABELLED, &properties);
2609 }
2610 }
2611 for (key, value) in &properties {
2612 self.update_secondary_property(
2613 StoredIndexEntity::Node,
2614 [label],
2615 node_id,
2616 key,
2617 Some(value),
2618 None,
2619 );
2620 }
2621 }
2622
2623 pub(super) fn on_node_deleted(&mut self, node: &NodeRecord) {
2624 for label in &node.labels {
2625 self.remove_node_label_index(node.id, label);
2626 }
2627 Self::count_distinct_values(
2628 &mut self.distinct_stats.nodes,
2629 node.labels.strs(),
2630 &node.properties,
2631 false,
2632 );
2633 self.unindex_active_node_properties(node.id, node.labels.strs(), &node.properties);
2634 self.maintain_node_secondary_indexes(node, SecondaryIndexMutation::Remove);
2635 }
2636
2637 pub(super) fn on_relationship_created(&mut self, rel: &RelationshipRecord) {
2638 self.attach_relationship(rel);
2639 Self::count_distinct_values(
2640 &mut self.distinct_stats.relationships,
2641 [rel.rel_type.as_str()],
2642 &rel.properties,
2643 true,
2644 );
2645 self.index_relationship_properties_if_active(
2646 rel.id,
2647 [rel.rel_type.as_str()],
2648 &rel.properties,
2649 );
2650 self.maintain_relationship_secondary_indexes(rel, SecondaryIndexMutation::Insert);
2651 }
2652
2653 pub(super) fn on_relationship_property_set(
2654 &mut self,
2655 rel_id: RelationshipId,
2656 key: &str,
2657 old: Option<&PropertyValue>,
2658 new: &PropertyValue,
2659 ) {
2660 let Some(rel_type) = self.rel_at(rel_id).map(|rel| rel.type_name().clone()) else {
2661 return;
2662 };
2663 self.distinct_stats
2664 .relationships
2665 .replace(&rel_type, key, old, new);
2666
2667 if self.relationship_property_index_is_active(key) {
2668 if let Some(old) = old {
2669 self.unindex_relationship_property_if_active(rel_id, [rel_type.as_str()], key, old);
2670 }
2671 self.index_relationship_property_if_active(rel_id, [rel_type.as_str()], key, new);
2672 }
2673
2674 self.update_secondary_property(
2675 StoredIndexEntity::Relationship,
2676 [rel_type.as_str()],
2677 rel_id,
2678 key,
2679 old,
2680 Some(new),
2681 );
2682 }
2683
2684 pub(super) fn on_relationship_property_removed(
2685 &mut self,
2686 rel_id: RelationshipId,
2687 key: &str,
2688 old: &PropertyValue,
2689 ) {
2690 let Some(rel_type) = self.rel_at(rel_id).map(|rel| rel.type_name().clone()) else {
2691 return;
2692 };
2693 self.distinct_stats
2694 .relationships
2695 .remove(&rel_type, key, old);
2696 if self.relationship_property_index_is_active(key) {
2697 self.unindex_relationship_property_if_active(rel_id, [rel_type.as_str()], key, old);
2698 }
2699 self.update_secondary_property(
2700 StoredIndexEntity::Relationship,
2701 [rel_type.as_str()],
2702 rel_id,
2703 key,
2704 Some(old),
2705 None,
2706 );
2707 }
2708
2709 pub(super) fn on_relationship_deleted(
2712 &mut self,
2713 rel: &RelationshipRecord,
2714 pending: Option<&mut PendingAdjRemovals>,
2715 ) {
2716 self.detach_relationship_indexes(rel, pending);
2717 Self::count_distinct_values(
2718 &mut self.distinct_stats.relationships,
2719 [rel.rel_type.as_str()],
2720 &rel.properties,
2721 false,
2722 );
2723 self.unindex_active_relationship_properties(
2724 rel.id,
2725 [rel.rel_type.as_str()],
2726 &rel.properties,
2727 );
2728 self.maintain_relationship_secondary_indexes(rel, SecondaryIndexMutation::Remove);
2729 }
2730
2731 fn index_node_property_if_active<'a>(
2732 &mut self,
2733 node_id: NodeId,
2734 labels: impl IntoIterator<Item = &'a str>,
2735 key: &str,
2736 value: &PropertyValue,
2737 ) {
2738 if self.active_node_property_index_count() == 0 {
2739 return;
2740 }
2741 let (indexes, records) = self.node_index_parts();
2742 if indexes.node_properties.is_active(key) {
2743 indexes
2744 .node_properties
2745 .insert_with_scopes(node_id, labels, key, value, &records);
2746 }
2747 }
2748
2749 fn index_node_properties_if_active<'a>(
2750 &mut self,
2751 node_id: NodeId,
2752 labels: impl IntoIterator<Item = &'a str> + Clone,
2753 properties: &Properties,
2754 ) {
2755 if self.active_node_property_index_count() == 0 {
2756 return;
2757 }
2758 let (indexes, records) = self.node_index_parts();
2759 for (key, value) in properties {
2760 if indexes.node_properties.is_active(key) {
2761 indexes.node_properties.insert_with_scopes(
2762 node_id,
2763 labels.clone(),
2764 key,
2765 value,
2766 &records,
2767 );
2768 }
2769 }
2770 }
2771
2772 fn unindex_node_property_if_active<'a>(
2773 &mut self,
2774 node_id: NodeId,
2775 labels: impl IntoIterator<Item = &'a str>,
2776 key: &str,
2777 value: &PropertyValue,
2778 ) {
2779 if self.active_node_property_index_count() == 0 {
2780 return;
2781 }
2782 let indexes = self.indexes_mut();
2783 if indexes.node_properties.is_active(key) {
2784 indexes
2785 .node_properties
2786 .remove_with_scopes(node_id, labels, key, value);
2787 }
2788 }
2789
2790 fn index_node_scope_properties_if_active(
2791 &mut self,
2792 node_id: NodeId,
2793 scope: &str,
2794 properties: &Properties,
2795 ) {
2796 if self.active_node_property_index_count() == 0 {
2797 return;
2798 }
2799 let (indexes, records) = self.node_index_parts();
2800 for (key, value) in properties {
2801 if indexes.node_properties.is_active(key) {
2802 indexes
2803 .node_properties
2804 .insert_scoped(node_id, scope, key, value, &records);
2805 }
2806 }
2807 }
2808
2809 fn unindex_node_scope_properties_if_active(
2810 &mut self,
2811 node_id: NodeId,
2812 scope: &str,
2813 properties: &Properties,
2814 ) {
2815 if self.active_node_property_index_count() == 0 {
2816 return;
2817 }
2818 let indexes = self.indexes_mut();
2819 for (key, value) in properties {
2820 if indexes.node_properties.is_active(key) {
2821 indexes
2822 .node_properties
2823 .remove_scoped(node_id, scope, key, value);
2824 }
2825 }
2826 }
2827
2828 fn unindex_active_node_properties<'a>(
2829 &mut self,
2830 node_id: NodeId,
2831 labels: impl IntoIterator<Item = &'a str> + Clone,
2832 properties: &Properties,
2833 ) {
2834 if self.active_node_property_index_count() == 0 {
2835 return;
2836 }
2837 let indexes = self.indexes_mut();
2838 for (key, value) in properties {
2839 if indexes.node_properties.is_active(key) {
2840 indexes
2841 .node_properties
2842 .remove_with_scopes(node_id, labels.clone(), key, value);
2843 }
2844 }
2845 }
2846
2847 fn index_relationship_property_if_active<'a>(
2848 &mut self,
2849 rel_id: RelationshipId,
2850 scopes: impl IntoIterator<Item = &'a str>,
2851 key: &str,
2852 value: &PropertyValue,
2853 ) {
2854 if self.active_relationship_property_index_count() == 0 {
2855 return;
2856 }
2857 let (indexes, records) = self.relationship_index_parts();
2858 if indexes.relationship_properties.is_active(key) {
2859 indexes
2860 .relationship_properties
2861 .insert_with_scopes(rel_id, scopes, key, value, &records);
2862 }
2863 }
2864
2865 fn index_relationship_properties_if_active<'a>(
2866 &mut self,
2867 rel_id: RelationshipId,
2868 scopes: impl IntoIterator<Item = &'a str> + Clone,
2869 properties: &Properties,
2870 ) {
2871 if self.active_relationship_property_index_count() == 0 {
2872 return;
2873 }
2874 let (indexes, records) = self.relationship_index_parts();
2875 for (key, value) in properties {
2876 if indexes.relationship_properties.is_active(key) {
2877 indexes.relationship_properties.insert_with_scopes(
2878 rel_id,
2879 scopes.clone(),
2880 key,
2881 value,
2882 &records,
2883 );
2884 }
2885 }
2886 }
2887
2888 fn unindex_relationship_property_if_active<'a>(
2889 &mut self,
2890 rel_id: RelationshipId,
2891 scopes: impl IntoIterator<Item = &'a str>,
2892 key: &str,
2893 value: &PropertyValue,
2894 ) {
2895 if self.active_relationship_property_index_count() == 0 {
2896 return;
2897 }
2898 let indexes = self.indexes_mut();
2899 if indexes.relationship_properties.is_active(key) {
2900 indexes
2901 .relationship_properties
2902 .remove_with_scopes(rel_id, scopes, key, value);
2903 }
2904 }
2905
2906 fn unindex_active_relationship_properties<'a>(
2907 &mut self,
2908 rel_id: RelationshipId,
2909 scopes: impl IntoIterator<Item = &'a str> + Clone,
2910 properties: &Properties,
2911 ) {
2912 if self.active_relationship_property_index_count() == 0 {
2913 return;
2914 }
2915 let indexes = self.indexes_mut();
2916 for (key, value) in properties {
2917 if indexes.relationship_properties.is_active(key) {
2918 indexes.relationship_properties.remove_with_scopes(
2919 rel_id,
2920 scopes.clone(),
2921 key,
2922 value,
2923 );
2924 }
2925 }
2926 }
2927
2928 pub(super) fn scan_nodes_by_property(
2929 &self,
2930 label: Option<&str>,
2931 key: &str,
2932 value: &PropertyValue,
2933 ) -> Vec<NodeRecord> {
2934 match label {
2935 Some(label) => self
2936 .nodes_by_label
2937 .get(label)
2938 .into_iter()
2939 .flat_map(|ids| ids.iter())
2940 .filter_map(|id| self.node_at(id))
2941 .filter(|node| node.properties().get(key).is_some_and(|v| v == *value))
2942 .map(|node| node.to_record())
2943 .collect(),
2944 None => self
2945 .iter_node_refs()
2946 .filter(|node| node.properties().get(key).is_some_and(|v| v == *value))
2947 .map(|node| node.to_record())
2948 .collect(),
2949 }
2950 }
2951
2952 pub(super) fn scan_node_ids_by_property(
2953 &self,
2954 label: Option<&str>,
2955 key: &str,
2956 value: &PropertyValue,
2957 ) -> Vec<NodeId> {
2958 match label {
2959 Some(label) => self
2960 .nodes_by_label
2961 .get(label)
2962 .into_iter()
2963 .flat_map(|ids| ids.iter())
2964 .filter_map(|id| {
2965 (self
2966 .node_at(id)?
2967 .properties()
2968 .get(key)
2969 .is_some_and(|v| v == *value))
2970 .then_some(id)
2971 })
2972 .collect(),
2973 None => self
2974 .iter_nodes()
2975 .filter_map(|(id, node)| {
2976 (node.properties().get(key).is_some_and(|v| v == *value)).then_some(id)
2977 })
2978 .collect(),
2979 }
2980 }
2981
2982 pub(super) fn any_node_by_property(
2983 &self,
2984 label: &str,
2985 key: &str,
2986 value: &PropertyValue,
2987 ) -> bool {
2988 self.nodes_by_label
2989 .get(label)
2990 .into_iter()
2991 .flat_map(|ids| ids.iter())
2992 .filter_map(|id| self.node_at(id))
2993 .any(|node| node.properties().get(key).is_some_and(|v| v == *value))
2994 }
2995
2996 pub(super) fn scan_relationships_by_property(
2997 &self,
2998 rel_type: Option<&str>,
2999 key: &str,
3000 value: &PropertyValue,
3001 ) -> Vec<RelationshipRecord> {
3002 match rel_type {
3003 Some(rel_type) => self
3004 .relationships_by_type
3005 .get(rel_type)
3006 .into_iter()
3007 .flat_map(|ids| ids.iter())
3008 .filter_map(|id| self.rel_at(id))
3009 .filter(|rel| rel.properties().get(key).is_some_and(|v| v == *value))
3010 .map(|rel| rel.to_record())
3011 .collect(),
3012 None => self
3013 .iter_rel_refs()
3014 .filter(|rel| rel.properties().get(key).is_some_and(|v| v == *value))
3015 .map(|rel| rel.to_record())
3016 .collect(),
3017 }
3018 }
3019
3020 pub(super) fn scan_relationship_ids_by_property(
3021 &self,
3022 rel_type: Option<&str>,
3023 key: &str,
3024 value: &PropertyValue,
3025 ) -> Vec<RelationshipId> {
3026 match rel_type {
3027 Some(rel_type) => self
3028 .relationships_by_type
3029 .get(rel_type)
3030 .into_iter()
3031 .flat_map(|ids| ids.iter())
3032 .filter_map(|id| {
3033 (self
3034 .rel_at(id)?
3035 .properties()
3036 .get(key)
3037 .is_some_and(|v| v == *value))
3038 .then_some(id)
3039 })
3040 .collect(),
3041 None => self
3042 .iter_rels()
3043 .filter_map(|(id, rel)| {
3044 (rel.properties().get(key).is_some_and(|v| v == *value)).then_some(id)
3045 })
3046 .collect(),
3047 }
3048 }
3049
3050 pub(super) fn any_relationship_by_property(
3051 &self,
3052 rel_type: &str,
3053 key: &str,
3054 value: &PropertyValue,
3055 ) -> bool {
3056 self.relationships_by_type
3057 .get(rel_type)
3058 .into_iter()
3059 .flat_map(|ids| ids.iter())
3060 .filter_map(|id| self.rel_at(id))
3061 .any(|rel| rel.properties().get(key).is_some_and(|v| v == *value))
3062 }
3063
3064 pub(super) fn attach_relationship(&mut self, rel: &RelationshipRecord) {
3065 let type_id = self.dicts.types.id_or_insert(&rel.rel_type);
3066 self.adjacency_push(
3067 rel.src,
3068 Side::Out,
3069 AdjEntry {
3070 type_id,
3071 neighbour: rel.dst,
3072 rel: rel.id,
3073 },
3074 );
3075 self.adjacency_push(
3076 rel.dst,
3077 Side::In,
3078 AdjEntry {
3079 type_id,
3080 neighbour: rel.src,
3081 rel: rel.id,
3082 },
3083 );
3084 self.insert_relationship_type_index(rel.id, &rel.rel_type);
3085 }
3086
3087 fn detach_relationship_indexes(
3088 &mut self,
3089 rel: &RelationshipRecord,
3090 mut pending: Option<&mut PendingAdjRemovals>,
3091 ) {
3092 self.adjacency_remove(rel.src, Side::Out, rel.id, pending.as_deref_mut());
3093 self.adjacency_remove(rel.dst, Side::In, rel.id, pending);
3094
3095 self.remove_relationship_type_index(rel.id, &rel.rel_type);
3096 }
3097
3098 pub(super) fn relationship_ids_for_direction(
3099 &self,
3100 node_id: NodeId,
3101 direction: Direction,
3102 ) -> Vec<RelationshipId> {
3103 let mut ids = Vec::new();
3104 let _ = self.try_for_each_adjacent_id_unchecked(node_id, direction, &[], |rel_id, _| {
3105 ids.push(rel_id);
3106 Ok::<(), ()>(())
3107 });
3108 ids
3109 }
3110
3111 pub(super) fn has_incident_relationships(&self, node_id: NodeId) -> bool {
3112 self.outgoing_at(node_id).is_some_and(|adj| !adj.is_empty())
3113 || self.incoming_at(node_id).is_some_and(|adj| !adj.is_empty())
3114 }
3115
3116 pub(super) fn incident_relationship_ids(&self, node_id: NodeId) -> Vec<RelationshipId> {
3117 self.relationship_ids_for_direction(node_id, Direction::Undirected)
3118 }
3119
3120 #[doc(hidden)]
3124 pub fn replay_create_node(
3125 &mut self,
3126 id: NodeId,
3127 labels: Vec<String>,
3128 properties: Properties,
3129 ) -> Result<NodeRecord, String> {
3130 if self.recorder.is_some() {
3131 return Err(
3132 "cannot replay node creation while a mutation recorder is installed".into(),
3133 );
3134 }
3135 if self.has_node_at(id) {
3136 return Err(format!("node id {id} already exists"));
3137 }
3138 let idx = self.ensure_node_slot_checked(id)?;
3139 self.bump_next_node_id_past(id)?;
3140
3141 let labels = Self::normalize_labels(labels);
3142 let node = NodeRecord {
3143 id,
3144 labels,
3145 properties,
3146 };
3147
3148 self.put_node_at_slot(idx, &node);
3149 self.on_node_created(&node);
3155
3156 Ok(node)
3157 }
3158
3159 #[doc(hidden)]
3163 pub fn replay_create_relationship(
3164 &mut self,
3165 id: RelationshipId,
3166 src: NodeId,
3167 dst: NodeId,
3168 rel_type: &str,
3169 properties: Properties,
3170 ) -> Result<RelationshipRecord, String> {
3171 if self.recorder.is_some() {
3172 return Err(
3173 "cannot replay relationship creation while a mutation recorder is installed".into(),
3174 );
3175 }
3176 if self.has_rel_at(id) {
3177 return Err(format!("relationship id {id} already exists"));
3178 }
3179 if !self.has_node_at(src) {
3180 return Err(format!(
3181 "relationship {id} references missing source node {src}"
3182 ));
3183 }
3184 if !self.has_node_at(dst) {
3185 return Err(format!(
3186 "relationship {id} references missing target node {dst}"
3187 ));
3188 }
3189
3190 let trimmed = rel_type.trim();
3191 if trimmed.is_empty() {
3192 return Err(format!("relationship {id} has an empty type"));
3193 }
3194 let idx = self.ensure_rel_slot_checked(id)?;
3195 self.bump_next_rel_id_past(id)?;
3196
3197 let rel = RelationshipRecord {
3198 id,
3199 src,
3200 dst,
3201 rel_type: trimmed.into(),
3202 properties,
3203 };
3204
3205 self.put_rel_at_slot(idx, &rel);
3206 self.on_relationship_created(&rel);
3208
3209 Ok(rel)
3210 }
3211
3212 #[cfg(test)]
3213 pub(super) fn assert_property_indexes_match_scan(&self) {
3214 let indexes = self.indexes_read();
3215 assert_eq!(
3216 indexes.node_properties.active_keys.len(),
3217 self.active_node_property_index_count(),
3218 "node property index counter diverged from active key set"
3219 );
3220 assert_eq!(
3221 indexes.relationship_properties.active_keys.len(),
3222 self.active_relationship_property_index_count(),
3223 "relationship property index counter diverged from active key set"
3224 );
3225
3226 let mut expected_nodes = PropertyIndexState {
3227 active_keys: indexes.node_properties.active_keys.clone(),
3228 any_scope_keys: indexes.node_properties.any_scope_keys.clone(),
3229 ..PropertyIndexState::default()
3230 };
3231 for (id, node) in self.iter_nodes() {
3232 for (key, value) in node.properties() {
3233 if expected_nodes.is_active(key) {
3234 expected_nodes.insert_with_scopes(
3235 id,
3236 node.labels().strs(),
3237 key,
3238 &value.to_owned(),
3239 &self.node_values(),
3240 );
3241 }
3242 }
3243 }
3244 assert_eq!(
3245 indexes.node_properties.scoped_values, expected_nodes.scoped_values,
3246 "node property scoped index values diverged from scan"
3247 );
3248 assert_eq!(
3249 indexes.node_properties.any_scope, expected_nodes.any_scope,
3250 "node property across-scopes index values diverged from scan"
3251 );
3252
3253 let mut expected_relationships = PropertyIndexState {
3254 active_keys: indexes.relationship_properties.active_keys.clone(),
3255 any_scope_keys: indexes.relationship_properties.any_scope_keys.clone(),
3256 ..PropertyIndexState::default()
3257 };
3258 for (id, rel) in self.iter_rels() {
3259 for (key, value) in rel.properties() {
3260 if expected_relationships.is_active(key) {
3261 expected_relationships.insert_with_scopes(
3262 id,
3263 [rel.rel_type()],
3264 key,
3265 &value.to_owned(),
3266 &self.relationship_values(),
3267 );
3268 }
3269 }
3270 }
3271 assert_eq!(
3272 indexes.relationship_properties.scoped_values, expected_relationships.scoped_values,
3273 "relationship property scoped index values diverged from scan"
3274 );
3275 assert_eq!(
3276 indexes.relationship_properties.any_scope, expected_relationships.any_scope,
3277 "relationship property across-scopes index values diverged from scan"
3278 );
3279 }
3280}
3281
3282pub(super) struct RecordValues<'g> {
3285 slab: &'g RecordSlab,
3286 dicts: &'g Dicts,
3287 nodes: bool,
3288}
3289
3290impl<'g> RecordValues<'g> {
3291 pub(super) fn nodes(slab: &'g RecordSlab, dicts: &'g Dicts) -> Self {
3292 Self {
3293 slab,
3294 dicts,
3295 nodes: true,
3296 }
3297 }
3298
3299 pub(super) fn relationships(slab: &'g RecordSlab, dicts: &'g Dicts) -> Self {
3300 Self {
3301 slab,
3302 dicts,
3303 nodes: false,
3304 }
3305 }
3306}
3307
3308impl EntityValues for RecordValues<'_> {
3309 #[inline]
3310 fn get(&self, id: u64, key: &str) -> Option<crate::ValueRef<'_>> {
3311 let bytes = self.slab.get(usize::try_from(id).ok()?)?;
3312 if self.nodes {
3313 StoredNode::new(id, bytes, self.dicts).properties().get(key)
3314 } else {
3315 StoredRel::new(id, bytes, self.dicts).properties().get(key)
3316 }
3317 }
3318}
3319
3320#[inline]
3324fn edit_record_property(
3325 slab: &mut RecordSlab,
3326 idx: usize,
3327 kind: encoded::RecordKind,
3328 dicts: &mut Dicts,
3329 key: &str,
3330 edit: encoded::PropEdit<'_>,
3331) -> Option<Option<PropertyValue>> {
3332 let bytes = slab.get(idx)?;
3333 let mut out = Scratch::take();
3334 let old = encoded::edit_property(bytes, kind, dicts, key, edit, &mut out)?;
3335 slab.set(idx, &out);
3336 Some(old)
3337}
3338
3339pub(super) const SORTED_OPTION: &str = "sorted";
3342
3343pub(super) fn is_sorted(def: &IndexDefinition) -> bool {
3345 !matches!(
3346 def.options.get(SORTED_OPTION),
3347 Some(IndexConfigValue::Bool(false))
3348 )
3349}
3350
3351const PARALLEL_INDEX_MIN: usize = 1 << 17;
3354
3355#[derive(Default)]
3358pub(super) struct DeferredData {
3359 pub(super) populate: Vec<IndexDefinition>,
3361 pub(super) validate: Vec<super::ConstraintDefinition>,
3363}