Rindle docs and package mapSkip to main content

rindle/
btree.rs

1//! **Primitive #4 — the COW B+tree (the clean win).** Spike for spec
2//! `04-sources-memory-and-btree.md` §4.4 (the M3 milestone) and the two open
3//! questions it flags as the #1 implementation risk:
4//!
5//! - **OQ-1 — `BTreeCursor` self-reference / snapshot wiring** (§4.4.4, §12 Q1).
6//!   The cursor must hold its `Rc<BNode>` snapshot *and* vend a borrowed
7//!   `&OwnedRow` out of it. The naive `&'t BNode` field that borrows another
8//!   field of the same struct is a self-referential struct — not expressible in
9//!   safe Rust. This spike locks in the **recommended shape**: the cursor owns
10//!   `Rc<BNode>` clones along the spine (no `&'t` self-borrows) and `next_row`
11//!   returns `&self.leaf.keys[pos]`, a borrow of `self` valid only until the
12//!   next call — exactly the lending [`RowStream`] contract. *Result: the cursor
13//!   needs no lifetime parameter at all.*
14//!
15//! - **OQ-2 — owning a row at the node boundary without a deep copy** (§8.2,
16//!   §12 Q2). Node construction needs the stored `Arc<[OwnedValue]>` to *bump*
17//!   (refcount++), not deep-copy. A bare borrowed slice has lost its `Arc`
18//!   identity, so `Arc::from(slice)` would re-allocate. **Resolution — folded
19//!   into the shared trait:** the cursor's `RowStream::Row<'a>` is `&'a OwnedRow`
20//!   (not a slice), so `RowRef::to_owned_row` is just `Arc::clone` of the stored
21//!   row — a refcount bump, never a copy (proven by `Arc::ptr_eq` in the tests).
22//!   No concrete `current_arc` escape hatch is needed: the *same* `to_owned_row`
23//!   the SQLite leaf uses to *copy* out of its step buffer is, here, a *bump*.
24//!
25//! **What `Rc<BNode>` + `Rc::make_mut` deletes** (§4.4.1): the JS
26//! `BNode.isShared` transitive flag (`btree-set.ts:384-396`) — the hardest
27//! hand-port artifact — *vanishes*. Sharing is `Arc::strong_count > 1` (tracked
28//! by the runtime); "clone before mutate if shared" is precisely `Rc::make_mut`;
29//! transitivity falls out because `make_mut` is called lazily per node as the
30//! mutation descends, copying only the root→leaf path and only the nodes that are
31//! actually aliased. `fork()`/`clone()` is one `Arc` bump — O(1).
32//!
33//! Scope note: this uses the spike crate's existing **owned** `Value`/`Row`
34//! (`value.rs`); the production `Value<'a>`/`OwnedValue` split is Step 2 (a
35//! mechanical type swap — the algorithm here is the production one). `add`,
36//! `delete` (with the JS opportunistic `tryMerge` rebalancing), `from_sorted`, the
37//! forward/reverse bounded cursor, and `fork`/`make_mut` COW are all faithful
38//! ports of `shared/src/btree-set.ts` and differential-tested against it
39//! (`tests/btree_diff.rs`). The one **deliberate** divergence from JS is the
40//! `set` "shift-to-avoid-split" sibling rebalance (`btree-set.ts:558-583`): it is
41//! a pure micro-optimization (never changes contents, and is never triggered by
42//! ascending inserts or `from_sorted`, so it affects no benchmark), and it carries
43//! the most bug surface of any path — omitted on purpose, documented in
44//! `node_set`. In production this module is `#[cfg(feature = "memory")]`.
45
46use std::cmp::Ordering;
47use std::rc::Rc; // node pointer — single-threaded client default (README §7.1)
48
49use crate::change::SourceChange;
50use crate::value::{compare_rows, compare_values, ColId, OwnedRow, OwnedValue, Sort};
51
52// Re-export the lending traits so existing `rindle::btree::{RowStream, RowRef}`
53// import paths (and the integration tests) keep working now that the canonical
54// definitions live in `value.rs` — the union proven against BOTH backends.
55pub use crate::value::{RowRef, RowStream};
56
57/// The btree's element type is the canonical [`OwnedRow`]. The COW tree stores
58/// owned rows; the lending cursor vends `&OwnedRow`, so owning a row at the node
59/// boundary ([`RowRef::to_owned_row`]) is an `Arc` bump, not a copy.
60type Row = OwnedRow;
61
62/// Max keys per leaf / max children per internal node before a split. The JS uses
63/// 32 (`btree-set.ts:3`, tuned for V8); 64 is the Rust measured optimum (a swept
64/// benchmark — `examples/bench_rs.rs`): fewer levels → fewer pointer-chases per
65/// lookup and better scan locality, at a small cost to single-key path-copy. Node
66/// size changes no observable contents, so the JS differential still holds. Tests
67/// insert well past it to force multi-level trees.
68const MAX_NODE_SIZE: usize = 64;
69
70/// Pre-allocated cursor spine depth. A fanout-`MAX_NODE_SIZE` tree of this depth
71/// holds `32^24` rows — astronomically beyond any real dataset (real depth is
72/// < 8), so the spine `Vec` is allocated ONCE at cursor creation and **never
73/// reallocates during the scan**. This is what makes the per-row vend cost a
74/// clean 0 (the spine's O(log n) cost is paid once, not per row).
75const SPINE_CAP: usize = 24;
76
77// The lending `RowStream`/`RowRef` traits now live in `value.rs` (the canonical
78// union, refined by the SQLite step-buffer lifetime) and are re-exported above.
79// OQ-1 — "does the lending trait compose with a cursor that *owns* its `Rc`
80// snapshot?" — is answered by the `impl RowStream for BTreeCursor` at the bottom
81// of this file.
82
83// ---------------------------------------------------------------------------
84// Nodes — a Rust enum replaces the JS BNode / BNodeInternal subclass pair.
85// NO `isShared` field (§4.4.1): the whole reason this port is "the clean win".
86// ---------------------------------------------------------------------------
87
88#[derive(Clone)]
89enum BNode {
90    Leaf {
91        /// Sorted rows.
92        keys: Vec<Row>,
93    },
94    Internal {
95        /// `keys[i] == children[i].max_key()` — the cached subtree-max invariant
96        /// (`btree-set.ts:501-503`). Lets `max_key()` be O(1) and navigation a
97        /// binary search over child maxes.
98        keys: Vec<Row>,
99        children: Vec<Rc<BNode>>,
100    },
101}
102
103impl BNode {
104    fn empty_leaf() -> BNode {
105        BNode::Leaf { keys: Vec::new() }
106    }
107
108    fn is_leaf(&self) -> bool {
109        matches!(self, BNode::Leaf { .. })
110    }
111
112    /// The keys vec for either variant (rows for a leaf; cached child-maxes for an
113    /// internal node).
114    fn keys(&self) -> &[Row] {
115        match self {
116            BNode::Leaf { keys } | BNode::Internal { keys, .. } => keys,
117        }
118    }
119
120    fn num_children(&self) -> usize {
121        match self {
122            BNode::Internal { children, .. } => children.len(),
123            BNode::Leaf { .. } => 0,
124        }
125    }
126
127    /// Clone (Arc bump) of child `i`. Panics on a leaf — callers guard with
128    /// `is_leaf`.
129    fn child(&self, i: usize) -> Rc<BNode> {
130        match self {
131            BNode::Internal { children, .. } => Rc::clone(&children[i]),
132            BNode::Leaf { .. } => unreachable!("child() on a leaf"),
133        }
134    }
135
136    /// O(1) max key of this subtree, via the cache invariant: a leaf's max is its
137    /// last key; an internal node's max is `keys.last()` (which *is* the cached max
138    /// of its last child, recursively). Caller guarantees non-empty.
139    fn max_key(&self) -> &Row {
140        self.keys().last().expect("max_key on an empty node")
141    }
142
143    /// An empty leaf (0 keys) or an empty internal node (0 children) — pruned by
144    /// `delete` so the tree never contains empty interior nodes.
145    fn is_empty_node(&self) -> bool {
146        match self {
147            BNode::Leaf { keys } => keys.is_empty(),
148            BNode::Internal { children, .. } => children.is_empty(),
149        }
150    }
151}
152
153// ---------------------------------------------------------------------------
154// Seek bounds — the min/max sentinels (§4.3)
155// ---------------------------------------------------------------------------
156
157/// A scan-start cell: a concrete value or a type-extreme sentinel that sorts
158/// below/above *every* real value (including `Null`, the lowest real value).
159/// Mirrors JS `Bound = Value | minValue | maxValue` (`memory-source.ts:967`). The
160/// spike uses one owned form (the production `Bound<'a>`/`OwnedBound` split is the
161/// zero-copy concern of Step 2).
162#[derive(Clone)]
163pub enum Bound {
164    Min,
165    Max,
166    Val(OwnedValue),
167}
168
169/// A scan-start key: each index-sort column pinned to a [`Bound`]. Empty / absent
170/// columns are treated as `Min` (a safety default — a real seek pins every sort
171/// column). Index-addressed; a small `Vec`, not a map (`04` §4.3).
172pub type RowBound = Vec<(ColId, Bound)>;
173
174/// Build an all-`Val` `RowBound` from a concrete lower-bound row.
175pub fn row_bound_of(row: &Row, sort: &Sort) -> RowBound {
176    sort.iter()
177        .map(|&(c, _)| (c, Bound::Val(row.col(c).to_owned())))
178        .collect()
179}
180
181/// `compareBounds`-over-a-sort (`memory-source.ts:1000-1015`): compare a
182/// (possibly-sentinel) bound against a stored row under `sort`. Reverse is *not*
183/// baked in here — the caller negates as needed (the one-place convention of
184/// `04` §4.6 lives in the source, not the tree).
185fn cmp_bound_row(sort: &Sort, bound: &RowBound, row: &Row) -> Ordering {
186    for &(col, asc) in sort {
187        let b = bound.iter().find(|(c, _)| *c == col).map(|(_, b)| b);
188        let c = match b {
189            None | Some(Bound::Min) => Ordering::Less,
190            Some(Bound::Max) => Ordering::Greater,
191            Some(Bound::Val(v)) => compare_values(v.as_ref(), row.col(col)),
192        };
193        if c != Ordering::Equal {
194            return if asc { c } else { c.reverse() };
195        }
196    }
197    Ordering::Equal
198}
199
200// ---------------------------------------------------------------------------
201// Search / navigation helpers (free fns over `&Sort`, no boxed comparator —
202// foundations §11.6)
203// ---------------------------------------------------------------------------
204
205/// Binary search a sorted `keys` slice for `key`. `Ok(i)` = exact match at `i`;
206/// `Err(i)` = insertion point.
207fn search(keys: &[Row], key: &Row, sort: &Sort) -> Result<usize, usize> {
208    let mut lo = 0;
209    let mut hi = keys.len();
210    while lo < hi {
211        let mid = (lo + hi) / 2;
212        match compare_rows(sort, &keys[mid], key) {
213            Ordering::Less => lo = mid + 1,
214            Ordering::Greater => hi = mid,
215            Ordering::Equal => return Ok(mid),
216        }
217    }
218    Err(lo)
219}
220
221/// First index `i` with `keys[i] >= key` (lower bound). In `[0, keys.len()]` —
222/// unlike [`child_index`] it is NOT clamped, so `== keys.len()` signals "key is
223/// greater than every entry" (used by `node_delete` to detect a key past the end).
224fn lower_bound(keys: &[Row], key: &Row, sort: &Sort) -> usize {
225    let mut lo = 0;
226    let mut hi = keys.len();
227    while lo < hi {
228        let mid = (lo + hi) / 2;
229        if compare_rows(sort, &keys[mid], key) == Ordering::Less {
230            lo = mid + 1;
231        } else {
232            hi = mid;
233        }
234    }
235    lo
236}
237
238/// Child index whose subtree should contain `key`: the first child whose cached
239/// max is `>= key`, else the last child (for a key past the end). `keys` are the
240/// internal node's child-maxes; non-empty by the no-empty-node invariant.
241fn child_index(keys: &[Row], key: &Row, sort: &Sort) -> usize {
242    let mut lo = 0;
243    let mut hi = keys.len();
244    while lo < hi {
245        let mid = (lo + hi) / 2;
246        if compare_rows(sort, &keys[mid], key) == Ordering::Less {
247            lo = mid + 1;
248        } else {
249            hi = mid;
250        }
251    }
252    if lo == keys.len() {
253        keys.len() - 1
254    } else {
255        lo
256    }
257}
258
259/// Child index for a seek bound: first child whose cached max is `>= bound`, else
260/// the last child.
261fn child_index_bound(keys: &[Row], bound: &RowBound, sort: &Sort) -> usize {
262    let mut lo = 0;
263    let mut hi = keys.len();
264    while lo < hi {
265        let mid = (lo + hi) / 2;
266        if cmp_bound_row(sort, bound, &keys[mid]) == Ordering::Greater {
267            lo = mid + 1;
268        } else {
269            hi = mid;
270        }
271    }
272    if lo == keys.len() {
273        keys.len() - 1
274    } else {
275        lo
276    }
277}
278
279/// First leaf index `>= bound` (lower bound). May equal `keys.len()` (the element
280/// is in the next leaf — the cursor normalizes).
281fn leaf_lower_bound(keys: &[Row], bound: &RowBound, sort: &Sort) -> usize {
282    let mut lo = 0;
283    let mut hi = keys.len();
284    while lo < hi {
285        let mid = (lo + hi) / 2;
286        if cmp_bound_row(sort, bound, &keys[mid]) == Ordering::Greater {
287            lo = mid + 1;
288        } else {
289            hi = mid;
290        }
291    }
292    lo
293}
294
295/// First leaf index strictly `> bound` (upper bound). Used by the reverse seek:
296/// the largest element `<= bound` sits at `leaf_upper_bound - 1`.
297fn leaf_upper_bound(keys: &[Row], bound: &RowBound, sort: &Sort) -> usize {
298    let mut lo = 0;
299    let mut hi = keys.len();
300    while lo < hi {
301        let mid = (lo + hi) / 2;
302        if cmp_bound_row(sort, bound, &keys[mid]) == Ordering::Less {
303            hi = mid;
304        } else {
305            lo = mid + 1;
306        }
307    }
308    lo
309}
310
311/// Recursive read descent (`btree-set.ts:415-423`, `537-547`). Borrows the stored
312/// row out of the tree — zero allocation and zero refcount traffic (unlike an
313/// iterative `Arc`-cloning walk). Recursion (rather than a loop) sidesteps the
314/// borrow-reassign trap while keeping the descent by reference; depth is
315/// `log_32 n` so the call chain is tiny.
316fn find<'a>(node: &'a BNode, key: &Row, sort: &Sort) -> Option<&'a Row> {
317    match node {
318        BNode::Leaf { keys } => match search(keys, key, sort) {
319            Ok(i) => Some(&keys[i]),
320            Err(_) => None,
321        },
322        BNode::Internal { keys, children } => {
323            if children.is_empty() {
324                return None;
325            }
326            find(&children[child_index(keys, key, sort)], key, sort)
327        }
328    }
329}
330
331// ---------------------------------------------------------------------------
332// The COW B+tree
333// ---------------------------------------------------------------------------
334
335/// A copy-on-write B+tree set of [`OwnedRow`]s, ordered by a `Sort` comparator passed
336/// per call (no stored closure — foundations §11.6). Replaces `BTreeSet<Row>`
337/// (`btree-set.ts:7`) with `Rc<BNode>` + `Rc::make_mut`. The comparator lives
338/// with the enclosing `Index` in the real source; the spike threads `&Sort`.
339///
340/// **Preconditions** (schema invariants the builder/source guarantee; a violation
341/// is a programming error and panics, mirroring the JS oracle's `compareValues`
342/// throw — `data.ts`):
343/// - every `Row` is at least as wide as `max(ColId in sort/bound) + 1` (so column
344///   indexing never goes out of bounds);
345/// - within any one sort column, all rows carry the **same** `Value` variant
346///   (so `compare_values` never hits a cross-type pair — risk-register R10; the
347///   builder coerces literals to the column type at build time).
348pub struct BTree {
349    root: Rc<BNode>,
350    size: usize,
351}
352
353impl Default for BTree {
354    fn default() -> Self {
355        BTree::new()
356    }
357}
358
359/// Outcome of an internal `node_set` descent.
360enum InsertResult {
361    /// An equal key already existed; its slot was overwritten. Size unchanged.
362    /// Carries the row that was displaced — rows are `Arc<[u8]>` of *varying* length,
363    /// so a caller doing memory accounting (`BatchDelta`'s byte budget, design 306 D4)
364    /// needs the outgoing payload to stay net. Free to produce: the leaf overwrite is
365    /// a `mem::replace` either way.
366    Existed(Row),
367    /// A new key was inserted; no split propagated.
368    Inserted,
369    /// The node overflowed and split; this is the new right sibling to graft into
370    /// the parent (or grow a new root).
371    Split(Rc<BNode>),
372}
373
374impl BTree {
375    /// Empty tree. O(1).
376    pub fn new() -> BTree {
377        BTree {
378            root: Rc::new(BNode::empty_leaf()),
379            size: 0,
380        }
381    }
382
383    pub fn len(&self) -> usize {
384        self.size
385    }
386
387    pub fn is_empty(&self) -> bool {
388        self.size == 0
389    }
390
391    /// **The headline: O(1) structural clone — one `Arc` bump + a `usize` copy.**
392    /// JS `clone()` (`btree-set.ts:28-34`) sets `root.isShared = true` and shares
393    /// the root; Rust just clones the `Arc`. `fork()` and `#getOrCreateIndex`'s
394    /// `data.clone()` both become this. NO flag mutation, NO node allocation.
395    /// (Not `impl Clone` so the O(1)-ness is named at every call site.)
396    pub fn fork(&self) -> BTree {
397        BTree {
398            root: Rc::clone(&self.root),
399            size: self.size,
400        }
401    }
402
403    /// `has` (`btree-set.ts:56-58`). O(log n), read-only — never calls `make_mut`,
404    /// never bumps a refcount (descends by reference).
405    pub fn has(&self, key: &Row, sort: &Sort) -> bool {
406        find(&self.root, key, sort).is_some()
407    }
408
409    /// `get` (`btree-set.ts:36-38`): the stored row equal to `key` under the
410    /// comparator (rows may carry payload beyond the sort columns), borrowed out of
411    /// the tree. O(log n), read-only, zero allocation/refcount traffic — the caller
412    /// `Arc::clone`s only if it needs to own (one bump, OQ-2).
413    pub fn get(&self, key: &Row, sort: &Sort) -> Option<&Row> {
414        find(&self.root, key, sort)
415    }
416
417    /// `add` (`btree-set.ts:40-47`). Inserts `key`; returns `true` iff newly
418    /// inserted (`false` overwrites the equal slot, like the JS). **Path-copying
419    /// COW:** `make_mut` the root, then each child as the insert descends — only
420    /// the root→leaf path is copied, and only nodes that are actually shared.
421    pub fn add(&mut self, key: Row, sort: &Sort) -> bool {
422        self.add_replacing(key, sort).is_none()
423    }
424
425    /// [`add`](Self::add), returning the row it **displaced** (`None` iff newly
426    /// inserted). Same cost — the overwrite is a `mem::replace` either way; `add` is
427    /// this with the payload dropped. Exists for callers that track the bytes their
428    /// tree holds (`BatchDelta`'s byte budget, design 306 D4): rows are variable-length
429    /// `Arc<[u8]>`, so "did it overwrite" is not enough to stay net — the accounting
430    /// needs the outgoing row's size.
431    pub fn add_replacing(&mut self, key: Row, sort: &Sort) -> Option<Row> {
432        // `make_mut` IS the JS `if (root.isShared) root = root.clone()` — but it
433        // clones *iff* `strong_count > 1`, else mutates in place.
434        let root = Rc::make_mut(&mut self.root);
435        match node_set(root, key, sort) {
436            InsertResult::Existed(prev) => Some(prev),
437            InsertResult::Inserted => {
438                self.size += 1;
439                None
440            }
441            InsertResult::Split(right) => {
442                // Root split: grow a new root one level taller.
443                self.size += 1;
444                let left = std::mem::replace(&mut self.root, Rc::new(BNode::empty_leaf()));
445                let lk = left.max_key().clone();
446                let rk = right.max_key().clone();
447                self.root = Rc::new(BNode::Internal {
448                    keys: vec![lk, rk],
449                    children: vec![left, right],
450                });
451                None
452            }
453        }
454    }
455
456    /// `delete` (`btree-set.ts:66-89`). Removes `key`; returns `true` iff present.
457    /// Path-copying COW (same `make_mut` descent as `add`) + the JS opportunistic
458    /// rebalancing: after removing from a child, an emptied child is dropped and an
459    /// underfull child (`<= MAX/2`) is `tryMerge`d with its right neighbour when the
460    /// combined size fits (`btree-set.ts:639-721`). Each merged-into sibling is
461    /// `make_mut`'d before mutation (the `isShared`→`Arc` substitution, §4.4.5).
462    /// Then the root-collapse loop runs. This matches the JS `BTreeSet` occupancy
463    /// behaviour exactly (it too tolerates underfull nodes that can't merge — it is
464    /// *not* a strict B-tree).
465    pub fn delete(&mut self, key: &Row, sort: &Sort) -> bool {
466        let root = Rc::make_mut(&mut self.root);
467        let removed = node_delete(root, key, sort);
468        if removed {
469            self.size -= 1;
470            self.collapse_root();
471        }
472        removed
473    }
474
475    /// `btree-set.ts:79-88`: while the root is an internal node with a single
476    /// child, descend it (the tree got shorter). A fully-emptied internal root
477    /// becomes an empty leaf. `make_mut` already copied the path; no `isShared`
478    /// re-propagation needed (`Arc` handles it).
479    fn collapse_root(&mut self) {
480        loop {
481            let only_child = match &*self.root {
482                BNode::Internal { children, .. } if children.len() == 1 => {
483                    Some(Rc::clone(&children[0]))
484                }
485                _ => None,
486            };
487            match only_child {
488                Some(c) => self.root = c,
489                None => break,
490            }
491        }
492        if matches!(&*self.root, BNode::Internal { children, .. } if children.is_empty()) {
493            self.root = Rc::new(BNode::empty_leaf());
494        }
495    }
496
497    /// `fromSorted` (`btree-set.ts:140-188`): O(N) bottom-up bulk load from a
498    /// PRE-SORTED iterator (caller guarantees order under `sort`). Build leaves of
499    /// `MAX_NODE_SIZE`, then internal levels bottom-up until one root. NO per-key
500    /// descent — this is the O(N)-vs-O(N log n) win the lazy index build needs
501    /// (`04` §5.4).
502    pub fn from_sorted(iter: impl Iterator<Item = Row>, sort: &Sort) -> BTree {
503        let rows: Vec<Row> = iter.collect();
504        let size = rows.len();
505        debug_assert!(
506            rows.windows(2)
507                .all(|w| compare_rows(sort, &w[0], &w[1]) != Ordering::Greater),
508            "from_sorted: input not sorted under `sort`"
509        );
510        if rows.is_empty() {
511            return BTree::new();
512        }
513        // Move rows into leaves (no per-row clone): drain the owned Vec in chunks.
514        let mut level: Vec<Rc<BNode>> = Vec::with_capacity(size / MAX_NODE_SIZE + 1);
515        let mut it = rows.into_iter();
516        loop {
517            let chunk: Vec<Row> = it.by_ref().take(MAX_NODE_SIZE).collect();
518            if chunk.is_empty() {
519                break;
520            }
521            level.push(Rc::new(BNode::Leaf { keys: chunk }));
522        }
523        while level.len() > 1 {
524            level = level
525                .chunks(MAX_NODE_SIZE)
526                .map(|chunk| {
527                    let children: Vec<Rc<BNode>> = chunk.to_vec();
528                    let keys: Vec<Row> = children.iter().map(|c| c.max_key().clone()).collect();
529                    Rc::new(BNode::Internal { keys, children })
530                })
531                .collect();
532        }
533        BTree {
534            root: level.into_iter().next().unwrap(),
535            size,
536        }
537    }
538
539    /// Validate structural invariants — a test/debug helper (cheap to keep `pub`;
540    /// it walks the whole tree, so call it in tests/property checks, not the hot
541    /// path). Returns `Err(reason)` on the first violation. Checks: balanced (every
542    /// leaf at the same depth), per-node strict sortedness, the cached-max invariant
543    /// (`keys[i] == children[i].max_key()`), `keys.len() == children.len()` for
544    /// internal nodes, node occupancy `<= MAX_NODE_SIZE`, no empty non-root node,
545    /// and `size` equals the actual key count.
546    pub fn check_invariants(&self, sort: &Sort) -> Result<(), String> {
547        fn walk(node: &BNode, sort: &Sort, is_root: bool) -> Result<(usize, usize), String> {
548            match node {
549                BNode::Leaf { keys } => {
550                    if !is_root && keys.is_empty() {
551                        return Err("empty non-root leaf".into());
552                    }
553                    if keys.len() > MAX_NODE_SIZE {
554                        return Err(format!("overfull leaf: {} keys", keys.len()));
555                    }
556                    for w in keys.windows(2) {
557                        if compare_rows(sort, &w[0], &w[1]) != Ordering::Less {
558                            return Err("leaf keys not strictly sorted".into());
559                        }
560                    }
561                    Ok((0, keys.len()))
562                }
563                BNode::Internal { keys, children } => {
564                    if children.is_empty() {
565                        return Err("empty internal node".into());
566                    }
567                    if keys.len() != children.len() {
568                        return Err(format!(
569                            "keys.len {} != children.len {}",
570                            keys.len(),
571                            children.len()
572                        ));
573                    }
574                    if children.len() > MAX_NODE_SIZE {
575                        return Err(format!("overfull internal: {} children", children.len()));
576                    }
577                    let mut total = 0;
578                    let mut depth: Option<usize> = None;
579                    for (i, child) in children.iter().enumerate() {
580                        // The cached separator must be the SAME Arc as the child's
581                        // max (every refresh does `child.max_key().clone()`, and
582                        // Arc clones are ptr-equal). This is stronger than a
583                        // sort-column compare: it also catches a stale *payload* in
584                        // a separator after an overwrite (the JS-divergence bug the
585                        // adversarial workflow found).
586                        if !Row::ptr_eq(&keys[i], child.max_key()) {
587                            return Err(format!("cached-max not Arc-identical at child {i}"));
588                        }
589                        let (d, c) = walk(child, sort, false)?;
590                        total += c;
591                        match depth {
592                            None => depth = Some(d),
593                            Some(dd) if dd != d => return Err("unbalanced subtree depths".into()),
594                            _ => {}
595                        }
596                    }
597                    for w in keys.windows(2) {
598                        if compare_rows(sort, &w[0], &w[1]) != Ordering::Less {
599                            return Err("separator keys not strictly increasing".into());
600                        }
601                    }
602                    Ok((depth.unwrap() + 1, total))
603                }
604            }
605        }
606        let (_, count) = walk(&self.root, sort, true)?;
607        if count != self.size {
608            return Err(format!("size {} != actual count {count}", self.size));
609        }
610        Ok(())
611    }
612
613    /// Forward ordered iteration from an optional lower bound (`valuesFrom`,
614    /// `btree-set.ts:99-101`). `bound = None` ⇒ from the start. `inclusive` selects
615    /// `>= bound` (true) vs `> bound` (false). Returns the LENDING [`BTreeCursor`]
616    /// — zero per-row alloc to vend.
617    pub fn values_from(
618        &self,
619        bound: Option<&RowBound>,
620        inclusive: bool,
621        sort: &Sort,
622    ) -> BTreeCursor {
623        if self.size == 0 {
624            return BTreeCursor::empty(false, Rc::clone(&self.root));
625        }
626        let mut c = BTreeCursor::empty(false, Rc::clone(&self.root));
627        match bound {
628            None => c.descend_first(Rc::clone(&self.root)),
629            Some(b) => c.descend_seek(Rc::clone(&self.root), b, inclusive, sort),
630        }
631        c.normalize_forward();
632        c
633    }
634
635    /// Reverse ordered (descending) iteration from an optional upper bound
636    /// (`valuesFromReversed`, `btree-set.ts:113-124`). `bound = None` ⇒ from the
637    /// maximum. `inclusive` selects `<= bound` (true) vs `< bound` (false). The
638    /// first row yielded is the largest qualifying row; iteration then descends.
639    pub fn values_from_reversed(
640        &self,
641        bound: Option<&RowBound>,
642        inclusive: bool,
643        sort: &Sort,
644    ) -> BTreeCursor {
645        if self.size == 0 {
646            return BTreeCursor::empty(true, Rc::clone(&self.root));
647        }
648        let mut c = BTreeCursor::empty(true, Rc::clone(&self.root));
649        match bound {
650            None => c.descend_last(Rc::clone(&self.root)),
651            Some(b) => c.descend_seek_reverse(Rc::clone(&self.root), b, inclusive, sort),
652        }
653        c.normalize_reverse();
654        c
655    }
656}
657
658/// Insert descent (`btree-set.ts:425-453` / `549-605`). `node` is already
659/// `make_mut`'d by the caller; this `make_mut`s each child it descends — that ONE
660/// substitution (`if (child.isShared) clone` → `Rc::make_mut`) is the entire
661/// `isShared`→`Arc` port (§4.4.5).
662///
663/// DELIBERATE DIVERGENCE FROM JS: the JS `BNodeInternal.set` shifts an element to
664/// a non-full sibling to *avoid* a split when descending into a full child
665/// (`btree-set.ts:558-583`, `takeFromLeft`/`takeFromRight`). We omit it. It never
666/// changes contents (only node occupancy), it is never triggered by ascending
667/// inserts or `from_sorted` (so it affects none of the benchmarks), and it is the
668/// single most intricate, alias-sensitive mutation in the tree. The plain
669/// split-and-grow below is correct and simpler; the differential suite confirms
670/// identical contents. Reintroduce it only behind a benchmark that shows an
671/// occupancy regression on a real workload.
672fn node_set(node: &mut BNode, key: Row, sort: &Sort) -> InsertResult {
673    match node {
674        BNode::Leaf { keys } => match search(keys, &key, sort) {
675            Ok(i) => {
676                // Overwrite the equal slot (btree-set.ts:451), handing the displaced
677                // row back for `add_replacing`'s byte accounting.
678                InsertResult::Existed(std::mem::replace(&mut keys[i], key))
679            }
680            Err(i) => {
681                keys.insert(i, key);
682                if keys.len() > MAX_NODE_SIZE {
683                    let mid = keys.len() / 2;
684                    let right_keys = keys.split_off(mid);
685                    InsertResult::Split(Rc::new(BNode::Leaf { keys: right_keys }))
686                } else {
687                    InsertResult::Inserted
688                }
689            }
690        },
691        BNode::Internal { keys, children } => {
692            let i = child_index(keys, &key, sort);
693            // COW: clone child i iff it is actually shared, then recurse.
694            let res = {
695                let child = Rc::make_mut(&mut children[i]);
696                node_set(child, key, sort)
697            };
698            // Refresh the cached separator UNCONDITIONALLY, mirroring JS
699            // `this.keys[i] = child.maxKey()` (`btree-set.ts:586`) which runs after
700            // every `child.set` regardless of result. This matters on the
701            // *overwrite* (`Existed`) path: when the overwritten row was the leaf's
702            // max, its full Row is this node's separator, so the new payload (a
703            // non-sort column may differ) must propagate up — otherwise the cached
704            // max goes stale on the payload columns (caught by the adversarial
705            // verification workflow; a JS divergence).
706            keys[i] = children[i].max_key().clone();
707            match res {
708                InsertResult::Existed(prev) => InsertResult::Existed(prev),
709                InsertResult::Inserted => InsertResult::Inserted,
710                InsertResult::Split(right) => {
711                    keys[i] = children[i].max_key().clone(); // left half's new max
712                    let rmax = right.max_key().clone();
713                    children.insert(i + 1, right);
714                    keys.insert(i + 1, rmax);
715                    if children.len() > MAX_NODE_SIZE {
716                        let mid = children.len() / 2;
717                        let right_children = children.split_off(mid);
718                        let right_keys = keys.split_off(mid);
719                        InsertResult::Split(Rc::new(BNode::Internal {
720                            keys: right_keys,
721                            children: right_children,
722                        }))
723                    } else {
724                        InsertResult::Inserted
725                    }
726                }
727            }
728        }
729    }
730}
731
732/// Delete descent — a faithful port of JS `BNode.delete` / `BNodeInternal.delete`
733/// (`btree-set.ts:469-674`). `node` is already `make_mut`'d by the caller; this
734/// `make_mut`s each child it descends. The JS `try/finally` is straight-line code
735/// here (the JS uses `finally` for control flow, not errors).
736fn node_delete(node: &mut BNode, key: &Row, sort: &Sort) -> bool {
737    match node {
738        BNode::Leaf { keys } => match search(keys, key, sort) {
739            Ok(i) => {
740                keys.remove(i);
741                true
742            }
743            Err(_) => false,
744        },
745        BNode::Internal { keys, children } => {
746            // iLow = first child whose cached max is `>= key`; if past the end the
747            // key cannot be present (JS: `i <= iHigh` guard, `btree-set.ts:646`).
748            let i = lower_bound(keys, key, sort);
749            if i >= children.len() {
750                return false;
751            }
752            let removed = {
753                let child = Rc::make_mut(&mut children[i]);
754                node_delete(child, key, sort)
755            };
756            // Refresh the cached max (unless the child emptied — then it is dropped
757            // in the merge scan; JS leaves keys[i]=undefined here, fixed below).
758            if !children[i].is_empty_node() {
759                keys[i] = children[i].max_key().clone();
760            }
761            // The JS `finally` merge scan over children[i] and its left neighbour
762            // (`btree-set.ts:656-671`): drop emptied children; `tryMerge` underfull
763            // ones with their right sibling when the combined size fits.
764            let half = MAX_NODE_SIZE / 2;
765            let lo = i.saturating_sub(1);
766            for j in (lo..=i).rev() {
767                if j < children.len() && children[j].keys().len() <= half {
768                    if children[j].is_empty_node() {
769                        children.remove(j);
770                        keys.remove(j);
771                    } else {
772                        try_merge(keys, children, j);
773                    }
774                }
775            }
776            removed
777        }
778    }
779}
780
781/// `BNodeInternal.tryMerge` (`btree-set.ts:677-695`): merge child `i` into its
782/// right sibling-pair (`i` and `i+1`) when their combined size fits in a node.
783/// `make_mut`s the left child before merging into it; drops the right child.
784fn try_merge(keys: &mut Vec<Row>, children: &mut Vec<Rc<BNode>>, i: usize) -> bool {
785    if i + 1 < children.len()
786        && children[i].keys().len() + children[i + 1].keys().len() <= MAX_NODE_SIZE
787    {
788        let right = children.remove(i + 1);
789        keys.remove(i + 1);
790        {
791            let left = Rc::make_mut(&mut children[i]);
792            merge_into(left, &right);
793        }
794        keys[i] = children[i].max_key().clone();
795        return true;
796    }
797    false
798}
799
800/// `BNode.mergeSibling` (`btree-set.ts:494-496`, `702-721`): append `right`'s
801/// contents into `left`. For internal nodes this also recursively `tryMerge`s the
802/// new seam (the JS `tryMerge(oldLength-1, …)`). Child `Arc`s are cloned in
803/// (refcount bumps), so sharing stays correct with zero `isShared` bookkeeping.
804fn merge_into(left: &mut BNode, right: &BNode) {
805    match (left, right) {
806        (BNode::Leaf { keys: lk }, BNode::Leaf { keys: rk }) => {
807            lk.extend(rk.iter().cloned());
808        }
809        (
810            BNode::Internal {
811                keys: lk,
812                children: lc,
813            },
814            BNode::Internal {
815                keys: rk,
816                children: rc,
817            },
818        ) => {
819            let seam = lc.len() - 1;
820            lk.extend(rk.iter().cloned());
821            lc.extend(rc.iter().cloned());
822            try_merge(lk, lc, seam);
823        }
824        _ => unreachable!("merge_into: mixing a leaf with an internal node"),
825    }
826}
827
828// ---------------------------------------------------------------------------
829// The lending cursor (OQ-1) — owns its Arc snapshot, vends a borrowed row.
830// ---------------------------------------------------------------------------
831
832/// A forward-or-reverse ordered cursor over a `BTree`. **No lifetime parameter**
833/// (the OQ-1 result): it OWNS `Rc<BNode>` clones along the spine + the root
834/// snapshot, so it borrows nothing externally; the only borrow is the one
835/// `next_row` lends out of `self` per call. Holding the `_snapshot` root `Arc`
836/// keeps the *entire pre-write tree* alive and immutable for the cursor's life —
837/// a concurrent `BTree::add`/`delete` `make_mut`s a fresh path (because the
838/// snapshot pins `strong_count > 1`) and never touches these nodes. That is what
839/// makes reentrant fetch-during-push safe (§6, foundations §6.3).
840pub struct BTreeCursor {
841    /// (internal node, child index we descended into) from root to the leaf's
842    /// parent. Used to walk to the next/prev leaf.
843    stack: Vec<(Rc<BNode>, usize)>,
844    /// Current leaf (an `Arc` clone — NOT a `&'t` self-borrow; that is the OQ-1
845    /// fix). `None` once exhausted.
846    leaf: Option<Rc<BNode>>,
847    /// Index within `leaf` of the element to yield.
848    pos: usize,
849    reverse: bool,
850    /// `false` until the first `next_row`; the cursor is constructed positioned at
851    /// the first element, so the first pull yields without stepping.
852    started: bool,
853    /// Keep-alive for the whole snapshot tree (the immutability guarantee). Held
854    /// even after the root is popped off `stack` during iteration.
855    _snapshot: Rc<BNode>,
856}
857
858impl BTreeCursor {
859    fn empty(reverse: bool, snapshot: Rc<BNode>) -> BTreeCursor {
860        BTreeCursor {
861            // Sized once so the scan never reallocates the spine (see SPINE_CAP).
862            stack: Vec::with_capacity(SPINE_CAP),
863            leaf: None,
864            pos: 0,
865            reverse,
866            started: false,
867            _snapshot: snapshot,
868        }
869    }
870
871    /// The element currently under the cursor: the stored `&OwnedRow`, borrowed
872    /// out of the leaf `Rc<BNode>` held in `self`. The returned borrow is tied to
873    /// `&self` (hence to the `next_row` call) and invalidated by the next pull —
874    /// the lending contract. Because this is a `&OwnedRow` (not a bare slice),
875    /// `RowRef::to_owned_row` on it is an `Arc` bump, not a copy (OQ-2).
876    fn current(&self) -> Option<&Row> {
877        let leaf = self.leaf.as_deref()?;
878        let keys = leaf.keys();
879        if self.pos < keys.len() {
880            Some(&keys[self.pos])
881        } else {
882            None
883        }
884    }
885
886    /// Descend leftmost (forward start / next-leaf): push each internal node taking
887    /// child 0; land on the leftmost leaf at pos 0.
888    fn descend_first(&mut self, mut node: Rc<BNode>) {
889        loop {
890            if node.is_leaf() {
891                break;
892            }
893            let child = node.child(0);
894            self.stack.push((node, 0));
895            node = child;
896        }
897        self.leaf = Some(node);
898        self.pos = 0;
899    }
900
901    /// Descend rightmost (reverse start / prev-leaf): take the last child each
902    /// level; land on the rightmost leaf at its last index.
903    fn descend_last(&mut self, mut node: Rc<BNode>) {
904        loop {
905            if node.is_leaf() {
906                break;
907            }
908            let idx = node.num_children() - 1;
909            let child = node.child(idx);
910            self.stack.push((node, idx));
911            node = child;
912        }
913        let len = node.keys().len();
914        self.leaf = Some(node);
915        self.pos = len.saturating_sub(1);
916    }
917
918    /// Descend to the first row `>= bound` (inclusive) / `> bound` (exclusive),
919    /// building the spine for continued forward iteration.
920    fn descend_seek(
921        &mut self,
922        mut node: Rc<BNode>,
923        bound: &RowBound,
924        inclusive: bool,
925        sort: &Sort,
926    ) {
927        loop {
928            if node.is_leaf() {
929                break;
930            }
931            let idx = child_index_bound(node.keys(), bound, sort);
932            let child = node.child(idx);
933            self.stack.push((node, idx));
934            node = child;
935        }
936        let keys = node.keys();
937        let mut pos = leaf_lower_bound(keys, bound, sort);
938        // Exclusive: skip the single equal element (a set has at most one).
939        if !inclusive
940            && pos < keys.len()
941            && cmp_bound_row(sort, bound, &keys[pos]) == Ordering::Equal
942        {
943            pos += 1;
944        }
945        self.pos = pos;
946        self.leaf = Some(node);
947    }
948
949    /// Descend to position the reverse cursor at the largest row `<= bound`
950    /// (inclusive) / `< bound` (exclusive). Mirrors JS `valuesFromReversed`
951    /// (`btree-set.ts:319-349`). The descent picks the same child as the forward
952    /// seek (first child whose max `>= bound`); if that leaf has no qualifying row
953    /// (all of it `> bound`), the answer is the previous leaf's max — which is
954    /// `< bound` because the chosen child is the *first* with max `>= bound`, so the
955    /// previous child's max is `< bound` (`prev_leaf` handles it).
956    fn descend_seek_reverse(
957        &mut self,
958        mut node: Rc<BNode>,
959        bound: &RowBound,
960        inclusive: bool,
961        sort: &Sort,
962    ) {
963        loop {
964            if node.is_leaf() {
965                break;
966            }
967            let idx = child_index_bound(node.keys(), bound, sort);
968            let child = node.child(idx);
969            self.stack.push((node, idx));
970            node = child;
971        }
972        // `j` = first index NOT qualifying as `<= bound`/`< bound`; the largest
973        // qualifying row sits at `j - 1`.
974        let keys = node.keys();
975        let j = if inclusive {
976            leaf_upper_bound(keys, bound, sort) // first > bound
977        } else {
978            leaf_lower_bound(keys, bound, sort) // first >= bound
979        };
980        self.leaf = Some(node);
981        if j == 0 {
982            // Nothing qualifies in this leaf; the previous leaf's max qualifies.
983            self.prev_leaf();
984        } else {
985            self.pos = j - 1;
986        }
987    }
988
989    /// Walk to the next leaf (forward): pop spine entries until one has an
990    /// unvisited right child, descend it leftmost. Exhausts to `leaf = None`.
991    fn next_leaf(&mut self) {
992        loop {
993            match self.stack.pop() {
994                None => {
995                    self.leaf = None;
996                    return;
997                }
998                Some((node, descended)) => {
999                    if descended + 1 < node.num_children() {
1000                        let next = descended + 1;
1001                        let child = node.child(next);
1002                        self.stack.push((node, next));
1003                        self.descend_first(child);
1004                        return;
1005                    }
1006                    // fully consumed; keep popping
1007                }
1008            }
1009        }
1010    }
1011
1012    /// Walk to the previous leaf (reverse): mirror of `next_leaf`.
1013    fn prev_leaf(&mut self) {
1014        loop {
1015            match self.stack.pop() {
1016                None => {
1017                    self.leaf = None;
1018                    return;
1019                }
1020                Some((node, descended)) => {
1021                    if descended > 0 {
1022                        let next = descended - 1;
1023                        let child = node.child(next);
1024                        self.stack.push((node, next));
1025                        self.descend_last(child);
1026                        return;
1027                    }
1028                }
1029            }
1030        }
1031    }
1032
1033    /// Ensure (leaf, pos) points at a valid element or `leaf = None` (forward).
1034    /// Handles a seek landing at end-of-leaf (element is in the next leaf).
1035    fn normalize_forward(&mut self) {
1036        while let Some(l) = &self.leaf {
1037            if self.pos < l.keys().len() {
1038                return;
1039            }
1040            self.next_leaf();
1041        }
1042    }
1043
1044    fn normalize_reverse(&mut self) {
1045        while let Some(l) = &self.leaf {
1046            if !l.keys().is_empty() && self.pos < l.keys().len() {
1047                return;
1048            }
1049            self.prev_leaf();
1050        }
1051    }
1052
1053    fn step(&mut self) {
1054        if self.reverse {
1055            // step back
1056            if self.pos == 0 {
1057                self.prev_leaf();
1058            } else {
1059                self.pos -= 1;
1060            }
1061        } else {
1062            // step forward
1063            self.pos += 1;
1064            let in_leaf = self
1065                .leaf
1066                .as_ref()
1067                .is_some_and(|l| self.pos < l.keys().len());
1068            if !in_leaf {
1069                self.next_leaf();
1070            }
1071        }
1072    }
1073}
1074
1075impl RowStream for BTreeCursor {
1076    // The held-`Rc` backend vends a borrowed `&OwnedRow` straight out of the leaf
1077    // `Rc<BNode>` it owns — the *opposite* provenance to SQLite's step buffer,
1078    // yet the same trait. Vending `&OwnedRow` (not a bare slice) is what makes
1079    // `RowRef::to_owned_row` an O(1) `Arc` bump here (OQ-2), the one place this
1080    // backend differs in cost from the SQLite leaf (which must copy).
1081    type Row<'a> = &'a Row;
1082
1083    fn next_row(&mut self) -> Option<Self::Row<'_>> {
1084        if self.started {
1085            self.step();
1086        } else {
1087            self.started = true;
1088        }
1089        self.current()
1090    }
1091}
1092
1093// ---------------------------------------------------------------------------
1094// Structural diff — the optimistic-writes "rewind" primitive.
1095//
1096// A faithful port of the NIM `diffAgainst` dual-cursor reverse walk
1097// (`quasar-pulse/lq-query/lq-nivm/src/btree.nim:540`), the algorithm reference
1098// named in `OPTIMISTIC-WRITES-DESIGN.md` §2.1. It computes the row-level changes
1099// that turn `self` into `other` by walking both COW trees in lockstep, in
1100// DESCENDING sort order, using `Rc`/`Arc` pointer identity to skip the shared
1101// structure a `fork()` leaves behind — so the cost tracks *divergence*, not data
1102// size.
1103//
1104// Two invariants, both from design §2.2:
1105//   1. **Value-correct without identity.** The pointer skips are a pure
1106//      optimization: disabling them (or having them never fire) yields the
1107//      identical emitted change set, just computed by a full value scan. The leaf
1108//      path therefore value-compares rows (`full_row_equal`) before emitting an
1109//      edit, rather than trusting "the pointers differ" to mean "the content
1110//      differs". This makes correctness environment-independent (§2.2.1).
1111//   2. **Observable minimality.** [`DiffStats`] counts skips / visits / compares
1112//      so a performance oracle can assert that `fork + N point-mutations` does
1113//      O(N·height) work — a silent degradation to O(tree) is invisible to a
1114//      correctness test alone (§2.2.2).
1115// ---------------------------------------------------------------------------
1116
1117/// Work counters for the structural diff — the performance oracle (design
1118/// §2.2.2). A `fork + N point-mutations` diff must show O(N·height) visits and
1119/// O(N) value compares; a regression (lost node sharing, or `ptr_eq` not firing
1120/// on some target) surfaces as visits proportional to *tree size* instead.
1121#[derive(Default, Debug, Clone, PartialEq, Eq)]
1122pub struct DiffStats {
1123    /// Whole-subtree skips via `Rc::ptr_eq` on a shared internal node (the
1124    /// O(divergence) win).
1125    pub internal_node_skips: u64,
1126    /// Leaf rows skipped via `Arc::ptr_eq` (identical stored row → no value scan).
1127    pub leaf_row_skips: u64,
1128    /// Internal nodes actually descended into (NOT skipped).
1129    pub internal_nodes_visited: u64,
1130    /// Leaves actually descended into.
1131    pub leaves_visited: u64,
1132    /// Full-width row value comparisons (`full_row_equal`) — the O(columns) op.
1133    pub row_value_compares: u64,
1134    /// Cursor comparisons (`compare_cursors`).
1135    pub cursor_compares: u64,
1136}
1137
1138/// Sink for [`BTree::diff_visit`]: one callback per difference, emitted in
1139/// descending sort order. "this" is `self`; "other" is the argument tree.
1140pub trait DiffSink {
1141    /// A row present in `self` but not in `other`.
1142    fn only_this(&mut self, row: &OwnedRow);
1143    /// A row present in `other` but not in `self`.
1144    fn only_other(&mut self, row: &OwnedRow);
1145    /// Equal sort-key but differing content: `this` (in `self`) → `other`.
1146    fn edit(&mut self, this: &OwnedRow, other: &OwnedRow);
1147}
1148
1149impl BTree {
1150    /// Root-to-leaf edge count (a single-leaf root → 0). Walks the leftmost path;
1151    /// O(height). Used to set up a [`DiffCursor`]'s height normalization.
1152    fn height(&self) -> usize {
1153        let mut h = 0;
1154        let mut node = Rc::clone(&self.root);
1155        while let BNode::Internal { children, .. } = &*node {
1156            let c = Rc::clone(&children[0]);
1157            node = c;
1158            h += 1;
1159        }
1160        h
1161    }
1162
1163    /// Visit the row-level differences that turn `self` into `other`, in
1164    /// descending sort order. Both trees MUST be ordered by `sort` (the diff is
1165    /// meaningless otherwise).
1166    ///
1167    /// `use_identity` enables the `ptr_eq` short-circuits; production callers pass
1168    /// `true` (see [`BTree::structural_diff`]). Passing `false` forces the full
1169    /// value walk — used by the differential test to prove the skips are a pure
1170    /// optimization (design §2.2.1). `stats` accumulates the work counters
1171    /// (§2.2.2); pass `&mut DiffStats::default()` if you don't care.
1172    pub fn diff_visit(
1173        &self,
1174        other: &BTree,
1175        sort: &Sort,
1176        use_identity: bool,
1177        stats: &mut DiffStats,
1178        sink: &mut dyn DiffSink,
1179    ) {
1180        let this_empty = self.size == 0;
1181        let other_empty = other.size == 0;
1182        if this_empty || other_empty {
1183            if this_empty && other_empty {
1184                return;
1185            }
1186            if this_empty {
1187                let mut c = DiffCursor::new(other);
1188                step_to_end(&mut c, stats, &mut |r| sink.only_other(r));
1189            } else {
1190                let mut c = DiffCursor::new(self);
1191                step_to_end(&mut c, stats, &mut |r| sink.only_this(r));
1192            }
1193            return;
1194        }
1195
1196        let mut tc = DiffCursor::new(self);
1197        let mut oc = DiffCursor::new(other);
1198        let mut tok = true;
1199        let mut ook = true;
1200        // The previous step's cursor order; gates emission so converging cursors
1201        // are not double-counted (NIM `prevCursorOrder`).
1202        let mut prev = compare_cursors(&tc, &oc, sort, stats);
1203
1204        while tok && ook {
1205            let order = compare_cursors(&tc, &oc, sort, stats);
1206            let tl = tc.leaf.is_some();
1207            let ol = oc.leaf.is_some();
1208
1209            // Equal sort-key with BOTH cursors on leaf rows: pair them SYMMETRICALLY —
1210            // emit an edit iff the full rows differ, then advance BOTH cursors (the
1211            // linear-merge oracle's `i+=1; j+=1`). The `prev`-gated two-step pairing below
1212            // (advance `oc`, then advance `tc` next iteration under the gate) assumes ONE
1213            // row per sort key; on a run of k≥2 equal-key rows it mis-pairs the run and the
1214            // gate then bleeds into a later `Less` compare, emitting a spurious `Remove`
1215            // (adversarial-review HIGH #6 — value-identical multiset trees diverged). Handle
1216            // the leaf-pairing here so a duplicate-key run is consumed pairwise.
1217            if order == Ordering::Equal && tl && ol {
1218                let a = &tc.current_key;
1219                let b = &oc.current_key;
1220                if use_identity && Row::ptr_eq(a, b) {
1221                    stats.leaf_row_skips += 1;
1222                } else {
1223                    stats.row_value_compares += 1;
1224                    if !full_row_equal(a, b) {
1225                        sink.edit(a, b);
1226                    }
1227                }
1228                // Both rows are fully consumed — unlike the two-step pairing below, there is
1229                // no pending follow-up to suppress, so `prev` must be NON-Equal or the next
1230                // (independent) comparison's Less/Greater emission would be wrongly gated out.
1231                prev = Ordering::Less;
1232                tok = tc.step(false, stats);
1233                ook = oc.step(false, stats);
1234                continue;
1235            }
1236
1237            if tl || ol {
1238                if prev != Ordering::Equal {
1239                    match order {
1240                        // Equal sort-key but NOT both on leaves (one cursor still on an
1241                        // internal node): nothing to emit here — the dual-cursor walk
1242                        // descends below (the leaf pairing is handled by the pre-check above).
1243                        Ordering::Equal => {}
1244                        // `other` is behind in the descending walk → it holds a row
1245                        // `self` does not.
1246                        Ordering::Greater => {
1247                            if ol {
1248                                sink.only_other(&oc.current_key);
1249                            }
1250                        }
1251                        // `self` is behind → it holds a row `other` does not.
1252                        Ordering::Less => {
1253                            if tl {
1254                                sink.only_this(&tc.current_key);
1255                            }
1256                        }
1257                    }
1258                }
1259            } else if order == Ordering::Equal
1260                && use_identity
1261                && Rc::ptr_eq(tc.current_node(), oc.current_node())
1262            {
1263                // Both cursors sit on the SAME internal node by reference: the whole
1264                // subtree is shared and identical → skip both past it.
1265                stats.internal_node_skips += 1;
1266                prev = Ordering::Equal;
1267                tok = tc.step(true, stats);
1268                ook = oc.step(true, stats);
1269                continue;
1270            }
1271
1272            prev = order;
1273            if order == Ordering::Less {
1274                tok = tc.step(false, stats);
1275            } else {
1276                ook = oc.step(false, stats);
1277            }
1278        }
1279
1280        // Exactly one cursor (at most) still has rows: drain it as only-this /
1281        // only-other.
1282        if tok {
1283            finish_walk(&mut tc, &oc, sort, stats, &mut |r| sink.only_this(r));
1284        }
1285        if ook {
1286            finish_walk(&mut oc, &tc, sort, stats, &mut |r| sink.only_other(r));
1287        }
1288    }
1289
1290    /// The optimistic-writes "rewind" primitive: the [`SourceChange`]s that turn
1291    /// `self` into `other` (apply them to a source holding `self` and it becomes
1292    /// `other`). `only_this → Remove`, `only_other → Add`, `edit → Edit`. Both
1293    /// trees must be ordered by `sort`. Identity short-circuits are on.
1294    pub fn structural_diff(&self, other: &BTree, sort: &Sort) -> Vec<SourceChange> {
1295        struct Collect(Vec<SourceChange>);
1296        impl DiffSink for Collect {
1297            fn only_this(&mut self, row: &OwnedRow) {
1298                self.0.push(SourceChange::Remove(row.clone()));
1299            }
1300            fn only_other(&mut self, row: &OwnedRow) {
1301                self.0.push(SourceChange::Add(row.clone()));
1302            }
1303            fn edit(&mut self, this: &OwnedRow, other: &OwnedRow) {
1304                self.0.push(SourceChange::Edit {
1305                    old: this.clone(),
1306                    row: other.clone(),
1307                });
1308            }
1309        }
1310        let mut sink = Collect(Vec::new());
1311        let mut stats = DiffStats::default();
1312        self.diff_visit(other, sort, true, &mut stats, &mut sink);
1313        sink.0
1314    }
1315}
1316
1317/// Apply one [`SourceChange`](crate::change::SourceChange) to a COW tree: `Add` → [`BTree::add`], `Remove` →
1318/// [`BTree::delete`], `Edit` → delete `old` + add `row`. The inverse direction of
1319/// [`BTree::structural_diff`] (apply what a diff would produce and the tree becomes
1320/// the other one).
1321///
1322/// The write helper for the off-graph transient tree the optimistic rewind builds
1323/// ([`crate::optimistic`], `S' = fork(sync) + D`). Behavior is delete-old-then-add-new
1324/// for `Edit`, tolerating no existence assertions — this is a private transient tree,
1325/// not the live push path.
1326#[cfg_attr(not(feature = "wasm"), allow(dead_code))]
1327pub(crate) fn apply_source_change_to_tree(tree: &mut BTree, c: &SourceChange, sort: &Sort) {
1328    match c {
1329        SourceChange::Add(r) => {
1330            tree.add(r.clone(), sort);
1331        }
1332        SourceChange::Remove(r) => {
1333            tree.delete(r, sort);
1334        }
1335        SourceChange::Edit { old, row } => {
1336            tree.delete(old, sort);
1337            tree.add(row.clone(), sort);
1338        }
1339    }
1340}
1341
1342/// Full-width row equality (every column, `null == null`) — the value-correct
1343/// basis for edit detection. Two rows equal under `sort` (same walk position) but
1344/// differing here are an EDIT; the `Arc::ptr_eq` leaf skip is merely a fast path
1345/// over this check (design §2.2.1).
1346fn full_row_equal(a: &Row, b: &Row) -> bool {
1347    a.len() == b.len()
1348        && a.cells()
1349            .zip(b.cells())
1350            .all(|(x, y)| compare_values(x, y) == Ordering::Equal)
1351}
1352
1353/// A reverse-order (descending) walk cursor for the structural diff. Unlike
1354/// [`BTreeCursor`] it can pause ON an internal node — which is what lets the diff
1355/// skip a whole shared subtree — so it gets its own type. Owns `Rc`/`Arc` clones
1356/// along the spine, borrowing nothing external. Port of NIM `DiffCursor`.
1357struct DiffCursor {
1358    /// Root-to-leaf edge count of the tree (for height-normalized comparison).
1359    height: usize,
1360    /// `spine[level]` = the sibling array at that level; `spine[0] == [root]`.
1361    spine: Vec<Vec<Rc<BNode>>>,
1362    /// `indices[level]` = selected entry within `spine[level]`. When `leaf` is
1363    /// `Some`, a trailing extra entry indexes the current value within the leaf.
1364    indices: Vec<usize>,
1365    /// The leaf the cursor is inside, if any. `None` while sitting on an internal
1366    /// node (the state that enables the subtree skip).
1367    leaf: Option<Rc<BNode>>,
1368    /// `Arc` clone of the row/separator under the cursor — a cheap bump that
1369    /// preserves identity, so `Arc::ptr_eq` leaf skips work.
1370    current_key: Row,
1371}
1372
1373impl DiffCursor {
1374    /// Position at the rightmost (maximum) element. Precondition: `tree` non-empty.
1375    fn new(tree: &BTree) -> DiffCursor {
1376        DiffCursor {
1377            height: tree.height(),
1378            spine: vec![vec![Rc::clone(&tree.root)]],
1379            indices: vec![0],
1380            leaf: None,
1381            current_key: tree.root.max_key().clone(),
1382        }
1383    }
1384
1385    /// The internal node the cursor currently sits on. Valid only when `leaf` is
1386    /// `None` (the both-internal skip check is the only caller).
1387    fn current_node(&self) -> &Rc<BNode> {
1388        let last = self.spine.len() - 1;
1389        &self.spine[last][self.indices[last]]
1390    }
1391
1392    /// Advance one step in the descending walk. `step_to_node = true` forces a jump
1393    /// past the entire current subtree (the internal-node skip). Returns `false`
1394    /// when the cursor would walk off the far-left end (no state change then).
1395    fn step(&mut self, step_to_node: bool, stats: &mut DiffStats) -> bool {
1396        if step_to_node || self.leaf.is_some() {
1397            let levels = self.indices.len();
1398            if step_to_node || self.indices[levels - 1] == 0 {
1399                // Step to the previous NODE: walk up to the deepest spine level that
1400                // still has an unvisited left sibling, move to it.
1401                let node_level = self.spine.len() - 1;
1402                let mut found = None;
1403                for k in (0..=node_level).rev() {
1404                    if self.indices[k] > 0 {
1405                        found = Some(k);
1406                        break;
1407                    }
1408                }
1409                match found {
1410                    None => false, // far-left end reached
1411                    Some(k) => {
1412                        // Drop the leaf value-index AND every internal level we
1413                        // ascended past, restoring `indices.len() == spine.len()`.
1414                        //
1415                        // (NIM pops `levelIndices` exactly ONCE here — correct only
1416                        // for a single-level walk-back. Multi-level ascents in deep
1417                        // trees need both `spine` and `indices` truncated together;
1418                        // truncating both to `k + 1` is the faithful generalization,
1419                        // confirmed by the differential oracle over deep trees.)
1420                        self.leaf = None;
1421                        self.spine.truncate(k + 1);
1422                        self.indices.truncate(k + 1);
1423                        self.indices[k] -= 1;
1424                        self.current_key = self.spine[k][self.indices[k]].max_key().clone();
1425                        true
1426                    }
1427                }
1428            } else {
1429                // Move to the previous value within the current leaf.
1430                self.indices[levels - 1] -= 1;
1431                let vi = self.indices[levels - 1];
1432                self.current_key = self.leaf.as_ref().unwrap().keys()[vi].clone();
1433                true
1434            }
1435        } else {
1436            // Descend into the currently-selected node, rightmost child / value
1437            // first.
1438            let level = self.spine.len() - 1;
1439            let node = Rc::clone(&self.spine[level][self.indices[level]]);
1440            if node.is_leaf() {
1441                stats.leaves_visited += 1;
1442                let vi = node.keys().len() - 1;
1443                self.current_key = node.keys()[vi].clone();
1444                self.indices.push(vi);
1445                self.leaf = Some(node);
1446            } else if let BNode::Internal { children, .. } = &*node {
1447                stats.internal_nodes_visited += 1;
1448                let ci = children.len() - 1;
1449                self.current_key = children[ci].max_key().clone();
1450                self.spine.push(children.clone());
1451                self.indices.push(ci);
1452            }
1453            true
1454        }
1455    }
1456}
1457
1458/// Compare two cursors in the descending walk. Equal `current_key` ties break on
1459/// height-normalized depth: a cursor on a shallower internal node with the same
1460/// `maxKey` is "behind", so trees of differing heights land on shared nodes at
1461/// the same time — which is what lets the subtree skip fire (design §2.1).
1462fn compare_cursors(a: &DiffCursor, b: &DiffCursor, sort: &Sort, stats: &mut DiffStats) -> Ordering {
1463    stats.cursor_compares += 1;
1464    // Reversed key order: cursors advance in DESCENDING sort order.
1465    let key_cmp = compare_rows(sort, &b.current_key, &a.current_key);
1466    if key_cmp != Ordering::Equal {
1467        return key_cmp;
1468    }
1469    let hmin = a.height.min(b.height);
1470    let da = a.indices.len() as isize - (a.height - hmin) as isize;
1471    let db = b.indices.len() as isize - (b.height - hmin) as isize;
1472    da.cmp(&db)
1473}
1474
1475/// Drive `c` to its far-left end, invoking `f` on every leaf row it passes
1476/// (NIM `stepToEnd`).
1477fn step_to_end(c: &mut DiffCursor, stats: &mut DiffStats, f: &mut dyn FnMut(&OwnedRow)) {
1478    loop {
1479        if c.leaf.is_some() {
1480            f(&c.current_key);
1481        }
1482        if !c.step(false, stats) {
1483            break;
1484        }
1485    }
1486}
1487
1488/// Drain the still-running cursor `c` once the other (`finished`) has run off its
1489/// end (NIM `finishCursorWalk`): align past any shared boundary point, then emit
1490/// the remainder.
1491fn finish_walk(
1492    c: &mut DiffCursor,
1493    finished: &DiffCursor,
1494    sort: &Sort,
1495    stats: &mut DiffStats,
1496    f: &mut dyn FnMut(&OwnedRow),
1497) {
1498    match compare_cursors(c, finished, sort, stats) {
1499        Ordering::Equal => {
1500            if !c.step(false, stats) {
1501                return;
1502            }
1503        }
1504        // NIM raises here ("cursor walk terminated early") — an invariant
1505        // violation. Be defensive: assert in debug, drain in release.
1506        Ordering::Less => debug_assert!(false, "diff: cursor walk terminated early"),
1507        Ordering::Greater => {}
1508    }
1509    step_to_end(c, stats, f);
1510}
1511
1512// ---------------------------------------------------------------------------
1513// Test-only COW structural-sharing introspection (rebase invariant Q1). `#[cfg(test)]`
1514// so it compiles ONLY for the crate's own unit tests (`btree.rs` / `optimistic.rs`) — never in a
1515// release, wasm, or separate integration-test build; zero production API surface or overhead.
1516// ---------------------------------------------------------------------------
1517
1518#[cfg(test)]
1519impl BTree {
1520    /// Do `self` and `other` share the SAME COW root allocation (`Rc::ptr_eq`)? True iff one is a
1521    /// fork of the other with no intervening structural mutation — the maximal-sharing signal.
1522    pub(crate) fn shares_root(&self, other: &BTree) -> bool {
1523        Rc::ptr_eq(&self.root, &other.root)
1524    }
1525
1526    /// The number of nodes in `self`'s tree NOT `Rc::ptr_eq`-shared with `other` — the rigorous
1527    /// structural-divergence measure. A COW fork + K path-mutations diverges by ≈ K·height nodes
1528    /// (the copied root→leaf paths); a from-scratch rebuild diverges by ALL of `self`'s nodes.
1529    pub(crate) fn unshared_node_count(&self, other: &BTree) -> usize {
1530        let mut shared = std::collections::HashSet::new();
1531        collect_node_ptrs(&other.root, &mut shared);
1532        count_unshared_nodes(&self.root, &shared)
1533    }
1534}
1535
1536#[cfg(test)]
1537fn collect_node_ptrs(node: &Rc<BNode>, out: &mut std::collections::HashSet<usize>) {
1538    out.insert(Rc::as_ptr(node) as usize);
1539    if let BNode::Internal { children, .. } = &**node {
1540        for c in children {
1541            collect_node_ptrs(c, out);
1542        }
1543    }
1544}
1545
1546#[cfg(test)]
1547fn count_unshared_nodes(node: &Rc<BNode>, shared: &std::collections::HashSet<usize>) -> usize {
1548    // A shared node's ENTIRE subtree is shared (COW: a node not copied ⇒ its children weren't) —
1549    // prune there.
1550    if shared.contains(&(Rc::as_ptr(node) as usize)) {
1551        return 0;
1552    }
1553    let mut n = 1;
1554    if let BNode::Internal { children, .. } = &**node {
1555        for c in children {
1556            n += count_unshared_nodes(c, shared);
1557        }
1558    }
1559    n
1560}
1561
1562#[cfg(test)]
1563mod tests {
1564    use super::*;
1565    use crate::change::SourceChange;
1566    use crate::value::owned_row as row;
1567    use crate::value::OwnedValue::{Int, Null};
1568    use crate::value::Value;
1569
1570    fn s1() -> Sort {
1571        vec![(0, true)] // single asc key on col 0
1572    }
1573
1574    /// Row whose col-0 key is `k` (the only column).
1575    fn rk(k: i64) -> Row {
1576        row(vec![Int(k)])
1577    }
1578
1579    /// Extract the col-0 int from a borrowed row (vended as `&OwnedRow`).
1580    fn val(r: &Row) -> i64 {
1581        match r.col(0) {
1582            Value::Int(i) => i,
1583            other => panic!("expected Int, got {other:?}"),
1584        }
1585    }
1586
1587    fn val_row(r: &Row) -> i64 {
1588        val(r)
1589    }
1590
1591    // -- rebase invariant Q2: `structural_diff` is bounded by divergence, not tree size --
1592
1593    /// A `(key, value)` row: key = col 0 (the sort key), value = col 1 (mutated for an Edit).
1594    fn kv(k: i64, v: i64) -> Row {
1595        row(vec![Int(k), Int(v)])
1596    }
1597
1598    /// Total nodes the diff DESCENDED into (a `Rc::ptr_eq`-skipped subtree contributes 0 — that is
1599    /// the whole point). Reads the pre-existing [`DiffStats`] performance-oracle counters (design
1600    /// §2.2.2); `use_identity = false` is the built-in "skip off" switch (the full value walk).
1601    fn diff_visits(a: &BTree, b: &BTree, sort: &Sort, use_identity: bool) -> u64 {
1602        struct NullSink;
1603        impl DiffSink for NullSink {
1604            fn only_this(&mut self, _: &Row) {}
1605            fn only_other(&mut self, _: &Row) {}
1606            fn edit(&mut self, _: &Row, _: &Row) {}
1607        }
1608        let mut stats = DiffStats::default();
1609        let mut sink = NullSink;
1610        a.diff_visit(b, sort, use_identity, &mut stats, &mut sink);
1611        stats.internal_nodes_visited + stats.leaves_visited
1612    }
1613
1614    #[test]
1615    fn q2_structural_diff_is_bounded_by_divergence() {
1616        let sort = s1();
1617        let n = 50_000i64;
1618        let tree = BTree::from_sorted((0..n).map(|k| kv(k, k)), &sort);
1619        assert_eq!(tree.len(), n as usize);
1620
1621        // fork + ONE point-edit (same key, new value): path-copies exactly one root→leaf path.
1622        let mut s_prime = tree.fork();
1623        let mid = n / 2;
1624        assert!(s_prime.delete(&kv(mid, mid), &sort));
1625        assert!(s_prime.add(kv(mid, mid + 777), &sort));
1626
1627        // (a) value-correct: exactly the one edit.
1628        let changes = tree.structural_diff(&s_prime, &sort);
1629        assert_eq!(
1630            changes.len(),
1631            1,
1632            "one changed key ⇒ one change: {changes:?}"
1633        );
1634        match &changes[0] {
1635            SourceChange::Edit { old, row } => {
1636                assert_eq!(val(old), mid, "the edited key");
1637                assert_eq!(val_row(row), mid, "key unchanged (value col differs)");
1638            }
1639            other => panic!("expected an Edit, got {other:?}"),
1640        }
1641
1642        // (b) bounded: the identity walk descends O(log N) nodes, unambiguously << N.
1643        let visited = diff_visits(&tree, &s_prime, &sort, true);
1644        assert!(
1645            visited < 100,
1646            "fork + 1 change must visit O(log N) nodes, got {visited} at N={n}"
1647        );
1648
1649        // The `Rc::ptr_eq` subtree-skip is LOAD-BEARING: the SAME diff with identity OFF (the full
1650        // value walk) descends EVERY node (≈ 2·N/leaf_size — linear in N, the whole tree), while the
1651        // identity-on walk stays O(log N). This contrast is the guard the `visited < 100` assertion
1652        // would catch if the skip ever stopped firing.
1653        let visited_full = diff_visits(&tree, &s_prime, &sort, false);
1654        assert!(
1655            visited_full > (n as u64) / 64,
1656            "identity OFF descends the whole tree (≈ N/64 nodes per cursor), linear in N: \
1657             {visited_full} at N={n}"
1658        );
1659        assert!(
1660            visited.saturating_mul(15) < visited_full,
1661            "the skip must cut visits by more than an order of magnitude: {visited} (on) vs \
1662             {visited_full} (off)"
1663        );
1664    }
1665
1666    #[test]
1667    fn q2_diff_against_unmutated_fork_is_o1() {
1668        // Floor case: an unmutated fork shares the ROOT, so the ptr_eq root-skip fires immediately
1669        // — the diff is empty and visits O(1) (independent of N).
1670        let sort = s1();
1671        let tree = BTree::from_sorted((0..50_000i64).map(|k| kv(k, k)), &sort);
1672        let twin = tree.fork();
1673        assert!(tree.shares_root(&twin), "an unmutated fork shares the root");
1674        assert!(tree.structural_diff(&twin, &sort).is_empty());
1675        let visited = diff_visits(&tree, &twin, &sort, true);
1676        assert!(
1677            visited <= 2,
1678            "unmutated fork ⇒ root ptr_eq skip ⇒ O(1) visits, got {visited}"
1679        );
1680    }
1681
1682    /// Drain a forward cursor to a Vec of col-0 ints (using the lending API).
1683    fn drain(cur: &mut BTreeCursor) -> Vec<i64> {
1684        let mut out = Vec::new();
1685        while let Some(r) = cur.next_row() {
1686            out.push(val(r));
1687        }
1688        out
1689    }
1690
1691    // -- tiny deterministic PRNG (zero-dep) --
1692    struct Lcg(u64);
1693    impl Lcg {
1694        fn new(seed: u64) -> Lcg {
1695            Lcg(seed)
1696        }
1697        fn next(&mut self) -> u64 {
1698            // numerical recipes LCG
1699            self.0 = self
1700                .0
1701                .wrapping_mul(6364136223846793005)
1702                .wrapping_add(1442695040888963407);
1703            self.0 >> 16
1704        }
1705    }
1706
1707    #[test]
1708    fn empty_tree_scans_empty() {
1709        let t = BTree::new();
1710        assert_eq!(t.len(), 0);
1711        let mut cur = t.values_from(None, true, &s1());
1712        assert!(cur.next_row().is_none());
1713        let mut rev = t.values_from_reversed(None, true, &s1());
1714        assert!(rev.next_row().is_none());
1715    }
1716
1717    #[test]
1718    fn add_get_has_basic() {
1719        let sort = s1();
1720        let mut t = BTree::new();
1721        for k in [5, 1, 3, 2, 4] {
1722            assert!(t.add(rk(k), &sort));
1723        }
1724        assert_eq!(t.len(), 5);
1725        assert!(t.has(&rk(3), &sort));
1726        assert!(!t.has(&rk(9), &sort));
1727        assert_eq!(t.get(&rk(4), &sort).map(val_row), Some(4));
1728        let mut cur = t.values_from(None, true, &sort);
1729        assert_eq!(drain(&mut cur), vec![1, 2, 3, 4, 5]);
1730    }
1731
1732    #[test]
1733    fn add_existing_overwrites_not_grows() {
1734        // key = col 0; col 1 is payload. Equal col-0 ⇒ overwrite the slot.
1735        let sort = vec![(0, true)];
1736        let mut t = BTree::new();
1737        assert!(t.add(row(vec![Int(1), Int(100)]), &sort));
1738        assert!(!t.add(row(vec![Int(1), Int(200)]), &sort)); // existed
1739        assert_eq!(t.len(), 1);
1740        let got = t.get(&row(vec![Int(1), Null]), &sort).unwrap();
1741        assert_eq!(
1742            match got.col(1) {
1743                Value::Int(i) => i,
1744                _ => panic!(),
1745            },
1746            200,
1747            "payload should be overwritten"
1748        );
1749    }
1750
1751    #[test]
1752    fn overwrite_of_separator_max_refreshes_cached_payload() {
1753        // Regression for the JS-divergence the adversarial workflow found: when
1754        // add() overwrites a row that is a leaf's max (and thus a parent
1755        // separator), the new payload must propagate into the cached separator
1756        // (JS refreshes keys[i] unconditionally). Sort by col 0 only; col 1 is
1757        // payload, so an overwrite changes a non-sort column.
1758        let sort = vec![(0, true)];
1759        let mut t = BTree::new();
1760        for k in 0..=(MAX_NODE_SIZE as i64) {
1761            t.add(row(vec![Int(k), Int(1)]), &sort); // payload 1; forces a split
1762        }
1763        assert!(!t.root.is_leaf(), "need an internal root with separators");
1764        // Overwrite every key with payload 2 (covers whichever keys are separators).
1765        for k in 0..=(MAX_NODE_SIZE as i64) {
1766            assert!(!t.add(row(vec![Int(k), Int(2)]), &sort), "should overwrite");
1767        }
1768        // The strengthened invariant (Arc-identical cached max) must hold: pre-fix,
1769        // a separator kept the old payload-1 Arc while its leaf held payload-2.
1770        t.check_invariants(&sort)
1771            .expect("cached separators must be refreshed");
1772        // And every separator in the root must carry the new payload.
1773        if let BNode::Internal { keys, .. } = &*t.root {
1774            for sep in keys {
1775                assert_eq!(
1776                    match sep.col(1) {
1777                        Value::Int(p) => p,
1778                        _ => panic!(),
1779                    },
1780                    2,
1781                    "separator payload went stale"
1782                );
1783            }
1784        }
1785    }
1786
1787    #[test]
1788    fn split_grows_internal_root() {
1789        let sort = s1();
1790        let mut t = BTree::new();
1791        assert!(t.root.is_leaf());
1792        for k in 0..=(MAX_NODE_SIZE as i64) {
1793            t.add(rk(k), &sort); // MAX+1 inserts ⇒ at least one split
1794        }
1795        assert!(!t.root.is_leaf(), "root should be internal after overflow");
1796        assert_eq!(t.len(), MAX_NODE_SIZE + 1);
1797        let mut cur = t.values_from(None, true, &sort);
1798        assert_eq!(
1799            drain(&mut cur),
1800            (0..=(MAX_NODE_SIZE as i64)).collect::<Vec<_>>()
1801        );
1802    }
1803
1804    #[test]
1805    fn fork_is_o1_and_independent() {
1806        let sort = s1();
1807        let mut a = BTree::new();
1808        for k in 0..2000 {
1809            a.add(rk(k), &sort);
1810        }
1811        let b = a.fork();
1812        // Shared root immediately after fork.
1813        assert!(Rc::ptr_eq(&a.root, &b.root), "fork shares the root Arc");
1814        // Mutating `a` must not disturb `b` (COW).
1815        a.add(rk(10_000), &sort);
1816        a.delete(&rk(0), &sort);
1817        assert!(!Rc::ptr_eq(&a.root, &b.root), "write forked the root");
1818        assert_eq!(b.len(), 2000);
1819        assert!(b.has(&rk(0), &sort) && !b.has(&rk(10_000), &sort));
1820        assert!(!a.has(&rk(0), &sort) && a.has(&rk(10_000), &sort));
1821    }
1822
1823    #[test]
1824    fn make_mut_copies_only_the_path() {
1825        let sort = s1();
1826        let mut a = BTree::new();
1827        for k in 0..2000 {
1828            a.add(rk(k), &sort); // multi-level tree
1829        }
1830        let mut c = a.fork();
1831        c.add(rk(10_000), &sort); // larger than all ⇒ rightmost path only
1832        assert!(!Rc::ptr_eq(&a.root, &c.root), "root copied on write");
1833        match (&*a.root, &*c.root) {
1834            (BNode::Internal { children: ac, .. }, BNode::Internal { children: cc, .. }) => {
1835                assert!(
1836                    Rc::ptr_eq(&ac[0], &cc[0]),
1837                    "off-path (leftmost) subtree must stay SHARED"
1838                );
1839                assert!(
1840                    !Rc::ptr_eq(ac.last().unwrap(), cc.last().unwrap()),
1841                    "on-path (rightmost) subtree must be copied"
1842                );
1843            }
1844            _ => panic!("expected internal roots for a 2000-row tree"),
1845        }
1846    }
1847
1848    #[test]
1849    fn snapshot_stable_under_mid_iteration_write() {
1850        // The reentrancy invariant (§6): a cursor opened before a write keeps
1851        // yielding the pre-write snapshot, because it pins the old root Arc and
1852        // the writer `make_mut`s a fresh path. This is the COW analogue of the
1853        // spike's `self_referential_add_needs_source_overlay`.
1854        let sort = s1();
1855        let mut t = BTree::new();
1856        for k in 0..100 {
1857            t.add(rk(k), &sort);
1858        }
1859        let mut cur = t.values_from(None, true, &sort);
1860        let mut seen = Vec::new();
1861        for _ in 0..10 {
1862            seen.push(val(cur.next_row().unwrap())); // read 0..10
1863        }
1864        // Mutate the SAME tree mid-iteration.
1865        for k in 100..150 {
1866            t.add(rk(k), &sort);
1867        }
1868        assert!(t.delete(&rk(0), &sort));
1869        assert!(t.delete(&rk(50), &sort));
1870        // The cursor must still see the ORIGINAL 0..100 (incl. 50, excl. 100+).
1871        while let Some(r) = cur.next_row() {
1872            seen.push(val(r));
1873        }
1874        assert_eq!(
1875            seen,
1876            (0..100).collect::<Vec<_>>(),
1877            "cursor saw the snapshot"
1878        );
1879        // The tree itself reflects the writes.
1880        let after: Vec<i64> = {
1881            let mut c = t.values_from(None, true, &sort);
1882            drain(&mut c)
1883        };
1884        assert!(after.contains(&149) && !after.contains(&0) && !after.contains(&50));
1885        assert_eq!(t.len(), 100 + 50 - 2);
1886    }
1887
1888    #[test]
1889    fn to_owned_row_bumps_not_copies() {
1890        // OQ-2, folded into the shared `RowRef`: the memory cursor vends a
1891        // `&OwnedRow`, so `to_owned_row()` is a refcount bump (SAME allocation as
1892        // the stored row); rebuilding from a bare slice deep-copies (different
1893        // allocation). The SAME trait method that COPIES on the SQLite leaf is a
1894        // BUMP here — the one-trait-two-costs result.
1895        let sort = s1();
1896        let mut t = BTree::new();
1897        let r = rk(7);
1898        t.add(r.clone(), &sort);
1899        let mut cur = t.values_from(None, true, &sort);
1900        let row_ref: &Row = cur.next_row().unwrap();
1901        // to_owned_row on the vended `&OwnedRow` bumps the stored Arc.
1902        let bumped = row_ref.to_owned_row();
1903        assert!(
1904            Row::ptr_eq(&bumped, &r),
1905            "to_owned_row on a `&OwnedRow` must bump the stored Arc, not allocate"
1906        );
1907        // For contrast: the `&[OwnedValue]` slice impl builds a fresh flat row
1908        // (loses buffer identity) — exactly why the cursor vends `&OwnedRow`.
1909        let cells = r.to_value_vec();
1910        let slice: &[OwnedValue] = &cells;
1911        let rebuilt: Row = slice.to_owned_row();
1912        assert!(
1913            !Row::ptr_eq(&rebuilt, &r),
1914            "slice.to_owned_row() rebuilds (loses buffer identity)"
1915        );
1916    }
1917
1918    #[test]
1919    fn values_from_seeks() {
1920        let sort = s1();
1921        let mut t = BTree::new();
1922        for k in (0..200).map(|i| i * 2) {
1923            t.add(rk(k), &sort); // evens 0..=398
1924        }
1925        // first >= 51 is 52
1926        let b = row_bound_of(&rk(51), &sort);
1927        let mut cur = t.values_from(Some(&b), true, &sort);
1928        assert_eq!(val(cur.next_row().unwrap()), 52);
1929        assert_eq!(val(cur.next_row().unwrap()), 54);
1930        // Min sentinel ⇒ from the very start
1931        let bmin: RowBound = vec![(0, Bound::Min)];
1932        let mut c2 = t.values_from(Some(&bmin), true, &sort);
1933        assert_eq!(val(c2.next_row().unwrap()), 0);
1934        // Max sentinel ⇒ past the end ⇒ empty
1935        let bmax: RowBound = vec![(0, Bound::Max)];
1936        let mut c3 = t.values_from(Some(&bmax), true, &sort);
1937        assert!(c3.next_row().is_none());
1938        // exact hit is inclusive
1939        let bhit = row_bound_of(&rk(100), &sort);
1940        let mut c4 = t.values_from(Some(&bhit), true, &sort);
1941        assert_eq!(val(c4.next_row().unwrap()), 100);
1942    }
1943
1944    #[test]
1945    fn reverse_scans_descending() {
1946        let sort = s1();
1947        let mut t = BTree::new();
1948        for k in 0..500 {
1949            t.add(rk(k), &sort);
1950        }
1951        let mut cur = t.values_from_reversed(None, true, &sort);
1952        let got = drain(&mut cur);
1953        assert_eq!(got, (0..500).rev().collect::<Vec<_>>());
1954    }
1955
1956    #[test]
1957    fn from_sorted_matches_repeated_add() {
1958        let sort = s1();
1959        let rows: Vec<Row> = (0..1000).map(rk).collect();
1960        let t = BTree::from_sorted(rows.into_iter(), &sort);
1961        assert_eq!(t.len(), 1000);
1962        let mut cur = t.values_from(None, true, &sort);
1963        assert_eq!(drain(&mut cur), (0..1000).collect::<Vec<_>>());
1964        // and lookups work through the bulk-built internal nodes
1965        assert!(t.has(&rk(0), &sort) && t.has(&rk(999), &sort) && !t.has(&rk(1000), &sort));
1966    }
1967
1968    #[test]
1969    fn differential_against_sorted_vec_oracle() {
1970        // Random add/delete sequence; assert contents + return-flags match a
1971        // sorted-Vec set oracle under the same comparator. Full-row key (col 0),
1972        // so compare==Equal ⇔ identical.
1973        let sort = s1();
1974        let mut t = BTree::new();
1975        let mut oracle: Vec<i64> = Vec::new();
1976        let mut rng = Lcg::new(0x00C0_FFEE_D00D);
1977        for _ in 0..20_000 {
1978            let k = (rng.next() % 600) as i64;
1979            if rng.next() & 1 == 0 {
1980                let newly = t.add(rk(k), &sort);
1981                match oracle.binary_search(&k) {
1982                    Ok(_) => assert!(!newly, "add of existing {k} should return false"),
1983                    Err(i) => {
1984                        assert!(newly, "add of new {k} should return true");
1985                        oracle.insert(i, k);
1986                    }
1987                }
1988            } else {
1989                let removed = t.delete(&rk(k), &sort);
1990                match oracle.binary_search(&k) {
1991                    Ok(i) => {
1992                        assert!(removed, "delete of present {k} should return true");
1993                        oracle.remove(i);
1994                    }
1995                    Err(_) => assert!(!removed, "delete of absent {k} should return false"),
1996                }
1997            }
1998            assert_eq!(t.len(), oracle.len());
1999            // Structural invariants must hold after EVERY mutation.
2000            t.check_invariants(&sort).expect("invariants after op");
2001        }
2002        // In-order traversal equals the oracle.
2003        let mut cur = t.values_from(None, true, &sort);
2004        assert_eq!(drain(&mut cur), oracle);
2005        // Reverse equals reversed oracle.
2006        let mut rev = t.values_from_reversed(None, true, &sort);
2007        let mut want_rev = oracle.clone();
2008        want_rev.reverse();
2009        assert_eq!(drain(&mut rev), want_rev);
2010        // has/get parity across the whole key space.
2011        for k in 0..600 {
2012            assert_eq!(t.has(&rk(k), &sort), oracle.binary_search(&k).is_ok());
2013        }
2014        // Seek parity: for each k, values_from(>=k) first element matches.
2015        for k in 0..600 {
2016            let b = row_bound_of(&rk(k), &sort);
2017            let mut c = t.values_from(Some(&b), true, &sort);
2018            let got = c.next_row().map(val);
2019            let want = oracle.iter().find(|&&x| x >= k).copied();
2020            assert_eq!(got, want, "seek >= {k}");
2021        }
2022    }
2023
2024    #[test]
2025    fn fork_isolation_both_directions() {
2026        // Fork, then mutate BOTH copies in different ways; each must reflect ONLY
2027        // its own mutations (structural sharing + COW isolation).
2028        let sort = s1();
2029        let mut a = BTree::new();
2030        for k in 0..300 {
2031            a.add(rk(k), &sort);
2032        }
2033        let mut b = a.fork();
2034        // a: remove evens in [0,100); b: add 1000..1100 and remove 200..250.
2035        for k in (0..100).filter(|k| k % 2 == 0) {
2036            a.delete(&rk(k), &sort);
2037        }
2038        for k in 1000..1100 {
2039            b.add(rk(k), &sort);
2040        }
2041        for k in 200..250 {
2042            b.delete(&rk(k), &sort);
2043        }
2044        a.check_invariants(&sort).unwrap();
2045        b.check_invariants(&sort).unwrap();
2046
2047        let a_want: Vec<i64> = (0..300).filter(|k| !(*k < 100 && k % 2 == 0)).collect();
2048        let mut b_want: Vec<i64> = (0..300).filter(|k| !(200..250).contains(k)).collect();
2049        b_want.extend(1000..1100);
2050        let mut ca = a.values_from(None, true, &sort);
2051        let mut cb = b.values_from(None, true, &sort);
2052        assert_eq!(drain(&mut ca), a_want);
2053        assert_eq!(drain(&mut cb), b_want);
2054    }
2055
2056    #[test]
2057    fn reverse_bounded_seek_inclusive_and_exclusive() {
2058        let sort = s1();
2059        let mut t = BTree::new();
2060        for k in (0..400).map(|i| i * 2) {
2061            t.add(rk(k), &sort); // evens 0..=798
2062        }
2063        // inclusive: largest <= 101 is 100, then descending
2064        let b = row_bound_of(&rk(101), &sort);
2065        let mut cur = t.values_from_reversed(Some(&b), true, &sort);
2066        assert_eq!(val(cur.next_row().unwrap()), 100);
2067        assert_eq!(val(cur.next_row().unwrap()), 98);
2068        // inclusive at an exact element: includes it
2069        let bhit = row_bound_of(&rk(100), &sort);
2070        let mut c2 = t.values_from_reversed(Some(&bhit), true, &sort);
2071        assert_eq!(val(c2.next_row().unwrap()), 100);
2072        // exclusive at an exact element: skips it
2073        let mut c3 = t.values_from_reversed(Some(&bhit), false, &sort);
2074        assert_eq!(val(c3.next_row().unwrap()), 98);
2075        // below the minimum ⇒ empty
2076        let bneg = row_bound_of(&rk(-5), &sort);
2077        let mut c4 = t.values_from_reversed(Some(&bneg), true, &sort);
2078        assert!(c4.next_row().is_none());
2079        // above the maximum ⇒ starts at the max
2080        let bbig = row_bound_of(&rk(10_000), &sort);
2081        let mut c5 = t.values_from_reversed(Some(&bbig), true, &sort);
2082        assert_eq!(val(c5.next_row().unwrap()), 798);
2083    }
2084
2085    #[test]
2086    fn forward_exclusive_seek() {
2087        let sort = s1();
2088        let mut t = BTree::new();
2089        for k in 0..200 {
2090            t.add(rk(k), &sort);
2091        }
2092        let bhit = row_bound_of(&rk(50), &sort);
2093        // inclusive includes 50
2094        let mut ci = t.values_from(Some(&bhit), true, &sort);
2095        assert_eq!(val(ci.next_row().unwrap()), 50);
2096        // exclusive skips 50 → 51
2097        let mut ce = t.values_from(Some(&bhit), false, &sort);
2098        assert_eq!(val(ce.next_row().unwrap()), 51);
2099    }
2100
2101    #[test]
2102    fn reverse_bounded_seek_differential() {
2103        // Reverse-from-k must equal the oracle's "<= k, descending" for every k,
2104        // across a tree shaped by random churn (exercises seek + prev-leaf walks).
2105        let sort = s1();
2106        let mut t = BTree::new();
2107        let mut oracle: Vec<i64> = Vec::new();
2108        let mut rng = Lcg::new(0xBEEF_1234_5678);
2109        for _ in 0..8_000 {
2110            let k = (rng.next() % 500) as i64;
2111            if rng.next() & 1 == 0 {
2112                if t.add(rk(k), &sort) {
2113                    let i = oracle.binary_search(&k).unwrap_err();
2114                    oracle.insert(i, k);
2115                }
2116            } else if t.delete(&rk(k), &sort) {
2117                let i = oracle.binary_search(&k).unwrap();
2118                oracle.remove(i);
2119            }
2120        }
2121        t.check_invariants(&sort).unwrap();
2122        for k in -2..502 {
2123            let b = row_bound_of(&rk(k), &sort);
2124            // inclusive
2125            let mut c = t.values_from_reversed(Some(&b), true, &sort);
2126            let got: Vec<i64> = std::iter::from_fn(|| c.next_row().map(val)).collect();
2127            let want: Vec<i64> = oracle.iter().rev().filter(|&&x| x <= k).copied().collect();
2128            assert_eq!(got, want, "reverse inclusive <= {k}");
2129            // exclusive
2130            let mut ce = t.values_from_reversed(Some(&b), false, &sort);
2131            let got_e: Vec<i64> = std::iter::from_fn(|| ce.next_row().map(val)).collect();
2132            let want_e: Vec<i64> = oracle.iter().rev().filter(|&&x| x < k).copied().collect();
2133            assert_eq!(got_e, want_e, "reverse exclusive < {k}");
2134        }
2135    }
2136
2137    #[test]
2138    fn delete_keeps_occupancy_reasonable() {
2139        // After heavy deletes the JS opportunistic tryMerge keeps the tree compact
2140        // (the old lazy-prune left one sparse node per surviving key). Note JS does
2141        // NOT globally minimize — tryMerge is local to each delete site — so we
2142        // assert a *bound*, not zero-mergeable-pairs (that is not a theorem).
2143        let sort = s1();
2144        let mut t = BTree::new();
2145        for k in 0..5000 {
2146            t.add(rk(k), &sort);
2147        }
2148        for k in 0..5000 {
2149            if k % 10 != 0 {
2150                t.delete(&rk(k), &sort); // delete 90%
2151            }
2152        }
2153        assert_eq!(t.len(), 500);
2154        t.check_invariants(&sort).unwrap();
2155        // 500 keys: the optimal leaf count is ~16 (500/32). A compact tree stays
2156        // within a small multiple; a degenerate (un-merged) tree would have
2157        // hundreds of nodes. Assert well under that.
2158        let (nodes, leaves, depth) = node_stats(&t.root);
2159        assert!(
2160            leaves <= 60,
2161            "expected compact tree after deletes, got {leaves} leaves ({nodes} nodes, depth {depth})"
2162        );
2163        // depth for 500 elems should be 2 (root + leaves) or at most 3.
2164        assert!(depth <= 3, "tree too deep after deletes: depth {depth}");
2165    }
2166
2167    /// (total nodes, leaf count, depth) for occupancy assertions.
2168    fn node_stats(node: &BNode) -> (usize, usize, usize) {
2169        match node {
2170            BNode::Leaf { .. } => (1, 1, 1),
2171            BNode::Internal { children, .. } => {
2172                let mut nodes = 1;
2173                let mut leaves = 0;
2174                let mut depth = 0;
2175                for c in children {
2176                    let (n, l, d) = node_stats(c);
2177                    nodes += n;
2178                    leaves += l;
2179                    depth = depth.max(d);
2180                }
2181                (nodes, leaves, depth + 1)
2182            }
2183        }
2184    }
2185}