1use crate::be_tree::{BeTree, LeafVersions};
14use crate::epoch::{Epoch, Snapshot};
15use crate::rowid::RowId;
16use serde::{Deserialize, Serialize};
17use std::collections::{BTreeMap, HashMap};
18use std::sync::Arc;
19
20#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
23pub enum Value {
24 Null,
25 Bool(bool),
26 Int64(i64),
27 Float64(f64),
28 Bytes(Vec<u8>),
29 Embedding(Vec<f32>),
30 Decimal(i128),
33 Interval {
35 months: i64,
36 days: i32,
37 nanos: i64,
38 },
39 Uuid([u8; 16]),
41 Json(Vec<u8>),
43 GeneratedEmbedding(Box<crate::embedding::GeneratedEmbeddingValue>),
47}
48
49impl Value {
50 pub fn as_embedding(&self) -> Option<&[f32]> {
51 match self {
52 Self::Embedding(values) => Some(values),
53 Self::GeneratedEmbedding(value) => Some(&value.vector),
54 _ => None,
55 }
56 }
57
58 pub fn generated_embedding_metadata(
59 &self,
60 ) -> Option<&crate::embedding::GeneratedEmbeddingMetadata> {
61 match self {
62 Self::GeneratedEmbedding(value) => Some(&value.metadata),
63 _ => None,
64 }
65 }
66
67 pub fn encode_key(&self) -> Vec<u8> {
70 match self {
71 Value::Null => Vec::new(),
72 Value::Bool(b) => vec![*b as u8],
73 Value::Int64(n) => n.to_be_bytes().to_vec(),
74 Value::Float64(f) => f.to_bits().to_be_bytes().to_vec(),
75 Value::Bytes(b) => b.clone(),
76 Value::Embedding(v) => {
77 let mut out = Vec::with_capacity(v.len() * 4);
78 for x in v {
79 out.extend_from_slice(&x.to_bits().to_be_bytes());
80 }
81 out
82 }
83 Value::GeneratedEmbedding(value) => {
84 let mut out = Vec::with_capacity(value.vector.len() * 4);
85 for x in &value.vector {
86 out.extend_from_slice(&x.to_bits().to_be_bytes());
87 }
88 out
89 }
90 Value::Decimal(d) => d.to_be_bytes().to_vec(),
91 Value::Interval {
92 months,
93 days,
94 nanos,
95 } => {
96 let mut out = Vec::with_capacity(20);
97 out.extend_from_slice(&months.to_be_bytes());
98 out.extend_from_slice(&days.to_be_bytes());
99 out.extend_from_slice(&nanos.to_be_bytes());
100 out
101 }
102 Value::Uuid(b) => b.to_vec(),
103 Value::Json(b) => b.clone(),
104 }
105 }
106
107 pub(crate) fn estimated_bytes(&self) -> u64 {
108 match self {
109 Value::Null => 1,
110 Value::Bool(_) => 1,
111 Value::Int64(_) | Value::Float64(_) => 8,
112 Value::Bytes(bytes) | Value::Json(bytes) => 16 + bytes.len() as u64,
113 Value::Embedding(values) => 16 + (values.len() as u64) * 4,
114 Value::GeneratedEmbedding(value) => {
115 16 + (value.vector.len() as u64) * 4
116 + value.metadata.provider_id.len() as u64
117 + value.metadata.model_id.len() as u64
118 + value.metadata.model_version.len() as u64
119 + value.metadata.preprocessing_version.len() as u64
120 + 48
121 }
122 Value::Decimal(_) | Value::Uuid(_) => 16,
123 Value::Interval { .. } => 20,
124 }
125 }
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct Row {
137 pub row_id: RowId,
138 pub committed_epoch: Epoch,
139 pub columns: HashMap<u16, Value>,
140 pub deleted: bool,
141 #[serde(skip)]
145 pub commit_ts: Option<mongreldb_types::hlc::HlcTimestamp>,
146}
147
148impl Row {
149 pub fn new(row_id: RowId, committed_epoch: Epoch) -> Self {
150 Self {
151 row_id,
152 committed_epoch,
153 columns: HashMap::new(),
154 deleted: false,
155 commit_ts: None,
156 }
157 }
158
159 pub fn new_with_hlc(
160 row_id: RowId,
161 committed_epoch: Epoch,
162 commit_ts: mongreldb_types::hlc::HlcTimestamp,
163 ) -> Self {
164 Self {
165 row_id,
166 committed_epoch,
167 columns: HashMap::new(),
168 deleted: false,
169 commit_ts: Some(commit_ts),
170 }
171 }
172
173 pub fn with_column(mut self, column_id: u16, value: Value) -> Self {
174 self.columns.insert(column_id, value);
175 self
176 }
177
178 pub fn estimated_bytes(&self) -> u64 {
180 self.columns
181 .values()
182 .fold(32, |bytes, value| bytes + value.estimated_bytes())
183 }
184}
185
186pub struct MemtableVisibleVersionCursor<'a> {
187 leaves: Vec<LeafVersions<'a>>,
188 leaf_index: usize,
189 snapshot: Snapshot,
190 current_row_id: Option<RowId>,
191 best: Option<&'a Row>,
192 finished: bool,
193}
194
195impl<'a> Iterator for MemtableVisibleVersionCursor<'a> {
196 type Item = (RowId, Epoch, &'a Row);
197
198 fn next(&mut self) -> Option<Self::Item> {
199 if self.finished {
200 return None;
201 }
202 loop {
203 let next = self
204 .leaves
205 .get_mut(self.leaf_index)
206 .and_then(Iterator::next);
207 let Some(version) = next else {
208 self.leaf_index += 1;
209 if self.leaf_index < self.leaves.len() {
210 continue;
211 }
212 self.finished = true;
213 return self.take_best();
214 };
215 let row = match version {
216 std::borrow::Cow::Borrowed(row) => row,
217 std::borrow::Cow::Owned(_) => continue,
218 };
219 if !self
220 .snapshot
221 .observes_version(row.committed_epoch, row.commit_ts)
222 {
223 continue;
224 }
225 if self.current_row_id != Some(row.row_id) {
226 let result = self.take_best();
227 self.current_row_id = Some(row.row_id);
228 self.best = Some(row);
229 if result.is_some() {
230 return result;
231 }
232 } else if self.best.is_none_or(|best| {
233 Snapshot::version_is_newer(
234 row.committed_epoch,
235 row.commit_ts,
236 best.committed_epoch,
237 best.commit_ts,
238 )
239 }) {
240 self.best = Some(row);
241 }
242 }
243 }
244}
245
246impl<'a> MemtableVisibleVersionCursor<'a> {
247 fn take_best(&mut self) -> Option<(RowId, Epoch, &'a Row)> {
248 let row = self.best.take()?;
249 Some((row.row_id, row.committed_epoch, row))
250 }
251}
252
253#[derive(Clone)]
257struct MemtableSegment {
258 tree: BeTree,
259 byte_size: u64,
260}
261
262#[derive(Clone)]
264pub struct Memtable {
265 frozen: Arc<Vec<Arc<MemtableSegment>>>,
266 active: MemtableSegment,
267 byte_size: u64,
268}
269
270impl Default for Memtable {
271 fn default() -> Self {
272 Self::new()
273 }
274}
275
276impl Memtable {
277 pub fn new() -> Self {
278 Self {
279 frozen: Arc::new(Vec::new()),
280 active: MemtableSegment {
281 tree: BeTree::new(),
282 byte_size: 0,
283 },
284 byte_size: 0,
285 }
286 }
287
288 pub fn upsert(&mut self, row: Row) {
291 let bytes = row.estimated_bytes();
292 self.byte_size = self.byte_size.saturating_add(bytes);
293 self.active.byte_size = self.active.byte_size.saturating_add(bytes);
294 self.active.tree.insert_row(row);
295 }
296
297 pub fn tombstone(&mut self, row_id: RowId, epoch: Epoch) {
301 let mut columns = HashMap::new();
302 if let Some(live) = self.get(row_id, Epoch(epoch.0.saturating_sub(1))) {
303 columns = live.columns;
304 }
305 let row = Row {
306 row_id,
307 committed_epoch: epoch,
308 columns,
309 deleted: true,
310 commit_ts: None,
311 };
312 self.upsert(row);
313 }
314
315 pub fn get(&self, row_id: RowId, snapshot_epoch: Epoch) -> Option<Row> {
319 self.get_version(row_id, snapshot_epoch)
320 .and_then(|(_, row)| (!row.deleted).then_some(row))
321 }
322
323 pub fn get_version(&self, row_id: RowId, snapshot_epoch: Epoch) -> Option<(Epoch, Row)> {
328 self.get_version_at(row_id, crate::epoch::Snapshot::at(snapshot_epoch))
329 }
330
331 pub fn get_version_at(
338 &self,
339 row_id: RowId,
340 snapshot: crate::epoch::Snapshot,
341 ) -> Option<(Epoch, Row)> {
342 if !snapshot.uses_hlc_authority() {
343 let mut best = self.active.tree.get_version(row_id, snapshot.epoch);
344 for segment in self.frozen.iter().rev() {
345 let Some(candidate) = segment.tree.get_version(row_id, snapshot.epoch) else {
346 continue;
347 };
348 if best.as_ref().is_none_or(|(epoch, _)| candidate.0 > *epoch) {
349 best = Some(candidate);
350 }
351 }
352 return best;
353 }
354
355 let mut best: Option<Row> = None;
356 for segment in self
357 .frozen
358 .iter()
359 .map(|segment| &segment.tree)
360 .chain(std::iter::once(&self.active.tree))
361 {
362 segment.visit_versions(row_id, |row| {
363 if !snapshot.observes_row(row.committed_epoch, row.commit_ts) {
364 return;
365 }
366 if best.as_ref().is_none_or(|current| {
367 crate::epoch::Snapshot::version_is_newer(
368 row.committed_epoch,
369 row.commit_ts,
370 current.committed_epoch,
371 current.commit_ts,
372 )
373 }) {
374 best = Some(row);
375 }
376 });
377 }
378 best.map(|row| (row.committed_epoch, row))
379 }
380
381 pub fn len(&self) -> usize {
383 self.active.tree.mutations()
384 + self
385 .frozen
386 .iter()
387 .map(|segment| segment.tree.mutations())
388 .sum::<usize>()
389 }
390
391 pub fn is_empty(&self) -> bool {
392 self.active.tree.is_empty() && self.frozen.is_empty()
393 }
394
395 pub fn approx_bytes(&self) -> u64 {
396 self.byte_size
397 }
398
399 pub fn visible_rows(&self, snapshot_epoch: Epoch) -> Vec<Row> {
402 self.visible_versions(snapshot_epoch)
403 .into_iter()
404 .filter(|r| !r.deleted)
405 .collect()
406 }
407
408 pub fn visible_versions(&self, snapshot_epoch: Epoch) -> Vec<Row> {
412 self.visible_versions_at(crate::epoch::Snapshot::at(snapshot_epoch))
413 }
414
415 pub fn visible_versions_at(&self, snapshot: crate::epoch::Snapshot) -> Vec<Row> {
416 self.newest_visible_map(snapshot).into_values().collect()
417 }
418
419 pub(crate) fn newest_visible_map(
423 &self,
424 snapshot: crate::epoch::Snapshot,
425 ) -> BTreeMap<RowId, Row> {
426 let mut by_row: BTreeMap<RowId, Row> = BTreeMap::new();
427 for segment in std::iter::once(&self.active.tree)
428 .chain(self.frozen.iter().rev().map(|segment| &segment.tree))
429 {
430 for row in segment.versions() {
431 if !snapshot.observes_version(row.committed_epoch, row.commit_ts) {
432 continue;
433 }
434 by_row
435 .entry(row.row_id)
436 .and_modify(|existing| {
437 if crate::epoch::Snapshot::version_is_newer(
438 row.committed_epoch,
439 row.commit_ts,
440 existing.committed_epoch,
441 existing.commit_ts,
442 ) {
443 *existing = row.clone();
444 }
445 })
446 .or_insert(row);
447 }
448 }
449 by_row
450 }
451
452 pub fn newest_visible_iter<'a>(
453 &'a self,
454 snapshot: &Snapshot,
455 ) -> MemtableVisibleVersionCursor<'a> {
456 let leaves = self
457 .frozen
458 .iter()
459 .map(|segment| segment.tree.leaf_versions_iter())
460 .chain(std::iter::once(self.active.tree.leaf_versions_iter()))
461 .collect();
462 MemtableVisibleVersionCursor {
463 leaves,
464 leaf_index: 0,
465 snapshot: *snapshot,
466 current_row_id: None,
467 best: None,
468 finished: false,
469 }
470 }
471
472 pub(crate) fn seal(&mut self) {
474 if self.active.tree.is_empty() {
475 return;
476 }
477 let active = std::mem::replace(
478 &mut self.active,
479 MemtableSegment {
480 tree: BeTree::new(),
481 byte_size: 0,
482 },
483 );
484 Arc::make_mut(&mut self.frozen).push(Arc::new(active));
485 if self.frozen.len() >= crate::MAX_READ_GENERATION_LAYERS {
486 self.consolidate();
487 }
488 }
489
490 fn consolidate(&mut self) {
491 let mut tree = BeTree::new();
492 for row in self
493 .frozen
494 .iter()
495 .flat_map(|segment| segment.tree.versions())
496 {
497 tree.insert_row(row);
498 }
499 self.frozen = Arc::new(vec![Arc::new(MemtableSegment {
500 tree,
501 byte_size: self.byte_size,
502 })]);
503 }
504
505 #[cfg(test)]
506 pub(crate) fn frozen_layer_count(&self) -> usize {
507 self.frozen.len()
508 }
509
510 pub fn drain_sorted(&mut self) -> Vec<Row> {
513 let mut out = self
514 .frozen
515 .iter()
516 .flat_map(|segment| segment.tree.versions())
517 .chain(self.active.tree.versions())
518 .collect::<Vec<_>>();
519 out.sort_by_key(|row| (row.row_id, row.committed_epoch));
520 self.frozen = Arc::new(Vec::new());
521 self.active = MemtableSegment {
522 tree: BeTree::new(),
523 byte_size: 0,
524 };
525 self.byte_size = 0;
526 out
527 }
528}
529
530#[cfg(test)]
531mod tests {
532 use super::*;
533
534 fn row(id: u64, epoch: u64) -> Row {
535 Row::new(RowId(id), Epoch(epoch)).with_column(1, Value::Int64(id as i64 * 10))
536 }
537
538 #[test]
539 fn upsert_get_and_visibility() {
540 let mut m = Memtable::new();
541 m.upsert(row(1, 5));
542 assert_eq!(m.len(), 1);
543 assert!(m.get(RowId(1), Epoch(5)).is_some());
544 assert!(m.get(RowId(1), Epoch(4)).is_none()); assert!(m.get(RowId(2), Epoch(9)).is_none()); }
547
548 #[test]
549 fn tombstone_supersedes_at_its_epoch() {
550 let mut m = Memtable::new();
551 m.upsert(row(1, 1));
552 assert!(m.get(RowId(1), Epoch(1)).is_some());
554 m.tombstone(RowId(1), Epoch(2));
555 assert!(m.get(RowId(1), Epoch(2)).is_none());
557 assert!(m.get(RowId(1), Epoch(9)).is_none());
558 assert!(m.get(RowId(1), Epoch(1)).is_some());
560 }
561
562 #[test]
563 fn sealed_generations_share_rows_and_consolidate() {
564 let mut writer = Memtable::new();
565 for id in 0..crate::MAX_READ_GENERATION_LAYERS as u64 + 2 {
566 writer.upsert(row(id, id + 1));
567 writer.seal();
568 }
569 assert!(writer.frozen_layer_count() < crate::MAX_READ_GENERATION_LAYERS);
570 let generation = writer.clone();
571 writer.upsert(row(99, 99));
572 assert!(generation.get(RowId(99), Epoch(99)).is_none());
573 assert!(writer.get(RowId(99), Epoch(99)).is_some());
574 }
575
576 #[test]
577 fn hlc_visibility_is_authoritative_when_stamped() {
578 use mongreldb_types::hlc::HlcTimestamp;
579 let mut m = Memtable::new();
580 let early = HlcTimestamp {
581 physical_micros: 100,
582 logical: 0,
583 node_tiebreaker: 1,
584 };
585 let late = HlcTimestamp {
586 physical_micros: 200,
587 logical: 0,
588 node_tiebreaker: 1,
589 };
590 let mut r1 = Row::new_with_hlc(RowId(1), Epoch(1), early);
591 r1.columns.insert(1, Value::Int64(1));
592 let mut r2 = Row::new_with_hlc(RowId(1), Epoch(2), late);
593 r2.columns.insert(1, Value::Int64(2));
594 m.upsert(r1);
595 m.upsert(r2);
596 let snap = crate::epoch::Snapshot::at_hlc(Epoch(99), early);
597 let versions = m.visible_versions_at(snap);
598 assert_eq!(versions.len(), 1);
599 assert_eq!(versions[0].columns.get(&1), Some(&Value::Int64(1)));
600 let snap2 = crate::epoch::Snapshot::at_hlc(Epoch(1), late);
601 assert_eq!(
602 m.visible_versions_at(snap2)[0].columns.get(&1),
603 Some(&Value::Int64(2))
604 );
605 }
606
607 #[test]
608 fn snapshot_hlc_hides_later_commit_ts_even_if_epoch_higher() {
609 use mongreldb_types::hlc::HlcTimestamp;
610 let mut m = Memtable::new();
611 let early = HlcTimestamp {
612 physical_micros: 100,
613 logical: 0,
614 node_tiebreaker: 1,
615 };
616 let late = HlcTimestamp {
617 physical_micros: 200,
618 logical: 0,
619 node_tiebreaker: 1,
620 };
621 let mut late_row = Row::new_with_hlc(RowId(1), Epoch(1), late);
624 late_row.columns.insert(1, Value::Int64(99));
625 let mut early_row = Row::new_with_hlc(RowId(1), Epoch(50), early);
626 early_row.columns.insert(1, Value::Int64(1));
627 m.upsert(late_row);
628 m.upsert(early_row);
629 let snap = crate::epoch::Snapshot::at_hlc(Epoch(99), early);
630 let versions = m.visible_versions_at(snap);
631 assert_eq!(versions.len(), 1);
632 assert_eq!(versions[0].columns.get(&1), Some(&Value::Int64(1)));
633 assert_eq!(versions[0].commit_ts, Some(early));
634 }
635
636 #[test]
637 fn epoch_only_snapshot_sees_hlc_stamped_rows_by_epoch() {
638 use mongreldb_types::hlc::HlcTimestamp;
639 let mut m = Memtable::new();
640 let ts = HlcTimestamp {
641 physical_micros: 50,
642 logical: 0,
643 node_tiebreaker: 1,
644 };
645 m.upsert(Row::new_with_hlc(RowId(1), Epoch(1), ts).with_column(1, Value::Int64(1)));
646 m.upsert(Row::new(RowId(2), Epoch(1)).with_column(1, Value::Int64(2)));
647 let legacy = crate::epoch::Snapshot::at(Epoch(99));
648 let versions = m.visible_versions_at(legacy);
649 assert_eq!(
650 versions.len(),
651 2,
652 "dual-model: epoch pin sees HLC rows by epoch"
653 );
654 assert!(m.get_version_at(RowId(1), legacy).is_some());
655 assert!(m.get_version_at(RowId(2), legacy).is_some());
656 let future = crate::epoch::Snapshot::at(Epoch(0));
657 assert!(m.get_version_at(RowId(1), future).is_none());
658 }
659
660 #[test]
661 fn get_version_at_prefers_hlc_over_epoch_order() {
662 use mongreldb_types::hlc::HlcTimestamp;
663 let mut m = Memtable::new();
664 let early = HlcTimestamp {
665 physical_micros: 100,
666 logical: 0,
667 node_tiebreaker: 1,
668 };
669 let late = HlcTimestamp {
670 physical_micros: 200,
671 logical: 0,
672 node_tiebreaker: 1,
673 };
674 m.upsert(Row::new_with_hlc(RowId(1), Epoch(1), late).with_column(1, Value::Int64(99)));
675 m.upsert(Row::new_with_hlc(RowId(1), Epoch(50), early).with_column(1, Value::Int64(1)));
676 let snap = crate::epoch::Snapshot::at_hlc(Epoch(99), early);
677 let (_, row) = m.get_version_at(RowId(1), snap).expect("visible");
678 assert_eq!(row.columns.get(&1), Some(&Value::Int64(1)));
679 assert_eq!(row.commit_ts, Some(early));
680 }
681
682 #[test]
686 fn wal_put_row_bincode_matches_0_63_1_layout() {
687 use mongreldb_types::hlc::HlcTimestamp;
688 use serde::{Deserialize, Serialize};
689
690 #[derive(Serialize, Deserialize)]
691 struct LegacyRow {
692 row_id: RowId,
693 committed_epoch: Epoch,
694 columns: HashMap<u16, Value>,
695 deleted: bool,
696 }
697
698 let legacy = LegacyRow {
699 row_id: RowId(7),
700 committed_epoch: Epoch(3),
701 columns: [(1, Value::Int64(42))].into_iter().collect(),
702 deleted: false,
703 };
704 let bytes = bincode::serialize(&legacy).expect("legacy encode");
705
706 let decoded: Row = bincode::deserialize(&bytes).expect("0.63.1 payload must decode");
707 assert_eq!(decoded.row_id, RowId(7));
708 assert_eq!(decoded.committed_epoch, Epoch(3));
709 assert_eq!(decoded.columns.get(&1), Some(&Value::Int64(42)));
710 assert!(!decoded.deleted);
711 assert!(decoded.commit_ts.is_none());
712
713 let stamped = HlcTimestamp {
715 physical_micros: 1_700_000_000_000,
716 logical: 2,
717 node_tiebreaker: 9,
718 };
719 let mut live = Row::new_with_hlc(RowId(7), Epoch(3), stamped);
720 live.columns.insert(1, Value::Int64(42));
721 let wire = bincode::serialize(&live).expect("row encode");
722 assert_eq!(
723 wire, bytes,
724 "WAL Put encoding must match the 0.63.1 four-field layout"
725 );
726 let again: Row = bincode::deserialize(&wire).expect("row decode");
727 assert!(
728 again.commit_ts.is_none(),
729 "commit_ts is restored from Op::CommitTimestamp, not the Put blob"
730 );
731 }
732
733 #[test]
734 fn drain_sorted_is_ascending_and_empties() {
735 let mut m = Memtable::new();
736 m.upsert(row(3, 1));
737 m.upsert(row(1, 1));
738 m.upsert(row(2, 1));
739 let out = m.drain_sorted();
740 let ids: Vec<u64> = out.iter().map(|r| r.row_id.0).collect();
741 assert_eq!(ids, vec![1, 2, 3]);
742 assert!(m.is_empty());
743 assert_eq!(m.approx_bytes(), 0);
744 }
745
746 #[test]
747 fn visible_rows_dedups_to_newest_version() {
748 let mut m = Memtable::new();
749 m.upsert(row(1, 1));
750 m.upsert(row(2, 9)); m.upsert(row(3, 1));
752 m.upsert(row(1, 3)); let ids: Vec<u64> = m
754 .visible_rows(Epoch(5))
755 .iter()
756 .map(|r| r.row_id.0)
757 .collect();
758 assert_eq!(ids, vec![1, 3]);
759 }
760
761 #[test]
762 fn newest_visible_map_prefers_active_on_equal_version() {
763 let mut m = Memtable::new();
764 let mut deleted = row(1, 2);
765 deleted.deleted = true;
766 m.upsert(deleted);
767 m.seal();
768 m.upsert(row(1, 2));
769
770 let versions = m.visible_versions_at(Snapshot::at(Epoch(2)));
771 assert_eq!(versions.len(), 1);
772 assert!(!versions[0].deleted);
773 }
774
775 #[test]
776 fn newest_visible_iter_empty_memtable_yields_nothing() {
777 let m = Memtable::new();
778 assert!(m
779 .newest_visible_iter(&Snapshot::at(Epoch(9)))
780 .next()
781 .is_none());
782 }
783
784 #[test]
785 fn newest_visible_iter_single_insert_yields_one() {
786 let mut m = Memtable::new();
787 m.upsert(row(1, 3));
788 let values: Vec<_> = m
789 .newest_visible_iter(&Snapshot::at(Epoch(3)))
790 .map(|(id, epoch, _)| (id, epoch))
791 .collect();
792 assert_eq!(values, vec![(RowId(1), Epoch(3))]);
793 }
794
795 #[test]
796 fn newest_visible_iter_newer_epoch_wins() {
797 let mut m = Memtable::new();
798 m.upsert(row(1, 1));
799 m.upsert(row(1, 2));
800 let values: Vec<_> = m
801 .newest_visible_iter(&Snapshot::at(Epoch(2)))
802 .map(|(_, epoch, _)| epoch)
803 .collect();
804 assert_eq!(values, vec![Epoch(2)]);
805 }
806
807 #[test]
808 fn newest_visible_iter_tombstone_suppresses_older_live_version() {
809 let mut m = Memtable::new();
814 m.upsert(row(1, 1));
815 m.tombstone(RowId(1), Epoch(2));
816 let snap = Snapshot::at(Epoch(2));
817 let got: Vec<(u64, u64, bool)> = m
818 .newest_visible_iter(&snap)
819 .map(|(rid, epoch, row)| (rid.0, epoch.0, row.deleted))
820 .collect();
821 assert_eq!(got, vec![(1, 2, true)], "tombstone is the newest");
822 let snap_early = Snapshot::at(Epoch(1));
824 let got_early: Vec<(u64, u64, bool)> = m
825 .newest_visible_iter(&snap_early)
826 .map(|(rid, epoch, row)| (rid.0, epoch.0, row.deleted))
827 .collect();
828 assert_eq!(got_early, vec![(1, 1, false)]);
829 }
830}