1use crate::be_tree::{BeTree, LeafVersions};
14use crate::epoch::{Epoch, Snapshot};
15use crate::rowid::RowId;
16use serde::{Deserialize, Serialize};
17use std::borrow::Cow;
18use std::cmp::Ordering;
19use std::collections::{BTreeMap, BinaryHeap, HashMap};
20use std::sync::Arc;
21
22#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
25pub enum Value {
26 Null,
27 Bool(bool),
28 Int64(i64),
29 Float64(f64),
30 Bytes(Vec<u8>),
31 Embedding(Vec<f32>),
32 Decimal(i128),
35 Interval {
37 months: i64,
38 days: i32,
39 nanos: i64,
40 },
41 Uuid([u8; 16]),
43 Json(Vec<u8>),
45 GeneratedEmbedding(Box<crate::embedding::GeneratedEmbeddingValue>),
49}
50
51impl Value {
52 pub fn as_embedding(&self) -> Option<&[f32]> {
53 match self {
54 Self::Embedding(values) => Some(values),
55 Self::GeneratedEmbedding(value) => Some(&value.vector),
56 _ => None,
57 }
58 }
59
60 pub fn generated_embedding_metadata(
61 &self,
62 ) -> Option<&crate::embedding::GeneratedEmbeddingMetadata> {
63 match self {
64 Self::GeneratedEmbedding(value) => Some(&value.metadata),
65 _ => None,
66 }
67 }
68
69 pub fn encode_key(&self) -> Vec<u8> {
72 match self {
73 Value::Null => Vec::new(),
74 Value::Bool(b) => vec![*b as u8],
75 Value::Int64(n) => n.to_be_bytes().to_vec(),
76 Value::Float64(f) => f.to_bits().to_be_bytes().to_vec(),
77 Value::Bytes(b) => b.clone(),
78 Value::Embedding(v) => {
79 let mut out = Vec::with_capacity(v.len() * 4);
80 for x in v {
81 out.extend_from_slice(&x.to_bits().to_be_bytes());
82 }
83 out
84 }
85 Value::GeneratedEmbedding(value) => {
86 let mut out = Vec::with_capacity(value.vector.len() * 4);
87 for x in &value.vector {
88 out.extend_from_slice(&x.to_bits().to_be_bytes());
89 }
90 out
91 }
92 Value::Decimal(d) => d.to_be_bytes().to_vec(),
93 Value::Interval {
94 months,
95 days,
96 nanos,
97 } => {
98 let mut out = Vec::with_capacity(20);
99 out.extend_from_slice(&months.to_be_bytes());
100 out.extend_from_slice(&days.to_be_bytes());
101 out.extend_from_slice(&nanos.to_be_bytes());
102 out
103 }
104 Value::Uuid(b) => b.to_vec(),
105 Value::Json(b) => b.clone(),
106 }
107 }
108
109 pub(crate) fn estimated_bytes(&self) -> u64 {
110 match self {
111 Value::Null => 1,
112 Value::Bool(_) => 1,
113 Value::Int64(_) | Value::Float64(_) => 8,
114 Value::Bytes(bytes) | Value::Json(bytes) => 16 + bytes.len() as u64,
115 Value::Embedding(values) => 16 + (values.len() as u64) * 4,
116 Value::GeneratedEmbedding(value) => {
117 16 + (value.vector.len() as u64) * 4
118 + value.metadata.provider_id.len() as u64
119 + value.metadata.model_id.len() as u64
120 + value.metadata.model_version.len() as u64
121 + value.metadata.preprocessing_version.len() as u64
122 + 48
123 }
124 Value::Decimal(_) | Value::Uuid(_) => 16,
125 Value::Interval { .. } => 20,
126 }
127 }
128}
129
130#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
138pub struct Row {
139 pub row_id: RowId,
140 pub committed_epoch: Epoch,
141 pub columns: HashMap<u16, Value>,
142 pub deleted: bool,
143 #[serde(skip)]
147 pub commit_ts: Option<mongreldb_types::hlc::HlcTimestamp>,
148}
149
150impl Row {
151 pub fn new(row_id: RowId, committed_epoch: Epoch) -> Self {
152 Self {
153 row_id,
154 committed_epoch,
155 columns: HashMap::new(),
156 deleted: false,
157 commit_ts: None,
158 }
159 }
160
161 pub fn new_with_hlc(
162 row_id: RowId,
163 committed_epoch: Epoch,
164 commit_ts: mongreldb_types::hlc::HlcTimestamp,
165 ) -> Self {
166 Self {
167 row_id,
168 committed_epoch,
169 columns: HashMap::new(),
170 deleted: false,
171 commit_ts: Some(commit_ts),
172 }
173 }
174
175 pub fn with_column(mut self, column_id: u16, value: Value) -> Self {
176 self.columns.insert(column_id, value);
177 self
178 }
179
180 pub fn estimated_bytes(&self) -> u64 {
182 self.columns
183 .values()
184 .fold(32, |bytes, value| bytes + value.estimated_bytes())
185 }
186}
187
188struct MemHead<'a> {
192 rid: RowId,
193 epoch: Epoch,
194 index: u32,
195 row: Cow<'a, Row>,
196}
197
198impl PartialEq for MemHead<'_> {
199 fn eq(&self, other: &Self) -> bool {
200 (self.rid, self.epoch, self.index) == (other.rid, other.epoch, other.index)
201 }
202}
203impl Eq for MemHead<'_> {}
204impl PartialOrd for MemHead<'_> {
205 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
206 Some(self.cmp(other))
207 }
208}
209impl Ord for MemHead<'_> {
210 fn cmp(&self, other: &Self) -> Ordering {
211 (other.rid, other.epoch, other.index).cmp(&(self.rid, self.epoch, self.index))
212 }
213}
214
215pub struct MemtableVisibleVersionCursor<'a> {
216 leaves: Vec<LeafVersions<'a>>,
217 heap: BinaryHeap<MemHead<'a>>,
218 snapshot: Snapshot,
219 finished: bool,
220 pub(crate) last_examined: usize,
223 pub(crate) peak_examined: usize,
225}
226
227impl<'a> Iterator for MemtableVisibleVersionCursor<'a> {
228 type Item = (RowId, Epoch, Cow<'a, Row>);
229
230 fn next(&mut self) -> Option<Self::Item> {
231 if self.finished {
232 return None;
233 }
234 self.last_examined = 0;
235 loop {
236 let MemHead {
237 rid,
238 epoch,
239 index,
240 row,
241 } = self.heap.pop()?;
242 self.last_examined += 1;
243 if let Some(next) = self.leaves[index as usize].next() {
245 let (nepoch, _nrow_id) = (next.committed_epoch, next.row_id);
246 self.heap.push(MemHead {
247 rid: next.row_id,
248 epoch: nepoch,
249 index,
250 row: next,
251 });
252 }
253 if !self.snapshot.observes_version(epoch, row.commit_ts) {
254 continue;
255 }
256 let mut best = Some(row);
259 while self.heap.peek().is_some_and(|h| h.rid == rid) {
260 let head = self.heap.pop().unwrap();
261 self.last_examined += 1;
262 if let Some(next) = self.leaves[head.index as usize].next() {
263 let nepoch = next.committed_epoch;
264 self.heap.push(MemHead {
265 rid: next.row_id,
266 epoch: nepoch,
267 index: head.index,
268 row: next,
269 });
270 }
271 if self
272 .snapshot
273 .observes_version(head.epoch, head.row.commit_ts)
274 && best.as_ref().is_none_or(|current| {
275 Snapshot::version_is_newer(
276 head.epoch,
277 head.row.commit_ts,
278 current.committed_epoch,
279 current.commit_ts,
280 )
281 })
282 {
283 best = Some(head.row);
284 }
285 }
286 let best = best?;
287 let row_id = best.row_id;
288 let epoch = best.committed_epoch;
289 self.peak_examined = self.peak_examined.max(self.last_examined);
290 return Some((row_id, epoch, best));
291 }
292 }
293}
294
295impl<'a> MemtableVisibleVersionCursor<'a> {
296 pub fn is_exhausted(&self) -> bool {
298 self.finished
299 }
300}
301
302#[derive(Clone)]
306struct MemtableSegment {
307 tree: BeTree,
308 byte_size: u64,
309}
310
311#[derive(Clone)]
313pub struct Memtable {
314 frozen: Arc<Vec<Arc<MemtableSegment>>>,
315 active: MemtableSegment,
316 byte_size: u64,
317}
318
319impl Default for Memtable {
320 fn default() -> Self {
321 Self::new()
322 }
323}
324
325impl Memtable {
326 pub fn new() -> Self {
327 Self {
328 frozen: Arc::new(Vec::new()),
329 active: MemtableSegment {
330 tree: BeTree::new(),
331 byte_size: 0,
332 },
333 byte_size: 0,
334 }
335 }
336
337 pub fn upsert(&mut self, row: Row) {
340 let bytes = row.estimated_bytes();
341 self.byte_size = self.byte_size.saturating_add(bytes);
342 self.active.byte_size = self.active.byte_size.saturating_add(bytes);
343 self.active.tree.insert_row(row);
344 }
345
346 pub fn tombstone(&mut self, row_id: RowId, epoch: Epoch) {
350 let mut columns = HashMap::new();
351 if let Some(live) = self.get(row_id, Epoch(epoch.0.saturating_sub(1))) {
352 columns = live.columns;
353 }
354 let row = Row {
355 row_id,
356 committed_epoch: epoch,
357 columns,
358 deleted: true,
359 commit_ts: None,
360 };
361 self.upsert(row);
362 }
363
364 pub fn get(&self, row_id: RowId, snapshot_epoch: Epoch) -> Option<Row> {
368 self.get_version(row_id, snapshot_epoch)
369 .and_then(|(_, row)| (!row.deleted).then_some(row))
370 }
371
372 pub fn get_version(&self, row_id: RowId, snapshot_epoch: Epoch) -> Option<(Epoch, Row)> {
377 self.get_version_at(row_id, crate::epoch::Snapshot::at(snapshot_epoch))
378 }
379
380 pub fn get_version_at(
387 &self,
388 row_id: RowId,
389 snapshot: crate::epoch::Snapshot,
390 ) -> Option<(Epoch, Row)> {
391 if !snapshot.uses_hlc_authority() {
392 let mut best = self.active.tree.get_version(row_id, snapshot.epoch);
393 for segment in self.frozen.iter().rev() {
394 let Some(candidate) = segment.tree.get_version(row_id, snapshot.epoch) else {
395 continue;
396 };
397 if best.as_ref().is_none_or(|(epoch, _)| candidate.0 > *epoch) {
398 best = Some(candidate);
399 }
400 }
401 return best;
402 }
403
404 let mut best: Option<Row> = None;
405 for segment in self
406 .frozen
407 .iter()
408 .map(|segment| &segment.tree)
409 .chain(std::iter::once(&self.active.tree))
410 {
411 segment.visit_versions(row_id, |row| {
412 if !snapshot.observes_row(row.committed_epoch, row.commit_ts) {
413 return;
414 }
415 if best.as_ref().is_none_or(|current| {
416 crate::epoch::Snapshot::version_is_newer(
417 row.committed_epoch,
418 row.commit_ts,
419 current.committed_epoch,
420 current.commit_ts,
421 )
422 }) {
423 best = Some(row);
424 }
425 });
426 }
427 best.map(|row| (row.committed_epoch, row))
428 }
429
430 pub fn len(&self) -> usize {
432 self.active.tree.mutations()
433 + self
434 .frozen
435 .iter()
436 .map(|segment| segment.tree.mutations())
437 .sum::<usize>()
438 }
439
440 pub fn is_empty(&self) -> bool {
441 self.active.tree.is_empty() && self.frozen.is_empty()
442 }
443
444 pub fn approx_bytes(&self) -> u64 {
445 self.byte_size
446 }
447
448 pub fn visible_rows(&self, snapshot_epoch: Epoch) -> Vec<Row> {
451 self.visible_versions(snapshot_epoch)
452 .into_iter()
453 .filter(|r| !r.deleted)
454 .collect()
455 }
456
457 pub fn visible_versions(&self, snapshot_epoch: Epoch) -> Vec<Row> {
461 self.visible_versions_at(crate::epoch::Snapshot::at(snapshot_epoch))
462 }
463
464 pub fn visible_versions_at(&self, snapshot: crate::epoch::Snapshot) -> Vec<Row> {
465 self.newest_visible_map(snapshot).into_values().collect()
466 }
467
468 pub(crate) fn newest_visible_map(
472 &self,
473 snapshot: crate::epoch::Snapshot,
474 ) -> BTreeMap<RowId, Row> {
475 let mut by_row: BTreeMap<RowId, Row> = BTreeMap::new();
476 for segment in std::iter::once(&self.active.tree)
477 .chain(self.frozen.iter().rev().map(|segment| &segment.tree))
478 {
479 for row in segment.versions() {
480 if !snapshot.observes_version(row.committed_epoch, row.commit_ts) {
481 continue;
482 }
483 by_row
484 .entry(row.row_id)
485 .and_modify(|existing| {
486 if crate::epoch::Snapshot::version_is_newer(
487 row.committed_epoch,
488 row.commit_ts,
489 existing.committed_epoch,
490 existing.commit_ts,
491 ) {
492 *existing = row.clone();
493 }
494 })
495 .or_insert(row);
496 }
497 }
498 by_row
499 }
500
501 pub fn newest_visible_iter<'a>(
502 &'a self,
503 snapshot: &Snapshot,
504 ) -> MemtableVisibleVersionCursor<'a> {
505 let mut leaves: Vec<LeafVersions<'a>> = self
506 .frozen
507 .iter()
508 .map(|segment| segment.tree.leaf_versions_iter())
509 .chain(std::iter::once(self.active.tree.leaf_versions_iter()))
510 .collect();
511 let mut heap = BinaryHeap::new();
512 for (index, leaf) in leaves.iter_mut().enumerate() {
513 if let Some(row) = leaf.next() {
514 heap.push(MemHead {
515 rid: row.row_id,
516 epoch: row.committed_epoch,
517 index: index as u32,
518 row,
519 });
520 }
521 }
522 MemtableVisibleVersionCursor {
523 leaves,
524 heap,
525 snapshot: *snapshot,
526 finished: false,
527 last_examined: 0,
528 peak_examined: 0,
529 }
530 }
531
532 pub(crate) fn seal(&mut self) {
534 if self.active.tree.is_empty() {
535 return;
536 }
537 let active = std::mem::replace(
538 &mut self.active,
539 MemtableSegment {
540 tree: BeTree::new(),
541 byte_size: 0,
542 },
543 );
544 Arc::make_mut(&mut self.frozen).push(Arc::new(active));
545 if self.frozen.len() >= crate::MAX_READ_GENERATION_LAYERS {
546 self.consolidate();
547 }
548 }
549
550 fn consolidate(&mut self) {
551 let mut tree = BeTree::new();
552 for row in self
553 .frozen
554 .iter()
555 .flat_map(|segment| segment.tree.versions())
556 {
557 tree.insert_row(row);
558 }
559 self.frozen = Arc::new(vec![Arc::new(MemtableSegment {
560 tree,
561 byte_size: self.byte_size,
562 })]);
563 }
564
565 #[cfg(test)]
566 pub(crate) fn frozen_layer_count(&self) -> usize {
567 self.frozen.len()
568 }
569
570 pub fn drain_sorted(&mut self) -> Vec<Row> {
573 let mut out = self
574 .frozen
575 .iter()
576 .flat_map(|segment| segment.tree.versions())
577 .chain(self.active.tree.versions())
578 .collect::<Vec<_>>();
579 out.sort_by_key(|row| (row.row_id, row.committed_epoch));
580 self.frozen = Arc::new(Vec::new());
581 self.active = MemtableSegment {
582 tree: BeTree::new(),
583 byte_size: 0,
584 };
585 self.byte_size = 0;
586 out
587 }
588}
589
590#[cfg(test)]
591mod tests {
592 use super::*;
593
594 fn row(id: u64, epoch: u64) -> Row {
595 Row::new(RowId(id), Epoch(epoch)).with_column(1, Value::Int64(id as i64 * 10))
596 }
597
598 #[test]
599 fn upsert_get_and_visibility() {
600 let mut m = Memtable::new();
601 m.upsert(row(1, 5));
602 assert_eq!(m.len(), 1);
603 assert!(m.get(RowId(1), Epoch(5)).is_some());
604 assert!(m.get(RowId(1), Epoch(4)).is_none()); assert!(m.get(RowId(2), Epoch(9)).is_none()); }
607
608 #[test]
609 fn tombstone_supersedes_at_its_epoch() {
610 let mut m = Memtable::new();
611 m.upsert(row(1, 1));
612 assert!(m.get(RowId(1), Epoch(1)).is_some());
614 m.tombstone(RowId(1), Epoch(2));
615 assert!(m.get(RowId(1), Epoch(2)).is_none());
617 assert!(m.get(RowId(1), Epoch(9)).is_none());
618 assert!(m.get(RowId(1), Epoch(1)).is_some());
620 }
621
622 #[test]
623 fn sealed_generations_share_rows_and_consolidate() {
624 let mut writer = Memtable::new();
625 for id in 0..crate::MAX_READ_GENERATION_LAYERS as u64 + 2 {
626 writer.upsert(row(id, id + 1));
627 writer.seal();
628 }
629 assert!(writer.frozen_layer_count() < crate::MAX_READ_GENERATION_LAYERS);
630 let generation = writer.clone();
631 writer.upsert(row(99, 99));
632 assert!(generation.get(RowId(99), Epoch(99)).is_none());
633 assert!(writer.get(RowId(99), Epoch(99)).is_some());
634 }
635
636 #[test]
637 fn hlc_visibility_is_authoritative_when_stamped() {
638 use mongreldb_types::hlc::HlcTimestamp;
639 let mut m = Memtable::new();
640 let early = HlcTimestamp {
641 physical_micros: 100,
642 logical: 0,
643 node_tiebreaker: 1,
644 };
645 let late = HlcTimestamp {
646 physical_micros: 200,
647 logical: 0,
648 node_tiebreaker: 1,
649 };
650 let mut r1 = Row::new_with_hlc(RowId(1), Epoch(1), early);
651 r1.columns.insert(1, Value::Int64(1));
652 let mut r2 = Row::new_with_hlc(RowId(1), Epoch(2), late);
653 r2.columns.insert(1, Value::Int64(2));
654 m.upsert(r1);
655 m.upsert(r2);
656 let snap = crate::epoch::Snapshot::at_hlc(Epoch(99), early);
657 let versions = m.visible_versions_at(snap);
658 assert_eq!(versions.len(), 1);
659 assert_eq!(versions[0].columns.get(&1), Some(&Value::Int64(1)));
660 let snap2 = crate::epoch::Snapshot::at_hlc(Epoch(1), late);
661 assert_eq!(
662 m.visible_versions_at(snap2)[0].columns.get(&1),
663 Some(&Value::Int64(2))
664 );
665 }
666
667 #[test]
668 fn snapshot_hlc_hides_later_commit_ts_even_if_epoch_higher() {
669 use mongreldb_types::hlc::HlcTimestamp;
670 let mut m = Memtable::new();
671 let early = HlcTimestamp {
672 physical_micros: 100,
673 logical: 0,
674 node_tiebreaker: 1,
675 };
676 let late = HlcTimestamp {
677 physical_micros: 200,
678 logical: 0,
679 node_tiebreaker: 1,
680 };
681 let mut late_row = Row::new_with_hlc(RowId(1), Epoch(1), late);
684 late_row.columns.insert(1, Value::Int64(99));
685 let mut early_row = Row::new_with_hlc(RowId(1), Epoch(50), early);
686 early_row.columns.insert(1, Value::Int64(1));
687 m.upsert(late_row);
688 m.upsert(early_row);
689 let snap = crate::epoch::Snapshot::at_hlc(Epoch(99), early);
690 let versions = m.visible_versions_at(snap);
691 assert_eq!(versions.len(), 1);
692 assert_eq!(versions[0].columns.get(&1), Some(&Value::Int64(1)));
693 assert_eq!(versions[0].commit_ts, Some(early));
694 }
695
696 #[test]
697 fn epoch_only_snapshot_sees_hlc_stamped_rows_by_epoch() {
698 use mongreldb_types::hlc::HlcTimestamp;
699 let mut m = Memtable::new();
700 let ts = HlcTimestamp {
701 physical_micros: 50,
702 logical: 0,
703 node_tiebreaker: 1,
704 };
705 m.upsert(Row::new_with_hlc(RowId(1), Epoch(1), ts).with_column(1, Value::Int64(1)));
706 m.upsert(Row::new(RowId(2), Epoch(1)).with_column(1, Value::Int64(2)));
707 let legacy = crate::epoch::Snapshot::at(Epoch(99));
708 let versions = m.visible_versions_at(legacy);
709 assert_eq!(
710 versions.len(),
711 2,
712 "dual-model: epoch pin sees HLC rows by epoch"
713 );
714 assert!(m.get_version_at(RowId(1), legacy).is_some());
715 assert!(m.get_version_at(RowId(2), legacy).is_some());
716 let future = crate::epoch::Snapshot::at(Epoch(0));
717 assert!(m.get_version_at(RowId(1), future).is_none());
718 }
719
720 #[test]
721 fn get_version_at_prefers_hlc_over_epoch_order() {
722 use mongreldb_types::hlc::HlcTimestamp;
723 let mut m = Memtable::new();
724 let early = HlcTimestamp {
725 physical_micros: 100,
726 logical: 0,
727 node_tiebreaker: 1,
728 };
729 let late = HlcTimestamp {
730 physical_micros: 200,
731 logical: 0,
732 node_tiebreaker: 1,
733 };
734 m.upsert(Row::new_with_hlc(RowId(1), Epoch(1), late).with_column(1, Value::Int64(99)));
735 m.upsert(Row::new_with_hlc(RowId(1), Epoch(50), early).with_column(1, Value::Int64(1)));
736 let snap = crate::epoch::Snapshot::at_hlc(Epoch(99), early);
737 let (_, row) = m.get_version_at(RowId(1), snap).expect("visible");
738 assert_eq!(row.columns.get(&1), Some(&Value::Int64(1)));
739 assert_eq!(row.commit_ts, Some(early));
740 }
741
742 #[test]
746 fn wal_put_row_bincode_matches_0_63_1_layout() {
747 use mongreldb_types::hlc::HlcTimestamp;
748 use serde::{Deserialize, Serialize};
749
750 #[derive(Serialize, Deserialize)]
751 struct LegacyRow {
752 row_id: RowId,
753 committed_epoch: Epoch,
754 columns: HashMap<u16, Value>,
755 deleted: bool,
756 }
757
758 let legacy = LegacyRow {
759 row_id: RowId(7),
760 committed_epoch: Epoch(3),
761 columns: [(1, Value::Int64(42))].into_iter().collect(),
762 deleted: false,
763 };
764 let bytes = bincode::serialize(&legacy).expect("legacy encode");
765
766 let decoded: Row = bincode::deserialize(&bytes).expect("0.63.1 payload must decode");
767 assert_eq!(decoded.row_id, RowId(7));
768 assert_eq!(decoded.committed_epoch, Epoch(3));
769 assert_eq!(decoded.columns.get(&1), Some(&Value::Int64(42)));
770 assert!(!decoded.deleted);
771 assert!(decoded.commit_ts.is_none());
772
773 let stamped = HlcTimestamp {
775 physical_micros: 1_700_000_000_000,
776 logical: 2,
777 node_tiebreaker: 9,
778 };
779 let mut live = Row::new_with_hlc(RowId(7), Epoch(3), stamped);
780 live.columns.insert(1, Value::Int64(42));
781 let wire = bincode::serialize(&live).expect("row encode");
782 assert_eq!(
783 wire, bytes,
784 "WAL Put encoding must match the 0.63.1 four-field layout"
785 );
786 let again: Row = bincode::deserialize(&wire).expect("row decode");
787 assert!(
788 again.commit_ts.is_none(),
789 "commit_ts is restored from Op::CommitTimestamp, not the Put blob"
790 );
791 }
792
793 #[test]
794 fn drain_sorted_is_ascending_and_empties() {
795 let mut m = Memtable::new();
796 m.upsert(row(3, 1));
797 m.upsert(row(1, 1));
798 m.upsert(row(2, 1));
799 let out = m.drain_sorted();
800 let ids: Vec<u64> = out.iter().map(|r| r.row_id.0).collect();
801 assert_eq!(ids, vec![1, 2, 3]);
802 assert!(m.is_empty());
803 assert_eq!(m.approx_bytes(), 0);
804 }
805
806 #[test]
807 fn visible_rows_dedups_to_newest_version() {
808 let mut m = Memtable::new();
809 m.upsert(row(1, 1));
810 m.upsert(row(2, 9)); m.upsert(row(3, 1));
812 m.upsert(row(1, 3)); let ids: Vec<u64> = m
814 .visible_rows(Epoch(5))
815 .iter()
816 .map(|r| r.row_id.0)
817 .collect();
818 assert_eq!(ids, vec![1, 3]);
819 }
820
821 #[test]
822 fn newest_visible_map_prefers_active_on_equal_version() {
823 let mut m = Memtable::new();
824 let mut deleted = row(1, 2);
825 deleted.deleted = true;
826 m.upsert(deleted);
827 m.seal();
828 m.upsert(row(1, 2));
829
830 let versions = m.visible_versions_at(Snapshot::at(Epoch(2)));
831 assert_eq!(versions.len(), 1);
832 assert!(!versions[0].deleted);
833 }
834
835 #[test]
836 fn newest_visible_iter_empty_memtable_yields_nothing() {
837 let m = Memtable::new();
838 assert!(m
839 .newest_visible_iter(&Snapshot::at(Epoch(9)))
840 .next()
841 .is_none());
842 }
843
844 #[test]
845 fn newest_visible_iter_single_insert_yields_one() {
846 let mut m = Memtable::new();
847 m.upsert(row(1, 3));
848 let values: Vec<_> = m
849 .newest_visible_iter(&Snapshot::at(Epoch(3)))
850 .map(|(id, epoch, _)| (id, epoch))
851 .collect();
852 assert_eq!(values, vec![(RowId(1), Epoch(3))]);
853 }
854
855 #[test]
856 fn newest_visible_iter_newer_epoch_wins() {
857 let mut m = Memtable::new();
858 m.upsert(row(1, 1));
859 m.upsert(row(1, 2));
860 let values: Vec<_> = m
861 .newest_visible_iter(&Snapshot::at(Epoch(2)))
862 .map(|(_, epoch, _)| epoch)
863 .collect();
864 assert_eq!(values, vec![Epoch(2)]);
865 }
866
867 #[test]
868 fn newest_visible_iter_tombstone_suppresses_older_live_version() {
869 let mut m = Memtable::new();
874 m.upsert(row(1, 1));
875 m.tombstone(RowId(1), Epoch(2));
876 let snap = Snapshot::at(Epoch(2));
877 let got: Vec<(u64, u64, bool)> = m
878 .newest_visible_iter(&snap)
879 .map(|(rid, epoch, row)| (rid.0, epoch.0, row.deleted))
880 .collect();
881 assert_eq!(got, vec![(1, 2, true)], "tombstone is the newest");
882 let snap_early = Snapshot::at(Epoch(1));
884 let got_early: Vec<(u64, u64, bool)> = m
885 .newest_visible_iter(&snap_early)
886 .map(|(rid, epoch, row)| (rid.0, epoch.0, row.deleted))
887 .collect();
888 assert_eq!(got_early, vec![(1, 1, false)]);
889 }
890
891 #[test]
906 fn newest_visible_iter_includes_root_buffer_rows() {
907 const N: u64 = 1_000;
908 let mut m = Memtable::new();
909 for i in 0..N {
913 let mut r = Row::new(RowId(i), Epoch(i + 1));
914 r.columns.insert(1, Value::Int64(i as i64 * 10));
915 m.upsert(r);
916 }
917 assert_eq!(m.len(), N as usize);
918
919 let snap = Snapshot::at(Epoch(N + 10));
921 let got: Vec<(u64, u64, i64)> = m
922 .newest_visible_iter(&snap)
923 .map(|(rid, epoch, row)| (rid.0, epoch.0, int_of_value(&row)))
924 .collect();
925
926 assert_eq!(
928 got.len(),
929 N as usize,
930 "buffered rows must be visible to a streaming scan (got {})",
931 got.len()
932 );
933 let mut seen_row_ids: std::collections::HashSet<u64> = std::collections::HashSet::new();
934 for (rid, _epoch, _v) in &got {
935 assert!(
936 seen_row_ids.insert(*rid),
937 "duplicate RowId {rid} in streaming scan output"
938 );
939 }
940 let expected_ids: std::collections::HashSet<u64> = (0..N).collect();
942 let got_ids: std::collections::HashSet<u64> = got.iter().map(|(rid, _, _)| *rid).collect();
943 assert_eq!(got_ids, expected_ids, "must yield every input RowId");
944
945 let (_, epoch_raw, v) = got
948 .iter()
949 .find(|(rid, _, _)| *rid == N - 1)
950 .copied()
951 .expect("highest RowId present");
952 assert_eq!(epoch_raw, N);
953 assert_eq!(v, (N as i64 - 1) * 10);
954 }
955
956 #[test]
962 fn newest_visible_iter_buffered_versions_dedup_against_leaf_resident() {
963 let mut m = Memtable::new();
965 for i in 0..16u64 {
966 let mut r = Row::new(RowId(7), Epoch(i + 1));
967 r.columns.insert(1, Value::Int64(i as i64));
968 m.upsert(r);
969 }
970 for i in 0u64..4_000 {
975 let mut r = Row::new(RowId(1000 + i), Epoch(i + 100));
976 r.columns.insert(1, Value::Int64(i as i64));
977 m.upsert(r);
978 }
979 let mut latest_seven = Row::new(RowId(7), Epoch(20_000));
982 latest_seven.columns.insert(1, Value::Int64(999));
983 m.upsert(latest_seven);
984
985 let snap = Snapshot::at(Epoch(20_001));
986 let seven = m
987 .newest_visible_iter(&snap)
988 .find(|(rid, _epoch, _row)| *rid == RowId(7))
989 .expect("row 7 visible");
990 assert_eq!(seven.1, Epoch(20_000), "buffered newest wins");
991 let total: u64 = m
994 .newest_visible_iter(&snap)
995 .map(|(rid, _epoch, _row)| rid.0)
996 .fold(0u64, |acc, _| acc + 1);
997 assert_eq!(total, 4_001, "buffer + leaf coverage");
998 }
999
1000 fn int_of_value(row: &Row) -> i64 {
1001 match row.columns.get(&1) {
1002 Some(Value::Int64(x)) => *x,
1003 other => panic!("expected Int64 column, got {other:?}"),
1004 }
1005 }
1006}