Skip to main content

mongreldb_core/
be_tree.rs

1//! Buffered Bε-tree (B-epsilon-tree) over composite `(RowId, Epoch)` keys —
2//! the Phase 1 memtable target.
3//!
4//! Keyed by `(RowId, Epoch)`, so every version of a logical row coexists (an
5//! update inserts a new key; the old version is untouched until compaction). A
6//! Bε-tree buffers many pending mutations per internal node; when a buffer
7//! fills, its messages flush to one child in bulk, giving write amplification
8//! approaching O(1). Reads consult every buffer along the root→leaf path and
9//! return the newest version with `epoch <= snapshot`.
10//!
11//! This is a drop-in MVCC alternative to the skip-list [`crate::Memtable`]; the
12//! engine ships the skip-list today because it is simpler, while this structure
13//! wins on update amplification at scale.
14
15use crate::epoch::Epoch;
16use crate::memtable::Row;
17use crate::rowid::RowId;
18use std::borrow::Cow;
19use std::collections::HashMap;
20
21/// Max children per internal node (`B`).
22const FANOUT: usize = 8;
23/// Messages buffered per internal node before flushing down to children.
24const BUFFER_CAP: usize = 16;
25/// Max rows per leaf before it splits.
26const LEAF_CAP: usize = 32;
27
28/// Composite version key: `(row_id, epoch)`.
29type VKey = (RowId, Epoch);
30
31/// A pending mutation pending application to the leaves below a node.
32#[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
86/// Versions examined between cooperative cancellation checkpoints inside the
87/// lazy cursor (REM-C §7.11).
88const CURSOR_CHECKPOINT_INTERVAL: usize = 256;
89
90/// Test-visible counters for the lazy Bε-tree version cursor (REM-C §7.6).
91///
92/// Each cursor node keeps its own local counts; [`BeTreeVersionCursor::stats`]
93/// aggregates the live cursor chain (subtrees that finish fold their counts
94/// into the parent before being dropped, so the aggregate is complete).
95#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
96#[doc(hidden)] // test instrumentation; not a stable public API
97pub struct BeTreeVersionCursorStats {
98    /// Live cursor frames (one per node on the current root→leaf path).
99    pub active_frames: usize,
100    /// Peak of `active_frames` over the cursor's lifetime.
101    pub peak_active_frames: usize,
102    /// Buffered messages currently owned by live frames. Bounded by tree
103    /// height × node buffer capacity — never by total version count.
104    pub buffered_messages_owned: usize,
105    /// Peak of `buffered_messages_owned` over the cursor's lifetime.
106    pub peak_buffered_messages_owned: usize,
107    /// Versions copied into a tree-wide collection before streaming. The
108    /// lazy cursor never precollects, so this is always zero; it exists to
109    /// fail loudly if a full materialization ever comes back.
110    pub total_versions_precollected: usize,
111    /// Versions emitted by this cursor node (aggregated: plus descendants).
112    pub versions_examined: usize,
113    /// `ExecutionControl::checkpoint` calls made by this cursor chain.
114    pub checkpoints: usize,
115}
116
117/// One buffered message prepared for the lazy merge at a single internal
118/// node: the message row plus its insertion sequence within the node buffer,
119/// which stabilizes the equal-key tie order (see [`BeTreeVersionCursor`]).
120struct 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    /// The current child's stream produced its last version. The matching
148    /// buffer group may still hold items and keeps draining until the
149    /// frame advances to the next child.
150    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
158/// Lazy ordered cursor over every version reachable from a [`BeTree`] root
159/// (REM-C §7.7–§7.11). Yields `Cow<'a, Row>` in ascending `(RowId, Epoch)`
160/// order without ever collecting all versions: each internal node merges its
161/// own bounded, per-child sorted buffer group with the live child cursor, and
162/// child key ranges are ordered and non-overlapping, so processing children
163/// left to right yields a globally ordered stream. Cursor-owned memory is
164/// bounded by tree depth × node buffer capacity.
165///
166/// ## Equal-key determinism (§7.10)
167///
168/// Equal `(RowId, Epoch)` keys are emitted in **physical write order**
169/// (oldest first): within a leaf, rows sit in insert-after-equal order; an
170/// internal node's child subtree (older, already-flushed writes) drains
171/// before its buffered messages (newer writes); ties inside one node buffer
172/// resolve in insertion order (`sequence`). Read folds walk this stream
173/// oldest-first and resolve exact-stamp ties to the *later* physical write
174/// via `epoch::version_supersedes`, so a same-span create+delete stays dead
175/// and a same-span delete+re-put stays live. The rule never depends on heap
176/// addresses or timing, so repeated scans over the same tree yield identical
177/// streams.
178///
179/// ## Cooperative cancellation (§7.11)
180///
181/// [`Self::next_controlled`] checkpoints the supplied `ExecutionControl`
182/// before descending into a new child (which sorts that node's buffer
183/// groups) and every [`CURSOR_CHECKPOINT_INTERVAL`] versions emitted per
184/// frame. The plain [`Iterator`] impl runs the same traversal without a
185/// control for callers that cannot cancel.
186pub 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                // Assign each buffered message to the one child that owns its
208                // key, then sort only that bounded per-child subset (§7.9).
209                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    /// Next version in ascending `(RowId, Epoch)` order, observing
247    /// cooperative cancellation (§7.11).
248    #[doc(hidden)] // streaming-cursor plumbing; used by the memtable adapter and tests
249    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    /// Aggregated cursor statistics: local counts plus every live descendant,
257    /// with finished subtrees already folded into their ancestors.
258    #[doc(hidden)] // test instrumentation; not a stable public API
259    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                // Checkpoint before descending into the new child — the
314                // descent sorts that node's buffer groups (§7.11).
315                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                    // This child range is fully drained; move to the next.
342                    *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                    // Exact-key ties emit the child (older, already-flushed
356                    // write) first; the buffered message (newer write) comes
357                    // last so `version_supersedes` folds hand the tie to the
358                    // later physical write.
359                    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    /// Live `(frames, owned buffered messages)` of the subtree rooted here.
372    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    /// Refresh `active`/`peak` footprint counters after the child cursor was
388    /// created, advanced, or dropped. Because a parent refreshes after every
389    /// child operation, the root's peaks are the true global peaks.
390    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    /// Fold a finished child subtree's counts into the parent so the
424    /// aggregate stays complete after the child is dropped.
425    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        // Footprint peaks were already folded by refresh_peaks while the
431        // child was live.
432    }
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/// Buffered Bε-tree over `(RowId, Epoch)` → [`Row`].
472#[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    /// Number of mutations buffered.
493    pub fn mutations(&self) -> usize {
494        self.mutations
495    }
496
497    pub fn is_empty(&self) -> bool {
498        self.mutations == 0
499    }
500
501    /// Insert a row version (keyed by its own `(row_id, committed_epoch)`).
502    pub fn insert_row(&mut self, row: Row) {
503        self.insert(Message::Upsert(row));
504    }
505
506    /// Insert a tombstone at `(row_id, epoch)`.
507    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    /// Newest version of `row_id` with `epoch <= snapshot`, including tombstones
528    /// (returned as a `Row` with `deleted=true`). `None` if no such version.
529    pub fn get(&self, row_id: RowId, snapshot: Epoch) -> Option<Row> {
530        self.get_version(row_id, snapshot).map(|(_, r)| r)
531    }
532
533    /// Same as [`Self::get`] but also returns the version's epoch — the shape
534    /// the engine's MVCC merge needs to pick the newest version across the
535    /// memtable, the mutable-run tier, and sorted runs.
536    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    /// Visible (non-deleted) row at `row_id` for `snapshot`.
543    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    /// Visit every buffered version of `row_id`, in the same relative order as
553    /// [`Self::versions`], without materializing the whole tree or descending
554    /// into unrelated key ranges.
555    ///
556    /// A logical row can span several leaves because the physical key includes
557    /// its epoch, so the range walk covers every child intersecting
558    /// `(row_id, Epoch::ZERO)..=(row_id, Epoch(u64::MAX))`.
559    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    /// Lazy ascending stream over every version in the tree (REM-C). The
564    /// cursor holds no tree-wide collection; see [`BeTreeVersionCursor`].
565    #[doc(hidden)] // streaming-cursor plumbing; used by the memtable adapter and tests
566    pub fn leaf_versions_iter(&self) -> BeTreeVersionCursor<'_> {
567        BeTreeVersionCursor::new(&self.root)
568    }
569
570    /// Every buffered version (non-consuming), in no defined order — leaves
571    /// plus every internal-node buffer. Used by the memtable adapter to dedup
572    /// the newest visible version per `RowId` for a full visible-rows scan.
573    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    /// Consume the tree, flushing all buffers to leaves, returning every version
580    /// in ascending `(RowId, Epoch)` order.
581    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    // ---- internals -----------------------------------------------------
587
588    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        // Insert *after* any equal `(row_id, epoch)` keys: a same-span
640        // create+delete produces two entries with the same composite key,
641        // and their physical write order (live first, tombstone last) must
642        // survive — the read folds resolve exact-stamp ties to the later
643        // write (`epoch::version_supersedes`). For unique keys this is
644        // identical to the previous `< key` partition point.
645        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                // Versions of `row_id` are contiguous; scan the slice whose
697                // (row_id, epoch) <= (row_id, snapshot) and row_id matches.
698                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                // Newest buffered message first: with `consider`'s
719                // first-on-tie rule this makes the *later physical write*
720                // win an exact (row_id, epoch) tie — the tombstone for a
721                // same-span create+delete, the live row for a same-span
722                // delete+re-put. Buffer contents are physically newer than
723                // anything already flushed into the child below.
724                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                // Children (older, already-flushed writes) before the buffer
752                // (newer writes), matching `collect_all_versions`: the whole
753                // traversal yields equal `(row_id, epoch)` keys in physical
754                // write order so `version_supersedes` folds hand the tie to
755                // the later write.
756                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                // Children (older, already-flushed writes) before the buffer
806                // (newer, not yet flushed): keeps the whole traversal in
807                // physical write order for equal `(row_id, epoch)` keys, so
808                // stable re-sorts and write-order-sensitive consumers (run
809                // writer, `version_supersedes` folds) see the tombstone of a
810                // same-span create+delete after the live version.
811                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        // The whole point of the composite key: an update keeps the old version.
864        let mut t = BeTree::new();
865        t.insert_row(val_row(7, 1, 100));
866        t.insert_row(val_row(7, 5, 200));
867        // Old snapshot sees the old value; new snapshot sees the new value.
868        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        // Still visible to a snapshot before the tombstone.
883        assert!(t.get_visible(RowId(3), Epoch(3)).is_some());
884    }
885
886    /// Same `(row_id, epoch)` live+tombstone pair (a create+delete inside one
887    /// commit span): the later physical write — the tombstone — must win the
888    /// fold no matter where the pair sits (leaf or internal buffer).
889    #[test]
890    fn same_epoch_live_tombstone_pair_resolves_to_last_write() {
891        // Small tree: pair lands in one leaf.
892        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        // Same span, delete then re-put: the live row is the later write.
899        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        // Force internal buffering above the pair so the fold crosses a
906        // buffer/leaf boundary.
907        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        // `versions()` must preserve physical write order for the pair so
917        // stable downstream consumers (run writer) keep it too.
918        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)); // newer version of row 30
939        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    /// Regression for the Phase 11 review's CRITICAL claim: when one row
955    /// accumulates enough versions to span multiple leaf splits (and internal
956    /// buffering), a `(row_id, snapshot)` point lookup must still return the
957    /// newest visible version. This forces many splits and exercises the descent
958    /// across child boundaries for a single high-churn row.
959    #[test]
960    fn many_versions_of_one_row_stay_lookupable_across_splits() {
961        let mut t = BeTree::new();
962        const N: u64 = 600;
963        // Interleave other rows so the composite-key space has many separators;
964        // the high-churn row is 7777, with a version at every epoch 0..N.
965        for e in 0..N {
966            t.insert_row(val_row(7777, e, e as i64 * 2));
967            // A few distinct sibling rows to vary the key space and force
968            // splits at separator keys that are NOT row 7777.
969            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        // Every snapshot epoch must see exactly epoch `s` as the newest version
988        // of row 7777 (versions are dense 0..N).
989        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        // Before the first version: nothing.
997        assert!(t.get_version(RowId(7777), Epoch(0)).is_some()); // epoch 0 exists
998                                                                 // The sibling rows are all visible at their own epoch.
999        for e in 0..N {
1000            assert!(t.get_visible(RowId(e), Epoch(e)).is_some(), "sibling {e}");
1001        }
1002
1003        // Tombstones mixed in across splits: delete row 7777 at a late epoch,
1004        // then confirm snapshots before/after the tombstone see the right thing.
1005        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    /// REM-C §7.13: an out-of-order insert sequence that leaves messages
1021    /// sitting in internal-node buffers must still stream in strictly
1022    /// ascending `(RowId, Epoch)` order.
1023    #[test]
1024    fn out_of_order_internal_buffers_emit_ascending() {
1025        let mut t = BeTree::new();
1026        const N: u64 = 20_000;
1027        // 7_919 is coprime with 20_000, so the multiply is a bijection:
1028        // unique RowIds in a scrambled insert order.
1029        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        // Late tombstones: many remain in internal-node buffers.
1034        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    /// REM-C §7.10: exact `(RowId, Epoch)` ties emit in physical write order
1063    /// (oldest first) — leaf rows before the node's buffered messages, buffer
1064    /// contents in insertion order — and never by heap addresses. Read folds
1065    /// resolve the tie to the *last* emitted version (`version_supersedes`).
1066    #[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), // leaf-resident live row (oldest write)
1103                (7, 5, false), // buffered upsert (sequence 0)
1104                (7, 5, true),  // buffered tombstone (sequence 1, newest write)
1105                (60, 1, false),
1106            ],
1107            "child rows precede buffered messages at equal keys; buffer in insertion order"
1108        );
1109    }
1110
1111    /// REM-C §7.6/§7.7: the first version is available after examining only a
1112    /// bounded number of versions — no full-tree walk or sort stands between
1113    /// construction and the first row.
1114    #[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    /// REM-C §7.11: a cancelled control is observed during tree traversal,
1148    /// before any version streams.
1149    #[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}