1use crate::be_tree::{BeTree, BeTreeVersionCursor, BeTreeVersionCursorStats};
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
188const CURSOR_GROUP_CHECKPOINT_INTERVAL: usize = 256;
191
192struct MemHead<'a> {
196 rid: RowId,
197 epoch: Epoch,
198 index: u32,
199 row: Cow<'a, Row>,
200}
201
202impl PartialEq for MemHead<'_> {
203 fn eq(&self, other: &Self) -> bool {
204 (self.rid, self.epoch, self.index) == (other.rid, other.epoch, other.index)
205 }
206}
207impl Eq for MemHead<'_> {}
208impl PartialOrd for MemHead<'_> {
209 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
210 Some(self.cmp(other))
211 }
212}
213impl Ord for MemHead<'_> {
214 fn cmp(&self, other: &Self) -> Ordering {
215 (other.rid, other.epoch, other.index).cmp(&(self.rid, self.epoch, self.index))
216 }
217}
218
219pub struct MemtableVisibleVersionCursor<'a> {
229 segments: Vec<BeTreeVersionCursor<'a>>,
230 heap: BinaryHeap<MemHead<'a>>,
231 snapshot: Snapshot,
232 primed: bool,
236 finished: bool,
237 pub(crate) last_examined: usize,
240 pub(crate) peak_examined: usize,
242}
243
244impl<'a> MemtableVisibleVersionCursor<'a> {
245 #[doc(hidden)] pub fn next_controlled(
250 &mut self,
251 control: &crate::ExecutionControl,
252 ) -> crate::Result<Option<(RowId, Epoch, Cow<'a, Row>)>> {
253 self.advance_impl(Some(control))
254 }
255
256 #[doc(hidden)] pub fn be_tree_cursor_stats(&self) -> BeTreeVersionCursorStats {
260 let mut total = BeTreeVersionCursorStats::default();
261 for segment in &self.segments {
262 let stats = segment.stats();
263 total.active_frames += stats.active_frames;
264 total.peak_active_frames = total.peak_active_frames.max(stats.peak_active_frames);
265 total.buffered_messages_owned += stats.buffered_messages_owned;
266 total.peak_buffered_messages_owned = total
267 .peak_buffered_messages_owned
268 .max(stats.peak_buffered_messages_owned);
269 total.total_versions_precollected += stats.total_versions_precollected;
270 total.versions_examined += stats.versions_examined;
271 total.checkpoints += stats.checkpoints;
272 }
273 total
274 }
275
276 fn segment_next(
277 &mut self,
278 index: usize,
279 control: Option<&crate::ExecutionControl>,
280 ) -> crate::Result<Option<Cow<'a, Row>>> {
281 match control {
282 Some(control) => self.segments[index].next_controlled(control),
283 None => Ok(self.segments[index].next()),
284 }
285 }
286
287 fn push_head(&mut self, index: usize, row: Cow<'a, Row>) {
288 self.heap.push(MemHead {
289 rid: row.row_id,
290 epoch: row.committed_epoch,
291 index: index as u32,
292 row,
293 });
294 }
295
296 fn prime(&mut self, control: Option<&crate::ExecutionControl>) -> crate::Result<()> {
297 for index in 0..self.segments.len() {
298 if let Some(row) = self.segment_next(index, control)? {
299 self.push_head(index, row);
300 }
301 }
302 Ok(())
303 }
304
305 fn advance_impl(
306 &mut self,
307 control: Option<&crate::ExecutionControl>,
308 ) -> crate::Result<Option<(RowId, Epoch, Cow<'a, Row>)>> {
309 if self.finished {
310 return Ok(None);
311 }
312 if !self.primed {
313 self.prime(control)?;
314 self.primed = true;
315 }
316 self.last_examined = 0;
317 loop {
318 let Some(MemHead {
319 rid,
320 epoch,
321 index,
322 row,
323 }) = self.heap.pop()
324 else {
325 self.finished = true;
326 return Ok(None);
327 };
328 self.last_examined += 1;
329 if let Some(next) = self.segment_next(index as usize, control)? {
331 self.push_head(index as usize, next);
332 }
333 if !self.snapshot.observes_version(epoch, row.commit_ts) {
334 continue;
335 }
336 let mut best = Some(row);
340 let mut gathered = 0usize;
341 while self.heap.peek().is_some_and(|h| h.rid == rid) {
342 gathered += 1;
343 if gathered.is_multiple_of(CURSOR_GROUP_CHECKPOINT_INTERVAL) {
344 if let Some(control) = control {
345 control.checkpoint()?;
346 }
347 }
348 let head = self.heap.pop().unwrap();
349 self.last_examined += 1;
350 if let Some(next) = self.segment_next(head.index as usize, control)? {
351 self.push_head(head.index as usize, next);
352 }
353 if self
354 .snapshot
355 .observes_version(head.epoch, head.row.commit_ts)
356 && best.as_ref().is_none_or(|current| {
357 crate::epoch::version_supersedes(
361 head.epoch,
362 head.row.commit_ts,
363 current.committed_epoch,
364 current.commit_ts,
365 )
366 })
367 {
368 best = Some(head.row);
369 }
370 }
371 let Some(best) = best else {
372 continue;
373 };
374 let row_id = best.row_id;
375 let epoch = best.committed_epoch;
376 self.peak_examined = self.peak_examined.max(self.last_examined);
377 return Ok(Some((row_id, epoch, best)));
378 }
379 }
380}
381
382impl<'a> Iterator for MemtableVisibleVersionCursor<'a> {
383 type Item = (RowId, Epoch, Cow<'a, Row>);
384
385 fn next(&mut self) -> Option<Self::Item> {
386 match self.advance_impl(None) {
387 Ok(item) => item,
388 Err(_) => unreachable!("the no-cancellation path cannot fail"),
389 }
390 }
391}
392
393impl<'a> MemtableVisibleVersionCursor<'a> {
394 pub fn is_exhausted(&self) -> bool {
396 self.finished
397 }
398}
399
400#[derive(Clone)]
404struct MemtableSegment {
405 tree: BeTree,
406 byte_size: u64,
407}
408
409#[derive(Clone)]
411pub struct Memtable {
412 frozen: Arc<Vec<Arc<MemtableSegment>>>,
413 active: MemtableSegment,
414 byte_size: u64,
415}
416
417impl Default for Memtable {
418 fn default() -> Self {
419 Self::new()
420 }
421}
422
423impl Memtable {
424 pub fn new() -> Self {
425 Self {
426 frozen: Arc::new(Vec::new()),
427 active: MemtableSegment {
428 tree: BeTree::new(),
429 byte_size: 0,
430 },
431 byte_size: 0,
432 }
433 }
434
435 pub fn upsert(&mut self, row: Row) {
438 let bytes = row.estimated_bytes();
439 self.byte_size = self.byte_size.saturating_add(bytes);
440 self.active.byte_size = self.active.byte_size.saturating_add(bytes);
441 self.active.tree.insert_row(row);
442 }
443
444 pub fn tombstone(&mut self, row_id: RowId, epoch: Epoch) {
448 let mut columns = HashMap::new();
449 if let Some(live) = self.get(row_id, Epoch(epoch.0.saturating_sub(1))) {
450 columns = live.columns;
451 }
452 let row = Row {
453 row_id,
454 committed_epoch: epoch,
455 columns,
456 deleted: true,
457 commit_ts: None,
458 };
459 self.upsert(row);
460 }
461
462 pub fn get(&self, row_id: RowId, snapshot_epoch: Epoch) -> Option<Row> {
466 self.get_version(row_id, snapshot_epoch)
467 .and_then(|(_, row)| (!row.deleted).then_some(row))
468 }
469
470 pub fn get_version(&self, row_id: RowId, snapshot_epoch: Epoch) -> Option<(Epoch, Row)> {
475 self.get_version_at(row_id, crate::epoch::Snapshot::at(snapshot_epoch))
476 }
477
478 pub fn get_version_at(
485 &self,
486 row_id: RowId,
487 snapshot: crate::epoch::Snapshot,
488 ) -> Option<(Epoch, Row)> {
489 if !snapshot.uses_hlc_authority() {
490 let mut best = self.active.tree.get_version(row_id, snapshot.epoch);
494 for segment in self.frozen.iter().rev() {
495 let Some(candidate) = segment.tree.get_version(row_id, snapshot.epoch) else {
496 continue;
497 };
498 if best.as_ref().is_none_or(|(epoch, _)| candidate.0 > *epoch) {
499 best = Some(candidate);
500 }
501 }
502 return best;
503 }
504
505 let mut best: Option<Row> = None;
506 for segment in self
507 .frozen
508 .iter()
509 .map(|segment| &segment.tree)
510 .chain(std::iter::once(&self.active.tree))
511 {
512 segment.visit_versions(row_id, |row| {
513 if !snapshot.observes_row(row.committed_epoch, row.commit_ts) {
514 return;
515 }
516 if best.as_ref().is_none_or(|current| {
519 crate::epoch::version_supersedes(
520 row.committed_epoch,
521 row.commit_ts,
522 current.committed_epoch,
523 current.commit_ts,
524 )
525 }) {
526 best = Some(row);
527 }
528 });
529 }
530 best.map(|row| (row.committed_epoch, row))
531 }
532
533 pub fn len(&self) -> usize {
535 self.active.tree.mutations()
536 + self
537 .frozen
538 .iter()
539 .map(|segment| segment.tree.mutations())
540 .sum::<usize>()
541 }
542
543 pub fn is_empty(&self) -> bool {
544 self.active.tree.is_empty() && self.frozen.is_empty()
545 }
546
547 pub fn approx_bytes(&self) -> u64 {
548 self.byte_size
549 }
550
551 pub fn visible_rows(&self, snapshot_epoch: Epoch) -> Vec<Row> {
554 self.visible_versions(snapshot_epoch)
555 .into_iter()
556 .filter(|r| !r.deleted)
557 .collect()
558 }
559
560 pub fn visible_versions(&self, snapshot_epoch: Epoch) -> Vec<Row> {
564 self.visible_versions_at(crate::epoch::Snapshot::at(snapshot_epoch))
565 }
566
567 pub fn visible_versions_at(&self, snapshot: crate::epoch::Snapshot) -> Vec<Row> {
568 self.newest_visible_map(snapshot).into_values().collect()
569 }
570
571 pub(crate) fn newest_visible_map(
575 &self,
576 snapshot: crate::epoch::Snapshot,
577 ) -> BTreeMap<RowId, Row> {
578 let mut by_row: BTreeMap<RowId, Row> = BTreeMap::new();
579 for segment in self
583 .frozen
584 .iter()
585 .map(|segment| &segment.tree)
586 .chain(std::iter::once(&self.active.tree))
587 {
588 for row in segment.versions() {
589 if !snapshot.observes_version(row.committed_epoch, row.commit_ts) {
590 continue;
591 }
592 by_row
593 .entry(row.row_id)
594 .and_modify(|existing| {
595 if crate::epoch::version_supersedes(
596 row.committed_epoch,
597 row.commit_ts,
598 existing.committed_epoch,
599 existing.commit_ts,
600 ) {
601 *existing = row.clone();
602 }
603 })
604 .or_insert(row);
605 }
606 }
607 by_row
608 }
609
610 pub fn newest_visible_iter<'a>(
611 &'a self,
612 snapshot: &Snapshot,
613 ) -> MemtableVisibleVersionCursor<'a> {
614 let segments: Vec<BeTreeVersionCursor<'a>> = self
615 .frozen
616 .iter()
617 .map(|segment| segment.tree.leaf_versions_iter())
618 .chain(std::iter::once(self.active.tree.leaf_versions_iter()))
619 .collect();
620 MemtableVisibleVersionCursor {
621 segments,
622 heap: BinaryHeap::new(),
623 snapshot: *snapshot,
624 primed: false,
625 finished: false,
626 last_examined: 0,
627 peak_examined: 0,
628 }
629 }
630
631 pub(crate) fn seal(&mut self) {
633 if self.active.tree.is_empty() {
634 return;
635 }
636 let active = std::mem::replace(
637 &mut self.active,
638 MemtableSegment {
639 tree: BeTree::new(),
640 byte_size: 0,
641 },
642 );
643 Arc::make_mut(&mut self.frozen).push(Arc::new(active));
644 if self.frozen.len() >= crate::MAX_READ_GENERATION_LAYERS {
645 self.consolidate();
646 }
647 }
648
649 fn consolidate(&mut self) {
650 let mut tree = BeTree::new();
651 for row in self
652 .frozen
653 .iter()
654 .flat_map(|segment| segment.tree.versions())
655 {
656 tree.insert_row(row);
657 }
658 self.frozen = Arc::new(vec![Arc::new(MemtableSegment {
659 tree,
660 byte_size: self.byte_size,
661 })]);
662 }
663
664 #[cfg(test)]
665 pub(crate) fn frozen_layer_count(&self) -> usize {
666 self.frozen.len()
667 }
668
669 pub fn drain_sorted(&mut self) -> Vec<Row> {
672 let mut out = self
673 .frozen
674 .iter()
675 .flat_map(|segment| segment.tree.versions())
676 .chain(self.active.tree.versions())
677 .collect::<Vec<_>>();
678 out.sort_by_key(|row| (row.row_id, row.committed_epoch));
679 self.frozen = Arc::new(Vec::new());
680 self.active = MemtableSegment {
681 tree: BeTree::new(),
682 byte_size: 0,
683 };
684 self.byte_size = 0;
685 out
686 }
687}
688
689#[cfg(test)]
690mod tests {
691 use super::*;
692
693 fn row(id: u64, epoch: u64) -> Row {
694 Row::new(RowId(id), Epoch(epoch)).with_column(1, Value::Int64(id as i64 * 10))
695 }
696
697 #[test]
698 fn upsert_get_and_visibility() {
699 let mut m = Memtable::new();
700 m.upsert(row(1, 5));
701 assert_eq!(m.len(), 1);
702 assert!(m.get(RowId(1), Epoch(5)).is_some());
703 assert!(m.get(RowId(1), Epoch(4)).is_none()); assert!(m.get(RowId(2), Epoch(9)).is_none()); }
706
707 #[test]
708 fn tombstone_supersedes_at_its_epoch() {
709 let mut m = Memtable::new();
710 m.upsert(row(1, 1));
711 assert!(m.get(RowId(1), Epoch(1)).is_some());
713 m.tombstone(RowId(1), Epoch(2));
714 assert!(m.get(RowId(1), Epoch(2)).is_none());
716 assert!(m.get(RowId(1), Epoch(9)).is_none());
717 assert!(m.get(RowId(1), Epoch(1)).is_some());
719 }
720
721 #[test]
722 fn sealed_generations_share_rows_and_consolidate() {
723 let mut writer = Memtable::new();
724 for id in 0..crate::MAX_READ_GENERATION_LAYERS as u64 + 2 {
725 writer.upsert(row(id, id + 1));
726 writer.seal();
727 }
728 assert!(writer.frozen_layer_count() < crate::MAX_READ_GENERATION_LAYERS);
729 let generation = writer.clone();
730 writer.upsert(row(99, 99));
731 assert!(generation.get(RowId(99), Epoch(99)).is_none());
732 assert!(writer.get(RowId(99), Epoch(99)).is_some());
733 }
734
735 #[test]
736 fn hlc_visibility_is_authoritative_when_stamped() {
737 use mongreldb_types::hlc::HlcTimestamp;
738 let mut m = Memtable::new();
739 let early = HlcTimestamp {
740 physical_micros: 100,
741 logical: 0,
742 node_tiebreaker: 1,
743 };
744 let late = HlcTimestamp {
745 physical_micros: 200,
746 logical: 0,
747 node_tiebreaker: 1,
748 };
749 let mut r1 = Row::new_with_hlc(RowId(1), Epoch(1), early);
750 r1.columns.insert(1, Value::Int64(1));
751 let mut r2 = Row::new_with_hlc(RowId(1), Epoch(2), late);
752 r2.columns.insert(1, Value::Int64(2));
753 m.upsert(r1);
754 m.upsert(r2);
755 let snap = crate::epoch::Snapshot::at_hlc(Epoch(99), early);
756 let versions = m.visible_versions_at(snap);
757 assert_eq!(versions.len(), 1);
758 assert_eq!(versions[0].columns.get(&1), Some(&Value::Int64(1)));
759 let snap2 = crate::epoch::Snapshot::at_hlc(Epoch(1), late);
760 assert_eq!(
761 m.visible_versions_at(snap2)[0].columns.get(&1),
762 Some(&Value::Int64(2))
763 );
764 }
765
766 #[test]
767 fn snapshot_hlc_hides_later_commit_ts_even_if_epoch_higher() {
768 use mongreldb_types::hlc::HlcTimestamp;
769 let mut m = Memtable::new();
770 let early = HlcTimestamp {
771 physical_micros: 100,
772 logical: 0,
773 node_tiebreaker: 1,
774 };
775 let late = HlcTimestamp {
776 physical_micros: 200,
777 logical: 0,
778 node_tiebreaker: 1,
779 };
780 let mut late_row = Row::new_with_hlc(RowId(1), Epoch(1), late);
783 late_row.columns.insert(1, Value::Int64(99));
784 let mut early_row = Row::new_with_hlc(RowId(1), Epoch(50), early);
785 early_row.columns.insert(1, Value::Int64(1));
786 m.upsert(late_row);
787 m.upsert(early_row);
788 let snap = crate::epoch::Snapshot::at_hlc(Epoch(99), early);
789 let versions = m.visible_versions_at(snap);
790 assert_eq!(versions.len(), 1);
791 assert_eq!(versions[0].columns.get(&1), Some(&Value::Int64(1)));
792 assert_eq!(versions[0].commit_ts, Some(early));
793 }
794
795 #[test]
796 fn epoch_only_snapshot_sees_hlc_stamped_rows_by_epoch() {
797 use mongreldb_types::hlc::HlcTimestamp;
798 let mut m = Memtable::new();
799 let ts = HlcTimestamp {
800 physical_micros: 50,
801 logical: 0,
802 node_tiebreaker: 1,
803 };
804 m.upsert(Row::new_with_hlc(RowId(1), Epoch(1), ts).with_column(1, Value::Int64(1)));
805 m.upsert(Row::new(RowId(2), Epoch(1)).with_column(1, Value::Int64(2)));
806 let legacy = crate::epoch::Snapshot::at(Epoch(99));
807 let versions = m.visible_versions_at(legacy);
808 assert_eq!(
809 versions.len(),
810 2,
811 "dual-model: epoch pin sees HLC rows by epoch"
812 );
813 assert!(m.get_version_at(RowId(1), legacy).is_some());
814 assert!(m.get_version_at(RowId(2), legacy).is_some());
815 let future = crate::epoch::Snapshot::at(Epoch(0));
816 assert!(m.get_version_at(RowId(1), future).is_none());
817 }
818
819 #[test]
820 fn get_version_at_prefers_hlc_over_epoch_order() {
821 use mongreldb_types::hlc::HlcTimestamp;
822 let mut m = Memtable::new();
823 let early = HlcTimestamp {
824 physical_micros: 100,
825 logical: 0,
826 node_tiebreaker: 1,
827 };
828 let late = HlcTimestamp {
829 physical_micros: 200,
830 logical: 0,
831 node_tiebreaker: 1,
832 };
833 m.upsert(Row::new_with_hlc(RowId(1), Epoch(1), late).with_column(1, Value::Int64(99)));
834 m.upsert(Row::new_with_hlc(RowId(1), Epoch(50), early).with_column(1, Value::Int64(1)));
835 let snap = crate::epoch::Snapshot::at_hlc(Epoch(99), early);
836 let (_, row) = m.get_version_at(RowId(1), snap).expect("visible");
837 assert_eq!(row.columns.get(&1), Some(&Value::Int64(1)));
838 assert_eq!(row.commit_ts, Some(early));
839 }
840
841 #[test]
845 fn wal_put_row_bincode_matches_0_63_1_layout() {
846 use mongreldb_types::hlc::HlcTimestamp;
847 use serde::{Deserialize, Serialize};
848
849 #[derive(Serialize, Deserialize)]
850 struct LegacyRow {
851 row_id: RowId,
852 committed_epoch: Epoch,
853 columns: HashMap<u16, Value>,
854 deleted: bool,
855 }
856
857 let legacy = LegacyRow {
858 row_id: RowId(7),
859 committed_epoch: Epoch(3),
860 columns: [(1, Value::Int64(42))].into_iter().collect(),
861 deleted: false,
862 };
863 let bytes = bincode::serialize(&legacy).expect("legacy encode");
864
865 let decoded: Row = bincode::deserialize(&bytes).expect("0.63.1 payload must decode");
866 assert_eq!(decoded.row_id, RowId(7));
867 assert_eq!(decoded.committed_epoch, Epoch(3));
868 assert_eq!(decoded.columns.get(&1), Some(&Value::Int64(42)));
869 assert!(!decoded.deleted);
870 assert!(decoded.commit_ts.is_none());
871
872 let stamped = HlcTimestamp {
874 physical_micros: 1_700_000_000_000,
875 logical: 2,
876 node_tiebreaker: 9,
877 };
878 let mut live = Row::new_with_hlc(RowId(7), Epoch(3), stamped);
879 live.columns.insert(1, Value::Int64(42));
880 let wire = bincode::serialize(&live).expect("row encode");
881 assert_eq!(
882 wire, bytes,
883 "WAL Put encoding must match the 0.63.1 four-field layout"
884 );
885 let again: Row = bincode::deserialize(&wire).expect("row decode");
886 assert!(
887 again.commit_ts.is_none(),
888 "commit_ts is restored from Op::CommitTimestamp, not the Put blob"
889 );
890 }
891
892 #[test]
893 fn drain_sorted_is_ascending_and_empties() {
894 let mut m = Memtable::new();
895 m.upsert(row(3, 1));
896 m.upsert(row(1, 1));
897 m.upsert(row(2, 1));
898 let out = m.drain_sorted();
899 let ids: Vec<u64> = out.iter().map(|r| r.row_id.0).collect();
900 assert_eq!(ids, vec![1, 2, 3]);
901 assert!(m.is_empty());
902 assert_eq!(m.approx_bytes(), 0);
903 }
904
905 #[test]
906 fn visible_rows_dedups_to_newest_version() {
907 let mut m = Memtable::new();
908 m.upsert(row(1, 1));
909 m.upsert(row(2, 9)); m.upsert(row(3, 1));
911 m.upsert(row(1, 3)); let ids: Vec<u64> = m
913 .visible_rows(Epoch(5))
914 .iter()
915 .map(|r| r.row_id.0)
916 .collect();
917 assert_eq!(ids, vec![1, 3]);
918 }
919
920 #[test]
921 fn newest_visible_map_prefers_active_on_equal_version() {
922 let mut m = Memtable::new();
923 let mut deleted = row(1, 2);
924 deleted.deleted = true;
925 m.upsert(deleted);
926 m.seal();
927 m.upsert(row(1, 2));
928
929 let versions = m.visible_versions_at(Snapshot::at(Epoch(2)));
930 assert_eq!(versions.len(), 1);
931 assert!(!versions[0].deleted);
932 }
933
934 #[test]
935 fn newest_visible_iter_empty_memtable_yields_nothing() {
936 let m = Memtable::new();
937 assert!(m
938 .newest_visible_iter(&Snapshot::at(Epoch(9)))
939 .next()
940 .is_none());
941 }
942
943 #[test]
944 fn newest_visible_iter_single_insert_yields_one() {
945 let mut m = Memtable::new();
946 m.upsert(row(1, 3));
947 let values: Vec<_> = m
948 .newest_visible_iter(&Snapshot::at(Epoch(3)))
949 .map(|(id, epoch, _)| (id, epoch))
950 .collect();
951 assert_eq!(values, vec![(RowId(1), Epoch(3))]);
952 }
953
954 #[test]
955 fn newest_visible_iter_newer_epoch_wins() {
956 let mut m = Memtable::new();
957 m.upsert(row(1, 1));
958 m.upsert(row(1, 2));
959 let values: Vec<_> = m
960 .newest_visible_iter(&Snapshot::at(Epoch(2)))
961 .map(|(_, epoch, _)| epoch)
962 .collect();
963 assert_eq!(values, vec![Epoch(2)]);
964 }
965
966 #[test]
967 fn newest_visible_iter_tombstone_suppresses_older_live_version() {
968 let mut m = Memtable::new();
973 m.upsert(row(1, 1));
974 m.tombstone(RowId(1), Epoch(2));
975 let snap = Snapshot::at(Epoch(2));
976 let got: Vec<(u64, u64, bool)> = m
977 .newest_visible_iter(&snap)
978 .map(|(rid, epoch, row)| (rid.0, epoch.0, row.deleted))
979 .collect();
980 assert_eq!(got, vec![(1, 2, true)], "tombstone is the newest");
981 let snap_early = Snapshot::at(Epoch(1));
983 let got_early: Vec<(u64, u64, bool)> = m
984 .newest_visible_iter(&snap_early)
985 .map(|(rid, epoch, row)| (rid.0, epoch.0, row.deleted))
986 .collect();
987 assert_eq!(got_early, vec![(1, 1, false)]);
988 }
989
990 #[test]
1006 fn newest_visible_iter_includes_root_buffer_rows() {
1007 const N: u64 = 1_000;
1008 let mut m = Memtable::new();
1009 for i in 0..N {
1013 let mut r = Row::new(RowId(i), Epoch(i + 1));
1014 r.columns.insert(1, Value::Int64(i as i64 * 10));
1015 m.upsert(r);
1016 }
1017 assert_eq!(m.len(), N as usize);
1018
1019 let snap = Snapshot::at(Epoch(N + 10));
1021 let got: Vec<(u64, u64, i64)> = m
1022 .newest_visible_iter(&snap)
1023 .map(|(rid, epoch, row)| (rid.0, epoch.0, int_of_value(&row)))
1024 .collect();
1025
1026 assert_eq!(
1028 got.len(),
1029 N as usize,
1030 "buffered rows must be visible to a streaming scan (got {})",
1031 got.len()
1032 );
1033 let mut seen_row_ids: std::collections::HashSet<u64> = std::collections::HashSet::new();
1034 for (rid, _epoch, _v) in &got {
1035 assert!(
1036 seen_row_ids.insert(*rid),
1037 "duplicate RowId {rid} in streaming scan output"
1038 );
1039 }
1040 let expected_ids: std::collections::HashSet<u64> = (0..N).collect();
1042 let got_ids: std::collections::HashSet<u64> = got.iter().map(|(rid, _, _)| *rid).collect();
1043 assert_eq!(got_ids, expected_ids, "must yield every input RowId");
1044
1045 let (_, epoch_raw, v) = got
1048 .iter()
1049 .find(|(rid, _, _)| *rid == N - 1)
1050 .copied()
1051 .expect("highest RowId present");
1052 assert_eq!(epoch_raw, N);
1053 assert_eq!(v, (N as i64 - 1) * 10);
1054 }
1055
1056 #[test]
1062 fn newest_visible_iter_buffered_versions_dedup_against_leaf_resident() {
1063 let mut m = Memtable::new();
1065 for i in 0..16u64 {
1066 let mut r = Row::new(RowId(7), Epoch(i + 1));
1067 r.columns.insert(1, Value::Int64(i as i64));
1068 m.upsert(r);
1069 }
1070 for i in 0u64..4_000 {
1075 let mut r = Row::new(RowId(1000 + i), Epoch(i + 100));
1076 r.columns.insert(1, Value::Int64(i as i64));
1077 m.upsert(r);
1078 }
1079 let mut latest_seven = Row::new(RowId(7), Epoch(20_000));
1082 latest_seven.columns.insert(1, Value::Int64(999));
1083 m.upsert(latest_seven);
1084
1085 let snap = Snapshot::at(Epoch(20_001));
1086 let seven = m
1087 .newest_visible_iter(&snap)
1088 .find(|(rid, _epoch, _row)| *rid == RowId(7))
1089 .expect("row 7 visible");
1090 assert_eq!(seven.1, Epoch(20_000), "buffered newest wins");
1091 let total: u64 = m
1094 .newest_visible_iter(&snap)
1095 .map(|(rid, _epoch, _row)| rid.0)
1096 .fold(0u64, |acc, _| acc + 1);
1097 assert_eq!(total, 4_001, "buffer + leaf coverage");
1098 }
1099
1100 fn int_of_value(row: &Row) -> i64 {
1101 match row.columns.get(&1) {
1102 Some(Value::Int64(x)) => *x,
1103 other => panic!("expected Int64 column, got {other:?}"),
1104 }
1105 }
1106
1107 fn memtable_with_leaf_resident_seven() -> Memtable {
1113 let mut m = Memtable::new();
1114 for i in 0..33u64 {
1115 m.upsert(row(i, 1));
1116 }
1117 for i in 100..110u64 {
1118 m.upsert(row(i, 1));
1119 }
1120 m
1121 }
1122
1123 #[test]
1126 fn same_rowid_in_buffer_and_leaf_dedups_once() {
1127 let mut m = memtable_with_leaf_resident_seven();
1128 m.upsert(row(7, 2)); let got: Vec<(u64, u64, i64)> = m
1130 .newest_visible_iter(&Snapshot::at(Epoch(3)))
1131 .filter(|(rid, _, _)| *rid == RowId(7))
1132 .map(|(rid, epoch, row)| (rid.0, epoch.0, int_of_value(&row)))
1133 .collect();
1134 assert_eq!(got, vec![(7, 2, 70)], "row 7 must emit exactly once");
1135 }
1136
1137 #[test]
1140 fn same_rowid_across_frozen_segments_dedups_once() {
1141 let mut m = Memtable::new();
1142 m.upsert(row(7, 1));
1143 m.seal();
1144 m.upsert(row(7, 2));
1145 m.seal();
1146 m.upsert(row(7, 3));
1147 let got: Vec<(u64, u64)> = m
1148 .newest_visible_iter(&Snapshot::at(Epoch(10)))
1149 .map(|(rid, epoch, _)| (rid.0, epoch.0))
1150 .collect();
1151 assert_eq!(got, vec![(7, 3)]);
1152 }
1153
1154 #[test]
1157 fn buffered_tombstone_suppresses_leaf_live_row() {
1158 let mut m = memtable_with_leaf_resident_seven();
1159 m.tombstone(RowId(7), Epoch(2)); let at_tombstone: Vec<(u64, bool)> = m
1161 .newest_visible_iter(&Snapshot::at(Epoch(3)))
1162 .filter(|(rid, _, _)| *rid == RowId(7))
1163 .map(|(_, epoch, row)| (epoch.0, row.deleted))
1164 .collect();
1165 assert_eq!(
1166 at_tombstone,
1167 vec![(2, true)],
1168 "buffered tombstone is the newest visible version"
1169 );
1170 let before: Vec<(u64, bool)> = m
1171 .newest_visible_iter(&Snapshot::at(Epoch(1)))
1172 .filter(|(rid, _, _)| *rid == RowId(7))
1173 .map(|(_, epoch, row)| (epoch.0, row.deleted))
1174 .collect();
1175 assert_eq!(before, vec![(1, false)]);
1176 }
1177
1178 #[test]
1181 fn hlc_inversion_inside_one_segment() {
1182 use mongreldb_types::hlc::HlcTimestamp;
1183 let early = HlcTimestamp {
1184 physical_micros: 100,
1185 logical: 0,
1186 node_tiebreaker: 1,
1187 };
1188 let late = HlcTimestamp {
1189 physical_micros: 200,
1190 logical: 0,
1191 node_tiebreaker: 1,
1192 };
1193 let mut m = Memtable::new();
1194 m.upsert(Row::new_with_hlc(RowId(1), Epoch(50), early).with_column(1, Value::Int64(1)));
1195 m.upsert(Row::new_with_hlc(RowId(1), Epoch(1), late).with_column(1, Value::Int64(99)));
1196 let snap = Snapshot::at_hlc(Epoch(99), late);
1197 let got: Vec<(u64, i64)> = m
1198 .newest_visible_iter(&snap)
1199 .map(|(_, epoch, row)| (epoch.0, int_of_value(&row)))
1200 .collect();
1201 assert_eq!(got, vec![(1, 99)], "higher HLC wins over higher epoch");
1202 }
1203
1204 #[test]
1207 fn hlc_inversion_across_frozen_segments() {
1208 use mongreldb_types::hlc::HlcTimestamp;
1209 let early = HlcTimestamp {
1210 physical_micros: 100,
1211 logical: 0,
1212 node_tiebreaker: 1,
1213 };
1214 let late = HlcTimestamp {
1215 physical_micros: 200,
1216 logical: 0,
1217 node_tiebreaker: 1,
1218 };
1219 let mut m = Memtable::new();
1220 m.upsert(Row::new_with_hlc(RowId(1), Epoch(50), early).with_column(1, Value::Int64(1)));
1221 m.seal();
1222 m.upsert(Row::new_with_hlc(RowId(1), Epoch(1), late).with_column(1, Value::Int64(99)));
1223 let snap = Snapshot::at_hlc(Epoch(99), late);
1224 let got: Vec<(u64, i64)> = m
1225 .newest_visible_iter(&snap)
1226 .map(|(_, epoch, row)| (epoch.0, int_of_value(&row)))
1227 .collect();
1228 assert_eq!(got, vec![(1, 99)], "higher HLC wins across segments");
1229 }
1230
1231 #[test]
1234 fn cancellation_during_large_same_row_group() {
1235 let mut m = Memtable::new();
1236 m.upsert(row(1, 1));
1237 for v in 0..10_000u64 {
1238 m.upsert(row(7, v + 1));
1239 }
1240 let snap = Snapshot::at(Epoch(20_000));
1241 let mut cursor = m.newest_visible_iter(&snap);
1242 let control = crate::ExecutionControl::new(None);
1243 let (rid, ..) = cursor
1244 .next_controlled(&control)
1245 .expect("first advance")
1246 .expect("row 1");
1247 assert_eq!(rid, RowId(1));
1248 control.cancel(crate::CancellationReason::ClientRequest);
1251 let err = cursor
1252 .next_controlled(&control)
1253 .expect_err("cancelled control must stop the gather");
1254 assert!(matches!(err, crate::MongrelError::Cancelled));
1255 }
1256}