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}