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