1use std::collections::{BTreeMap, HashMap, HashSet};
32use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
33
34use super::entity::{CrossRef, EntityData, EntityId, EntityKind, RefType, UnifiedEntity};
35use super::metadata::{Metadata, MetadataFilter, MetadataStorage};
36use super::segment_graph_index::SegmentGraphIndex;
37use super::segment_key_lookup::SegmentKeyLookup;
38use super::segment_unique_key::SegmentUniqueKey;
39use reddb_types::value_compare::partial_compare_values;
40use reddb_types::{value_to_canonical_key, CanonicalKey, Value};
41
42pub type SegmentId = u64;
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum SegmentState {
48 Growing,
50 Sealing,
52 Sealed,
54 Flushed,
56 Archived,
58}
59
60impl SegmentState {
61 pub fn is_writable(&self) -> bool {
63 matches!(self, Self::Growing)
64 }
65
66 pub fn is_queryable(&self) -> bool {
68 !matches!(self, Self::Sealing)
69 }
70
71 pub fn is_immutable(&self) -> bool {
73 matches!(self, Self::Sealed | Self::Flushed | Self::Archived)
74 }
75}
76
77#[derive(Debug, Clone)]
79pub struct SegmentConfig {
80 pub max_entities: usize,
82 pub max_bytes: usize,
84 pub max_age_secs: u64,
86 pub build_vector_index: bool,
88 pub build_graph_index: bool,
90 pub compression_level: u8,
92}
93
94impl Default for SegmentConfig {
95 fn default() -> Self {
96 Self {
97 max_entities: 100_000,
98 max_bytes: 256 * 1024 * 1024, max_age_secs: 3600, build_vector_index: true,
101 build_graph_index: true,
102 compression_level: 6,
103 }
104 }
105}
106
107#[derive(Debug, Clone, Default)]
109pub struct SegmentStats {
110 pub entity_count: usize,
112 pub deleted_count: usize,
114 pub memory_bytes: usize,
116 pub vector_count: usize,
118 pub node_count: usize,
120 pub edge_count: usize,
122 pub row_count: usize,
124 pub cross_ref_count: usize,
126}
127
128#[derive(Debug, Clone)]
130pub enum SegmentError {
131 NotWritable,
133 NotFound(EntityId),
135 AlreadyExists(EntityId),
137 Full,
139 InvalidState(SegmentState),
141 Internal(String),
143}
144
145impl std::fmt::Display for SegmentError {
146 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147 match self {
148 Self::NotWritable => write!(f, "segment is not writable"),
149 Self::NotFound(id) => write!(f, "entity not found: {}", id),
150 Self::AlreadyExists(id) => write!(f, "entity already exists: {}", id),
151 Self::Full => write!(f, "segment is full"),
152 Self::InvalidState(state) => write!(f, "invalid operation for state: {:?}", state),
153 Self::Internal(msg) => write!(f, "internal error: {}", msg),
154 }
155 }
156}
157
158impl std::error::Error for SegmentError {}
159
160use crate::clock::current_unix_secs;
161
162fn entity_id_from_probe_key(key: &[u8]) -> Option<EntityId> {
163 if let Ok(text) = std::str::from_utf8(key) {
164 if let Ok(raw) = text.parse::<u64>() {
165 return Some(EntityId::new(raw));
166 }
167 }
168
169 let bytes: [u8; 8] = key.try_into().ok()?;
170 Some(EntityId::new(u64::from_le_bytes(bytes)))
171}
172
173const SEALED_MULTI_ZONE_MAX_INTERVALS: usize = 4;
174
175const TOMBSTONE_ENTRY_BYTES: u64 = 24;
183
184#[derive(Debug, Clone)]
185struct UpdateIndexSnapshot {
186 pk_column_name: Option<String>,
187 pk_value: Option<Value>,
188 pk_index_key: Option<(String, String)>,
189 cross_refs: Vec<CrossRef>,
190}
191
192impl UpdateIndexSnapshot {
193 fn from_entity(entity: &UnifiedEntity) -> Self {
194 let (pk_column_name, pk_value) = match &entity.data {
195 EntityData::Row(row) => (
196 row.schema
197 .as_deref()
198 .and_then(|schema| schema.first().cloned()),
199 row.columns.first().cloned(),
200 ),
201 _ => (None, None),
202 };
203 let pk_index_key = pk_value
204 .as_ref()
205 .map(|value| (entity.kind.collection().to_string(), format!("{:?}", value)));
206 Self {
207 pk_column_name,
208 pk_value,
209 pk_index_key,
210 cross_refs: entity.cross_refs().to_vec(),
211 }
212 }
213}
214
215pub trait UnifiedSegment: Send + Sync {
217 fn id(&self) -> SegmentId;
219
220 fn state(&self) -> SegmentState;
222
223 fn collection(&self) -> &str;
225
226 fn stats(&self) -> SegmentStats;
228
229 fn entity_count(&self) -> usize;
231
232 fn contains(&self, id: EntityId) -> bool;
234
235 fn get(&self, id: EntityId) -> Option<&UnifiedEntity>;
237
238 fn get_mut(&mut self, id: EntityId) -> Option<&mut UnifiedEntity>;
240
241 fn insert(&mut self, entity: UnifiedEntity) -> Result<EntityId, SegmentError>;
243
244 fn update(&mut self, entity: UnifiedEntity) -> Result<(), SegmentError>;
246
247 fn update_hot(
251 &mut self,
252 entity: UnifiedEntity,
253 modified_columns: &[String],
254 ) -> Result<(), SegmentError> {
255 let _ = modified_columns;
256 self.update(entity)
257 }
258
259 fn delete(&mut self, id: EntityId) -> Result<bool, SegmentError>;
261
262 fn get_metadata(&self, id: EntityId) -> Option<Metadata>;
264
265 fn set_metadata(&mut self, id: EntityId, metadata: Metadata) -> Result<(), SegmentError>;
267
268 fn seal(&mut self) -> Result<(), SegmentError>;
270
271 fn should_seal(&self, config: &SegmentConfig) -> bool;
273
274 fn iter(&self) -> Box<dyn Iterator<Item = &UnifiedEntity> + '_>;
276
277 fn iter_kind(&self, kind_filter: &str) -> Box<dyn Iterator<Item = &UnifiedEntity> + '_>;
279
280 fn filter_metadata(
282 &self,
283 filters: &[(String, super::metadata::MetadataFilter)],
284 ) -> Vec<EntityId>;
285}
286
287#[derive(Debug, Clone)]
294pub struct ColZone {
295 pub min: Value,
296 pub max: Value,
297 min_key: Option<CanonicalKey>,
298 max_key: Option<CanonicalKey>,
299}
300
301impl ColZone {
302 fn payload_bytes(&self) -> usize {
303 super::memory_size::value_heap_bytes(&self.min)
304 .saturating_add(super::memory_size::value_heap_bytes(&self.max))
305 .saturating_add(
306 self.min_key
307 .as_ref()
308 .map_or(0, super::memory_size::canonical_heap_bytes),
309 )
310 .saturating_add(
311 self.max_key
312 .as_ref()
313 .map_or(0, super::memory_size::canonical_heap_bytes),
314 )
315 }
316
317 fn new(v: Value) -> Self {
318 Self {
319 min_key: value_to_canonical_key(&v),
320 max_key: value_to_canonical_key(&v),
321 min: v.clone(),
322 max: v,
323 }
324 }
325
326 fn with_bounds(min: Value, max: Value) -> Self {
327 Self {
328 min_key: value_to_canonical_key(&min),
329 max_key: value_to_canonical_key(&max),
330 min,
331 max,
332 }
333 }
334
335 fn update(&mut self, v: &Value) {
336 if compare_zone_values(v, None, &self.min, self.min_key.as_ref())
337 .map(|o| o == std::cmp::Ordering::Less)
338 .unwrap_or(false)
339 {
340 self.min = v.clone();
341 self.min_key = value_to_canonical_key(v);
342 }
343 if compare_zone_values(v, None, &self.max, self.max_key.as_ref())
344 .map(|o| o == std::cmp::Ordering::Greater)
345 .unwrap_or(false)
346 {
347 self.max = v.clone();
348 self.max_key = value_to_canonical_key(v);
349 }
350 }
351}
352
353#[derive(Debug, Clone, Default)]
354pub struct MultiColZone {
355 pub intervals: Vec<ColZone>,
356}
357
358impl MultiColZone {
359 fn can_skip(&self, pred: &ZoneColPred<'_>) -> bool {
360 !self.intervals.is_empty() && self.intervals.iter().all(|zone| pred.can_skip(zone))
361 }
362}
363
364fn compare_zone_values(
365 left: &Value,
366 left_key: Option<&CanonicalKey>,
367 right: &Value,
368 right_key: Option<&CanonicalKey>,
369) -> Option<std::cmp::Ordering> {
370 partial_compare_values(left, right).or_else(|| {
371 let left_key = left_key.cloned().or_else(|| value_to_canonical_key(left))?;
372 let right_key = right_key
373 .cloned()
374 .or_else(|| value_to_canonical_key(right))?;
375 (left_key.family() == right_key.family()).then(|| left_key.cmp(&right_key))
376 })
377}
378
379#[derive(Debug, Clone, Copy, PartialEq, Eq)]
381pub enum ZoneColPredKind {
382 Eq,
383 Gt,
384 Gte,
385 Lt,
386 Lte,
387}
388
389#[derive(Debug, Clone)]
391pub enum ZoneColPred<'a> {
392 Eq(&'a Value),
393 Gt(&'a Value),
394 Gte(&'a Value),
395 Lt(&'a Value),
396 Lte(&'a Value),
397}
398
399impl<'a> ZoneColPred<'a> {
400 pub fn can_skip(&self, zone: &ColZone) -> bool {
402 match self {
403 ZoneColPred::Eq(val) => {
405 compare_zone_values(val, None, &zone.min, zone.min_key.as_ref())
406 .map(|o| o == std::cmp::Ordering::Less)
407 .unwrap_or(false)
408 || compare_zone_values(val, None, &zone.max, zone.max_key.as_ref())
409 .map(|o| o == std::cmp::Ordering::Greater)
410 .unwrap_or(false)
411 }
412 ZoneColPred::Gt(val) => {
414 compare_zone_values(&zone.max, zone.max_key.as_ref(), val, None)
415 .map(|o| o != std::cmp::Ordering::Greater)
416 .unwrap_or(false)
417 }
418 ZoneColPred::Gte(val) => {
420 compare_zone_values(&zone.max, zone.max_key.as_ref(), val, None)
421 .map(|o| o == std::cmp::Ordering::Less)
422 .unwrap_or(false)
423 }
424 ZoneColPred::Lt(val) => {
426 compare_zone_values(&zone.min, zone.min_key.as_ref(), val, None)
427 .map(|o| o != std::cmp::Ordering::Less)
428 .unwrap_or(false)
429 }
430 ZoneColPred::Lte(val) => {
432 compare_zone_values(&zone.min, zone.min_key.as_ref(), val, None)
433 .map(|o| o == std::cmp::Ordering::Greater)
434 .unwrap_or(false)
435 }
436 }
437 }
438}
439
440#[derive(Clone, Copy)]
442pub enum GraphEntityKind {
443 Node,
444 Edge,
445}
446
447impl GraphEntityKind {
448 fn mask(self) -> u8 {
449 match self {
450 Self::Node => 1,
451 Self::Edge => 2,
452 }
453 }
454
455 pub fn matches(self, kind: &EntityKind) -> bool {
456 matches!(
457 (self, kind),
458 (Self::Node, EntityKind::GraphNode(_)) | (Self::Edge, EntityKind::GraphEdge(_))
459 )
460 }
461}
462
463pub const SCAN_BATCH_SIZE: usize = 256;
465
466pub const CHECKPOINT_MUTATIONS_MAX: usize = 8192;
468
469pub struct SegmentScanCursor {
471 flat_end: usize,
472 hash_end: usize,
473 deleted_end: usize,
474 position: usize,
475}
476
477impl SegmentScanCursor {
478 pub fn remaining(&self) -> usize {
479 self.flat_end + self.hash_end - self.position
480 }
481
482 pub fn finished(&self) -> bool {
483 self.remaining() == 0
484 }
485}
486
487pub struct GraphIndexCursor {
490 key_index: usize,
491 after: Option<EntityId>,
492 upper: EntityId,
493}
494
495impl GraphIndexCursor {
496 pub fn finished(&self, keys: &[EntityId]) -> bool {
497 self.key_index == keys.len()
498 }
499}
500
501pub struct GrowingSegment {
503 id: SegmentId,
505 collection: String,
507 state: SegmentState,
509 created_at: u64,
511 last_write_at: u64,
513
514 entities: HashMap<EntityId, UnifiedEntity>,
516 hash_scan_ids: Vec<EntityId>,
519 flat_entities: Vec<UnifiedEntity>,
522 base_entity_id: u64,
524 use_flat: bool,
526 deleted: HashMap<EntityId, usize>,
530 metadata: MetadataStorage,
532
533 pk_index: BTreeMap<(String, String), EntityId>,
535 unique_key_lookup: parking_lot::RwLock<Vec<SegmentUniqueKey>>,
538 unique_key_lookup_bytes: AtomicU64,
539 key_lookup: Option<SegmentKeyLookup>,
540 kind_index: HashMap<String, HashSet<EntityId>>,
542 graph_kind_mask: u8,
545 graph_read_index: parking_lot::RwLock<Option<SegmentGraphIndex>>,
547 graph_read_index_bytes: AtomicU64,
548 cross_ref_forward: HashMap<EntityId, Vec<(EntityId, RefType)>>,
550 cross_ref_reverse: HashMap<EntityId, Vec<(EntityId, RefType)>>,
552
553 col_zones: HashMap<String, ColZone>,
555 zone_payload_bytes: usize,
556 sealed_col_zones: HashMap<String, MultiColZone>,
558
559 sequence: AtomicU64,
561 checkpoint_revision: u64,
563 checkpoint_mutations: std::collections::VecDeque<(u64, EntityId)>,
564 checkpoint_latest_mutations: HashMap<EntityId, u64>,
567 checkpoint_mutation_floor: u64,
568 memory_bytes: AtomicU64,
573 dead_entity_bytes: u64,
576 dead_resident_count: usize,
578
579 published_flat_len: AtomicUsize,
586}
587
588impl GrowingSegment {
589 pub fn may_contain_graph_kind(&self, kind: GraphEntityKind) -> bool {
590 self.graph_kind_mask & kind.mask() != 0
591 }
592
593 fn observe_graph_kind(&mut self, kind: &EntityKind) {
594 self.graph_kind_mask |= match kind {
595 EntityKind::GraphNode(_) => GraphEntityKind::Node.mask(),
596 EntityKind::GraphEdge(_) => GraphEntityKind::Edge.mask(),
597 _ => 0,
598 };
599 }
600
601 pub fn scan_cursor(&self) -> SegmentScanCursor {
602 SegmentScanCursor {
603 flat_end: self.flat_entities.len(),
604 hash_end: self.hash_scan_ids.len(),
605 deleted_end: self.deleted.len(),
606 position: 0,
607 }
608 }
609
610 pub fn scan_batch(
614 &self,
615 cursor: &mut SegmentScanCursor,
616 before_work: &mut impl FnMut() -> bool,
617 visit: &mut impl FnMut(EntityId),
618 ) -> bool {
619 for _ in 0..SCAN_BATCH_SIZE {
620 if cursor.finished() {
621 break;
622 }
623 if !before_work() {
624 return false;
625 }
626 let id = if cursor.position < cursor.flat_end {
627 self.flat_entities[cursor.position].id
628 } else {
629 self.hash_scan_ids[cursor.position - cursor.flat_end]
630 };
631 cursor.position += 1;
632 let eligible = match self.deleted.get(&id) {
633 Some(&ordinal) => ordinal >= cursor.deleted_end,
634 None => self.get(id).is_some(),
637 };
638 if eligible {
639 visit(id);
640 }
641 }
642 true
643 }
644
645 #[inline]
648 pub fn for_each_fast<F>(&self, mut f: F) -> bool
649 where
650 F: FnMut(&UnifiedEntity) -> bool,
651 {
652 if self.use_flat {
653 if self.deleted.is_empty() {
655 for entity in &self.flat_entities {
656 if !f(entity) {
657 return false;
658 }
659 }
660 for entity in self.entities.values() {
663 if !f(entity) {
664 return false;
665 }
666 }
667 } else {
668 for entity in &self.flat_entities {
669 if self.deleted.contains_key(&entity.id) {
670 continue;
671 }
672 if !f(entity) {
673 return false;
674 }
675 }
676 for entity in self.entities.values() {
677 if self.deleted.contains_key(&entity.id) {
678 continue;
679 }
680 if !f(entity) {
681 return false;
682 }
683 }
684 }
685 } else {
686 if self.deleted.is_empty() {
688 for entity in self.entities.values() {
689 if !f(entity) {
690 return false;
691 }
692 }
693 } else {
694 for entity in self.entities.values() {
695 if self.deleted.contains_key(&entity.id) {
696 continue;
697 }
698 if !f(entity) {
699 return false;
700 }
701 }
702 }
703 }
704 true
705 }
706
707 pub fn new(id: SegmentId, collection: impl Into<String>) -> Self {
709 let now = current_unix_secs();
710
711 Self {
712 id,
713 collection: collection.into(),
714 state: SegmentState::Growing,
715 created_at: now,
716 last_write_at: now,
717 entities: HashMap::new(),
718 hash_scan_ids: Vec::new(),
719 flat_entities: Vec::new(),
720 base_entity_id: 0,
721 use_flat: false,
722 deleted: HashMap::new(),
723 metadata: MetadataStorage::new(),
724 pk_index: BTreeMap::new(),
725 unique_key_lookup: parking_lot::RwLock::new(Vec::new()),
726 unique_key_lookup_bytes: AtomicU64::new(0),
727 key_lookup: Some(SegmentKeyLookup::default()),
728 kind_index: HashMap::new(),
729 graph_read_index: parking_lot::RwLock::new(Some(SegmentGraphIndex::default())),
730 graph_read_index_bytes: AtomicU64::new(0),
731 graph_kind_mask: 0,
732 cross_ref_forward: HashMap::new(),
733 cross_ref_reverse: HashMap::new(),
734 col_zones: HashMap::new(),
735 zone_payload_bytes: 0,
736 sealed_col_zones: HashMap::new(),
737 sequence: AtomicU64::new(0),
738 checkpoint_revision: 0,
739 checkpoint_mutations: std::collections::VecDeque::new(),
740 checkpoint_latest_mutations: HashMap::new(),
741 checkpoint_mutation_floor: 0,
742 memory_bytes: AtomicU64::new(0),
743 dead_entity_bytes: 0,
744 dead_resident_count: 0,
745 published_flat_len: AtomicUsize::new(0),
746 }
747 }
748
749 fn next_sequence(&self) -> u64 {
751 self.sequence.fetch_add(1, Ordering::SeqCst)
752 }
753
754 pub fn checkpoint_revision(&self) -> u64 {
755 self.checkpoint_revision
756 }
757
758 fn mark_checkpoint_entity_dirty(&mut self, id: EntityId) {
759 self.checkpoint_revision = self
760 .checkpoint_revision
761 .checked_add(1)
762 .expect("segment mutation counter must not overflow");
763 if !self.checkpoint_latest_mutations.contains_key(&id)
764 && self.checkpoint_latest_mutations.len() == CHECKPOINT_MUTATIONS_MAX
765 {
766 while let Some((revision, previous)) = self.checkpoint_mutations.pop_front() {
769 if self.checkpoint_latest_mutations.get(&previous) == Some(&revision) {
770 self.checkpoint_latest_mutations.remove(&previous);
771 self.checkpoint_mutation_floor = self.checkpoint_mutation_floor.max(revision);
772 break;
773 }
774 }
775 }
776 self.checkpoint_latest_mutations
777 .insert(id, self.checkpoint_revision);
778 if self.checkpoint_mutations.len() == 2 * CHECKPOINT_MUTATIONS_MAX {
779 self.checkpoint_mutations.retain(|(revision, previous)| {
782 self.checkpoint_latest_mutations.get(previous) == Some(revision)
783 });
784 }
785 self.checkpoint_mutations
786 .push_back((self.checkpoint_revision, id));
787 }
788
789 pub fn checkpoint_mutations_since(&self, revision: u64) -> Option<Vec<EntityId>> {
790 if revision == self.checkpoint_revision {
791 return Some(Vec::new());
792 }
793 if revision < self.checkpoint_mutation_floor || revision > self.checkpoint_revision {
794 return None;
795 }
796 let mut ids = Vec::new();
797 for &(sequence, id) in &self.checkpoint_mutations {
798 if sequence > revision && self.checkpoint_latest_mutations.get(&id) == Some(&sequence) {
799 ids.push(id);
800 }
801 }
802 Some(ids)
803 }
804
805 pub fn checkpoint_tracking_bytes(&self) -> usize {
806 self.checkpoint_mutations.capacity() * std::mem::size_of::<(u64, EntityId)>()
807 + self.checkpoint_latest_mutations.capacity()
809 * (std::mem::size_of::<(EntityId, u64)>() + 16)
810 }
811
812 fn has_live_entity(&self, id: EntityId) -> bool {
813 if self.deleted.contains_key(&id) {
814 return false;
815 }
816 if self.use_flat {
817 let raw = id.raw();
818 if raw >= self.base_entity_id {
819 let idx = (raw - self.base_entity_id) as usize;
820 if self
821 .flat_entities
822 .get(idx)
823 .is_some_and(|entity| entity.id == id)
824 {
825 return true;
826 }
827 }
828 self.entities.contains_key(&id)
831 } else {
832 self.entities.contains_key(&id)
833 }
834 }
835
836 fn update_existing_entity_in_place(
837 &mut self,
838 entity: &UnifiedEntity,
839 ) -> Result<UpdateIndexSnapshot, SegmentError> {
840 self.mark_checkpoint_entity_dirty(entity.id);
841 self.invalidate_unique_key_lookup();
842 if self
843 .get(entity.id)
844 .is_some_and(|old| SegmentKeyLookup::key(old) != SegmentKeyLookup::key(entity))
845 {
846 if let Some(mut index) = self.key_lookup.take() {
847 index.remove(
848 self.get(entity.id)
849 .expect("invariant: existing replacement row"),
850 );
851 index.insert(entity);
852 self.key_lookup = Some(index);
853 }
854 }
855 if (self.graph_kind_mask != 0
856 || matches!(
857 entity.kind,
858 EntityKind::GraphNode(_) | EntityKind::GraphEdge(_)
859 ))
860 && self
861 .get(entity.id)
862 .is_some_and(|old| !SegmentGraphIndex::same_keys(old, entity))
863 {
864 self.invalidate_graph_read_index();
865 }
866 self.observe_graph_kind(&entity.kind);
869 if self.use_flat {
870 let raw = entity.id.raw();
871 if raw >= self.base_entity_id {
872 let idx = (raw - self.base_entity_id) as usize;
873 if let Some(slot) = self
874 .flat_entities
875 .get_mut(idx)
876 .filter(|slot| slot.id == entity.id)
877 {
878 let snapshot = UpdateIndexSnapshot::from_entity(slot);
879 let old_bytes = Self::estimate_entity_size(slot);
880 slot.clone_from(entity);
881 self.replace_entity_memory(old_bytes, Self::estimate_entity_size(entity));
882 return Ok(snapshot);
883 }
884 }
885 let Some(slot) = self.entities.get_mut(&entity.id) else {
886 return Err(SegmentError::NotFound(entity.id));
887 };
888 let snapshot = UpdateIndexSnapshot::from_entity(slot);
889 let old_bytes = Self::estimate_entity_size(slot);
890 slot.clone_from(entity);
891 self.replace_entity_memory(old_bytes, Self::estimate_entity_size(entity));
892 Ok(snapshot)
893 } else {
894 let Some(slot) = self.entities.get_mut(&entity.id) else {
895 return Err(SegmentError::NotFound(entity.id));
896 };
897 let snapshot = UpdateIndexSnapshot::from_entity(slot);
898 let old_bytes = Self::estimate_entity_size(slot);
899 slot.clone_from(entity);
900 self.replace_entity_memory(old_bytes, Self::estimate_entity_size(entity));
901 Ok(snapshot)
902 }
903 }
904
905 fn apply_hot_update_with_metadata(
906 &mut self,
907 entity: &UnifiedEntity,
908 modified_columns: &[String],
909 metadata: Option<&Metadata>,
910 ) -> Result<(), SegmentError> {
911 let old = self.update_existing_entity_in_place(entity)?;
912 self.reindex_for_update(&old, entity, Some(modified_columns));
913 self.update_col_zones_from_entity(entity);
914 if let Some(metadata) = metadata {
915 self.metadata.set_all(entity.id, metadata);
916 }
917 Ok(())
918 }
919
920 fn apply_update_with_metadata(
921 &mut self,
922 entity: &UnifiedEntity,
923 metadata: Option<&Metadata>,
924 ) -> Result<(), SegmentError> {
925 let old = self.update_existing_entity_in_place(entity)?;
926 self.reindex_for_update(&old, entity, None);
927 self.update_col_zones_from_entity(entity);
928 if let Some(metadata) = metadata {
929 self.metadata.set_all(entity.id, metadata);
930 }
931 Ok(())
932 }
933
934 pub fn update_hot_batch_with_metadata<'a, I>(&mut self, items: I) -> Result<(), SegmentError>
935 where
936 I: IntoIterator<Item = (&'a UnifiedEntity, &'a [String], Option<&'a Metadata>)>,
937 {
938 if !self.state.is_writable() {
939 return Err(SegmentError::NotWritable);
940 }
941
942 let items: Vec<(&UnifiedEntity, &[String], Option<&Metadata>)> =
943 items.into_iter().collect();
944 if items.is_empty() {
945 return Ok(());
946 }
947
948 for (entity, _, _) in &items {
949 if !self.has_live_entity(entity.id) {
950 return Err(SegmentError::NotFound(entity.id));
951 }
952 }
953
954 for (entity, modified_columns, metadata) in items {
955 self.apply_hot_update_with_metadata(entity, modified_columns, metadata)?;
956 }
957
958 self.last_write_at = current_unix_secs();
959 Ok(())
960 }
961
962 fn record_tombstone(&mut self, id: EntityId) -> bool {
964 self.mark_checkpoint_entity_dirty(id);
965 let ordinal = self.deleted.len();
966 match self.deleted.entry(id) {
967 std::collections::hash_map::Entry::Vacant(entry) => {
968 entry.insert(ordinal);
969 true
970 }
971 std::collections::hash_map::Entry::Occupied(_) => false,
972 }
973 }
974
975 fn tombstone_flat_entity(&mut self, idx: usize, id: EntityId) -> bool {
981 self.invalidate_unique_key_lookup();
982 if !self.record_tombstone(id) {
983 return false;
984 }
985 if let Some(index) = self.key_lookup.as_mut() {
986 index.remove(&self.flat_entities[idx]);
987 }
988 if let Some(index) = self.graph_read_index.get_mut() {
989 index.remove(&self.flat_entities[idx]);
990 self.graph_read_index_bytes
991 .store(index.memory_bytes(), Ordering::Relaxed);
992 }
993 let size = Self::estimate_entity_size(&self.flat_entities[idx]) as u64;
994 self.dead_entity_bytes += size;
995 self.dead_resident_count += 1;
996 self.metadata.remove_all(id);
997 true
998 }
999
1000 fn remove_hashmap_entity(&mut self, id: EntityId) -> bool {
1005 self.invalidate_unique_key_lookup();
1006 let Some(entity) = self.entities.remove(&id) else {
1007 return false;
1008 };
1009 self.release_memory(Self::estimate_entity_size(&entity));
1010 self.unindex_entity(&entity);
1011 self.metadata.remove_all(id);
1012 self.record_tombstone(id);
1013 true
1014 }
1015
1016 pub fn delete_batch(&mut self, ids: &[EntityId]) -> Result<Vec<EntityId>, SegmentError> {
1017 if !self.state.is_writable() {
1018 return Err(SegmentError::NotWritable);
1019 }
1020 if ids.is_empty() {
1021 return Ok(Vec::new());
1022 }
1023
1024 let mut deleted_ids = Vec::with_capacity(ids.len());
1025
1026 if self.use_flat {
1027 for &id in ids {
1028 let raw = id.raw();
1029 if raw < self.base_entity_id {
1030 if self.remove_hashmap_entity(id) {
1031 deleted_ids.push(id);
1032 }
1033 continue;
1034 }
1035 let idx = (raw - self.base_entity_id) as usize;
1036 if idx < self.flat_entities.len() && self.flat_entities[idx].id == id {
1037 if self.tombstone_flat_entity(idx, id) {
1038 deleted_ids.push(id);
1039 }
1040 } else if self.remove_hashmap_entity(id) {
1041 deleted_ids.push(id);
1042 }
1043 }
1044 } else {
1045 for &id in ids {
1046 if self.remove_hashmap_entity(id) {
1047 deleted_ids.push(id);
1048 }
1049 }
1050 }
1051
1052 if !deleted_ids.is_empty() {
1053 self.last_write_at = current_unix_secs();
1054 }
1055
1056 Ok(deleted_ids)
1057 }
1058
1059 fn add_memory(&self, bytes: usize) {
1061 self.memory_bytes.fetch_add(bytes as u64, Ordering::Relaxed);
1062 }
1063
1064 fn replace_entity_memory(&self, previous: usize, current: usize) {
1065 if current >= previous {
1066 self.add_memory(current - previous);
1067 } else {
1068 self.release_memory(previous - current);
1069 }
1070 }
1071
1072 pub(crate) fn compact_recovered_rows(&mut self, schema: &std::sync::Arc<Vec<String>>) -> usize {
1075 let mut removed = 0usize;
1076 let mut added = 0usize;
1077 let mut compacted = 0;
1078 for entity in self
1079 .entities
1080 .values_mut()
1081 .chain(self.flat_entities.iter_mut())
1082 {
1083 let before = super::memory_size::entity_bytes(entity);
1084 if let EntityData::Row(row) = &mut entity.data {
1085 if row.compact_with_schema(schema) {
1086 removed = removed.saturating_add(before);
1087 added = added.saturating_add(super::memory_size::entity_bytes(entity));
1088 compacted += 1;
1089 }
1090 }
1091 }
1092 self.release_memory(removed);
1093 self.add_memory(added);
1094 compacted
1095 }
1096
1097 pub fn memory_bytes(&self) -> u64 {
1102 self.memory_bytes.load(Ordering::Relaxed)
1103 + self.zone_payload_bytes as u64
1104 + self.unique_key_lookup_bytes.load(Ordering::Relaxed)
1105 + self
1106 .key_lookup
1107 .as_ref()
1108 .map_or(0, SegmentKeyLookup::memory_bytes)
1109 + self.graph_read_index_bytes.load(Ordering::Relaxed)
1110 + (self.hash_scan_ids.capacity() * std::mem::size_of::<EntityId>()) as u64
1111 }
1112
1113 fn release_memory(&self, bytes: usize) {
1116 let mut current = self.memory_bytes.load(Ordering::Relaxed);
1117 loop {
1118 let next = current.saturating_sub(bytes as u64);
1119 match self.memory_bytes.compare_exchange_weak(
1120 current,
1121 next,
1122 Ordering::Relaxed,
1123 Ordering::Relaxed,
1124 ) {
1125 Ok(_) => break,
1126 Err(observed) => current = observed,
1127 }
1128 }
1129 }
1130
1131 pub fn resident_entity_count(&self) -> usize {
1133 self.flat_entities.len() + self.entities.len()
1134 }
1135
1136 pub fn live_entity_count(&self) -> usize {
1138 self.resident_entity_count()
1139 .saturating_sub(self.dead_resident_count)
1140 }
1141
1142 pub(crate) fn is_tombstoned(&self, id: EntityId) -> bool {
1143 self.deleted.contains_key(&id)
1144 }
1145
1146 pub fn tombstone_count(&self) -> usize {
1149 self.deleted.len()
1150 }
1151
1152 pub fn resident_bytes(&self) -> u64 {
1155 self.memory_bytes() + self.deleted.len() as u64 * TOMBSTONE_ENTRY_BYTES
1156 }
1157
1158 pub fn reclaimable_bytes(&self) -> u64 {
1161 self.dead_entity_bytes + self.deleted.len() as u64 * TOMBSTONE_ENTRY_BYTES
1162 }
1163
1164 pub fn live_entity_ids(&self) -> Vec<EntityId> {
1170 let mut ids = Vec::with_capacity(self.live_entity_count());
1171 self.for_each_fast(|entity| {
1172 ids.push(entity.id);
1173 true
1174 });
1175 ids
1176 }
1177
1178 pub fn insert_preserving_sequence(
1182 &mut self,
1183 entity: UnifiedEntity,
1184 ) -> Result<EntityId, SegmentError> {
1185 if !self.state.is_writable() {
1186 return Err(SegmentError::NotWritable);
1187 }
1188 if self.entities.contains_key(&entity.id) {
1189 return Err(SegmentError::AlreadyExists(entity.id));
1190 }
1191 let id = entity.id;
1192 self.adopt_entity(entity, None);
1193 self.last_write_at = current_unix_secs();
1194 Ok(id)
1195 }
1196
1197 pub fn adopt_entity(&mut self, entity: UnifiedEntity, metadata: Option<Metadata>) {
1205 let id = entity.id;
1206
1207 let next = entity.sequence_id.saturating_add(1);
1210 if self.sequence.load(Ordering::Relaxed) < next {
1211 self.sequence.store(next, Ordering::Relaxed);
1212 }
1213
1214 self.add_memory(Self::estimate_entity_size(&entity));
1215 self.index_entity(&entity);
1216 self.update_col_zones_from_entity(&entity);
1217 self.entities.insert(id, entity);
1218
1219 if let Some(metadata) = metadata {
1220 if !metadata.is_empty() {
1221 self.metadata.set_all(id, &metadata);
1222 }
1223 }
1224 }
1225
1226 pub fn evict_entity(&mut self, id: EntityId) -> bool {
1232 self.mark_checkpoint_entity_dirty(id);
1233 self.invalidate_unique_key_lookup();
1234 let Some(entity) = self.entities.remove(&id) else {
1235 return false;
1236 };
1237 self.release_memory(Self::estimate_entity_size(&entity));
1238 self.unindex_entity(&entity);
1239 for cross_ref in entity.cross_refs() {
1240 if let Some(reverse) = self.cross_ref_reverse.get_mut(&cross_ref.target) {
1241 reverse.retain(|(source, _)| *source != id);
1242 }
1243 }
1244 self.metadata.remove_all(id);
1245 true
1246 }
1247
1248 fn estimate_entity_size(entity: &UnifiedEntity) -> usize {
1250 super::memory_size::entity_bytes(entity)
1251 }
1252
1253 fn update_column_zone(&mut self, column: &str, value: &Value) {
1261 if matches!(value, Value::Null) {
1262 return;
1263 }
1264 match self.col_zones.entry(column.to_string()) {
1265 std::collections::hash_map::Entry::Occupied(mut entry) => {
1266 let before = entry.get().payload_bytes();
1267 entry.get_mut().update(value);
1268 self.zone_payload_bytes = self
1269 .zone_payload_bytes
1270 .saturating_sub(before)
1271 .saturating_add(entry.get().payload_bytes());
1272 }
1273 std::collections::hash_map::Entry::Vacant(entry) => {
1274 let zone = ColZone::new(value.clone());
1275 self.zone_payload_bytes =
1276 self.zone_payload_bytes.saturating_add(zone.payload_bytes());
1277 entry.insert(zone);
1278 }
1279 }
1280 }
1281
1282 fn update_col_zones_from_entity(&mut self, entity: &UnifiedEntity) {
1283 if let EntityData::Row(row) = &entity.data {
1284 if let Some(named) = &row.named {
1285 for (column, value) in named {
1286 self.update_column_zone(column, value);
1287 }
1288 } else if let Some(schema) = &row.schema {
1289 for (column, value) in schema.iter().zip(&row.columns) {
1290 self.update_column_zone(column, value);
1291 }
1292 }
1293 }
1294 }
1295
1296 fn rebuild_sealed_col_zones(&mut self) {
1297 let mut values_by_col: HashMap<String, Vec<(CanonicalKey, Value)>> = HashMap::new();
1298 let mut family_by_col: HashMap<String, reddb_types::CanonicalKeyFamily> = HashMap::new();
1299 let mut mixed_family_cols = HashSet::new();
1300 let mut unsupported_cols = HashSet::new();
1301
1302 let mut observe_row = |row: &super::entity::RowData| {
1303 for (col, value) in row.iter_fields() {
1304 if matches!(value, Value::Null) {
1305 continue;
1306 }
1307 let Some(key) = value_to_canonical_key(value) else {
1308 unsupported_cols.insert(col.to_string());
1309 continue;
1310 };
1311 match family_by_col.get(col).copied() {
1312 Some(existing) if existing != key.family() => {
1313 mixed_family_cols.insert(col.to_string());
1314 }
1315 None => {
1316 family_by_col.insert(col.to_string(), key.family());
1317 }
1318 _ => {}
1319 }
1320 values_by_col
1321 .entry(col.to_string())
1322 .or_default()
1323 .push((key, value.clone()));
1324 }
1325 };
1326
1327 if self.use_flat {
1328 for entity in &self.flat_entities {
1329 if self.deleted.contains_key(&entity.id) {
1330 continue;
1331 }
1332 if let EntityData::Row(row) = &entity.data {
1333 observe_row(row);
1334 }
1335 }
1336 } else {
1337 for entity in self.entities.values() {
1338 if self.deleted.contains_key(&entity.id) {
1339 continue;
1340 }
1341 if let EntityData::Row(row) = &entity.data {
1342 observe_row(row);
1343 }
1344 }
1345 }
1346
1347 let mut sealed_col_zones = HashMap::new();
1348 for (col, mut entries) in values_by_col {
1349 if mixed_family_cols.contains(&col)
1350 || unsupported_cols.contains(&col)
1351 || entries.is_empty()
1352 {
1353 continue;
1354 }
1355 entries.sort_unstable_by(|left, right| left.0.cmp(&right.0));
1356 entries.dedup_by(|left, right| left.0 == right.0);
1357
1358 let intervals = build_minmax_multi_intervals(&entries, SEALED_MULTI_ZONE_MAX_INTERVALS);
1359 if intervals.len() > 1 {
1360 sealed_col_zones.insert(col, MultiColZone { intervals });
1361 }
1362 }
1363
1364 let previous = self
1365 .sealed_col_zones
1366 .values()
1367 .flat_map(|zones| &zones.intervals)
1368 .map(ColZone::payload_bytes)
1369 .fold(0, usize::saturating_add);
1370 let current = sealed_col_zones
1371 .values()
1372 .flat_map(|zones| &zones.intervals)
1373 .map(ColZone::payload_bytes)
1374 .fold(0, usize::saturating_add);
1375 self.zone_payload_bytes = self
1376 .zone_payload_bytes
1377 .saturating_sub(previous)
1378 .saturating_add(current);
1379 self.sealed_col_zones = sealed_col_zones;
1380 }
1381
1382 pub fn can_skip_zone_preds(&self, preds: &[(&str, ZoneColPred<'_>)]) -> bool {
1386 if preds.is_empty() {
1387 return false;
1388 }
1389 for (col, pred) in preds {
1390 if let Some(zone) = self.sealed_col_zones.get(*col) {
1391 if zone.can_skip(pred) {
1392 return true;
1393 }
1394 continue;
1395 }
1396 if let Some(zone) = self.col_zones.get(*col) {
1397 if pred.can_skip(zone) {
1398 return true; }
1400 }
1401 }
1402 false
1403 }
1404
1405 fn index_entity(&mut self, entity: &UnifiedEntity) {
1407 self.mark_checkpoint_entity_dirty(entity.id);
1408 if let Some(index) = self.key_lookup.as_mut() {
1409 index.insert(entity);
1410 }
1411 self.hash_scan_ids.push(entity.id);
1412 self.observe_graph_kind(&entity.kind);
1413 if let Some(index) = self.graph_read_index.get_mut() {
1414 index.insert(entity);
1415 self.graph_read_index_bytes
1416 .store(index.memory_bytes(), Ordering::Relaxed);
1417 }
1418 let indexes = self.unique_key_lookup.get_mut();
1419 for index in indexes.iter_mut() {
1420 index.insert(entity);
1421 }
1422 self.unique_key_lookup_bytes.store(
1423 indexes
1424 .iter()
1425 .map(|index| index.memory_bytes() as u64)
1426 .sum(),
1427 Ordering::Relaxed,
1428 );
1429 let kind_key = entity.kind.storage_type().to_string();
1431 self.kind_index
1432 .entry(kind_key)
1433 .or_default()
1434 .insert(entity.id);
1435
1436 if let EntityData::Row(row) = &entity.data {
1438 if let Some(first_col) = row.columns.first() {
1439 let pk_str = format!("{:?}", first_col);
1440 self.pk_index
1441 .insert((entity.kind.collection().to_string(), pk_str), entity.id);
1442 }
1443 }
1444
1445 for cross_ref in entity.cross_refs() {
1447 self.cross_ref_forward
1448 .entry(cross_ref.source)
1449 .or_default()
1450 .push((cross_ref.target, cross_ref.ref_type));
1451
1452 self.cross_ref_reverse
1453 .entry(cross_ref.target)
1454 .or_default()
1455 .push((cross_ref.source, cross_ref.ref_type));
1456 }
1457 }
1458
1459 fn invalidate_graph_read_index(&mut self) {
1460 *self.graph_read_index.get_mut() = None;
1461 self.graph_read_index_bytes.store(0, Ordering::Relaxed);
1462 }
1463
1464 #[cfg(test)]
1467 pub fn visit_graph_index_candidates(
1468 &self,
1469 kind: GraphEntityKind,
1470 keys: &[EntityId],
1471 before_work: &mut impl FnMut() -> bool,
1472 visit: &mut impl FnMut(&UnifiedEntity) -> bool,
1473 ) -> bool {
1474 let Some(cached) = self.graph_read_index_for_query(before_work) else {
1475 return false;
1476 };
1477 let index = cached.as_ref().expect("complete graph index");
1478 for key in keys {
1479 if !before_work() {
1480 return false;
1481 }
1482 for id in index.candidates(kind, *key) {
1483 if !before_work() {
1484 return false;
1485 }
1486 if let Some(entity) = self.get(id) {
1487 if !visit(entity) {
1488 return false;
1489 }
1490 }
1491 }
1492 }
1493 true
1494 }
1495
1496 fn graph_read_index_for_query(
1497 &self,
1498 before_work: &mut impl FnMut() -> bool,
1499 ) -> Option<parking_lot::RwLockReadGuard<'_, Option<SegmentGraphIndex>>> {
1500 let mut cached = self.graph_read_index.read();
1501 if cached.is_none() {
1502 drop(cached);
1503 let mut writer = self.graph_read_index.write();
1504 if writer.is_none() {
1505 let mut index = SegmentGraphIndex::default();
1506 if !self.for_each_fast(|entity| {
1509 if !before_work() {
1510 return false;
1511 }
1512 index.insert(entity);
1513 true
1514 }) {
1515 return None;
1516 }
1517 self.graph_read_index_bytes
1518 .store(index.memory_bytes(), Ordering::Relaxed);
1519 *writer = Some(index);
1520 }
1521 cached = parking_lot::RwLockWriteGuard::downgrade(writer);
1522 }
1523 Some(cached)
1524 }
1525
1526 pub fn graph_index_cursor(
1527 &self,
1528 kind: GraphEntityKind,
1529 keys: &[EntityId],
1530 before_work: &mut impl FnMut() -> bool,
1531 ) -> Option<GraphIndexCursor> {
1532 let cached = self.graph_read_index_for_query(before_work)?;
1533 let index = cached.as_ref().expect("complete graph index");
1534 let mut upper = EntityId::new(0);
1535 for key in keys {
1536 if !before_work() {
1537 return None;
1538 }
1539 if let Some(last) = index.candidates(kind, *key).next_back() {
1540 upper = upper.max(last);
1541 }
1542 }
1543 Some(GraphIndexCursor {
1544 key_index: 0,
1545 after: None,
1546 upper,
1547 })
1548 }
1549
1550 pub fn graph_index_batch(
1553 &self,
1554 kind: GraphEntityKind,
1555 keys: &[EntityId],
1556 cursor: &mut GraphIndexCursor,
1557 before_work: &mut impl FnMut() -> bool,
1558 visit: &mut impl FnMut(EntityId),
1559 ) -> bool {
1560 self.graph_index_batch_with_limit(kind, keys, cursor, before_work, visit, SCAN_BATCH_SIZE)
1561 }
1562
1563 pub fn graph_index_batch_with_limit(
1564 &self,
1565 kind: GraphEntityKind,
1566 keys: &[EntityId],
1567 cursor: &mut GraphIndexCursor,
1568 before_work: &mut impl FnMut() -> bool,
1569 visit: &mut impl FnMut(EntityId),
1570 limit: usize,
1571 ) -> bool {
1572 assert!(
1573 limit > 0 && limit <= SCAN_BATCH_SIZE,
1574 "bounded graph index batch"
1575 );
1576 let Some(cached) = self.graph_read_index_for_query(before_work) else {
1577 return false;
1578 };
1579 let index = cached.as_ref().expect("complete graph index");
1580 let mut count = 0;
1581 while !cursor.finished(keys) && count < limit {
1582 let key = keys[cursor.key_index];
1583 if !before_work() {
1584 return false;
1585 }
1586 for id in index
1587 .candidates_after(kind, key, cursor.after, cursor.upper)
1588 .take(limit - count)
1589 {
1590 if !before_work() {
1591 return false;
1592 }
1593 cursor.after = Some(id);
1594 count += 1;
1595 visit(id);
1596 }
1597 if count == limit && cursor.after != Some(cursor.upper) {
1598 break;
1599 }
1600 cursor.key_index += 1;
1601 cursor.after = None;
1602 }
1603 true
1604 }
1605
1606 pub fn find_unique_key_conflict(
1611 &self,
1612 columns: &[Vec<String>],
1613 key_index: usize,
1614 signatures: &[String],
1615 exclude_id: Option<EntityId>,
1616 reserves_key: &impl Fn(&UnifiedEntity) -> bool,
1617 ) -> Option<EntityId> {
1618 assert!(
1619 key_index < columns.len(),
1620 "constraint index belongs to current schema"
1621 );
1622 let cached = self.unique_key_lookup.read();
1623 if cached.iter().map(|index| &index.columns).eq(columns.iter()) {
1624 return self.find_unique_key_candidate(
1625 &cached[key_index],
1626 signatures,
1627 exclude_id,
1628 reserves_key,
1629 );
1630 }
1631 drop(cached);
1632 let mut cached = self.unique_key_lookup.write();
1633 if !cached.iter().map(|index| &index.columns).eq(columns.iter()) {
1634 let mut indexes: Vec<_> = columns
1635 .iter()
1636 .map(|columns| SegmentUniqueKey::new(columns))
1637 .collect();
1638 self.for_each_fast(|entity| {
1639 for index in &mut indexes {
1640 index.insert(entity);
1641 }
1642 true
1643 });
1644 self.unique_key_lookup_bytes.store(
1645 indexes
1646 .iter()
1647 .map(|index| index.memory_bytes() as u64)
1648 .sum(),
1649 Ordering::Relaxed,
1650 );
1651 *cached = indexes;
1652 }
1653 self.find_unique_key_candidate(&cached[key_index], signatures, exclude_id, reserves_key)
1654 }
1655
1656 fn find_unique_key_candidate(
1657 &self,
1658 index: &SegmentUniqueKey,
1659 signatures: &[String],
1660 exclude_id: Option<EntityId>,
1661 reserves_key: &impl Fn(&UnifiedEntity) -> bool,
1662 ) -> Option<EntityId> {
1663 index.candidates(signatures).iter().copied().find(|id| {
1664 if Some(*id) == exclude_id {
1665 return false;
1666 }
1667 let Some(
1668 entity @ UnifiedEntity {
1669 data: EntityData::Row(row),
1670 ..
1671 },
1672 ) = self.get(*id)
1673 else {
1674 return false;
1675 };
1676 index
1677 .columns
1678 .iter()
1679 .zip(signatures)
1680 .all(|(column, signature)| {
1681 row.get_field(column)
1682 .is_some_and(|value| format!("{value:?}") == *signature)
1683 })
1684 && reserves_key(entity)
1685 })
1686 }
1687
1688 fn invalidate_unique_key_lookup(&mut self) {
1689 *self.unique_key_lookup.get_mut() = Vec::new();
1690 self.unique_key_lookup_bytes.store(0, Ordering::Relaxed);
1691 }
1692
1693 pub fn find_key_entity(
1696 &self,
1697 key: &str,
1698 newest: bool,
1699 visible: &mut impl FnMut(&UnifiedEntity) -> bool,
1700 ) -> Option<&UnifiedEntity> {
1701 let index = self
1704 .key_lookup
1705 .as_ref()
1706 .expect("segment owns its key index");
1707 self.find_key_candidate(index, key, newest, visible)
1708 }
1709
1710 fn find_key_candidate(
1711 &self,
1712 index: &SegmentKeyLookup,
1713 key: &str,
1714 newest: bool,
1715 visible: &mut impl FnMut(&UnifiedEntity) -> bool,
1716 ) -> Option<&UnifiedEntity> {
1717 self.select_key_entity(
1718 index
1719 .candidates(key)
1720 .iter()
1721 .chain(index.mutated_candidates())
1722 .filter_map(|id| self.get(*id)),
1723 key,
1724 newest,
1725 visible,
1726 )
1727 }
1728
1729 fn select_key_entity<'a>(
1730 &self,
1731 entities: impl Iterator<Item = &'a UnifiedEntity>,
1732 key: &str,
1733 newest: bool,
1734 visible: &mut impl FnMut(&UnifiedEntity) -> bool,
1735 ) -> Option<&'a UnifiedEntity> {
1736 let mut best: Option<&UnifiedEntity> = None;
1737 for entity in entities {
1738 if SegmentKeyLookup::key(entity) != Some(key) || !visible(entity) {
1740 continue;
1741 }
1742 if !newest {
1743 return Some(entity);
1744 }
1745 if best.is_none_or(|current| entity.xmin >= current.xmin) {
1746 best = Some(entity);
1747 }
1748 }
1749 best
1750 }
1751
1752 pub fn may_contain_exact_key(&self, key: &[u8]) -> bool {
1754 if let Some(id) = entity_id_from_probe_key(key) {
1755 return self.has_live_entity(id);
1756 }
1757
1758 let Ok(pk_str) = std::str::from_utf8(key) else {
1759 return false;
1760 };
1761 let pk_key = (self.collection.clone(), pk_str.to_string());
1762 self.pk_index
1763 .get(&pk_key)
1764 .is_some_and(|id| self.has_live_entity(*id))
1765 }
1766
1767 fn unindex_entity(&mut self, entity: &UnifiedEntity) {
1769 if let Some(index) = self.key_lookup.as_mut() {
1770 index.remove(entity);
1771 }
1772 if let Some(index) = self.graph_read_index.get_mut() {
1773 index.remove(entity);
1774 self.graph_read_index_bytes
1775 .store(index.memory_bytes(), Ordering::Relaxed);
1776 }
1777 let kind_key = entity.kind.storage_type().to_string();
1779 if let Some(set) = self.kind_index.get_mut(&kind_key) {
1780 set.remove(&entity.id);
1781 }
1782
1783 if let EntityData::Row(row) = &entity.data {
1785 if let Some(first_col) = row.columns.first() {
1786 let pk_str = format!("{:?}", first_col);
1787 self.pk_index
1788 .remove(&(entity.kind.collection().to_string(), pk_str));
1789 }
1790 }
1791
1792 self.cross_ref_forward.remove(&entity.id);
1794 }
1796
1797 fn reindex_for_update(
1807 &mut self,
1808 old: &UpdateIndexSnapshot,
1809 new: &UnifiedEntity,
1810 modified_columns: Option<&[String]>,
1811 ) {
1812 let pk_changed = match &new.data {
1817 EntityData::Row(new_row) => {
1818 if let Some(cols) = modified_columns {
1819 let pk_col_name = old.pk_column_name.as_deref().or_else(|| {
1822 new_row
1823 .schema
1824 .as_deref()
1825 .and_then(|schema| schema.first().map(|name| name.as_str()))
1826 });
1827 match pk_col_name {
1828 Some(pk_name) => cols.iter().any(|c| c.eq_ignore_ascii_case(pk_name)),
1829 None => old.pk_value.as_ref() != new_row.columns.first(),
1831 }
1832 } else {
1833 old.pk_value.as_ref() != new_row.columns.first()
1834 }
1835 }
1836 _ => false,
1838 };
1839
1840 if pk_changed {
1841 if let Some((collection, pk_str)) = &old.pk_index_key {
1843 self.pk_index.remove(&(collection.clone(), pk_str.clone()));
1844 }
1845 if let EntityData::Row(row) = &new.data {
1847 if let Some(first_col) = row.columns.first() {
1848 let pk_str = format!("{:?}", first_col);
1849 self.pk_index
1850 .insert((new.kind.collection().to_string(), pk_str), new.id);
1851 }
1852 }
1853 }
1854
1855 let new_refs = new.cross_refs();
1857 if old.cross_refs.as_slice() != new_refs {
1858 self.cross_ref_forward.remove(&new.id);
1860 for cross_ref in &old.cross_refs {
1862 if let Some(rev) = self.cross_ref_reverse.get_mut(&cross_ref.target) {
1863 rev.retain(|(src, _)| *src != new.id);
1864 }
1865 }
1866 for cross_ref in new_refs {
1868 self.cross_ref_forward
1869 .entry(cross_ref.source)
1870 .or_default()
1871 .push((cross_ref.target, cross_ref.ref_type));
1872 self.cross_ref_reverse
1873 .entry(cross_ref.target)
1874 .or_default()
1875 .push((cross_ref.source, cross_ref.ref_type));
1876 }
1877 }
1878 }
1879
1880 pub fn get_references_to(&self, id: EntityId) -> Vec<(EntityId, RefType)> {
1882 self.cross_ref_reverse.get(&id).cloned().unwrap_or_default()
1883 }
1884
1885 pub fn get_references_from(&self, id: EntityId) -> Vec<(EntityId, RefType)> {
1887 self.cross_ref_forward.get(&id).cloned().unwrap_or_default()
1888 }
1889
1890 pub fn age_secs(&self) -> u64 {
1892 let now = current_unix_secs();
1893 now.saturating_sub(self.created_at)
1894 }
1895
1896 pub fn idle_secs(&self) -> u64 {
1898 let now = current_unix_secs();
1899 now.saturating_sub(self.last_write_at)
1900 }
1901
1902 pub fn bulk_insert(
1911 &mut self,
1912 entities: Vec<UnifiedEntity>,
1913 ) -> Result<Vec<EntityId>, SegmentError> {
1914 if !self.state.is_writable() {
1915 return Err(SegmentError::NotWritable);
1916 }
1917
1918 for entity in &entities {
1919 self.mark_checkpoint_entity_dirty(entity.id);
1920 }
1921
1922 if let Some(index) = self.key_lookup.as_mut() {
1923 for entity in &entities {
1924 index.insert(entity);
1925 }
1926 }
1927
1928 let indexes = self.unique_key_lookup.get_mut();
1929 for index in indexes.iter_mut() {
1930 for entity in &entities {
1931 index.insert(entity);
1932 }
1933 }
1934 self.unique_key_lookup_bytes.store(
1935 indexes
1936 .iter()
1937 .map(|index| index.memory_bytes() as u64)
1938 .sum(),
1939 Ordering::Relaxed,
1940 );
1941
1942 let n = entities.len();
1943
1944 if entities.is_empty() {
1945 return Ok(Vec::new());
1946 }
1947
1948 for run in
1951 entities.chunk_by(|left, right| left.kind.storage_type() == right.kind.storage_type())
1952 {
1953 let kind = run[0].kind.storage_type();
1954 self.observe_graph_kind(&run[0].kind);
1955 if matches!(
1956 run[0].kind,
1957 EntityKind::GraphNode(_) | EntityKind::GraphEdge(_)
1958 ) {
1959 if let Some(index) = self.graph_read_index.get_mut() {
1960 for entity in run {
1961 index.insert(entity);
1962 }
1963 self.graph_read_index_bytes
1964 .store(index.memory_bytes(), Ordering::Relaxed);
1965 }
1966 }
1967 let kind_set = match self.kind_index.get_mut(kind) {
1968 Some(ids) => ids,
1969 None => self.kind_index.entry(kind.to_string()).or_default(),
1970 };
1971 kind_set.reserve(run.len());
1972 kind_set.extend(run.iter().map(|entity| entity.id));
1973 }
1974
1975 let now = current_unix_secs();
1976
1977 let base_seq = self.sequence.fetch_add(n as u64, Ordering::Relaxed);
1978
1979 let mut ids = Vec::with_capacity(n);
1980
1981 if self.flat_entities.is_empty() && self.entities.is_empty() {
1983 self.base_entity_id = entities.first().map(|e| e.id.raw()).unwrap_or(0);
1985 self.use_flat = true;
1986 }
1987
1988 let mut columnar_zone_updates: Vec<Vec<Value>> = Vec::new();
1998 let mut columnar_schema: Option<std::sync::Arc<Vec<String>>> = None;
1999 let mut named_zone_updates: Vec<(String, Value)> = Vec::new();
2003
2004 let mut batch_bytes: usize = 0;
2009
2010 if self.use_flat {
2011 self.flat_entities.reserve(n);
2012 for (i, mut entity) in entities.into_iter().enumerate() {
2013 entity.sequence_id = base_seq + i as u64;
2014 let id = entity.id;
2015 ids.push(id);
2016 batch_bytes += Self::estimate_entity_size(&entity);
2017 if let EntityData::Row(row) = &entity.data {
2018 if row.schema.is_some() && !row.columns.is_empty() {
2019 if columnar_zone_updates.is_empty() {
2020 columnar_zone_updates = vec![Vec::with_capacity(n); row.columns.len()];
2021 columnar_schema = row.schema.clone();
2022 }
2023 for (ci, val) in row.columns.iter().enumerate() {
2024 if !matches!(val, Value::Null) {
2025 if let Some(bucket) = columnar_zone_updates.get_mut(ci) {
2026 bucket.push(val.clone());
2027 }
2028 }
2029 }
2030 } else {
2031 for (col, val) in row.iter_fields() {
2032 if !matches!(val, Value::Null) {
2033 named_zone_updates.push((col.to_string(), val.clone()));
2034 }
2035 }
2036 }
2037 }
2038 let expected = self.base_entity_id + self.flat_entities.len() as u64;
2054 if id.raw() == expected {
2055 self.flat_entities.push(entity);
2056 } else {
2057 self.hash_scan_ids.push(id);
2058 self.entities.insert(id, entity);
2059 }
2060 }
2061 } else {
2062 self.entities.reserve(n);
2064 self.hash_scan_ids.reserve(n);
2065 let mut pairs = Vec::with_capacity(n);
2066 for (i, mut entity) in entities.into_iter().enumerate() {
2067 entity.sequence_id = base_seq + i as u64;
2068 let id = entity.id;
2069 ids.push(id);
2070 batch_bytes += Self::estimate_entity_size(&entity);
2071 if let EntityData::Row(row) = &entity.data {
2072 for (col, val) in row.iter_fields() {
2073 if !matches!(val, Value::Null) {
2074 named_zone_updates.push((col.to_string(), val.clone()));
2075 }
2076 }
2077 }
2078 self.hash_scan_ids.push(id);
2079 pairs.push((id, entity));
2080 }
2081 self.entities.extend(pairs);
2082 }
2083
2084 if !columnar_zone_updates.is_empty() {
2088 let schema = columnar_schema.as_ref();
2089 for (ci, values) in columnar_zone_updates.into_iter().enumerate() {
2090 if values.is_empty() {
2091 continue;
2092 }
2093 let Some(col_name) = schema.and_then(|s| s.get(ci)) else {
2094 continue;
2095 };
2096 let mut iter = values.into_iter();
2102 if let Some(first) = iter.next() {
2103 let previous = self
2104 .col_zones
2105 .get(col_name)
2106 .map_or(0, ColZone::payload_bytes);
2107 let zone = self
2108 .col_zones
2109 .entry(col_name.clone())
2110 .and_modify(|z| z.update(&first))
2111 .or_insert_with(|| ColZone::new(first));
2112 for v in iter {
2113 zone.update(&v);
2114 }
2115 self.zone_payload_bytes = self
2116 .zone_payload_bytes
2117 .saturating_sub(previous)
2118 .saturating_add(zone.payload_bytes());
2119 }
2120 }
2121 }
2122 for (col, val) in named_zone_updates {
2123 self.update_column_zone(&col, &val);
2124 }
2125
2126 self.add_memory(batch_bytes);
2127 self.last_write_at = now;
2128
2129 if self.use_flat {
2131 self.published_flat_len
2132 .store(self.flat_entities.len(), Ordering::Release);
2133 }
2134
2135 Ok(ids)
2136 }
2137
2138 pub fn force_delete(&mut self, id: EntityId) -> bool {
2141 if self.use_flat {
2142 let raw = id.raw();
2143 if raw >= self.base_entity_id {
2144 let idx = (raw - self.base_entity_id) as usize;
2145 if idx < self.flat_entities.len() && self.flat_entities[idx].id == id {
2146 self.tombstone_flat_entity(idx, id);
2147 return true;
2148 }
2149 }
2150 return self.remove_hashmap_entity(id);
2151 }
2152
2153 self.remove_hashmap_entity(id)
2154 }
2155
2156 pub fn force_update_with_metadata(
2159 &mut self,
2160 entity: &UnifiedEntity,
2161 modified_columns: &[String],
2162 metadata: Option<&Metadata>,
2163 ) -> Result<(), SegmentError> {
2164 self.apply_hot_update_with_metadata(entity, modified_columns, metadata)
2165 }
2166}
2167
2168impl UnifiedSegment for GrowingSegment {
2169 fn id(&self) -> SegmentId {
2170 self.id
2171 }
2172
2173 fn state(&self) -> SegmentState {
2174 self.state
2175 }
2176
2177 fn collection(&self) -> &str {
2178 &self.collection
2179 }
2180
2181 fn stats(&self) -> SegmentStats {
2182 let mut stats = SegmentStats {
2183 entity_count: self.live_entity_count(),
2184 deleted_count: self.deleted.len(),
2185 memory_bytes: self.resident_bytes() as usize,
2186 ..Default::default()
2187 };
2188
2189 for entity in self.entities.values() {
2190 match &entity.kind {
2191 EntityKind::TableRow { .. } => stats.row_count += 1,
2192 EntityKind::GraphNode(_) => stats.node_count += 1,
2193 EntityKind::GraphEdge(_) => stats.edge_count += 1,
2194 EntityKind::Vector { .. } => stats.vector_count += 1,
2195 EntityKind::TimeSeriesPoint(_) => stats.row_count += 1,
2196 EntityKind::QueueMessage { .. } => stats.row_count += 1,
2197 }
2198 stats.cross_ref_count += entity.cross_refs().len();
2199 }
2200
2201 stats
2202 }
2203
2204 fn entity_count(&self) -> usize {
2205 self.live_entity_count()
2206 }
2207
2208 fn contains(&self, id: EntityId) -> bool {
2209 self.has_live_entity(id)
2210 }
2211
2212 fn get(&self, id: EntityId) -> Option<&UnifiedEntity> {
2213 if self.deleted.contains_key(&id) {
2214 return None;
2215 }
2216 if self.use_flat {
2217 let raw = id.raw();
2218 if raw >= self.base_entity_id {
2219 let idx = (raw - self.base_entity_id) as usize;
2220 if let Some(entity) = self.flat_entities.get(idx).filter(|e| e.id == id) {
2221 return Some(entity);
2222 }
2223 }
2224 self.entities.get(&id)
2230 } else {
2231 self.entities.get(&id)
2232 }
2233 }
2234
2235 fn get_mut(&mut self, id: EntityId) -> Option<&mut UnifiedEntity> {
2236 self.mark_checkpoint_entity_dirty(id);
2237 self.invalidate_unique_key_lookup();
2238 self.invalidate_graph_read_index();
2239 if self.deleted.contains_key(&id) || !self.state.is_writable() {
2240 return None;
2241 }
2242 if let Some(mut index) = self.key_lookup.take() {
2243 if let Some(entity) = self.get(id) {
2244 index.mark_mutated(entity);
2245 }
2246 self.key_lookup = Some(index);
2247 }
2248 self.graph_kind_mask = GraphEntityKind::Node.mask() | GraphEntityKind::Edge.mask();
2251 if self.use_flat {
2252 let raw = id.raw();
2253 if raw >= self.base_entity_id {
2254 let idx = (raw - self.base_entity_id) as usize;
2255 if self
2256 .flat_entities
2257 .get(idx)
2258 .map(|e| e.id == id)
2259 .unwrap_or(false)
2260 {
2261 return self.flat_entities.get_mut(idx);
2262 }
2263 }
2264 self.entities.get_mut(&id)
2267 } else {
2268 self.entities.get_mut(&id)
2269 }
2270 }
2271
2272 fn insert(&mut self, mut entity: UnifiedEntity) -> Result<EntityId, SegmentError> {
2273 if !self.state.is_writable() {
2274 return Err(SegmentError::NotWritable);
2275 }
2276
2277 if self.entities.contains_key(&entity.id) {
2278 return Err(SegmentError::AlreadyExists(entity.id));
2279 }
2280
2281 entity.sequence_id = self.next_sequence();
2283
2284 let size = Self::estimate_entity_size(&entity);
2286 self.add_memory(size);
2287
2288 self.index_entity(&entity);
2290
2291 self.update_col_zones_from_entity(&entity);
2293
2294 let id = entity.id;
2296 self.entities.insert(id, entity);
2297
2298 self.last_write_at = current_unix_secs();
2300
2301 Ok(id)
2302 }
2303
2304 fn update(&mut self, entity: UnifiedEntity) -> Result<(), SegmentError> {
2305 if !self.state.is_writable() {
2306 return Err(SegmentError::NotWritable);
2307 }
2308
2309 self.apply_update_with_metadata(&entity, None)?;
2310 self.last_write_at = current_unix_secs();
2311
2312 Ok(())
2313 }
2314
2315 fn update_hot(
2316 &mut self,
2317 entity: UnifiedEntity,
2318 modified_columns: &[String],
2319 ) -> Result<(), SegmentError> {
2320 if !self.state.is_writable() {
2321 return Err(SegmentError::NotWritable);
2322 }
2323
2324 self.apply_hot_update_with_metadata(&entity, modified_columns, None)?;
2325 self.last_write_at = current_unix_secs();
2326 Ok(())
2327 }
2328
2329 fn delete(&mut self, id: EntityId) -> Result<bool, SegmentError> {
2330 if !self.state.is_writable() {
2331 return Err(SegmentError::NotWritable);
2332 }
2333
2334 if self.use_flat {
2336 let raw = id.raw();
2337 if raw >= self.base_entity_id {
2338 let idx = (raw - self.base_entity_id) as usize;
2339 if idx < self.flat_entities.len() && self.flat_entities[idx].id == id {
2340 self.tombstone_flat_entity(idx, id);
2341 return Ok(true);
2342 }
2343 }
2344 return Ok(self.remove_hashmap_entity(id));
2345 }
2346
2347 Ok(self.remove_hashmap_entity(id))
2348 }
2349
2350 fn get_metadata(&self, id: EntityId) -> Option<Metadata> {
2351 if !self.has_live_entity(id) {
2352 return None;
2353 }
2354 Some(self.metadata.get_all(id))
2355 }
2356
2357 fn set_metadata(&mut self, id: EntityId, metadata: Metadata) -> Result<(), SegmentError> {
2358 self.mark_checkpoint_entity_dirty(id);
2359 if !self.state.is_writable() {
2360 return Err(SegmentError::NotWritable);
2361 }
2362
2363 if !self.has_live_entity(id) {
2364 return Err(SegmentError::NotFound(id));
2365 }
2366
2367 self.metadata.set_all(id, &metadata);
2368 Ok(())
2369 }
2370
2371 fn seal(&mut self) -> Result<(), SegmentError> {
2372 if self.state != SegmentState::Growing {
2373 return Err(SegmentError::InvalidState(self.state));
2374 }
2375
2376 self.state = SegmentState::Sealing;
2377
2378 self.rebuild_sealed_col_zones();
2383
2384 self.state = SegmentState::Sealed;
2385 Ok(())
2386 }
2387
2388 fn should_seal(&self, config: &SegmentConfig) -> bool {
2389 if self.entities.len() >= config.max_entities {
2391 return true;
2392 }
2393
2394 if self.memory_bytes.load(Ordering::Relaxed) as usize >= config.max_bytes {
2396 return true;
2397 }
2398
2399 if self.age_secs() >= config.max_age_secs {
2401 return true;
2402 }
2403
2404 false
2405 }
2406
2407 fn iter(&self) -> Box<dyn Iterator<Item = &UnifiedEntity> + '_> {
2408 let base: Box<dyn Iterator<Item = &UnifiedEntity>> = if self.use_flat {
2409 Box::new(self.flat_entities.iter().chain(self.entities.values()))
2413 } else {
2414 Box::new(self.entities.values())
2415 };
2416 if self.deleted.is_empty() {
2417 base
2418 } else {
2419 Box::new(base.filter(|e| !self.deleted.contains_key(&e.id)))
2420 }
2421 }
2422
2423 fn iter_kind(&self, kind_filter: &str) -> Box<dyn Iterator<Item = &UnifiedEntity> + '_> {
2424 let ids = self.kind_index.get(kind_filter);
2427 let flat: Box<dyn Iterator<Item = &UnifiedEntity>> = if self.use_flat {
2430 Box::new(self.flat_entities.iter())
2431 } else {
2432 Box::new(std::iter::empty())
2433 };
2434 Box::new(flat.chain(self.entities.values()).filter(move |e| {
2435 if self.deleted.contains_key(&e.id) {
2436 return false;
2437 }
2438 if let Some(ids) = ids {
2439 ids.contains(&e.id)
2440 } else {
2441 false
2442 }
2443 }))
2444 }
2445
2446 fn filter_metadata(
2447 &self,
2448 filters: &[(String, super::metadata::MetadataFilter)],
2449 ) -> Vec<EntityId> {
2450 let flat_ids: Box<dyn Iterator<Item = EntityId> + '_> = if self.use_flat {
2453 Box::new(self.flat_entities.iter().map(|e| e.id))
2454 } else {
2455 Box::new(std::iter::empty())
2456 };
2457 flat_ids
2458 .chain(self.entities.keys().copied())
2459 .filter(|id| {
2460 if self.deleted.contains_key(id) {
2461 return false;
2462 }
2463 let metadata = self.metadata.get_all(*id);
2464 metadata.matches_all(filters)
2465 })
2466 .collect()
2467 }
2468}
2469
2470fn build_minmax_multi_intervals(
2471 entries: &[(CanonicalKey, Value)],
2472 max_intervals: usize,
2473) -> Vec<ColZone> {
2474 if entries.is_empty() {
2475 return Vec::new();
2476 }
2477 if entries.len() == 1 || max_intervals <= 1 {
2478 return vec![ColZone::with_bounds(
2479 entries[0].1.clone(),
2480 entries[entries.len() - 1].1.clone(),
2481 )];
2482 }
2483
2484 let mut split_points = if entries.len() <= max_intervals {
2485 (1..entries.len()).collect::<Vec<_>>()
2486 } else {
2487 let target_splits = max_intervals - 1;
2488 let mut selected = select_gap_split_points(entries, target_splits);
2489 if selected.len() < target_splits {
2490 for bucket in 1..max_intervals {
2491 let idx = bucket * entries.len() / max_intervals;
2492 if idx == 0 || idx >= entries.len() || selected.contains(&idx) {
2493 continue;
2494 }
2495 selected.push(idx);
2496 if selected.len() >= target_splits {
2497 break;
2498 }
2499 }
2500 }
2501 selected.sort_unstable();
2502 selected.dedup();
2503 selected
2504 };
2505
2506 split_points.push(entries.len());
2507
2508 let mut out = Vec::with_capacity(split_points.len());
2509 let mut start = 0usize;
2510 for end in split_points {
2511 if end <= start {
2512 continue;
2513 }
2514 out.push(ColZone::with_bounds(
2515 entries[start].1.clone(),
2516 entries[end - 1].1.clone(),
2517 ));
2518 start = end;
2519 }
2520
2521 if out.is_empty() {
2522 out.push(ColZone::with_bounds(
2523 entries[0].1.clone(),
2524 entries[entries.len() - 1].1.clone(),
2525 ));
2526 }
2527
2528 out
2529}
2530
2531fn select_gap_split_points(entries: &[(CanonicalKey, Value)], max_splits: usize) -> Vec<usize> {
2532 let mut gaps = Vec::new();
2533 for idx in 1..entries.len() {
2534 if let Some(score) = canonical_gap_score(&entries[idx - 1].0, &entries[idx].0) {
2535 if score > 0.0 {
2536 gaps.push((score, idx));
2537 }
2538 }
2539 }
2540 gaps.sort_by(|left, right| {
2541 right
2542 .0
2543 .partial_cmp(&left.0)
2544 .unwrap_or(std::cmp::Ordering::Equal)
2545 .then_with(|| left.1.cmp(&right.1))
2546 });
2547 gaps.into_iter()
2548 .take(max_splits)
2549 .map(|(_, idx)| idx)
2550 .collect()
2551}
2552
2553fn canonical_gap_score(left: &CanonicalKey, right: &CanonicalKey) -> Option<f64> {
2554 if left.family() != right.family() {
2555 return None;
2556 }
2557 match (left, right) {
2558 (CanonicalKey::Signed(_, l), CanonicalKey::Signed(_, r)) => {
2559 Some(r.saturating_sub(*l) as f64)
2560 }
2561 (CanonicalKey::Unsigned(_, l), CanonicalKey::Unsigned(_, r)) => {
2562 Some(r.saturating_sub(*l) as f64)
2563 }
2564 (CanonicalKey::Float(l), CanonicalKey::Float(r)) => {
2565 Some((f64::from_bits(*r) - f64::from_bits(*l)).abs())
2566 }
2567 _ => None,
2568 }
2569}
2570
2571impl UnifiedSegment for std::sync::Arc<parking_lot::RwLock<GrowingSegment>> {
2574 fn id(&self) -> SegmentId {
2575 self.read().id()
2576 }
2577
2578 fn state(&self) -> SegmentState {
2579 self.read().state()
2580 }
2581
2582 fn collection(&self) -> &str {
2583 "unknown"
2585 }
2586
2587 fn stats(&self) -> SegmentStats {
2588 self.read().stats()
2589 }
2590
2591 fn entity_count(&self) -> usize {
2592 self.read().entity_count()
2593 }
2594
2595 fn contains(&self, id: EntityId) -> bool {
2596 self.read().contains(id)
2597 }
2598
2599 fn get(&self, _id: EntityId) -> Option<&UnifiedEntity> {
2600 None
2603 }
2604
2605 fn get_mut(&mut self, _id: EntityId) -> Option<&mut UnifiedEntity> {
2606 None
2607 }
2608
2609 fn insert(&mut self, entity: UnifiedEntity) -> Result<EntityId, SegmentError> {
2610 self.write().insert(entity)
2611 }
2612
2613 fn update(&mut self, entity: UnifiedEntity) -> Result<(), SegmentError> {
2614 self.write().update(entity)
2615 }
2616
2617 fn update_hot(
2618 &mut self,
2619 entity: UnifiedEntity,
2620 modified_columns: &[String],
2621 ) -> Result<(), SegmentError> {
2622 self.write().update_hot(entity, modified_columns)
2623 }
2624
2625 fn delete(&mut self, id: EntityId) -> Result<bool, SegmentError> {
2626 self.write().delete(id)
2627 }
2628
2629 fn get_metadata(&self, id: EntityId) -> Option<Metadata> {
2630 self.read().get_metadata(id)
2631 }
2632
2633 fn set_metadata(&mut self, id: EntityId, metadata: Metadata) -> Result<(), SegmentError> {
2634 self.write().set_metadata(id, metadata)
2635 }
2636
2637 fn seal(&mut self) -> Result<(), SegmentError> {
2638 self.write().seal()
2639 }
2640
2641 fn should_seal(&self, config: &SegmentConfig) -> bool {
2642 self.read().should_seal(config)
2643 }
2644
2645 fn iter(&self) -> Box<dyn Iterator<Item = &UnifiedEntity> + '_> {
2646 Box::new(std::iter::empty())
2648 }
2649
2650 fn iter_kind(&self, _kind_filter: &str) -> Box<dyn Iterator<Item = &UnifiedEntity> + '_> {
2651 Box::new(std::iter::empty())
2652 }
2653
2654 fn filter_metadata(&self, filters: &[(String, MetadataFilter)]) -> Vec<EntityId> {
2655 self.read().filter_metadata(filters)
2656 }
2657}
2658
2659#[cfg(test)]
2660mod tests {
2661 use super::*;
2662 use crate::storage::entity::RowData;
2663 use crate::storage::metadata::MetadataValue;
2664 use reddb_types::Value;
2665
2666 fn keyed_row(id: u64, key: &str, xmin: u64, xmax: u64) -> UnifiedEntity {
2667 let mut entity = UnifiedEntity::table_row(EntityId::new(id), "keys", id, vec![]);
2668 entity.data = EntityData::Row(RowData::with_names(
2669 vec![Value::text(key), Value::Integer(id as i64)],
2670 vec!["key".into(), "value".into()],
2671 ));
2672 entity.set_xmin(xmin);
2673 entity.set_xmax(xmax);
2674 entity
2675 }
2676
2677 fn keyed_probe(segment: &GrowingSegment, key: &str) -> Option<EntityId> {
2678 segment
2679 .find_key_entity(key, true, &mut |_| true)
2680 .map(|entity| entity.id)
2681 }
2682
2683 #[test]
2684 fn bulk_checkpoint_journal_preserves_prior_changes_and_ignores_rejected_batches() {
2685 let mut segment = GrowingSegment::new(1, "keys");
2686 segment.insert(keyed_row(1, "one", 1, 0)).expect("row");
2687 segment
2688 .bulk_insert(vec![keyed_row(2, "two", 1, 0), keyed_row(3, "three", 1, 0)])
2689 .expect("batch");
2690 assert_eq!(
2691 segment.checkpoint_mutations_since(0),
2692 Some(vec![EntityId::new(1), EntityId::new(2), EntityId::new(3)])
2693 );
2694 let revision = segment.checkpoint_revision();
2695 segment.bulk_insert(Vec::new()).expect("empty batch");
2696 assert_eq!(segment.checkpoint_revision(), revision);
2697 segment.seal().expect("seal segment");
2698 assert!(matches!(
2699 segment.bulk_insert(vec![keyed_row(4, "four", 1, 0)]),
2700 Err(SegmentError::NotWritable)
2701 ));
2702 assert_eq!(segment.checkpoint_revision(), revision);
2703 assert_eq!(segment.checkpoint_mutations_since(revision), Some(vec![]));
2704 }
2705
2706 #[test]
2707 fn checkpoint_journal_retains_bounded_row_versions_through_repeated_updates_and_gc() {
2708 const ROWS: u64 = 1700;
2709 let mut segment = GrowingSegment::new(1, "keys");
2710 for id in 1..=ROWS {
2711 segment
2712 .insert(keyed_row(id, "export", 1, 0))
2713 .expect("seed export");
2714 }
2715 let baseline = segment.checkpoint_revision();
2716 for id in 1..=ROWS {
2720 segment
2721 .update(keyed_row(id, "export", 1, 2))
2722 .expect("supersede");
2723 let delivered = ROWS + id;
2724 segment
2725 .insert(keyed_row(delivered, "export", 2, 0))
2726 .expect("delivered");
2727 segment
2728 .update(keyed_row(delivered, "export", 2, 3))
2729 .expect("acknowledge");
2730 segment
2731 .insert(keyed_row(2 * ROWS + id, "export", 3, 0))
2732 .expect("final version");
2733 assert_eq!(
2734 segment
2735 .delete_batch(&[EntityId::new(id), EntityId::new(delivered)])
2736 .expect("GC")
2737 .len(),
2738 2
2739 );
2740 }
2741 let changed = segment
2742 .checkpoint_mutations_since(baseline)
2743 .expect("all changed IDs still fit the bounded checkpoint journal");
2744 let actual: std::collections::BTreeSet<_> = changed.into_iter().collect();
2745 let expected: std::collections::BTreeSet<_> = (1..=3 * ROWS).map(EntityId::new).collect();
2746 assert_eq!(actual, expected, "deleted versions must remain tracked too");
2747 }
2748
2749 #[test]
2750 fn checkpoint_journal_preserves_cold_changes_during_hot_row_churn() {
2751 let mut segment = GrowingSegment::new(1, "keys");
2752 segment
2753 .insert(keyed_row(1, "cold", 1, 0))
2754 .expect("cold row");
2755 segment.insert(keyed_row(2, "hot", 1, 0)).expect("hot row");
2756 for xid in 2..=(5 * CHECKPOINT_MUTATIONS_MAX) as u64 {
2757 segment
2758 .update(keyed_row(2, "hot", xid, 0))
2759 .expect("hot edit");
2760 }
2761 let changed: std::collections::BTreeSet<_> = segment
2762 .checkpoint_mutations_since(0)
2763 .expect("two physical IDs never exhaust the distinct-ID bound")
2764 .into_iter()
2765 .collect();
2766 assert_eq!(
2767 changed,
2768 [EntityId::new(1), EntityId::new(2)].into_iter().collect()
2769 );
2770 assert!(
2771 segment.checkpoint_tracking_bytes() <= 1024 * 1024,
2772 "retained proof memory must stay bounded under unlimited repeated edits"
2773 );
2774 }
2775
2776 #[test]
2777 fn checkpoint_journal_complete_suffix_matches_uncompressed_mutation_history() {
2778 let mut segment = GrowingSegment::new(1, "keys");
2779 let mut history = Vec::new();
2780 let mut seed = 0x7b3d_5961_a902_4c85_u64;
2781 for n in 0..3 * CHECKPOINT_MUTATIONS_MAX {
2782 seed ^= seed << 13;
2783 seed ^= seed >> 7;
2784 seed ^= seed << 17;
2785 let raw = if n < CHECKPOINT_MUTATIONS_MAX + 17 {
2786 n as u64 + 1
2787 } else {
2788 seed % (CHECKPOINT_MUTATIONS_MAX as u64 + 17) + 1
2789 };
2790 let id = EntityId::new(raw);
2791 segment.mark_checkpoint_entity_dirty(id);
2792 history.push(id);
2793 }
2794 let now = segment.checkpoint_revision();
2795 assert!(
2796 segment.checkpoint_mutations_since(0).is_none(),
2797 "an evicted ID must still invalidate an older baseline"
2798 );
2799 let mut complete = 0;
2800 for baseline in [0, 1, now / 3, now / 2, now - 2048, now - 128, now] {
2801 if let Some(ids) = segment.checkpoint_mutations_since(baseline) {
2802 let actual: std::collections::BTreeSet<_> = ids.into_iter().collect();
2803 let expected: std::collections::BTreeSet<_> =
2804 history[baseline as usize..].iter().copied().collect();
2805 assert_eq!(actual, expected, "baseline {baseline}");
2806 complete += 1;
2807 }
2808 }
2809 assert!(
2810 complete >= 3,
2811 "recent baselines must retain a complete proof"
2812 );
2813 assert!(segment.checkpoint_mutations_since(now + 1).is_none());
2814 assert!(segment.checkpoint_tracking_bytes() <= 1024 * 1024);
2815 }
2816
2817 #[test]
2818 fn bulk_checkpoint_journal_exhaustion_keeps_a_bounded_complete_suffix() {
2819 let mut segment = GrowingSegment::new(1, "keys");
2820 segment
2821 .bulk_insert(
2822 (1..=CHECKPOINT_MUTATIONS_MAX as u64 + 1)
2823 .map(|id| keyed_row(id, "key", 1, 0))
2824 .collect(),
2825 )
2826 .expect("large batch");
2827 assert!(segment.checkpoint_mutations_since(0).is_none());
2828 assert_eq!(
2829 segment
2830 .checkpoint_mutations_since(1)
2831 .expect("complete suffix")
2832 .len(),
2833 CHECKPOINT_MUTATIONS_MAX
2834 );
2835 let revision = segment.checkpoint_revision();
2836 let id = EntityId::new(CHECKPOINT_MUTATIONS_MAX as u64 + 2);
2837 segment
2838 .bulk_insert(vec![keyed_row(id.raw(), "next", 1, 0)])
2839 .expect("later batch");
2840 assert_eq!(segment.checkpoint_mutations_since(revision), Some(vec![id]));
2841 assert_eq!(segment.checkpoint_mutations.len(), CHECKPOINT_MUTATIONS_MAX);
2842 }
2843
2844 #[test]
2845 fn key_lookup_visits_only_requested_key_versions() {
2846 let mut segment = GrowingSegment::new(1, "keys");
2847 segment
2848 .bulk_insert(
2849 (1..=4096)
2850 .map(|id| keyed_row(id, &format!("other-{id}"), 1, 0))
2851 .collect(),
2852 )
2853 .expect("unrelated rows");
2854 segment
2855 .insert(keyed_row(5000, "wanted", 2, 8))
2856 .expect("old version");
2857 segment
2858 .insert(keyed_row(5001, "wanted", 8, 0))
2859 .expect("new version");
2860 let mut visits = 0;
2861 let result = segment.find_key_entity("wanted", true, &mut |entity| {
2862 visits += 1;
2863 entity.xmin < 8
2864 });
2865 assert_eq!(result.map(|entity| entity.id), Some(EntityId::new(5000)));
2866 assert_eq!(
2867 visits, 2,
2868 "unrelated rows must never reach visibility evaluation"
2869 );
2870 assert_eq!(keyed_probe(&segment, "wanted"), Some(EntityId::new(5001)));
2871 let mut misses = 0;
2872 assert!(segment
2873 .find_key_entity("absent", true, &mut |_| {
2874 misses += 1;
2875 true
2876 })
2877 .is_none());
2878 assert_eq!(misses, 0);
2879
2880 let mut collision = SegmentKeyLookup::default();
2882 collision.insert(&keyed_row(1, "wanted", 1, 0));
2883 assert!(segment
2884 .find_key_candidate(&collision, "wanted", true, &mut |_| true)
2885 .is_none());
2886 }
2887
2888 #[test]
2889 fn raw_mutation_does_not_disable_unrelated_key_candidates() {
2890 let mut segment = GrowingSegment::new(1, "keys");
2891 segment
2892 .bulk_insert(
2893 (1..=4096)
2894 .map(|id| keyed_row(id, &format!("key-{id}"), 1, 0))
2895 .collect(),
2896 )
2897 .expect("rows");
2898 for _ in 0..3 {
2899 *segment.get_mut(EntityId::new(1)).expect("mutable") = keyed_row(1, "renamed", 2, 0);
2900 }
2901 let index = segment.key_lookup.as_ref().expect("index retained");
2902 assert_eq!(index.candidates("key-4096"), &[EntityId::new(4096)]);
2903 assert_eq!(index.mutated_candidates().count(), 1);
2904 assert!(index.candidates("key-1").is_empty());
2905 assert_eq!(keyed_probe(&segment, "renamed"), Some(EntityId::new(1)));
2906 assert_eq!(keyed_probe(&segment, "key-4096"), Some(EntityId::new(4096)));
2907 segment.delete(EntityId::new(1)).expect("delete raw row");
2908 assert_eq!(
2909 segment
2910 .key_lookup
2911 .as_ref()
2912 .unwrap()
2913 .mutated_candidates()
2914 .count(),
2915 0
2916 );
2917 assert_eq!(keyed_probe(&segment, "renamed"), None);
2918 }
2919
2920 #[test]
2921 fn key_lookup_tracks_mutations_sealing_and_adoption() {
2922 let mut segment = GrowingSegment::new(1, "keys");
2923 segment
2924 .bulk_insert(vec![keyed_row(1, "flat", 1, 0)])
2925 .expect("flat row");
2926 segment
2927 .insert(keyed_row(3, "hash", 1, 0))
2928 .expect("hash fallback");
2929 segment
2930 .bulk_insert(vec![keyed_row(5, "gap", 1, 0)])
2931 .expect("bulk gap");
2932 assert!(segment.key_lookup.as_ref().expect("index").memory_bytes() > 0);
2933 assert_eq!(keyed_probe(&segment, "gap"), Some(EntityId::new(5)));
2934 segment
2935 .update(keyed_row(3, "renamed", 2, 0))
2936 .expect("rename key");
2937 assert!(
2938 segment.key_lookup.is_some(),
2939 "key replacements maintain the index"
2940 );
2941 assert_eq!(keyed_probe(&segment, "hash"), None);
2942 assert_eq!(keyed_probe(&segment, "renamed"), Some(EntityId::new(3)));
2943
2944 *segment.get_mut(EntityId::new(3)).expect("mutable row") = keyed_row(3, "raw", 3, 0);
2945 assert!(
2946 segment.key_lookup.is_some(),
2947 "raw mutation preserves unaffected key candidates"
2948 );
2949 assert_eq!(keyed_probe(&segment, "renamed"), None);
2950 assert_eq!(keyed_probe(&segment, "raw"), Some(EntityId::new(3)));
2951 segment.seal().expect("seal");
2952 segment
2953 .force_update_with_metadata(&keyed_row(5, "sealed", 4, 0), &["key".into()], None)
2954 .expect("sealed rename");
2955 assert_eq!(keyed_probe(&segment, "gap"), None);
2956 assert_eq!(keyed_probe(&segment, "sealed"), Some(EntityId::new(5)));
2957 assert!(segment.force_delete(EntityId::new(1)));
2958 assert!(segment.force_delete(EntityId::new(3)));
2959 assert_eq!(keyed_probe(&segment, "flat"), None);
2960 assert_eq!(keyed_probe(&segment, "raw"), None);
2961
2962 let mut adopted = GrowingSegment::new(2, "keys");
2963 segment.for_each_fast(|entity| {
2964 adopted.adopt_entity(entity.clone(), None);
2965 true
2966 });
2967 assert_eq!(keyed_probe(&adopted, "sealed"), Some(EntityId::new(5)));
2968 assert!(adopted.evict_entity(EntityId::new(5)));
2969 assert_eq!(keyed_probe(&adopted, "sealed"), None);
2970 }
2971
2972 #[test]
2973 #[ignore = "diagnostic microbenchmark; no timing assertions"]
2974 fn key_lookup_scaling_diagnostic() {
2975 use std::{hint::black_box, time::Instant};
2976 for count in [100, 10_000, 100_000] {
2977 let mut segment = GrowingSegment::new(1, "keys");
2978 segment
2979 .bulk_insert(
2980 (1..=count)
2981 .map(|id| keyed_row(id, &format!("key-{id}"), 1, 0))
2982 .collect(),
2983 )
2984 .expect("rows");
2985 let key = format!("key-{count}");
2986 let started = Instant::now();
2987 for _ in 0..1000 {
2988 assert_eq!(
2989 keyed_probe(black_box(&segment), black_box(&key)),
2990 Some(EntityId::new(count))
2991 );
2992 }
2993 let indexed = started.elapsed();
2994 let started = Instant::now();
2995 for _ in 0..1000 {
2996 let mut found = None;
2997 segment.for_each_fast(|entity| {
2998 if SegmentKeyLookup::key(entity) == Some(black_box(key.as_str())) {
2999 found = Some(entity.id);
3000 return false;
3001 }
3002 true
3003 });
3004 assert_eq!(black_box(found), Some(EntityId::new(count)));
3005 }
3006 eprintln!(
3007 "rows={count} reads=1000 indexed={indexed:?} scan={:?}",
3008 started.elapsed()
3009 );
3010 }
3011 }
3012
3013 fn pruning_node(id: u64) -> UnifiedEntity {
3014 UnifiedEntity::graph_node(EntityId::new(id), "mixed", "person", Default::default())
3015 }
3016
3017 fn pruning_edge(id: u64) -> UnifiedEntity {
3018 UnifiedEntity::graph_edge(
3019 EntityId::new(id),
3020 "mixed",
3021 "a",
3022 "b",
3023 1.0,
3024 Default::default(),
3025 )
3026 }
3027
3028 #[test]
3029 fn graph_presence_covers_bulk_gaps_inserts_and_adoption() {
3030 let mut segment = GrowingSegment::new(1, "mixed");
3031 assert!(!segment.may_contain_graph_kind(GraphEntityKind::Node));
3032 assert!(!segment.may_contain_graph_kind(GraphEntityKind::Edge));
3033 segment
3034 .bulk_insert(vec![
3035 UnifiedEntity::vector(EntityId::new(1), "mixed", vec![]),
3036 pruning_node(2),
3037 pruning_edge(4),
3038 ])
3039 .expect("mixed bulk with gap");
3040 assert!(segment.may_contain_graph_kind(GraphEntityKind::Node));
3041 assert!(segment.may_contain_graph_kind(GraphEntityKind::Edge));
3042 segment.seal().expect("seal");
3043 assert!(segment.force_delete(EntityId::new(2)));
3044 assert!(segment.force_delete(EntityId::new(4)));
3045 assert!(segment.may_contain_graph_kind(GraphEntityKind::Node));
3047 let mut rebuilt = GrowingSegment::new(2, "mixed");
3048 for item in segment.iter() {
3049 rebuilt.adopt_entity(item.clone(), None);
3050 }
3051 assert!(!rebuilt.may_contain_graph_kind(GraphEntityKind::Node));
3052 assert!(!rebuilt.may_contain_graph_kind(GraphEntityKind::Edge));
3053 rebuilt.adopt_entity(pruning_node(5), None);
3054 assert!(rebuilt.may_contain_graph_kind(GraphEntityKind::Node));
3055 rebuilt.insert(pruning_edge(6)).expect("single edge");
3056 assert!(rebuilt.may_contain_graph_kind(GraphEntityKind::Edge));
3057 }
3058
3059 #[test]
3060 fn graph_presence_covers_replacement_and_unrestricted_mutation() {
3061 for flat in [false, true] {
3062 for operation in 0..4 {
3063 let mut segment = GrowingSegment::new(1, "mixed");
3064 let vector = UnifiedEntity::vector(EntityId::new(1), "mixed", vec![]);
3065 if flat {
3066 segment.bulk_insert(vec![vector]).expect("flat vector");
3067 } else {
3068 segment.insert(vector).expect("hashmap vector");
3069 }
3070 match operation {
3071 0 => segment.update(pruning_node(1)).expect("replace with node"),
3072 1 => segment
3073 .update_hot(pruning_node(1), &[])
3074 .expect("hot replacement"),
3075 2 => {
3076 segment.seal().expect("seal vector");
3077 segment
3078 .force_update_with_metadata(&pruning_node(1), &[], None)
3079 .expect("sealed replacement");
3080 }
3081 _ => {
3082 *segment.get_mut(EntityId::new(1)).expect("mutable vector") =
3083 pruning_node(1)
3084 }
3085 }
3086 assert!(segment.may_contain_graph_kind(GraphEntityKind::Node));
3087 assert!(matches!(
3088 segment.get(EntityId::new(1)).expect("node").kind,
3089 EntityKind::GraphNode(_)
3090 ));
3091 if operation == 3 {
3092 assert!(segment.may_contain_graph_kind(GraphEntityKind::Edge));
3093 }
3094 segment
3095 .force_update_with_metadata(&pruning_edge(1), &[], None)
3096 .expect("replace with edge");
3097 assert!(segment.may_contain_graph_kind(GraphEntityKind::Edge));
3098 assert!(segment.may_contain_graph_kind(GraphEntityKind::Node));
3099 }
3100 }
3101 }
3102
3103 fn primary_key_row(id: u64, key: Value) -> UnifiedEntity {
3104 UnifiedEntity::new(
3105 EntityId::new(id),
3106 EntityKind::TableRow {
3107 table: "keys".into(),
3108 row_id: id,
3109 },
3110 EntityData::Row(RowData::with_names(
3111 vec![Value::text("payload"), key],
3112 vec!["payload".into(), "primary".into()],
3113 )),
3114 )
3115 }
3116
3117 fn primary_key_probe(
3118 segment: &GrowingSegment,
3119 key: Value,
3120 exclude: Option<EntityId>,
3121 ) -> Option<EntityId> {
3122 segment.find_unique_key_conflict(
3123 &[vec!["primary".into()]],
3124 0,
3125 &[format!("{key:?}")],
3126 exclude,
3127 &|_| true,
3128 )
3129 }
3130
3131 #[test]
3132 fn unique_key_lookup_follows_segment_mutations_and_sealing() {
3133 let mut segment = GrowingSegment::new(1, "keys");
3134 segment
3135 .bulk_insert(vec![primary_key_row(1, Value::Integer(10))])
3136 .expect("bulk row");
3137 let payload_bytes = segment.memory_bytes();
3138 assert_eq!(
3139 primary_key_probe(&segment, Value::Integer(10), None),
3140 Some(EntityId::new(1))
3141 );
3142 assert!(segment.memory_bytes() > payload_bytes);
3143 segment
3144 .insert(primary_key_row(3, Value::Integer(30)))
3145 .expect("hashmap fallback row");
3146 segment
3147 .bulk_insert(vec![primary_key_row(4, Value::Integer(40))])
3148 .expect("bulk gap row");
3149 assert_eq!(
3150 primary_key_probe(&segment, Value::Integer(40), None),
3151 Some(EntityId::new(4))
3152 );
3153 assert_eq!(
3154 primary_key_probe(&segment, Value::Integer(30), None),
3155 Some(EntityId::new(3))
3156 );
3157 segment
3158 .update_hot(primary_key_row(1, Value::Integer(11)), &["primary".into()])
3159 .expect("replace flat key");
3160 assert_eq!(segment.unique_key_lookup_bytes.load(Ordering::Relaxed), 0);
3161 assert_eq!(primary_key_probe(&segment, Value::Integer(10), None), None);
3162 assert_eq!(
3163 primary_key_probe(&segment, Value::Integer(11), None),
3164 Some(EntityId::new(1))
3165 );
3166 segment
3167 .delete_batch(&[EntityId::new(1), EntityId::new(3)])
3168 .expect("delete flat and map");
3169 assert_eq!(primary_key_probe(&segment, Value::Integer(11), None), None);
3170 assert_eq!(primary_key_probe(&segment, Value::Integer(30), None), None);
3171 segment.seal().expect("seal");
3172 assert_eq!(
3173 primary_key_probe(&segment, Value::Integer(40), None),
3174 Some(EntityId::new(4))
3175 );
3176 segment
3177 .force_update_with_metadata(
3178 &primary_key_row(4, Value::Integer(41)),
3179 &["primary".into()],
3180 None,
3181 )
3182 .expect("sealed update");
3183 assert_eq!(primary_key_probe(&segment, Value::Integer(40), None), None);
3184 assert_eq!(
3185 primary_key_probe(&segment, Value::Integer(41), None),
3186 Some(EntityId::new(4))
3187 );
3188 assert!(segment.force_delete(EntityId::new(4)));
3189 assert_eq!(primary_key_probe(&segment, Value::Integer(41), None), None);
3190 }
3191
3192 #[test]
3193 fn unique_lookup_retains_all_current_definitions_and_rechecks_candidates() {
3194 let mut segment = GrowingSegment::new(1, "keys");
3195 segment
3196 .bulk_insert(vec![
3197 primary_key_row(1, Value::Integer(10)),
3198 primary_key_row(2, Value::Integer(20)),
3199 ])
3200 .expect("initial rows");
3201 let definitions = vec![
3202 vec!["primary".into()],
3203 vec!["payload".into()],
3204 vec!["primary".into(), "payload".into()],
3205 ];
3206 let payload = format!("{:?}", Value::text("payload"));
3207 assert_eq!(
3208 segment.find_unique_key_conflict(
3209 &definitions,
3210 1,
3211 std::slice::from_ref(&payload),
3212 None,
3213 &|entity| entity.id != EntityId::new(1)
3214 ),
3215 Some(EntityId::new(2))
3216 );
3217 assert_eq!(segment.unique_key_lookup.read().len(), 3);
3218 segment
3219 .insert(primary_key_row(3, Value::Integer(30)))
3220 .expect("append");
3221 assert_eq!(
3222 segment.find_unique_key_conflict(
3223 &definitions,
3224 2,
3225 &[format!("{:?}", Value::Integer(30)), payload.clone()],
3226 None,
3227 &|_| true
3228 ),
3229 Some(EntityId::new(3))
3230 );
3231 let bytes = segment.unique_key_lookup_bytes.load(Ordering::Relaxed);
3232 assert_eq!(
3233 segment.find_unique_key_conflict(
3234 &definitions,
3235 0,
3236 &[format!("{:?}", Value::Integer(20))],
3237 None,
3238 &|_| true
3239 ),
3240 Some(EntityId::new(2))
3241 );
3242 assert_eq!(segment.unique_key_lookup.read().len(), 3);
3243 assert_eq!(
3244 segment.unique_key_lookup_bytes.load(Ordering::Relaxed),
3245 bytes
3246 );
3247 let reduced = vec![vec!["payload".into()]];
3248 assert_eq!(
3249 segment.find_unique_key_conflict(&reduced, 0, &[payload], None, &|_| true),
3250 Some(EntityId::new(1))
3251 );
3252 assert_eq!(segment.unique_key_lookup.read().len(), 1);
3253 assert!(segment.unique_key_lookup_bytes.load(Ordering::Relaxed) < bytes);
3254 }
3255
3256 #[test]
3257 fn unique_key_lookup_preserves_signature_semantics_and_exclusion() {
3258 let mut segment = GrowingSegment::new(1, "keys");
3259 let values = [
3260 Value::Float(f64::NAN),
3261 Value::Float(-0.0),
3262 Value::Float(0.0),
3263 Value::Integer(1),
3264 Value::UnsignedInteger(1),
3265 Value::text("1"),
3266 ];
3267 for (offset, value) in values.iter().enumerate() {
3268 let id = offset as u64 + 1;
3269 segment
3270 .insert(primary_key_row(id, value.clone()))
3271 .expect("distinct physical row");
3272 assert_eq!(
3273 primary_key_probe(&segment, value.clone(), None),
3274 Some(EntityId::new(id))
3275 );
3276 assert_eq!(
3277 primary_key_probe(&segment, value.clone(), Some(EntityId::new(id))),
3278 None
3279 );
3280 }
3281 segment
3282 .insert(primary_key_row(20, Value::Integer(1)))
3283 .expect("duplicate physical key");
3284 assert_eq!(
3285 primary_key_probe(&segment, Value::Integer(1), Some(EntityId::new(4))),
3286 Some(EntityId::new(20))
3287 );
3288 segment
3291 .entities
3292 .insert(EntityId::new(4), primary_key_row(4, Value::Integer(99)));
3293 assert_eq!(
3294 primary_key_probe(&segment, Value::Integer(1), Some(EntityId::new(20))),
3295 None
3296 );
3297 let entity = segment.get_mut(EntityId::new(4)).expect("mutable row");
3299 *entity = primary_key_row(4, Value::Integer(100));
3300 assert_eq!(
3301 primary_key_probe(&segment, Value::Integer(100), None),
3302 Some(EntityId::new(4))
3303 );
3304 }
3305
3306 #[test]
3307 fn test_growing_segment_basic() {
3308 let mut segment = GrowingSegment::new(1, "test");
3309
3310 let entity = UnifiedEntity::table_row(
3311 EntityId::new(1),
3312 "users",
3313 1,
3314 vec![Value::text("Alice".to_string())],
3315 );
3316
3317 let id = segment.insert(entity).unwrap();
3318 assert_eq!(id, EntityId::new(1));
3319 assert!(segment.contains(id));
3320
3321 let stats = segment.stats();
3322 assert_eq!(stats.entity_count, 1);
3323 assert_eq!(stats.row_count, 1);
3324 }
3325
3326 #[test]
3327 fn test_segment_metadata() {
3328 let mut segment = GrowingSegment::new(1, "test");
3329
3330 let entity = UnifiedEntity::table_row(
3331 EntityId::new(1),
3332 "users",
3333 1,
3334 vec![Value::text("Alice".to_string())],
3335 );
3336 segment.insert(entity).unwrap();
3337
3338 let mut meta = Metadata::new();
3339 meta.set("role", MetadataValue::String("admin".to_string()));
3340 meta.set("level", MetadataValue::Int(5));
3341
3342 segment.set_metadata(EntityId::new(1), meta).unwrap();
3343
3344 let retrieved = segment.get_metadata(EntityId::new(1)).unwrap();
3345 assert_eq!(
3346 retrieved.get("role"),
3347 Some(&MetadataValue::String("admin".to_string()))
3348 );
3349 }
3350
3351 #[test]
3352 fn test_segment_seal() {
3353 let mut segment = GrowingSegment::new(1, "test");
3354
3355 let entity = UnifiedEntity::vector(EntityId::new(1), "embeddings", vec![0.1, 0.2, 0.3]);
3356 segment.insert(entity).unwrap();
3357
3358 assert!(segment.state().is_writable());
3360
3361 segment.seal().unwrap();
3363 assert_eq!(segment.state(), SegmentState::Sealed);
3364
3365 let entity2 = UnifiedEntity::vector(EntityId::new(2), "embeddings", vec![0.4, 0.5, 0.6]);
3367 assert!(segment.insert(entity2).is_err());
3368 }
3369
3370 #[test]
3371 fn test_should_seal() {
3372 let mut segment = GrowingSegment::new(1, "test");
3373
3374 let config = SegmentConfig {
3375 max_entities: 2,
3376 ..Default::default()
3377 };
3378
3379 assert!(!segment.should_seal(&config));
3380
3381 segment
3382 .insert(UnifiedEntity::vector(EntityId::new(1), "v", vec![0.1]))
3383 .unwrap();
3384 assert!(!segment.should_seal(&config));
3385
3386 segment
3387 .insert(UnifiedEntity::vector(EntityId::new(2), "v", vec![0.2]))
3388 .unwrap();
3389 assert!(segment.should_seal(&config));
3390 }
3391
3392 #[test]
3393 fn test_cross_references() {
3394 let mut segment = GrowingSegment::new(1, "test");
3395
3396 let mut entity1 = UnifiedEntity::table_row(
3397 EntityId::new(1),
3398 "hosts",
3399 1,
3400 vec![Value::text("192.168.1.1".to_string())],
3401 );
3402 entity1.add_cross_ref(CrossRef::new(
3403 EntityId::new(1),
3404 EntityId::new(2),
3405 "nodes",
3406 RefType::RowToNode,
3407 ));
3408 segment.insert(entity1).unwrap();
3409
3410 let refs_from = segment.get_references_from(EntityId::new(1));
3411 assert_eq!(refs_from.len(), 1);
3412 assert_eq!(refs_from[0], (EntityId::new(2), RefType::RowToNode));
3413
3414 let refs_to = segment.get_references_to(EntityId::new(2));
3415 assert_eq!(refs_to.len(), 1);
3416 assert_eq!(refs_to[0], (EntityId::new(1), RefType::RowToNode));
3417 }
3418
3419 #[test]
3420 fn test_zone_predicate_uses_canonical_fallback_for_email_values() {
3421 let mut zone = ColZone::new(Value::Email("bravo@example.com".to_string()));
3422 zone.update(&Value::Email("delta@example.com".to_string()));
3423
3424 let probe = Value::Email("alpha@example.com".to_string());
3425 assert!(ZoneColPred::Eq(&probe).can_skip(&zone));
3426
3427 let in_range = Value::Email("charlie@example.com".to_string());
3428 assert!(!ZoneColPred::Eq(&in_range).can_skip(&zone));
3429 }
3430
3431 #[test]
3432 fn test_sealed_multi_zone_prunes_numeric_gap_outlier() {
3433 let mut segment = GrowingSegment::new(1, "test");
3434
3435 for (row_id, age) in [(1_u64, 1_i64), (2, 2), (3, 3), (4, 1000)] {
3436 let entity = UnifiedEntity::new(
3437 EntityId::new(row_id),
3438 EntityKind::TableRow {
3439 table: "users".into(),
3440 row_id,
3441 },
3442 EntityData::Row(RowData::with_names(
3443 vec![Value::Integer(age)],
3444 vec!["age".to_string()],
3445 )),
3446 );
3447 segment.insert(entity).unwrap();
3448 }
3449
3450 segment.seal().unwrap();
3451
3452 let miss = Value::Integer(500);
3453 assert!(segment.can_skip_zone_preds(&[("age", ZoneColPred::Eq(&miss))]));
3454
3455 let hit = Value::Integer(1000);
3456 assert!(!segment.can_skip_zone_preds(&[("age", ZoneColPred::Eq(&hit))]));
3457 }
3458}