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