1use crate::epoch::Epoch;
16use crate::memtable::Row;
17use crate::rowid::RowId;
18use std::borrow::Cow;
19use std::collections::HashMap;
20
21const FANOUT: usize = 8;
23const BUFFER_CAP: usize = 16;
25const LEAF_CAP: usize = 32;
27
28type VKey = (RowId, Epoch);
30
31#[derive(Debug, Clone)]
33pub(crate) enum Message {
34 Upsert(Row),
35 Tombstone { row_id: RowId, epoch: Epoch },
36}
37
38impl Message {
39 fn key(&self) -> VKey {
40 match self {
41 Message::Upsert(r) => (r.row_id, r.committed_epoch),
42 Message::Tombstone { row_id, epoch } => (*row_id, *epoch),
43 }
44 }
45
46 fn to_row(&self) -> (Epoch, Row) {
47 match self {
48 Message::Upsert(r) => (r.committed_epoch, r.clone()),
49 Message::Tombstone { row_id, epoch } => (
50 *epoch,
51 Row {
52 row_id: *row_id,
53 committed_epoch: *epoch,
54 columns: HashMap::new(),
55 deleted: true,
56 commit_ts: None,
57 },
58 ),
59 }
60 }
61}
62
63#[derive(Clone)]
64pub(crate) enum Node {
65 Leaf {
66 rows: Vec<Row>,
67 },
68 Internal {
69 keys: Vec<VKey>,
70 children: Vec<Node>,
71 buffer: Vec<Message>,
72 },
73}
74
75impl Node {
76 fn empty_leaf() -> Self {
77 Node::Leaf { rows: Vec::new() }
78 }
79}
80
81struct Split {
82 key: VKey,
83 node: Node,
84}
85
86const CURSOR_CHECKPOINT_INTERVAL: usize = 256;
89
90#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
96#[doc(hidden)] pub struct BeTreeVersionCursorStats {
98 pub active_frames: usize,
100 pub peak_active_frames: usize,
102 pub buffered_messages_owned: usize,
105 pub peak_buffered_messages_owned: usize,
107 pub total_versions_precollected: usize,
111 pub versions_examined: usize,
113 pub checkpoints: usize,
115}
116
117struct BufferedCursorItem<'a> {
121 key: VKey,
122 sequence: usize,
123 row: Cow<'a, Row>,
124}
125
126fn message_as_cow(message: &Message) -> Cow<'_, Row> {
127 match message {
128 Message::Upsert(row) => Cow::Borrowed(row),
129 Message::Tombstone { row_id, epoch } => Cow::Owned(Row {
130 row_id: *row_id,
131 committed_epoch: *epoch,
132 columns: HashMap::new(),
133 deleted: true,
134 commit_ts: None,
135 }),
136 }
137}
138
139enum NodeCursorFrame<'a> {
140 Leaf { rows: std::slice::Iter<'a, Row> },
141 Internal(Box<InternalCursorFrame<'a>>),
142}
143
144struct InternalCursorFrame<'a> {
145 children: &'a [Node],
146 child_index: usize,
147 child_exhausted: bool,
151 buffer_groups: Vec<Vec<BufferedCursorItem<'a>>>,
152 current_child: Option<Box<BeTreeVersionCursor<'a>>>,
153 current_buffer: std::vec::IntoIter<BufferedCursorItem<'a>>,
154 pending_child: Option<Cow<'a, Row>>,
155 pending_buffer: Option<Cow<'a, Row>>,
156}
157
158pub struct BeTreeVersionCursor<'a> {
187 node: NodeCursorFrame<'a>,
188 stats: BeTreeVersionCursorStats,
189}
190
191impl<'a> BeTreeVersionCursor<'a> {
192 fn new(node: &'a Node) -> Self {
193 match node {
194 Node::Leaf { rows } => Self {
195 node: NodeCursorFrame::Leaf { rows: rows.iter() },
196 stats: BeTreeVersionCursorStats {
197 active_frames: 1,
198 peak_active_frames: 1,
199 ..BeTreeVersionCursorStats::default()
200 },
201 },
202 Node::Internal {
203 keys,
204 children,
205 buffer,
206 } => {
207 let mut groups: Vec<Vec<BufferedCursorItem<'a>>> =
210 (0..children.len()).map(|_| Vec::new()).collect();
211 for (sequence, message) in buffer.iter().enumerate() {
212 let child = BeTree::child_index(keys, message.key());
213 groups[child].push(BufferedCursorItem {
214 key: message.key(),
215 sequence,
216 row: message_as_cow(message),
217 });
218 }
219 for group in &mut groups {
220 group.sort_by_key(|item| (item.key, item.sequence));
221 }
222 let owned: usize = groups.iter().map(Vec::len).sum();
223 Self {
224 node: NodeCursorFrame::Internal(Box::new(InternalCursorFrame {
225 children,
226 child_index: 0,
227 child_exhausted: false,
228 buffer_groups: groups,
229 current_child: None,
230 current_buffer: Vec::new().into_iter(),
231 pending_child: None,
232 pending_buffer: None,
233 })),
234 stats: BeTreeVersionCursorStats {
235 active_frames: 1,
236 peak_active_frames: 1,
237 buffered_messages_owned: owned,
238 peak_buffered_messages_owned: owned,
239 ..BeTreeVersionCursorStats::default()
240 },
241 }
242 }
243 }
244 }
245
246 #[doc(hidden)] pub fn next_controlled(
250 &mut self,
251 control: &crate::ExecutionControl,
252 ) -> crate::Result<Option<Cow<'a, Row>>> {
253 self.advance(Some(control))
254 }
255
256 #[doc(hidden)] pub fn stats(&self) -> BeTreeVersionCursorStats {
260 let mut out = self.stats;
261 let (frames, buffered) = self.footprint();
262 out.active_frames = frames;
263 out.buffered_messages_owned = buffered;
264 if let NodeCursorFrame::Internal(frame) = &self.node {
265 if let Some(child) = &frame.current_child {
266 let child = child.stats();
267 out.versions_examined += child.versions_examined;
268 out.checkpoints += child.checkpoints;
269 out.total_versions_precollected += child.total_versions_precollected;
270 }
271 }
272 out
273 }
274
275 fn advance(
276 &mut self,
277 control: Option<&crate::ExecutionControl>,
278 ) -> crate::Result<Option<Cow<'a, Row>>> {
279 match &mut self.node {
280 NodeCursorFrame::Leaf { rows } => {
281 let Some(row) = rows.next() else {
282 return Ok(None);
283 };
284 Self::note_version(&mut self.stats, control)?;
285 Ok(Some(Cow::Borrowed(row)))
286 }
287 NodeCursorFrame::Internal(frame) => {
288 Self::advance_internal_frame(frame, &mut self.stats, control)
289 }
290 }
291 }
292
293 fn advance_internal_frame(
294 frame: &mut InternalCursorFrame<'a>,
295 stats: &mut BeTreeVersionCursorStats,
296 control: Option<&crate::ExecutionControl>,
297 ) -> crate::Result<Option<Cow<'a, Row>>> {
298 let InternalCursorFrame {
299 children,
300 child_index,
301 child_exhausted,
302 buffer_groups,
303 current_child,
304 current_buffer,
305 pending_child,
306 pending_buffer,
307 } = frame;
308 loop {
309 if current_child.is_none() && !*child_exhausted {
310 if *child_index >= children.len() {
311 return Ok(None);
312 }
313 Self::checkpoint(stats, control)?;
316 *current_buffer = std::mem::take(&mut buffer_groups[*child_index]).into_iter();
317 *current_child = Some(Box::new(BeTreeVersionCursor::new(&children[*child_index])));
318 Self::refresh_peaks(buffer_groups, current_buffer, current_child, stats);
319 continue;
320 }
321 if pending_child.is_none() && !*child_exhausted {
322 let child = current_child.as_mut().expect("live child cursor");
323 match child.advance(control)? {
324 Some(row) => *pending_child = Some(row),
325 None => {
326 Self::absorb_child(stats, child);
327 *current_child = None;
328 *child_exhausted = true;
329 }
330 }
331 Self::refresh_peaks(buffer_groups, current_buffer, current_child, stats);
332 continue;
333 }
334 if pending_buffer.is_none() {
335 if let Some(item) = current_buffer.next() {
336 *pending_buffer = Some(item.row);
337 }
338 }
339 let take_buffer = match (pending_child.is_some(), pending_buffer.is_some()) {
340 (false, false) => {
341 *child_index += 1;
343 *child_exhausted = false;
344 continue;
345 }
346 (true, false) => false,
347 (false, true) => true,
348 (true, true) => {
349 let child_key = pending_child
350 .as_ref()
351 .map(|row| (row.row_id, row.committed_epoch));
352 let buffer_key = pending_buffer
353 .as_ref()
354 .map(|row| (row.row_id, row.committed_epoch));
355 buffer_key < child_key
360 }
361 };
362 Self::note_version(stats, control)?;
363 return Ok(if take_buffer {
364 pending_buffer.take()
365 } else {
366 pending_child.take()
367 });
368 }
369 }
370
371 fn footprint(&self) -> (usize, usize) {
373 match &self.node {
374 NodeCursorFrame::Leaf { .. } => (1, 0),
375 NodeCursorFrame::Internal(frame) => {
376 let own: usize = frame.buffer_groups.iter().map(Vec::len).sum::<usize>()
377 + frame.current_buffer.len();
378 let (child_frames, child_buffered) = frame
379 .current_child
380 .as_ref()
381 .map_or((0, 0), |child| child.footprint());
382 (1 + child_frames, own + child_buffered)
383 }
384 }
385 }
386
387 fn refresh_peaks(
391 buffer_groups: &[Vec<BufferedCursorItem<'a>>],
392 current_buffer: &std::vec::IntoIter<BufferedCursorItem<'a>>,
393 current_child: &Option<Box<BeTreeVersionCursor<'a>>>,
394 stats: &mut BeTreeVersionCursorStats,
395 ) {
396 let own_buffered: usize =
397 buffer_groups.iter().map(Vec::len).sum::<usize>() + current_buffer.len();
398 let (child_frames, child_buffered, child_peak_frames, child_peak_buffered) =
399 match current_child {
400 Some(child) => {
401 let (frames, buffered) = child.footprint();
402 (
403 frames,
404 buffered,
405 child.stats.peak_active_frames,
406 child.stats.peak_buffered_messages_owned,
407 )
408 }
409 None => (0, 0, 0, 0),
410 };
411 stats.active_frames = 1 + child_frames;
412 stats.buffered_messages_owned = own_buffered + child_buffered;
413 stats.peak_active_frames = stats
414 .peak_active_frames
415 .max(stats.active_frames)
416 .max(1 + child_peak_frames);
417 stats.peak_buffered_messages_owned = stats
418 .peak_buffered_messages_owned
419 .max(stats.buffered_messages_owned)
420 .max(own_buffered + child_peak_buffered);
421 }
422
423 fn absorb_child(stats: &mut BeTreeVersionCursorStats, child: &BeTreeVersionCursor<'a>) {
426 let child = child.stats();
427 stats.versions_examined += child.versions_examined;
428 stats.checkpoints += child.checkpoints;
429 stats.total_versions_precollected += child.total_versions_precollected;
430 }
433
434 fn checkpoint(
435 stats: &mut BeTreeVersionCursorStats,
436 control: Option<&crate::ExecutionControl>,
437 ) -> crate::Result<()> {
438 if let Some(control) = control {
439 control.checkpoint()?;
440 stats.checkpoints += 1;
441 }
442 Ok(())
443 }
444
445 fn note_version(
446 stats: &mut BeTreeVersionCursorStats,
447 control: Option<&crate::ExecutionControl>,
448 ) -> crate::Result<()> {
449 stats.versions_examined += 1;
450 if stats
451 .versions_examined
452 .is_multiple_of(CURSOR_CHECKPOINT_INTERVAL)
453 {
454 Self::checkpoint(stats, control)?;
455 }
456 Ok(())
457 }
458}
459
460impl<'a> Iterator for BeTreeVersionCursor<'a> {
461 type Item = Cow<'a, Row>;
462
463 fn next(&mut self) -> Option<Self::Item> {
464 match self.advance(None) {
465 Ok(item) => item,
466 Err(_) => unreachable!("the no-cancellation path cannot fail"),
467 }
468 }
469}
470
471#[derive(Clone)]
473pub struct BeTree {
474 root: Node,
475 mutations: usize,
476}
477
478impl Default for BeTree {
479 fn default() -> Self {
480 Self::new()
481 }
482}
483
484impl BeTree {
485 pub fn new() -> Self {
486 Self {
487 root: Node::empty_leaf(),
488 mutations: 0,
489 }
490 }
491
492 pub fn mutations(&self) -> usize {
494 self.mutations
495 }
496
497 pub fn is_empty(&self) -> bool {
498 self.mutations == 0
499 }
500
501 pub fn insert_row(&mut self, row: Row) {
503 self.insert(Message::Upsert(row));
504 }
505
506 pub fn delete(&mut self, row_id: RowId, epoch: Epoch) {
508 self.insert(Message::Tombstone { row_id, epoch });
509 }
510
511 fn insert(&mut self, msg: Message) {
512 self.mutations += 1;
513 match &mut self.root {
514 Node::Leaf { rows } => Self::leaf_apply(rows, msg),
515 Node::Internal { buffer, .. } => buffer.push(msg),
516 }
517 if let Some(split) = Self::maintain(&mut self.root) {
518 let left = std::mem::replace(&mut self.root, Node::empty_leaf());
519 self.root = Node::Internal {
520 keys: vec![split.key],
521 children: vec![left, split.node],
522 buffer: Vec::new(),
523 };
524 }
525 }
526
527 pub fn get(&self, row_id: RowId, snapshot: Epoch) -> Option<Row> {
530 self.get_version(row_id, snapshot).map(|(_, r)| r)
531 }
532
533 pub fn get_version(&self, row_id: RowId, snapshot: Epoch) -> Option<(Epoch, Row)> {
537 let mut best: Option<(Epoch, Row)> = None;
538 Self::collect(&self.root, row_id, snapshot, &mut best);
539 best
540 }
541
542 pub fn get_visible(&self, row_id: RowId, snapshot: Epoch) -> Option<Row> {
544 let r = self.get(row_id, snapshot)?;
545 if r.deleted {
546 None
547 } else {
548 Some(r)
549 }
550 }
551
552 pub(crate) fn visit_versions(&self, row_id: RowId, mut visit: impl FnMut(Row)) {
560 Self::visit_row_versions(&self.root, row_id, &mut visit);
561 }
562
563 #[doc(hidden)] pub fn leaf_versions_iter(&self) -> BeTreeVersionCursor<'_> {
567 BeTreeVersionCursor::new(&self.root)
568 }
569
570 pub fn versions(&self) -> Vec<Row> {
574 let mut out = Vec::with_capacity(self.mutations);
575 Self::collect_all_versions(&self.root, &mut out);
576 out
577 }
578
579 pub fn into_sorted_rows(mut self) -> Vec<Row> {
582 Self::flush_all(&mut self.root);
583 Self::collect_leaves(&self.root)
584 }
585
586 fn maintain(node: &mut Node) -> Option<Split> {
589 match node {
590 Node::Leaf { rows } => {
591 if rows.len() > LEAF_CAP {
592 Some(Self::split_leaf(rows))
593 } else {
594 None
595 }
596 }
597 Node::Internal {
598 keys,
599 children,
600 buffer,
601 } => {
602 if buffer.len() > BUFFER_CAP {
603 let drained = std::mem::take(buffer);
604 for msg in drained {
605 let i = Self::child_index(keys, msg.key());
606 Self::push_into_child(&mut children[i], msg);
607 }
608 let mut i = 0;
609 while i < children.len() {
610 if let Some(split) = Self::maintain(&mut children[i]) {
611 keys.insert(i, split.key);
612 children.insert(i + 1, split.node);
613 i += 1;
614 }
615 i += 1;
616 }
617 }
618 if children.len() > FANOUT {
619 Some(Self::split_internal(keys, children))
620 } else {
621 None
622 }
623 }
624 }
625 }
626
627 fn leaf_apply(rows: &mut Vec<Row>, msg: Message) {
628 let key = msg.key();
629 let row = match msg {
630 Message::Upsert(r) => r,
631 Message::Tombstone { row_id, epoch } => Row {
632 row_id,
633 committed_epoch: epoch,
634 columns: HashMap::new(),
635 deleted: true,
636 commit_ts: None,
637 },
638 };
639 let i = rows.partition_point(|r| (r.row_id, r.committed_epoch) <= key);
646 rows.insert(i, row);
647 }
648
649 fn push_into_child(child: &mut Node, msg: Message) {
650 match child {
651 Node::Leaf { rows } => Self::leaf_apply(rows, msg),
652 Node::Internal { buffer, .. } => buffer.push(msg),
653 }
654 }
655
656 fn child_index(keys: &[VKey], key: VKey) -> usize {
657 keys.partition_point(|k| *k <= key)
658 }
659
660 fn split_leaf(rows: &mut Vec<Row>) -> Split {
661 let mid = rows.len() / 2;
662 let right = rows.split_off(mid);
663 let key = (right[0].row_id, right[0].committed_epoch);
664 Split {
665 key,
666 node: Node::Leaf { rows: right },
667 }
668 }
669
670 fn split_internal(keys: &mut Vec<VKey>, children: &mut Vec<Node>) -> Split {
671 let m = keys.len() / 2;
672 let promoted = keys[m];
673 let right_keys = keys.split_off(m + 1);
674 keys.pop();
675 let right_children = children.split_off(m + 1);
676 Split {
677 key: promoted,
678 node: Node::Internal {
679 keys: right_keys,
680 children: right_children,
681 buffer: Vec::new(),
682 },
683 }
684 }
685
686 fn consider(best: &mut Option<(Epoch, Row)>, epoch: Epoch, row: Row) {
687 match best {
688 Some((be, _)) if *be >= epoch => {}
689 _ => *best = Some((epoch, row)),
690 }
691 }
692
693 fn collect(node: &Node, row_id: RowId, snapshot: Epoch, best: &mut Option<(Epoch, Row)>) {
694 match node {
695 Node::Leaf { rows } => {
696 let upper =
699 rows.partition_point(|r| (r.row_id, r.committed_epoch) <= (row_id, snapshot));
700 let mut i = upper;
701 while i > 0 {
702 let i2 = i - 1;
703 if rows[i2].row_id != row_id {
704 break;
705 }
706 let r = &rows[i2];
707 if r.committed_epoch <= snapshot {
708 Self::consider(best, r.committed_epoch, r.clone());
709 }
710 i = i2;
711 }
712 }
713 Node::Internal {
714 keys,
715 children,
716 buffer,
717 } => {
718 for msg in buffer.iter().rev() {
725 let (rid, e) = msg.key();
726 if rid == row_id && e <= snapshot {
727 let (epoch, row) = msg.to_row();
728 Self::consider(best, epoch, row);
729 }
730 }
731 let i = Self::child_index(keys, (row_id, snapshot));
732 Self::collect(&children[i], row_id, snapshot, best);
733 }
734 }
735 }
736
737 fn visit_row_versions(node: &Node, row_id: RowId, visit: &mut impl FnMut(Row)) {
738 match node {
739 Node::Leaf { rows } => {
740 let start = rows.partition_point(|row| row.row_id < row_id);
741 let end = rows.partition_point(|row| row.row_id <= row_id);
742 for row in &rows[start..end] {
743 visit(row.clone());
744 }
745 }
746 Node::Internal {
747 keys,
748 children,
749 buffer,
750 } => {
751 let first = Self::child_index(keys, (row_id, Epoch::ZERO));
757 let last = Self::child_index(keys, (row_id, Epoch(u64::MAX)));
758 for child in &children[first..=last] {
759 Self::visit_row_versions(child, row_id, visit);
760 }
761 for message in buffer {
762 if message.key().0 == row_id {
763 visit(message.to_row().1);
764 }
765 }
766 }
767 }
768 }
769
770 fn flush_all(node: &mut Node) {
771 match node {
772 Node::Leaf { .. } => {}
773 Node::Internal {
774 keys,
775 children,
776 buffer,
777 } => {
778 let drained = std::mem::take(buffer);
779 for msg in drained {
780 let i = Self::child_index(keys, msg.key());
781 Self::push_into_child(&mut children[i], msg);
782 }
783 for c in children.iter_mut() {
784 Self::flush_all(c);
785 }
786 }
787 }
788 }
789
790 fn collect_leaves(node: &Node) -> Vec<Row> {
791 match node {
792 Node::Leaf { rows } => rows.clone(),
793 Node::Internal { children, .. } => {
794 children.iter().flat_map(Self::collect_leaves).collect()
795 }
796 }
797 }
798
799 fn collect_all_versions(node: &Node, out: &mut Vec<Row>) {
800 match node {
801 Node::Leaf { rows } => out.extend(rows.iter().cloned()),
802 Node::Internal {
803 children, buffer, ..
804 } => {
805 for c in children {
812 Self::collect_all_versions(c, out);
813 }
814 for msg in buffer {
815 out.push(msg.to_row().1);
816 }
817 }
818 }
819 }
820}
821
822#[cfg(test)]
823mod tests {
824 use super::*;
825 use crate::memtable::Value;
826
827 fn val_row(id: u64, epoch: u64, v: i64) -> Row {
828 Row::new(RowId(id), Epoch(epoch)).with_column(1, Value::Int64(v))
829 }
830
831 #[test]
832 fn point_lookups_round_trip() {
833 let mut t = BeTree::new();
834 for i in 0..50u64 {
835 t.insert_row(val_row(i, i, i as i64 * 10));
836 }
837 for i in 0..50u64 {
838 let r = t.get_visible(RowId(i), Epoch(100)).expect("row present");
839 assert_eq!(r.row_id, RowId(i));
840 assert!(matches!(r.columns.get(&1), Some(Value::Int64(v)) if *v == i as i64 * 10));
841 }
842 assert!(t.get_visible(RowId(500), Epoch(100)).is_none());
843 }
844
845 #[test]
846 fn many_inserts_force_depth_growth() {
847 let mut t = BeTree::new();
848 let n = 5_000u64;
849 for i in 0..n {
850 t.insert_row(val_row(i, i, i as i64));
851 }
852 for i in 0..n {
853 assert!(
854 t.get_visible(RowId(i), Epoch(n + 1)).is_some(),
855 "missing {i}"
856 );
857 }
858 assert_eq!(t.into_sorted_rows().len(), n as usize);
859 }
860
861 #[test]
862 fn multiple_versions_of_same_row_coexist_with_mvcc() {
863 let mut t = BeTree::new();
865 t.insert_row(val_row(7, 1, 100));
866 t.insert_row(val_row(7, 5, 200));
867 let old = t.get_visible(RowId(7), Epoch(2)).unwrap();
869 assert!(matches!(old.columns.get(&1), Some(Value::Int64(v)) if *v == 100));
870 let new = t.get_visible(RowId(7), Epoch(10)).unwrap();
871 assert!(matches!(new.columns.get(&1), Some(Value::Int64(v)) if *v == 200));
872 }
873
874 #[test]
875 fn tombstone_hides_row_at_and_after_epoch_but_not_before() {
876 let mut t = BeTree::new();
877 t.insert_row(val_row(3, 1, 42));
878 assert!(t.get_visible(RowId(3), Epoch(1)).is_some());
879 t.delete(RowId(3), Epoch(4));
880 assert!(t.get_visible(RowId(3), Epoch(4)).is_none());
881 assert!(t.get_visible(RowId(3), Epoch(9)).is_none());
882 assert!(t.get_visible(RowId(3), Epoch(3)).is_some());
884 }
885
886 #[test]
890 fn same_epoch_live_tombstone_pair_resolves_to_last_write() {
891 let mut t = BeTree::new();
893 t.insert_row(val_row(5, 2, 1));
894 t.delete(RowId(5), Epoch(2));
895 let (_, winner) = t.get_version(RowId(5), Epoch(2)).expect("version");
896 assert!(winner.deleted, "tombstone must win the same-epoch tie");
897
898 let mut t = BeTree::new();
900 t.delete(RowId(5), Epoch(2));
901 t.insert_row(val_row(5, 2, 1));
902 let (_, winner) = t.get_version(RowId(5), Epoch(2)).expect("version");
903 assert!(!winner.deleted, "later live write must win the tie");
904
905 let mut t = BeTree::new();
908 t.insert_row(val_row(7, 1, 1));
909 for i in 0..3_000u64 {
910 t.insert_row(val_row(10_000 + i, 1, i as i64));
911 }
912 t.delete(RowId(7), Epoch(1));
913 let (_, winner) = t.get_version(RowId(7), Epoch(1)).expect("version");
914 assert!(winner.deleted, "tombstone must win across buffer/leaf");
915
916 let mut t = BeTree::new();
919 t.insert_row(val_row(9, 3, 1));
920 for i in 0..3_000u64 {
921 t.insert_row(val_row(20_000 + i, 1, i as i64));
922 }
923 t.delete(RowId(9), Epoch(3));
924 let pair: Vec<bool> = t
925 .versions()
926 .into_iter()
927 .filter(|r| r.row_id == RowId(9))
928 .map(|r| r.deleted)
929 .collect();
930 assert_eq!(pair, vec![false, true], "live first, tombstone second");
931 }
932
933 #[test]
934 fn into_sorted_rows_is_keyed_by_row_then_epoch() {
935 let mut t = BeTree::new();
936 t.insert_row(val_row(30, 1, 1));
937 t.insert_row(val_row(10, 1, 1));
938 t.insert_row(val_row(30, 5, 2)); t.delete(RowId(10), Epoch(2));
940 let rows = t.into_sorted_rows();
941 let keys: Vec<(u64, u64)> = rows
942 .iter()
943 .map(|r| (r.row_id.0, r.committed_epoch.0))
944 .collect();
945 assert_eq!(keys, vec![(10, 1), (10, 2), (30, 1), (30, 5)]);
946 assert!(
947 rows.iter()
948 .find(|r| r.row_id == RowId(10) && r.committed_epoch == Epoch(2))
949 .unwrap()
950 .deleted
951 );
952 }
953
954 #[test]
960 fn many_versions_of_one_row_stay_lookupable_across_splits() {
961 let mut t = BeTree::new();
962 const N: u64 = 600;
963 for e in 0..N {
966 t.insert_row(val_row(7777, e, e as i64 * 2));
967 t.insert_row(val_row(e, e, 0));
970 }
971 assert_eq!(t.mutations(), 2 * N as usize);
972 let expected = t
973 .versions()
974 .into_iter()
975 .filter(|row| row.row_id == RowId(7777))
976 .map(|row| row.committed_epoch)
977 .collect::<Vec<_>>();
978 let mut visited = Vec::new();
979 t.visit_versions(RowId(7777), |row| {
980 assert_eq!(row.row_id, RowId(7777));
981 visited.push(row.committed_epoch);
982 });
983 assert_eq!(visited, expected);
984 visited.sort_unstable();
985 assert_eq!(visited, (0..N).map(Epoch).collect::<Vec<_>>());
986
987 for s in 0..N {
990 let r = t
991 .get_version(RowId(7777), Epoch(s))
992 .expect("missing version")
993 .0;
994 assert_eq!(r, Epoch(s), "snapshot {s} saw wrong newest version");
995 }
996 assert!(t.get_version(RowId(7777), Epoch(0)).is_some()); for e in 0..N {
1000 assert!(t.get_visible(RowId(e), Epoch(e)).is_some(), "sibling {e}");
1001 }
1002
1003 t.delete(RowId(7777), Epoch(N + 5));
1006 assert!(
1007 t.get_visible(RowId(7777), Epoch(N)).is_some(),
1008 "before tombstone"
1009 );
1010 assert!(
1011 t.get_visible(RowId(7777), Epoch(N + 5)).is_none(),
1012 "at tombstone"
1013 );
1014 assert!(
1015 t.get_visible(RowId(7777), Epoch(N + 99)).is_none(),
1016 "after tombstone"
1017 );
1018 }
1019
1020 #[test]
1024 fn out_of_order_internal_buffers_emit_ascending() {
1025 let mut t = BeTree::new();
1026 const N: u64 = 20_000;
1027 for i in 0..N {
1030 let rid = (i * 7_919) % N;
1031 t.insert_row(val_row(rid, 1, rid as i64));
1032 }
1033 for rid in (0..N).step_by(97) {
1035 t.delete(RowId(rid), Epoch(2));
1036 }
1037 let expected = t.mutations();
1038 let mut cursor = t.leaf_versions_iter();
1039 let mut prev: Option<(u64, u64)> = None;
1040 let mut count = 0usize;
1041 for row in cursor.by_ref() {
1042 let key = (row.row_id.0, row.committed_epoch.0);
1043 if let Some(p) = prev {
1044 assert!(p < key, "stream must ascend strictly: {p:?} then {key:?}");
1045 }
1046 prev = Some(key);
1047 count += 1;
1048 }
1049 assert_eq!(count, expected, "every version exactly once");
1050 let stats = cursor.stats();
1051 assert!(
1052 stats.peak_buffered_messages_owned > 0,
1053 "fixture must exercise internal-node buffers"
1054 );
1055 assert!(
1056 stats.peak_active_frames >= 2,
1057 "fixture must grow internal levels"
1058 );
1059 assert_eq!(stats.total_versions_precollected, 0);
1060 }
1061
1062 #[test]
1067 fn equal_key_tie_is_deterministic() {
1068 let live_leaf = val_row(7, 5, 42);
1069 let buffered_upsert = val_row(7, 5, 43);
1070 let tree = BeTree {
1071 root: Node::Internal {
1072 keys: vec![(RowId(50), Epoch(1))],
1073 children: vec![
1074 Node::Leaf {
1075 rows: vec![live_leaf],
1076 },
1077 Node::Leaf {
1078 rows: vec![val_row(60, 1, 1)],
1079 },
1080 ],
1081 buffer: vec![
1082 Message::Upsert(buffered_upsert),
1083 Message::Tombstone {
1084 row_id: RowId(7),
1085 epoch: Epoch(5),
1086 },
1087 ],
1088 },
1089 mutations: 4,
1090 };
1091 let collect = || {
1092 tree.leaf_versions_iter()
1093 .map(|row| (row.row_id.0, row.committed_epoch.0, row.deleted))
1094 .collect::<Vec<_>>()
1095 };
1096 let first = collect();
1097 let second = collect();
1098 assert_eq!(first, second, "tie order must not depend on iteration run");
1099 assert_eq!(
1100 first,
1101 vec![
1102 (7, 5, false), (7, 5, false), (7, 5, true), (60, 1, false),
1106 ],
1107 "child rows precede buffered messages at equal keys; buffer in insertion order"
1108 );
1109 }
1110
1111 #[test]
1115 fn lazy_cursor_first_row_is_bounded() {
1116 let mut t = BeTree::new();
1117 const N: u64 = 100_000;
1118 for i in 0..N {
1119 t.insert_row(val_row(i, 1, i as i64));
1120 }
1121 let mut cursor = t.leaf_versions_iter();
1122 let control = crate::ExecutionControl::new(None);
1123 let first = cursor
1124 .next_controlled(&control)
1125 .expect("controlled advance")
1126 .expect("non-empty tree");
1127 assert_eq!(first.row_id, RowId(0));
1128 let stats = cursor.stats();
1129 assert_eq!(stats.total_versions_precollected, 0);
1130 assert!(
1131 stats.versions_examined <= 2 * CURSOR_CHECKPOINT_INTERVAL,
1132 "first row must not wait for a tree scan: {}",
1133 stats.versions_examined
1134 );
1135 assert!(
1136 stats.peak_active_frames <= 64,
1137 "frames bounded by tree height: {}",
1138 stats.peak_active_frames
1139 );
1140 assert!(
1141 stats.peak_buffered_messages_owned <= 64 * (BUFFER_CAP + 1),
1142 "buffered messages bounded by height × BUFFER_CAP: {}",
1143 stats.peak_buffered_messages_owned
1144 );
1145 }
1146
1147 #[test]
1150 fn cancellation_is_observed_during_tree_traversal() {
1151 let mut t = BeTree::new();
1152 for i in 0..50_000u64 {
1153 t.insert_row(val_row(i, 1, i as i64));
1154 }
1155 let mut cursor = t.leaf_versions_iter();
1156 let control = crate::ExecutionControl::new(None);
1157 control.cancel(crate::CancellationReason::ClientRequest);
1158 let err = cursor
1159 .next_controlled(&control)
1160 .expect_err("cancelled control must stop the cursor");
1161 assert!(matches!(err, crate::MongrelError::Cancelled));
1162 assert_eq!(
1163 cursor.stats().versions_examined,
1164 0,
1165 "cancellation lands before the first version streams"
1166 );
1167 }
1168}