1use std::collections::{BTreeMap, HashMap, HashSet};
32use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
33use std::time::{SystemTime, UNIX_EPOCH};
34
35use super::entity::{CrossRef, EntityData, EntityId, EntityKind, RefType, UnifiedEntity};
36use super::metadata::{Metadata, MetadataStorage};
37use crate::storage::primitives::bloom::BloomFilter;
38use crate::storage::query::value_compare::partial_compare_values;
39use crate::storage::schema::{value_to_canonical_key, CanonicalKey, Value};
40
41pub type SegmentId = u64;
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum SegmentState {
47 Growing,
49 Sealing,
51 Sealed,
53 Flushed,
55 Archived,
57}
58
59impl SegmentState {
60 pub fn is_writable(&self) -> bool {
62 matches!(self, Self::Growing)
63 }
64
65 pub fn is_queryable(&self) -> bool {
67 !matches!(self, Self::Sealing)
68 }
69
70 pub fn is_immutable(&self) -> bool {
72 matches!(self, Self::Sealed | Self::Flushed | Self::Archived)
73 }
74}
75
76#[derive(Debug, Clone)]
78pub struct SegmentConfig {
79 pub max_entities: usize,
81 pub max_bytes: usize,
83 pub max_age_secs: u64,
85 pub build_vector_index: bool,
87 pub build_graph_index: bool,
89 pub compression_level: u8,
91}
92
93impl Default for SegmentConfig {
94 fn default() -> Self {
95 Self {
96 max_entities: 100_000,
97 max_bytes: 256 * 1024 * 1024, max_age_secs: 3600, build_vector_index: true,
100 build_graph_index: true,
101 compression_level: 6,
102 }
103 }
104}
105
106#[derive(Debug, Clone, Default)]
108pub struct SegmentStats {
109 pub entity_count: usize,
111 pub deleted_count: usize,
113 pub memory_bytes: usize,
115 pub vector_count: usize,
117 pub node_count: usize,
119 pub edge_count: usize,
121 pub row_count: usize,
123 pub cross_ref_count: usize,
125}
126
127#[derive(Debug, Clone)]
129pub enum SegmentError {
130 NotWritable,
132 NotFound(EntityId),
134 AlreadyExists(EntityId),
136 Full,
138 InvalidState(SegmentState),
140 Internal(String),
142}
143
144impl std::fmt::Display for SegmentError {
145 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146 match self {
147 Self::NotWritable => write!(f, "segment is not writable"),
148 Self::NotFound(id) => write!(f, "entity not found: {}", id),
149 Self::AlreadyExists(id) => write!(f, "entity already exists: {}", id),
150 Self::Full => write!(f, "segment is full"),
151 Self::InvalidState(state) => write!(f, "invalid operation for state: {:?}", state),
152 Self::Internal(msg) => write!(f, "internal error: {}", msg),
153 }
154 }
155}
156
157impl std::error::Error for SegmentError {}
158
159fn current_unix_secs() -> u64 {
160 SystemTime::now()
161 .duration_since(UNIX_EPOCH)
162 .unwrap_or_default()
163 .as_secs()
164}
165
166const SEALED_MULTI_ZONE_MAX_INTERVALS: usize = 4;
167
168const TOMBSTONE_ENTRY_BYTES: u64 = 16;
176
177#[derive(Debug, Clone)]
178struct UpdateIndexSnapshot {
179 pk_column_name: Option<String>,
180 pk_value: Option<Value>,
181 pk_index_key: Option<(String, String)>,
182 cross_refs: Vec<CrossRef>,
183}
184
185impl UpdateIndexSnapshot {
186 fn from_entity(entity: &UnifiedEntity) -> Self {
187 let (pk_column_name, pk_value) = match &entity.data {
188 EntityData::Row(row) => (
189 row.schema
190 .as_deref()
191 .and_then(|schema| schema.first().cloned()),
192 row.columns.first().cloned(),
193 ),
194 _ => (None, None),
195 };
196 let pk_index_key = pk_value
197 .as_ref()
198 .map(|value| (entity.kind.collection().to_string(), format!("{:?}", value)));
199 Self {
200 pk_column_name,
201 pk_value,
202 pk_index_key,
203 cross_refs: entity.cross_refs().to_vec(),
204 }
205 }
206}
207
208pub trait UnifiedSegment: Send + Sync {
210 fn id(&self) -> SegmentId;
212
213 fn state(&self) -> SegmentState;
215
216 fn collection(&self) -> &str;
218
219 fn stats(&self) -> SegmentStats;
221
222 fn entity_count(&self) -> usize;
224
225 fn contains(&self, id: EntityId) -> bool;
227
228 fn get(&self, id: EntityId) -> Option<&UnifiedEntity>;
230
231 fn get_mut(&mut self, id: EntityId) -> Option<&mut UnifiedEntity>;
233
234 fn insert(&mut self, entity: UnifiedEntity) -> Result<EntityId, SegmentError>;
236
237 fn update(&mut self, entity: UnifiedEntity) -> Result<(), SegmentError>;
239
240 fn update_hot(
244 &mut self,
245 entity: UnifiedEntity,
246 modified_columns: &[String],
247 ) -> Result<(), SegmentError> {
248 let _ = modified_columns;
249 self.update(entity)
250 }
251
252 fn delete(&mut self, id: EntityId) -> Result<bool, SegmentError>;
254
255 fn get_metadata(&self, id: EntityId) -> Option<Metadata>;
257
258 fn set_metadata(&mut self, id: EntityId, metadata: Metadata) -> Result<(), SegmentError>;
260
261 fn seal(&mut self) -> Result<(), SegmentError>;
263
264 fn should_seal(&self, config: &SegmentConfig) -> bool;
266
267 fn iter(&self) -> Box<dyn Iterator<Item = &UnifiedEntity> + '_>;
269
270 fn iter_kind(&self, kind_filter: &str) -> Box<dyn Iterator<Item = &UnifiedEntity> + '_>;
272
273 fn filter_metadata(
275 &self,
276 filters: &[(String, super::metadata::MetadataFilter)],
277 ) -> Vec<EntityId>;
278}
279
280#[derive(Debug, Clone)]
287pub struct ColZone {
288 pub min: Value,
289 pub max: Value,
290 min_key: Option<CanonicalKey>,
291 max_key: Option<CanonicalKey>,
292}
293
294impl ColZone {
295 fn new(v: Value) -> Self {
296 Self {
297 min_key: value_to_canonical_key(&v),
298 max_key: value_to_canonical_key(&v),
299 min: v.clone(),
300 max: v,
301 }
302 }
303
304 fn with_bounds(min: Value, max: Value) -> Self {
305 Self {
306 min_key: value_to_canonical_key(&min),
307 max_key: value_to_canonical_key(&max),
308 min,
309 max,
310 }
311 }
312
313 fn update(&mut self, v: &Value) {
314 if compare_zone_values(v, None, &self.min, self.min_key.as_ref())
315 .map(|o| o == std::cmp::Ordering::Less)
316 .unwrap_or(false)
317 {
318 self.min = v.clone();
319 self.min_key = value_to_canonical_key(v);
320 }
321 if compare_zone_values(v, None, &self.max, self.max_key.as_ref())
322 .map(|o| o == std::cmp::Ordering::Greater)
323 .unwrap_or(false)
324 {
325 self.max = v.clone();
326 self.max_key = value_to_canonical_key(v);
327 }
328 }
329}
330
331#[derive(Debug, Clone, Default)]
332pub struct MultiColZone {
333 pub intervals: Vec<ColZone>,
334}
335
336impl MultiColZone {
337 fn can_skip(&self, pred: &ZoneColPred<'_>) -> bool {
338 !self.intervals.is_empty() && self.intervals.iter().all(|zone| pred.can_skip(zone))
339 }
340}
341
342fn compare_zone_values(
343 left: &Value,
344 left_key: Option<&CanonicalKey>,
345 right: &Value,
346 right_key: Option<&CanonicalKey>,
347) -> Option<std::cmp::Ordering> {
348 partial_compare_values(left, right).or_else(|| {
349 let left_key = left_key.cloned().or_else(|| value_to_canonical_key(left))?;
350 let right_key = right_key
351 .cloned()
352 .or_else(|| value_to_canonical_key(right))?;
353 (left_key.family() == right_key.family()).then(|| left_key.cmp(&right_key))
354 })
355}
356
357#[derive(Debug, Clone, Copy, PartialEq, Eq)]
359pub enum ZoneColPredKind {
360 Eq,
361 Gt,
362 Gte,
363 Lt,
364 Lte,
365}
366
367#[derive(Debug, Clone)]
369pub enum ZoneColPred<'a> {
370 Eq(&'a Value),
371 Gt(&'a Value),
372 Gte(&'a Value),
373 Lt(&'a Value),
374 Lte(&'a Value),
375}
376
377impl<'a> ZoneColPred<'a> {
378 pub fn can_skip(&self, zone: &ColZone) -> bool {
380 match self {
381 ZoneColPred::Eq(val) => {
383 compare_zone_values(val, None, &zone.min, zone.min_key.as_ref())
384 .map(|o| o == std::cmp::Ordering::Less)
385 .unwrap_or(false)
386 || compare_zone_values(val, None, &zone.max, zone.max_key.as_ref())
387 .map(|o| o == std::cmp::Ordering::Greater)
388 .unwrap_or(false)
389 }
390 ZoneColPred::Gt(val) => {
392 compare_zone_values(&zone.max, zone.max_key.as_ref(), val, None)
393 .map(|o| o != std::cmp::Ordering::Greater)
394 .unwrap_or(false)
395 }
396 ZoneColPred::Gte(val) => {
398 compare_zone_values(&zone.max, zone.max_key.as_ref(), val, None)
399 .map(|o| o == std::cmp::Ordering::Less)
400 .unwrap_or(false)
401 }
402 ZoneColPred::Lt(val) => {
404 compare_zone_values(&zone.min, zone.min_key.as_ref(), val, None)
405 .map(|o| o != std::cmp::Ordering::Less)
406 .unwrap_or(false)
407 }
408 ZoneColPred::Lte(val) => {
410 compare_zone_values(&zone.min, zone.min_key.as_ref(), val, None)
411 .map(|o| o == std::cmp::Ordering::Greater)
412 .unwrap_or(false)
413 }
414 }
415 }
416}
417
418pub struct GrowingSegment {
420 id: SegmentId,
422 collection: String,
424 state: SegmentState,
426 created_at: u64,
428 last_write_at: u64,
430
431 entities: HashMap<EntityId, UnifiedEntity>,
433 flat_entities: Vec<UnifiedEntity>,
436 base_entity_id: u64,
438 use_flat: bool,
440 deleted: HashSet<EntityId>,
442 metadata: MetadataStorage,
444
445 pk_index: BTreeMap<(String, String), EntityId>,
447 kind_index: HashMap<String, HashSet<EntityId>>,
449 cross_ref_forward: HashMap<EntityId, Vec<(EntityId, RefType)>>,
451 cross_ref_reverse: HashMap<EntityId, Vec<(EntityId, RefType)>>,
453
454 bloom: BloomFilter,
456
457 col_zones: HashMap<String, ColZone>,
459 sealed_col_zones: HashMap<String, MultiColZone>,
461
462 sequence: AtomicU64,
464 memory_bytes: AtomicU64,
469 dead_entity_bytes: u64,
472 dead_resident_count: usize,
474
475 pub(crate) published_flat_len: AtomicUsize,
482}
483
484impl GrowingSegment {
485 #[inline]
488 pub fn for_each_fast<F>(&self, mut f: F) -> bool
489 where
490 F: FnMut(&UnifiedEntity) -> bool,
491 {
492 if self.use_flat {
493 if self.deleted.is_empty() {
495 for entity in &self.flat_entities {
496 if !f(entity) {
497 return false;
498 }
499 }
500 for entity in self.entities.values() {
503 if !f(entity) {
504 return false;
505 }
506 }
507 } else {
508 for entity in &self.flat_entities {
509 if self.deleted.contains(&entity.id) {
510 continue;
511 }
512 if !f(entity) {
513 return false;
514 }
515 }
516 for entity in self.entities.values() {
517 if self.deleted.contains(&entity.id) {
518 continue;
519 }
520 if !f(entity) {
521 return false;
522 }
523 }
524 }
525 } else {
526 if self.deleted.is_empty() {
528 for entity in self.entities.values() {
529 if !f(entity) {
530 return false;
531 }
532 }
533 } else {
534 for entity in self.entities.values() {
535 if self.deleted.contains(&entity.id) {
536 continue;
537 }
538 if !f(entity) {
539 return false;
540 }
541 }
542 }
543 }
544 true
545 }
546
547 pub fn new(id: SegmentId, collection: impl Into<String>) -> Self {
549 Self::with_bloom_capacity(id, collection, 100_000)
550 }
551
552 pub(crate) fn with_bloom_capacity(
557 id: SegmentId,
558 collection: impl Into<String>,
559 expected_entities: usize,
560 ) -> Self {
561 let now = current_unix_secs();
562
563 Self {
564 id,
565 collection: collection.into(),
566 state: SegmentState::Growing,
567 created_at: now,
568 last_write_at: now,
569 entities: HashMap::new(),
570 flat_entities: Vec::new(),
571 base_entity_id: 0,
572 use_flat: false,
573 deleted: HashSet::new(),
574 metadata: MetadataStorage::new(),
575 pk_index: BTreeMap::new(),
576 kind_index: HashMap::new(),
577 cross_ref_forward: HashMap::new(),
578 cross_ref_reverse: HashMap::new(),
579 bloom: BloomFilter::with_capacity(expected_entities.max(1), 0.01),
580 col_zones: HashMap::new(),
581 sealed_col_zones: HashMap::new(),
582 sequence: AtomicU64::new(0),
583 memory_bytes: AtomicU64::new(0),
584 dead_entity_bytes: 0,
585 dead_resident_count: 0,
586 published_flat_len: AtomicUsize::new(0),
587 }
588 }
589
590 fn next_sequence(&self) -> u64 {
592 self.sequence.fetch_add(1, Ordering::SeqCst)
593 }
594
595 fn has_live_entity(&self, id: EntityId) -> bool {
596 if self.deleted.contains(&id) {
597 return false;
598 }
599 if self.use_flat {
600 let raw = id.raw();
601 if raw >= self.base_entity_id {
602 let idx = (raw - self.base_entity_id) as usize;
603 if self
604 .flat_entities
605 .get(idx)
606 .is_some_and(|entity| entity.id == id)
607 {
608 return true;
609 }
610 }
611 self.entities.contains_key(&id)
614 } else {
615 self.entities.contains_key(&id)
616 }
617 }
618
619 fn update_existing_entity_in_place(
620 &mut self,
621 entity: &UnifiedEntity,
622 ) -> Result<UpdateIndexSnapshot, SegmentError> {
623 if self.use_flat {
624 let raw = entity.id.raw();
625 if raw < self.base_entity_id {
626 return Err(SegmentError::NotFound(entity.id));
627 }
628 let idx = (raw - self.base_entity_id) as usize;
629 let Some(slot) = self.flat_entities.get_mut(idx) else {
630 return Err(SegmentError::NotFound(entity.id));
631 };
632 if slot.id != entity.id {
633 return Err(SegmentError::NotFound(entity.id));
634 }
635 let snapshot = UpdateIndexSnapshot::from_entity(slot);
636 slot.clone_from(entity);
637 Ok(snapshot)
638 } else {
639 let Some(slot) = self.entities.get_mut(&entity.id) else {
640 return Err(SegmentError::NotFound(entity.id));
641 };
642 let snapshot = UpdateIndexSnapshot::from_entity(slot);
643 slot.clone_from(entity);
644 Ok(snapshot)
645 }
646 }
647
648 fn apply_hot_update_with_metadata(
649 &mut self,
650 entity: &UnifiedEntity,
651 modified_columns: &[String],
652 metadata: Option<&Metadata>,
653 ) -> Result<(), SegmentError> {
654 let old = self.update_existing_entity_in_place(entity)?;
655 self.reindex_for_update(&old, entity, Some(modified_columns));
656 self.update_col_zones_from_entity(entity);
657 if let Some(metadata) = metadata {
658 self.metadata.set_all(entity.id, metadata);
659 }
660 Ok(())
661 }
662
663 fn apply_update_with_metadata(
664 &mut self,
665 entity: &UnifiedEntity,
666 metadata: Option<&Metadata>,
667 ) -> Result<(), SegmentError> {
668 let old = self.update_existing_entity_in_place(entity)?;
669 self.reindex_for_update(&old, entity, None);
670 self.update_col_zones_from_entity(entity);
671 if let Some(metadata) = metadata {
672 self.metadata.set_all(entity.id, metadata);
673 }
674 Ok(())
675 }
676
677 pub fn update_hot_batch_with_metadata<'a, I>(&mut self, items: I) -> Result<(), SegmentError>
678 where
679 I: IntoIterator<Item = (&'a UnifiedEntity, &'a [String], Option<&'a Metadata>)>,
680 {
681 if !self.state.is_writable() {
682 return Err(SegmentError::NotWritable);
683 }
684
685 let items: Vec<(&UnifiedEntity, &[String], Option<&Metadata>)> =
686 items.into_iter().collect();
687 if items.is_empty() {
688 return Ok(());
689 }
690
691 for (entity, _, _) in &items {
692 if !self.has_live_entity(entity.id) {
693 return Err(SegmentError::NotFound(entity.id));
694 }
695 }
696
697 for (entity, modified_columns, metadata) in items {
698 self.apply_hot_update_with_metadata(entity, modified_columns, metadata)?;
699 }
700
701 self.last_write_at = current_unix_secs();
702 Ok(())
703 }
704
705 fn tombstone_flat_entity(&mut self, idx: usize, id: EntityId) -> bool {
711 if !self.deleted.insert(id) {
712 return false;
713 }
714 let size = Self::estimate_entity_size(&self.flat_entities[idx]) as u64;
715 self.dead_entity_bytes += size;
716 self.dead_resident_count += 1;
717 self.metadata.remove_all(id);
718 true
719 }
720
721 fn remove_hashmap_entity(&mut self, id: EntityId) -> bool {
726 let Some(entity) = self.entities.remove(&id) else {
727 return false;
728 };
729 self.release_memory(Self::estimate_entity_size(&entity));
730 self.unindex_entity(&entity);
731 self.metadata.remove_all(id);
732 self.deleted.insert(id);
733 true
734 }
735
736 pub fn delete_batch(&mut self, ids: &[EntityId]) -> Result<Vec<EntityId>, SegmentError> {
737 if !self.state.is_writable() {
738 return Err(SegmentError::NotWritable);
739 }
740 if ids.is_empty() {
741 return Ok(Vec::new());
742 }
743
744 let mut deleted_ids = Vec::with_capacity(ids.len());
745
746 if self.use_flat {
747 for &id in ids {
748 let raw = id.raw();
749 if raw < self.base_entity_id {
750 if self.remove_hashmap_entity(id) {
751 deleted_ids.push(id);
752 }
753 continue;
754 }
755 let idx = (raw - self.base_entity_id) as usize;
756 if idx < self.flat_entities.len() && self.flat_entities[idx].id == id {
757 if self.tombstone_flat_entity(idx, id) {
758 deleted_ids.push(id);
759 }
760 } else if self.remove_hashmap_entity(id) {
761 deleted_ids.push(id);
762 }
763 }
764 } else {
765 for &id in ids {
766 if self.remove_hashmap_entity(id) {
767 deleted_ids.push(id);
768 }
769 }
770 }
771
772 if !deleted_ids.is_empty() {
773 self.last_write_at = current_unix_secs();
774 }
775
776 Ok(deleted_ids)
777 }
778
779 fn add_memory(&self, bytes: usize) {
781 self.memory_bytes.fetch_add(bytes as u64, Ordering::Relaxed);
782 }
783
784 pub fn memory_bytes(&self) -> u64 {
789 self.memory_bytes.load(Ordering::Relaxed)
790 }
791
792 fn release_memory(&self, bytes: usize) {
795 let mut current = self.memory_bytes.load(Ordering::Relaxed);
796 loop {
797 let next = current.saturating_sub(bytes as u64);
798 match self.memory_bytes.compare_exchange_weak(
799 current,
800 next,
801 Ordering::Relaxed,
802 Ordering::Relaxed,
803 ) {
804 Ok(_) => break,
805 Err(observed) => current = observed,
806 }
807 }
808 }
809
810 pub(crate) fn resident_entity_count(&self) -> usize {
812 self.flat_entities.len() + self.entities.len()
813 }
814
815 pub(crate) fn live_entity_count(&self) -> usize {
817 self.resident_entity_count()
818 .saturating_sub(self.dead_resident_count)
819 }
820
821 pub(crate) fn tombstone_count(&self) -> usize {
824 self.deleted.len()
825 }
826
827 pub(crate) fn resident_bytes(&self) -> u64 {
830 self.memory_bytes.load(Ordering::Relaxed)
831 + self.deleted.len() as u64 * TOMBSTONE_ENTRY_BYTES
832 }
833
834 pub(crate) fn reclaimable_bytes(&self) -> u64 {
837 self.dead_entity_bytes + self.deleted.len() as u64 * TOMBSTONE_ENTRY_BYTES
838 }
839
840 pub(crate) fn live_entity_ids(&self) -> Vec<EntityId> {
846 let mut ids = Vec::with_capacity(self.live_entity_count());
847 self.for_each_fast(|entity| {
848 ids.push(entity.id);
849 true
850 });
851 ids
852 }
853
854 pub(crate) fn adopt_entity(&mut self, entity: UnifiedEntity, metadata: Option<Metadata>) {
862 let id = entity.id;
863
864 let next = entity.sequence_id.saturating_add(1);
867 if self.sequence.load(Ordering::Relaxed) < next {
868 self.sequence.store(next, Ordering::Relaxed);
869 }
870
871 self.add_memory(Self::estimate_entity_size(&entity));
872 self.index_entity(&entity);
873 self.update_col_zones_from_entity(&entity);
874 self.entities.insert(id, entity);
875
876 if let Some(metadata) = metadata {
877 if !metadata.is_empty() {
878 self.metadata.set_all(id, &metadata);
879 }
880 }
881 }
882
883 pub(crate) fn evict_entity(&mut self, id: EntityId) -> bool {
889 let Some(entity) = self.entities.remove(&id) else {
890 return false;
891 };
892 self.release_memory(Self::estimate_entity_size(&entity));
893 self.unindex_entity(&entity);
894 for cross_ref in entity.cross_refs() {
895 if let Some(reverse) = self.cross_ref_reverse.get_mut(&cross_ref.target) {
896 reverse.retain(|(source, _)| *source != id);
897 }
898 }
899 self.metadata.remove_all(id);
900 true
901 }
902
903 fn estimate_entity_size(entity: &UnifiedEntity) -> usize {
905 let mut size = std::mem::size_of::<UnifiedEntity>();
906
907 size += match &entity.data {
909 EntityData::Row(row) => row.columns.len() * 64, EntityData::Node(node) => node.properties.len() * 128,
911 EntityData::Edge(edge) => edge.properties.len() * 128,
912 EntityData::Vector(vec) => {
913 vec.dense.len() * 4 + vec.sparse.as_ref().map_or(0, |s| s.indices.len() * 8)
914 }
915 EntityData::TimeSeries(_) => 64,
916 EntityData::QueueMessage(_) => 128,
917 };
918
919 for emb in entity.embeddings() {
921 size += emb.vector.len() * 4 + emb.name.len() + emb.model.len();
922 }
923
924 size += std::mem::size_of_val(entity.cross_refs());
926
927 size
928 }
929
930 fn update_col_zones_from_entity(&mut self, entity: &UnifiedEntity) {
938 if let EntityData::Row(row) = &entity.data {
939 if let Some(named) = &row.named {
940 for (col, val) in named {
942 if matches!(val, Value::Null) {
943 continue;
944 }
945 self.col_zones
946 .entry(col.clone())
947 .and_modify(|z| z.update(val))
948 .or_insert_with(|| ColZone::new(val.clone()));
949 }
950 } else if let Some(schema) = &row.schema {
951 for (col, val) in schema.iter().zip(row.columns.iter()) {
954 if matches!(val, Value::Null) {
955 continue;
956 }
957 self.col_zones
958 .entry(col.clone())
959 .and_modify(|z| z.update(val))
960 .or_insert_with(|| ColZone::new(val.clone()));
961 }
962 }
963 }
964 }
965
966 fn rebuild_sealed_col_zones(&mut self) {
967 let mut values_by_col: HashMap<String, Vec<(CanonicalKey, Value)>> = HashMap::new();
968 let mut family_by_col: HashMap<String, crate::storage::schema::CanonicalKeyFamily> =
969 HashMap::new();
970 let mut mixed_family_cols = HashSet::new();
971 let mut unsupported_cols = HashSet::new();
972
973 let mut observe_row = |row: &super::entity::RowData| {
974 for (col, value) in row.iter_fields() {
975 if matches!(value, Value::Null) {
976 continue;
977 }
978 let Some(key) = value_to_canonical_key(value) else {
979 unsupported_cols.insert(col.to_string());
980 continue;
981 };
982 match family_by_col.get(col).copied() {
983 Some(existing) if existing != key.family() => {
984 mixed_family_cols.insert(col.to_string());
985 }
986 None => {
987 family_by_col.insert(col.to_string(), key.family());
988 }
989 _ => {}
990 }
991 values_by_col
992 .entry(col.to_string())
993 .or_default()
994 .push((key, value.clone()));
995 }
996 };
997
998 if self.use_flat {
999 for entity in &self.flat_entities {
1000 if self.deleted.contains(&entity.id) {
1001 continue;
1002 }
1003 if let EntityData::Row(row) = &entity.data {
1004 observe_row(row);
1005 }
1006 }
1007 } else {
1008 for entity in self.entities.values() {
1009 if self.deleted.contains(&entity.id) {
1010 continue;
1011 }
1012 if let EntityData::Row(row) = &entity.data {
1013 observe_row(row);
1014 }
1015 }
1016 }
1017
1018 let mut sealed_col_zones = HashMap::new();
1019 for (col, mut entries) in values_by_col {
1020 if mixed_family_cols.contains(&col)
1021 || unsupported_cols.contains(&col)
1022 || entries.is_empty()
1023 {
1024 continue;
1025 }
1026 entries.sort_unstable_by(|left, right| left.0.cmp(&right.0));
1027 entries.dedup_by(|left, right| left.0 == right.0);
1028
1029 let intervals = build_minmax_multi_intervals(&entries, SEALED_MULTI_ZONE_MAX_INTERVALS);
1030 if intervals.len() > 1 {
1031 sealed_col_zones.insert(col, MultiColZone { intervals });
1032 }
1033 }
1034
1035 self.sealed_col_zones = sealed_col_zones;
1036 }
1037
1038 pub fn can_skip_zone_preds(&self, preds: &[(&str, ZoneColPred<'_>)]) -> bool {
1042 if preds.is_empty() {
1043 return false;
1044 }
1045 for (col, pred) in preds {
1046 if let Some(zone) = self.sealed_col_zones.get(*col) {
1047 if zone.can_skip(pred) {
1048 return true;
1049 }
1050 continue;
1051 }
1052 if let Some(zone) = self.col_zones.get(*col) {
1053 if pred.can_skip(zone) {
1054 return true; }
1056 }
1057 }
1058 false
1059 }
1060
1061 fn index_entity(&mut self, entity: &UnifiedEntity) {
1063 let kind_key = entity.kind.storage_type().to_string();
1065 self.kind_index
1066 .entry(kind_key)
1067 .or_default()
1068 .insert(entity.id);
1069
1070 let id_bytes = entity.id.raw().to_le_bytes();
1072 self.bloom.insert(&id_bytes);
1073
1074 if let EntityData::Row(row) = &entity.data {
1076 if let Some(first_col) = row.columns.first() {
1077 let pk_str = format!("{:?}", first_col);
1078 self.bloom.insert(pk_str.as_bytes());
1080 self.pk_index
1081 .insert((entity.kind.collection().to_string(), pk_str), entity.id);
1082 }
1083 }
1084
1085 for cross_ref in entity.cross_refs() {
1087 self.cross_ref_forward
1088 .entry(cross_ref.source)
1089 .or_default()
1090 .push((cross_ref.target, cross_ref.ref_type));
1091
1092 self.cross_ref_reverse
1093 .entry(cross_ref.target)
1094 .or_default()
1095 .push((cross_ref.source, cross_ref.ref_type));
1096 }
1097 }
1098
1099 pub fn bloom_might_contain_key(&self, key: &[u8]) -> bool {
1101 self.bloom.contains(key)
1102 }
1103
1104 pub fn bloom_stats(&self) -> (f64, u32) {
1106 (self.bloom.fill_ratio(), self.bloom.count_set_bits())
1107 }
1108
1109 fn unindex_entity(&mut self, entity: &UnifiedEntity) {
1111 let kind_key = entity.kind.storage_type().to_string();
1113 if let Some(set) = self.kind_index.get_mut(&kind_key) {
1114 set.remove(&entity.id);
1115 }
1116
1117 if let EntityData::Row(row) = &entity.data {
1119 if let Some(first_col) = row.columns.first() {
1120 let pk_str = format!("{:?}", first_col);
1121 self.pk_index
1122 .remove(&(entity.kind.collection().to_string(), pk_str));
1123 }
1124 }
1125
1126 self.cross_ref_forward.remove(&entity.id);
1128 }
1130
1131 fn reindex_for_update(
1143 &mut self,
1144 old: &UpdateIndexSnapshot,
1145 new: &UnifiedEntity,
1146 modified_columns: Option<&[String]>,
1147 ) {
1148 let pk_changed = match &new.data {
1155 EntityData::Row(new_row) => {
1156 if let Some(cols) = modified_columns {
1157 let pk_col_name = old.pk_column_name.as_deref().or_else(|| {
1160 new_row
1161 .schema
1162 .as_deref()
1163 .and_then(|schema| schema.first().map(|name| name.as_str()))
1164 });
1165 match pk_col_name {
1166 Some(pk_name) => cols.iter().any(|c| c.eq_ignore_ascii_case(pk_name)),
1167 None => old.pk_value.as_ref() != new_row.columns.first(),
1169 }
1170 } else {
1171 old.pk_value.as_ref() != new_row.columns.first()
1172 }
1173 }
1174 _ => false,
1176 };
1177
1178 if pk_changed {
1179 if let Some((collection, pk_str)) = &old.pk_index_key {
1181 self.pk_index.remove(&(collection.clone(), pk_str.clone()));
1182 }
1183 if let EntityData::Row(row) = &new.data {
1185 if let Some(first_col) = row.columns.first() {
1186 let pk_str = format!("{:?}", first_col);
1187 self.bloom.insert(pk_str.as_bytes());
1188 self.pk_index
1189 .insert((new.kind.collection().to_string(), pk_str), new.id);
1190 }
1191 }
1192 }
1193
1194 let new_refs = new.cross_refs();
1196 if old.cross_refs.as_slice() != new_refs {
1197 self.cross_ref_forward.remove(&new.id);
1199 for cross_ref in &old.cross_refs {
1201 if let Some(rev) = self.cross_ref_reverse.get_mut(&cross_ref.target) {
1202 rev.retain(|(src, _)| *src != new.id);
1203 }
1204 }
1205 for cross_ref in new_refs {
1207 self.cross_ref_forward
1208 .entry(cross_ref.source)
1209 .or_default()
1210 .push((cross_ref.target, cross_ref.ref_type));
1211 self.cross_ref_reverse
1212 .entry(cross_ref.target)
1213 .or_default()
1214 .push((cross_ref.source, cross_ref.ref_type));
1215 }
1216 }
1217 }
1218
1219 pub fn get_references_to(&self, id: EntityId) -> Vec<(EntityId, RefType)> {
1221 self.cross_ref_reverse.get(&id).cloned().unwrap_or_default()
1222 }
1223
1224 pub fn get_references_from(&self, id: EntityId) -> Vec<(EntityId, RefType)> {
1226 self.cross_ref_forward.get(&id).cloned().unwrap_or_default()
1227 }
1228
1229 pub fn age_secs(&self) -> u64 {
1231 let now = current_unix_secs();
1232 now.saturating_sub(self.created_at)
1233 }
1234
1235 pub fn idle_secs(&self) -> u64 {
1237 let now = current_unix_secs();
1238 now.saturating_sub(self.last_write_at)
1239 }
1240
1241 pub fn bulk_insert(
1250 &mut self,
1251 entities: Vec<UnifiedEntity>,
1252 ) -> Result<Vec<EntityId>, SegmentError> {
1253 if !self.state.is_writable() {
1254 return Err(SegmentError::NotWritable);
1255 }
1256
1257 let n = entities.len();
1258
1259 let kind_key = if let Some(first) = entities.first() {
1261 first.kind.storage_type().to_string()
1262 } else {
1263 return Ok(Vec::new());
1264 };
1265
1266 let kind_set = self.kind_index.entry(kind_key).or_default();
1267 kind_set.reserve(n);
1268
1269 let now = current_unix_secs();
1270
1271 let base_seq = self.sequence.fetch_add(n as u64, Ordering::Relaxed);
1272
1273 let mut ids = Vec::with_capacity(n);
1274
1275 if self.flat_entities.is_empty() && self.entities.is_empty() {
1277 self.base_entity_id = entities.first().map(|e| e.id.raw()).unwrap_or(0);
1279 self.use_flat = true;
1280 }
1281
1282 let mut columnar_zone_updates: Vec<Vec<Value>> = Vec::new();
1292 let mut columnar_schema: Option<std::sync::Arc<Vec<String>>> = None;
1293 let mut named_zone_updates: Vec<(String, Value)> = Vec::new();
1297
1298 let mut batch_bytes: usize = 0;
1303
1304 if self.use_flat {
1305 self.flat_entities.reserve(n);
1306 for (i, mut entity) in entities.into_iter().enumerate() {
1307 entity.sequence_id = base_seq + i as u64;
1308 let id = entity.id;
1309 kind_set.insert(id);
1310 ids.push(id);
1311 batch_bytes += Self::estimate_entity_size(&entity);
1312 if let EntityData::Row(row) = &entity.data {
1313 if row.schema.is_some() && !row.columns.is_empty() {
1314 if columnar_zone_updates.is_empty() {
1315 columnar_zone_updates = vec![Vec::with_capacity(n); row.columns.len()];
1316 columnar_schema = row.schema.clone();
1317 }
1318 for (ci, val) in row.columns.iter().enumerate() {
1319 if !matches!(val, Value::Null) {
1320 if let Some(bucket) = columnar_zone_updates.get_mut(ci) {
1321 bucket.push(val.clone());
1322 }
1323 }
1324 }
1325 } else {
1326 for (col, val) in row.iter_fields() {
1327 if !matches!(val, Value::Null) {
1328 named_zone_updates.push((col.to_string(), val.clone()));
1329 }
1330 }
1331 }
1332 }
1333 let expected = self.base_entity_id + self.flat_entities.len() as u64;
1349 if id.raw() == expected {
1350 self.flat_entities.push(entity);
1351 } else {
1352 self.entities.insert(id, entity);
1353 }
1354 }
1355 } else {
1356 self.entities.reserve(n);
1358 let mut pairs = Vec::with_capacity(n);
1359 for (i, mut entity) in entities.into_iter().enumerate() {
1360 entity.sequence_id = base_seq + i as u64;
1361 let id = entity.id;
1362 kind_set.insert(id);
1363 ids.push(id);
1364 batch_bytes += Self::estimate_entity_size(&entity);
1365 if let EntityData::Row(row) = &entity.data {
1366 for (col, val) in row.iter_fields() {
1367 if !matches!(val, Value::Null) {
1368 named_zone_updates.push((col.to_string(), val.clone()));
1369 }
1370 }
1371 }
1372 pairs.push((id, entity));
1373 }
1374 self.entities.extend(pairs);
1375 }
1376
1377 let _ = kind_set;
1381 if !columnar_zone_updates.is_empty() {
1382 let schema = columnar_schema.as_ref();
1383 for (ci, values) in columnar_zone_updates.into_iter().enumerate() {
1384 if values.is_empty() {
1385 continue;
1386 }
1387 let Some(col_name) = schema.and_then(|s| s.get(ci)) else {
1388 continue;
1389 };
1390 let mut iter = values.into_iter();
1396 if let Some(first) = iter.next() {
1397 let zone = self
1398 .col_zones
1399 .entry(col_name.clone())
1400 .and_modify(|z| z.update(&first))
1401 .or_insert_with(|| ColZone::new(first));
1402 for v in iter {
1403 zone.update(&v);
1404 }
1405 }
1406 }
1407 }
1408 for (col, val) in named_zone_updates {
1409 self.col_zones
1410 .entry(col)
1411 .and_modify(|z| z.update(&val))
1412 .or_insert_with(|| ColZone::new(val));
1413 }
1414
1415 self.add_memory(batch_bytes);
1416 self.last_write_at = now;
1417
1418 if self.use_flat {
1420 self.published_flat_len
1421 .store(self.flat_entities.len(), Ordering::Release);
1422 }
1423
1424 Ok(ids)
1425 }
1426
1427 pub(crate) fn force_delete(&mut self, id: EntityId) -> bool {
1430 if self.use_flat {
1431 let raw = id.raw();
1432 if raw >= self.base_entity_id {
1433 let idx = (raw - self.base_entity_id) as usize;
1434 if idx < self.flat_entities.len() && self.flat_entities[idx].id == id {
1435 self.tombstone_flat_entity(idx, id);
1436 return true;
1437 }
1438 }
1439 return self.remove_hashmap_entity(id);
1440 }
1441
1442 self.remove_hashmap_entity(id)
1443 }
1444
1445 pub(crate) fn force_update_with_metadata(
1448 &mut self,
1449 entity: &UnifiedEntity,
1450 modified_columns: &[String],
1451 metadata: Option<&Metadata>,
1452 ) -> Result<(), SegmentError> {
1453 self.apply_hot_update_with_metadata(entity, modified_columns, metadata)
1454 }
1455}
1456
1457impl UnifiedSegment for GrowingSegment {
1458 fn id(&self) -> SegmentId {
1459 self.id
1460 }
1461
1462 fn state(&self) -> SegmentState {
1463 self.state
1464 }
1465
1466 fn collection(&self) -> &str {
1467 &self.collection
1468 }
1469
1470 fn stats(&self) -> SegmentStats {
1471 let mut stats = SegmentStats {
1472 entity_count: self.live_entity_count(),
1473 deleted_count: self.deleted.len(),
1474 memory_bytes: self.resident_bytes() as usize,
1475 ..Default::default()
1476 };
1477
1478 for entity in self.entities.values() {
1479 match &entity.kind {
1480 EntityKind::TableRow { .. } => stats.row_count += 1,
1481 EntityKind::GraphNode(_) => stats.node_count += 1,
1482 EntityKind::GraphEdge(_) => stats.edge_count += 1,
1483 EntityKind::Vector { .. } => stats.vector_count += 1,
1484 EntityKind::TimeSeriesPoint(_) => stats.row_count += 1,
1485 EntityKind::QueueMessage { .. } => stats.row_count += 1,
1486 }
1487 stats.cross_ref_count += entity.cross_refs().len();
1488 }
1489
1490 stats
1491 }
1492
1493 fn entity_count(&self) -> usize {
1494 self.live_entity_count()
1495 }
1496
1497 fn contains(&self, id: EntityId) -> bool {
1498 self.has_live_entity(id)
1499 }
1500
1501 fn get(&self, id: EntityId) -> Option<&UnifiedEntity> {
1502 if self.deleted.contains(&id) {
1503 return None;
1504 }
1505 if self.use_flat {
1506 let raw = id.raw();
1507 if raw >= self.base_entity_id {
1508 let idx = (raw - self.base_entity_id) as usize;
1509 if let Some(entity) = self.flat_entities.get(idx).filter(|e| e.id == id) {
1510 return Some(entity);
1511 }
1512 }
1513 self.entities.get(&id)
1519 } else {
1520 self.entities.get(&id)
1521 }
1522 }
1523
1524 fn get_mut(&mut self, id: EntityId) -> Option<&mut UnifiedEntity> {
1525 if self.deleted.contains(&id) || !self.state.is_writable() {
1526 return None;
1527 }
1528 if self.use_flat {
1529 let raw = id.raw();
1530 if raw >= self.base_entity_id {
1531 let idx = (raw - self.base_entity_id) as usize;
1532 if self
1533 .flat_entities
1534 .get(idx)
1535 .map(|e| e.id == id)
1536 .unwrap_or(false)
1537 {
1538 return self.flat_entities.get_mut(idx);
1539 }
1540 }
1541 self.entities.get_mut(&id)
1544 } else {
1545 self.entities.get_mut(&id)
1546 }
1547 }
1548
1549 fn insert(&mut self, mut entity: UnifiedEntity) -> Result<EntityId, SegmentError> {
1550 if !self.state.is_writable() {
1551 return Err(SegmentError::NotWritable);
1552 }
1553
1554 if self.entities.contains_key(&entity.id) {
1555 return Err(SegmentError::AlreadyExists(entity.id));
1556 }
1557
1558 entity.sequence_id = self.next_sequence();
1560
1561 let size = Self::estimate_entity_size(&entity);
1563 self.add_memory(size);
1564
1565 self.index_entity(&entity);
1567
1568 self.update_col_zones_from_entity(&entity);
1570
1571 let id = entity.id;
1573 self.entities.insert(id, entity);
1574
1575 self.last_write_at = current_unix_secs();
1577
1578 Ok(id)
1579 }
1580
1581 fn update(&mut self, entity: UnifiedEntity) -> Result<(), SegmentError> {
1582 if !self.state.is_writable() {
1583 return Err(SegmentError::NotWritable);
1584 }
1585
1586 self.apply_update_with_metadata(&entity, None)?;
1587 self.last_write_at = current_unix_secs();
1588
1589 Ok(())
1590 }
1591
1592 fn update_hot(
1593 &mut self,
1594 entity: UnifiedEntity,
1595 modified_columns: &[String],
1596 ) -> Result<(), SegmentError> {
1597 if !self.state.is_writable() {
1598 return Err(SegmentError::NotWritable);
1599 }
1600
1601 self.apply_hot_update_with_metadata(&entity, modified_columns, None)?;
1602 self.last_write_at = current_unix_secs();
1603 Ok(())
1604 }
1605
1606 fn delete(&mut self, id: EntityId) -> Result<bool, SegmentError> {
1607 if !self.state.is_writable() {
1608 return Err(SegmentError::NotWritable);
1609 }
1610
1611 if self.use_flat {
1613 let raw = id.raw();
1614 if raw >= self.base_entity_id {
1615 let idx = (raw - self.base_entity_id) as usize;
1616 if idx < self.flat_entities.len() && self.flat_entities[idx].id == id {
1617 self.tombstone_flat_entity(idx, id);
1618 return Ok(true);
1619 }
1620 }
1621 return Ok(self.remove_hashmap_entity(id));
1622 }
1623
1624 Ok(self.remove_hashmap_entity(id))
1625 }
1626
1627 fn get_metadata(&self, id: EntityId) -> Option<Metadata> {
1628 if !self.has_live_entity(id) {
1629 return None;
1630 }
1631 Some(self.metadata.get_all(id))
1632 }
1633
1634 fn set_metadata(&mut self, id: EntityId, metadata: Metadata) -> Result<(), SegmentError> {
1635 if !self.state.is_writable() {
1636 return Err(SegmentError::NotWritable);
1637 }
1638
1639 if !self.has_live_entity(id) {
1640 return Err(SegmentError::NotFound(id));
1641 }
1642
1643 self.metadata.set_all(id, &metadata);
1644 Ok(())
1645 }
1646
1647 fn seal(&mut self) -> Result<(), SegmentError> {
1648 if self.state != SegmentState::Growing {
1649 return Err(SegmentError::InvalidState(self.state));
1650 }
1651
1652 self.state = SegmentState::Sealing;
1653
1654 self.rebuild_sealed_col_zones();
1660
1661 self.state = SegmentState::Sealed;
1662 Ok(())
1663 }
1664
1665 fn should_seal(&self, config: &SegmentConfig) -> bool {
1666 if self.entities.len() >= config.max_entities {
1668 return true;
1669 }
1670
1671 if self.memory_bytes.load(Ordering::Relaxed) as usize >= config.max_bytes {
1673 return true;
1674 }
1675
1676 if self.age_secs() >= config.max_age_secs {
1678 return true;
1679 }
1680
1681 false
1682 }
1683
1684 fn iter(&self) -> Box<dyn Iterator<Item = &UnifiedEntity> + '_> {
1685 let base: Box<dyn Iterator<Item = &UnifiedEntity>> = if self.use_flat {
1686 Box::new(self.flat_entities.iter().chain(self.entities.values()))
1690 } else {
1691 Box::new(self.entities.values())
1692 };
1693 if self.deleted.is_empty() {
1694 base
1695 } else {
1696 Box::new(base.filter(|e| !self.deleted.contains(&e.id)))
1697 }
1698 }
1699
1700 fn iter_kind(&self, kind_filter: &str) -> Box<dyn Iterator<Item = &UnifiedEntity> + '_> {
1701 let ids = self.kind_index.get(kind_filter).cloned();
1702 let flat: Box<dyn Iterator<Item = &UnifiedEntity>> = if self.use_flat {
1705 Box::new(self.flat_entities.iter())
1706 } else {
1707 Box::new(std::iter::empty())
1708 };
1709 Box::new(flat.chain(self.entities.values()).filter(move |e| {
1710 if self.deleted.contains(&e.id) {
1711 return false;
1712 }
1713 if let Some(ref ids) = ids {
1714 ids.contains(&e.id)
1715 } else {
1716 false
1717 }
1718 }))
1719 }
1720
1721 fn filter_metadata(
1722 &self,
1723 filters: &[(String, super::metadata::MetadataFilter)],
1724 ) -> Vec<EntityId> {
1725 let flat_ids: Box<dyn Iterator<Item = EntityId> + '_> = if self.use_flat {
1728 Box::new(self.flat_entities.iter().map(|e| e.id))
1729 } else {
1730 Box::new(std::iter::empty())
1731 };
1732 flat_ids
1733 .chain(self.entities.keys().copied())
1734 .filter(|id| {
1735 if self.deleted.contains(id) {
1736 return false;
1737 }
1738 let metadata = self.metadata.get_all(*id);
1739 metadata.matches_all(filters)
1740 })
1741 .collect()
1742 }
1743}
1744
1745fn build_minmax_multi_intervals(
1746 entries: &[(CanonicalKey, Value)],
1747 max_intervals: usize,
1748) -> Vec<ColZone> {
1749 if entries.is_empty() {
1750 return Vec::new();
1751 }
1752 if entries.len() == 1 || max_intervals <= 1 {
1753 return vec![ColZone::with_bounds(
1754 entries[0].1.clone(),
1755 entries[entries.len() - 1].1.clone(),
1756 )];
1757 }
1758
1759 let mut split_points = if entries.len() <= max_intervals {
1760 (1..entries.len()).collect::<Vec<_>>()
1761 } else {
1762 let target_splits = max_intervals - 1;
1763 let mut selected = select_gap_split_points(entries, target_splits);
1764 if selected.len() < target_splits {
1765 for bucket in 1..max_intervals {
1766 let idx = bucket * entries.len() / max_intervals;
1767 if idx == 0 || idx >= entries.len() || selected.contains(&idx) {
1768 continue;
1769 }
1770 selected.push(idx);
1771 if selected.len() >= target_splits {
1772 break;
1773 }
1774 }
1775 }
1776 selected.sort_unstable();
1777 selected.dedup();
1778 selected
1779 };
1780
1781 split_points.push(entries.len());
1782
1783 let mut out = Vec::with_capacity(split_points.len());
1784 let mut start = 0usize;
1785 for end in split_points {
1786 if end <= start {
1787 continue;
1788 }
1789 out.push(ColZone::with_bounds(
1790 entries[start].1.clone(),
1791 entries[end - 1].1.clone(),
1792 ));
1793 start = end;
1794 }
1795
1796 if out.is_empty() {
1797 out.push(ColZone::with_bounds(
1798 entries[0].1.clone(),
1799 entries[entries.len() - 1].1.clone(),
1800 ));
1801 }
1802
1803 out
1804}
1805
1806fn select_gap_split_points(entries: &[(CanonicalKey, Value)], max_splits: usize) -> Vec<usize> {
1807 let mut gaps = Vec::new();
1808 for idx in 1..entries.len() {
1809 if let Some(score) = canonical_gap_score(&entries[idx - 1].0, &entries[idx].0) {
1810 if score > 0.0 {
1811 gaps.push((score, idx));
1812 }
1813 }
1814 }
1815 gaps.sort_by(|left, right| {
1816 right
1817 .0
1818 .partial_cmp(&left.0)
1819 .unwrap_or(std::cmp::Ordering::Equal)
1820 .then_with(|| left.1.cmp(&right.1))
1821 });
1822 gaps.into_iter()
1823 .take(max_splits)
1824 .map(|(_, idx)| idx)
1825 .collect()
1826}
1827
1828fn canonical_gap_score(left: &CanonicalKey, right: &CanonicalKey) -> Option<f64> {
1829 if left.family() != right.family() {
1830 return None;
1831 }
1832 match (left, right) {
1833 (CanonicalKey::Signed(_, l), CanonicalKey::Signed(_, r)) => {
1834 Some(r.saturating_sub(*l) as f64)
1835 }
1836 (CanonicalKey::Unsigned(_, l), CanonicalKey::Unsigned(_, r)) => {
1837 Some(r.saturating_sub(*l) as f64)
1838 }
1839 (CanonicalKey::Float(l), CanonicalKey::Float(r)) => {
1840 Some((f64::from_bits(*r) - f64::from_bits(*l)).abs())
1841 }
1842 _ => None,
1843 }
1844}
1845
1846#[cfg(test)]
1847mod tests {
1848 use super::*;
1849 use crate::storage::schema::Value;
1850 use crate::storage::unified::entity::RowData;
1851 use crate::storage::unified::MetadataValue;
1852
1853 #[test]
1854 fn test_growing_segment_basic() {
1855 let mut segment = GrowingSegment::new(1, "test");
1856
1857 let entity = UnifiedEntity::table_row(
1858 EntityId::new(1),
1859 "users",
1860 1,
1861 vec![Value::text("Alice".to_string())],
1862 );
1863
1864 let id = segment.insert(entity).unwrap();
1865 assert_eq!(id, EntityId::new(1));
1866 assert!(segment.contains(id));
1867
1868 let stats = segment.stats();
1869 assert_eq!(stats.entity_count, 1);
1870 assert_eq!(stats.row_count, 1);
1871 }
1872
1873 #[test]
1874 fn test_segment_metadata() {
1875 let mut segment = GrowingSegment::new(1, "test");
1876
1877 let entity = UnifiedEntity::table_row(
1878 EntityId::new(1),
1879 "users",
1880 1,
1881 vec![Value::text("Alice".to_string())],
1882 );
1883 segment.insert(entity).unwrap();
1884
1885 let mut meta = Metadata::new();
1886 meta.set("role", MetadataValue::String("admin".to_string()));
1887 meta.set("level", MetadataValue::Int(5));
1888
1889 segment.set_metadata(EntityId::new(1), meta).unwrap();
1890
1891 let retrieved = segment.get_metadata(EntityId::new(1)).unwrap();
1892 assert_eq!(
1893 retrieved.get("role"),
1894 Some(&MetadataValue::String("admin".to_string()))
1895 );
1896 }
1897
1898 #[test]
1899 fn test_segment_seal() {
1900 let mut segment = GrowingSegment::new(1, "test");
1901
1902 let entity = UnifiedEntity::vector(EntityId::new(1), "embeddings", vec![0.1, 0.2, 0.3]);
1903 segment.insert(entity).unwrap();
1904
1905 assert!(segment.state().is_writable());
1907
1908 segment.seal().unwrap();
1910 assert_eq!(segment.state(), SegmentState::Sealed);
1911
1912 let entity2 = UnifiedEntity::vector(EntityId::new(2), "embeddings", vec![0.4, 0.5, 0.6]);
1914 assert!(segment.insert(entity2).is_err());
1915 }
1916
1917 #[test]
1918 fn test_should_seal() {
1919 let mut segment = GrowingSegment::new(1, "test");
1920
1921 let config = SegmentConfig {
1922 max_entities: 2,
1923 ..Default::default()
1924 };
1925
1926 assert!(!segment.should_seal(&config));
1927
1928 segment
1929 .insert(UnifiedEntity::vector(EntityId::new(1), "v", vec![0.1]))
1930 .unwrap();
1931 assert!(!segment.should_seal(&config));
1932
1933 segment
1934 .insert(UnifiedEntity::vector(EntityId::new(2), "v", vec![0.2]))
1935 .unwrap();
1936 assert!(segment.should_seal(&config));
1937 }
1938
1939 #[test]
1940 fn test_cross_references() {
1941 let mut segment = GrowingSegment::new(1, "test");
1942
1943 let mut entity1 = UnifiedEntity::table_row(
1944 EntityId::new(1),
1945 "hosts",
1946 1,
1947 vec![Value::text("192.168.1.1".to_string())],
1948 );
1949 entity1.add_cross_ref(CrossRef::new(
1950 EntityId::new(1),
1951 EntityId::new(2),
1952 "nodes",
1953 RefType::RowToNode,
1954 ));
1955 segment.insert(entity1).unwrap();
1956
1957 let refs_from = segment.get_references_from(EntityId::new(1));
1958 assert_eq!(refs_from.len(), 1);
1959 assert_eq!(refs_from[0], (EntityId::new(2), RefType::RowToNode));
1960
1961 let refs_to = segment.get_references_to(EntityId::new(2));
1962 assert_eq!(refs_to.len(), 1);
1963 assert_eq!(refs_to[0], (EntityId::new(1), RefType::RowToNode));
1964 }
1965
1966 #[test]
1967 fn test_zone_predicate_uses_canonical_fallback_for_email_values() {
1968 let mut zone = ColZone::new(Value::Email("bravo@example.com".to_string()));
1969 zone.update(&Value::Email("delta@example.com".to_string()));
1970
1971 let probe = Value::Email("alpha@example.com".to_string());
1972 assert!(ZoneColPred::Eq(&probe).can_skip(&zone));
1973
1974 let in_range = Value::Email("charlie@example.com".to_string());
1975 assert!(!ZoneColPred::Eq(&in_range).can_skip(&zone));
1976 }
1977
1978 #[test]
1979 fn test_sealed_multi_zone_prunes_numeric_gap_outlier() {
1980 let mut segment = GrowingSegment::new(1, "test");
1981
1982 for (row_id, age) in [(1_u64, 1_i64), (2, 2), (3, 3), (4, 1000)] {
1983 let entity = UnifiedEntity::new(
1984 EntityId::new(row_id),
1985 EntityKind::TableRow {
1986 table: "users".into(),
1987 row_id,
1988 },
1989 EntityData::Row(RowData::with_names(
1990 vec![Value::Integer(age)],
1991 vec!["age".to_string()],
1992 )),
1993 );
1994 segment.insert(entity).unwrap();
1995 }
1996
1997 segment.seal().unwrap();
1998
1999 let miss = Value::Integer(500);
2000 assert!(segment.can_skip_zone_preds(&[("age", ZoneColPred::Eq(&miss))]));
2001
2002 let hit = Value::Integer(1000);
2003 assert!(!segment.can_skip_zone_preds(&[("age", ZoneColPred::Eq(&hit))]));
2004 }
2005}