Skip to main content

kernel/
btree.rs

1//! A B+tree over the buffer pool.
2//!
3//! Leaves hold every key and value; interior pages hold separator keys and child
4//! page numbers and are wholly derivable from the leaves. That derivability is
5//! what makes Law 5 cheap: recovery sweeps for intact leaves and repacks.
6//!
7//! SACRIFICE (Law 4): a split leaves two leaves about half full, so the file is
8//! roughly 1.3-1.5x the size of a packed layout. Bought: an insert lands in a page
9//! that already exists, so adding one row never rewrites the store.
10
11use crate::page::{PageKind, PageMut, PageRef, PAGE_SIZE};
12use crate::pool::{BufferPool, PinnedRead, PinnedWrite};
13use crate::verify::decode_record;
14use crate::{Error, Result};
15use std::cell::Cell;
16
17/// Open a resident page through the frame's validated bit (2f-A1): the first
18/// open of a residency pays the full slot validation and sets the bit; every
19/// later open skips the O(entries) loop. See `PageRef::open_resident_validated`.
20fn open_cached<'a>(r: &'a crate::pool::PinnedRead<'_>, no: u32) -> Result<PageRef<'a>> {
21    if r.validated() {
22        PageRef::open_resident_validated(r, no)
23    } else {
24        let p = PageRef::open_resident(r, no)?;
25        validate_records(&p)?;
26        r.set_validated();
27        Ok(p)
28    }
29}
30
31/// Validate every record once per buffer residency. The pool's `validated`
32/// bit makes this page-local pass disappear on hot reopens, preserving the
33/// scan path's existing cost while still ensuring no record field is used
34/// before the shared fallible decoder has accepted it.
35fn validate_records(page: &PageRef<'_>) -> Result<()> {
36    if !matches!(page.kind(), PageKind::Leaf | PageKind::Interior) {
37        return Ok(());
38    }
39    for i in 0..page.nentries() {
40        decode_record(page.slot(i), page.page_no(), page.kind())?;
41    }
42    Ok(())
43}
44
45/// 2f copy-on-write: duplicate a frozen page under a fresh page number.
46/// Byte-identical except its own number; the old version stays on disk,
47/// still serving any snapshot reader pinned at a published root. The old
48/// FRAME may remain cached -- it is clean (frozen pages cannot be dirtied)
49/// and reads of the old number still deserve it; the new page starts dirty
50/// and reaches disk by eviction or checkpoint like any other write.
51fn shadow_page(pool: &BufferPool, page: u32) -> Result<u32> {
52    let content = { pool.get(page)?.to_vec() };
53    let mut w = pool.allocate()?;
54    let no = w.page_no();
55    let b = w.bytes_mut();
56    b.copy_from_slice(&content);
57    b[12..16].copy_from_slice(&no.to_le_bytes());
58    PageMut::reopen(b).finalise(0);
59    // 2n: the original is superseded the moment this epoch publishes --
60    // record it for recycling (safe once the dual-slot fallback and every
61    // live reader have moved past it; see pool's `free` field).
62    let birth = u64::from_le_bytes(content[24..32].try_into().unwrap());
63    pool.free_shadow_page(page, birth)?;
64    Ok(no)
65}
66
67
68/// 2n: detach `child_no` from `parent_no` (both this epoch's writable
69/// copies). Returns the removed child POSITION (always >= 1) so a walk
70/// holding saved indices into this parent can shift them; None -- nothing
71/// removed -- when the child is child0 or the parent's last child: those
72/// leaves are kept cleared-in-place instead. (Child0 removal would need a
73/// saved-index of -1 to express "revisit position 0"; a skipped child left
74/// rows undeleted -- the model test measured 70 of 10000 -- so child0
75/// simply stays, one kept-empty leaf per parent per pass.)
76fn unlink_child(pool: &BufferPool, tree_id: u16, parent_no: u32, child_no: u32) -> Result<Option<usize>> {
77    let pos = {
78        let r = pool.get(parent_no)?;
79        let p = open_cached(&r, parent_no)?;
80        if p.kind() != PageKind::Interior || p.tree_id() != tree_id { return Ok(None); }
81        let n = p.nentries();
82        if p.child0() == child_no {
83            None // child0 stays (see the doc comment)
84        } else {
85            let mut found = None;
86            for i in 0..n {
87                if validated_child(p.slot(i)) == child_no {
88                    found = Some(i + 1);
89                    break;
90                }
91            }
92            found
93        }
94    };
95    let Some(pos) = pos else { return Ok(None) };
96    let mut w = pool.get_mut(parent_no)?;
97    let mut p = PageMut::reopen(w.bytes_mut());
98    p.remove_slot(pos - 1);
99    p.finalise(0);
100    Ok(Some(pos))
101}
102
103/// Point entry `i` of interior `page` at `new_child` (i == 0 is child0,
104/// otherwise slot i-1). `page` must already be unfrozen -- the shadowed
105/// descent guarantees it, and `get_mut` asserts it.
106fn patch_child(pool: &BufferPool, page: u32, i: usize, new_child: u32) -> Result<()> {
107    let mut w = pool.get_mut(page)?;
108    if i == 0 {
109        let p = PageRef::open_resident(w.bytes(), page)?;
110        validate_records(&p)?;
111        if p.kind() != PageKind::Interior {
112            return Err(Error::Corrupt { page_no: page, why: "child patch reached a non-interior page" });
113        }
114        let mut p = PageMut::reopen(w.bytes_mut());
115        p.set_child0(new_child);
116        p.finalise(0);
117        return Ok(());
118    }
119    // The child is the trailing 4 bytes of the slot's record (enc_interior).
120    // Patch it in place: same length, same key, only the pointer changes.
121    let (off, len) = {
122        let p = PageRef::open_resident(w.bytes(), page)?;
123        validate_records(&p)?;
124        if p.kind() != PageKind::Interior || i > p.nentries() {
125            return Err(Error::Corrupt { page_no: page, why: "child patch slot is out of bounds" });
126        }
127        validated_child(p.slot(i - 1));
128        let b = w.bytes();
129        let base = crate::page::HEADER_LEN + (i - 1) * 4;
130        let off = u16::from_le_bytes([b[base], b[base + 1]]) as usize;
131        let len = u16::from_le_bytes([b[base + 2], b[base + 3]]) as usize;
132        (off, len)
133    };
134    let b = w.bytes_mut();
135    b[off + len - 4..off + len].copy_from_slice(&new_child.to_le_bytes());
136    PageMut::reopen(b).finalise(0);
137    Ok(())
138}
139
140/// How many leaves the per-keyspace append hint remembers at once.
141///
142/// Bounded and constant (Law 1): the cache never grows with the store, and a
143/// slot it cannot spare costs one ordinary descent, never an answer. Sixteen
144/// is chosen from the shape of the write, not from taste: the multimodel load
145/// writes at most a handful of tags per document (mapping, row, vector,
146/// forward edge, reverse edge, field index) and the reverse-edge tag alone
147/// needs two -- a near-ascending run over people and a second, far lower run
148/// over the hundred organizations -- so the working set is single digits with
149/// room left for the indexes a collection adds.
150pub const TAG_HINTS: usize = 16;
151
152/// The longest fence a hint will hold, inline.
153///
154/// INLINE, not a `Vec`, and that is a measured requirement rather than a
155/// preference: `tests/edge_write_budget.rs` counts the allocations one
156/// relationship is allowed, and two owned fences per armed leaf pushed a
157/// two-put edge from 8 allocations to 10. Fences are cached on the descending
158/// path, which is the path that is supposed to be getting cheaper.
159///
160/// 48 bytes holds every key this cache is for: an edge row is at most 41 bytes
161/// (tag, two entities, context, type), a data row about 12, a mapping its
162/// external key. A separator longer than this simply does not arm a hint --
163/// the run keeps descending, which is what it did before.
164const FENCE_MAX: usize = 48;
165
166/// One fence, inline. `len` is meaningful only when `present`.
167#[derive(Clone, Copy)]
168struct Fence { present: bool, len: u8, bytes: [u8; FENCE_MAX] }
169
170impl Fence {
171    const ABSENT: Fence = Fence { present: false, len: 0, bytes: [0; FENCE_MAX] };
172    /// `None` when the key does not fit, so the caller can decline to arm
173    /// rather than remember a truncated fence -- a truncated upper fence is
174    /// LOWER than the real one, which would only ever refuse keys, but a
175    /// truncated LOWER fence is lower too, which would accept them.
176    fn of(key: &[u8]) -> Option<Fence> {
177        if key.len() > FENCE_MAX { return None; }
178        let mut f = Fence { present: true, len: key.len() as u8, bytes: [0; FENCE_MAX] };
179        f.bytes[..key.len()].copy_from_slice(key);
180        Some(f)
181    }
182    fn get(&self) -> Option<&[u8]> {
183        if self.present { Some(&self.bytes[..self.len as usize]) } else { None }
184    }
185}
186
187/// The fences of one leaf, as a descent found them. `armable` is false when a
188/// separator was too long to hold inline.
189#[derive(Clone, Copy)]
190pub(crate) struct LeafFences { lower: Fence, upper: Fence, armable: bool }
191
192impl LeafFences {
193    const NONE: LeafFences = LeafFences { lower: Fence::ABSENT, upper: Fence::ABSENT, armable: true };
194    fn narrow_lower(&mut self, key: &[u8]) {
195        match Fence::of(key) { Some(f) => self.lower = f, None => self.armable = false }
196    }
197    fn narrow_upper(&mut self, key: &[u8]) {
198        match Fence::of(key) { Some(f) => self.upper = f, None => self.armable = false }
199    }
200    /// The half of this interval below `sep`, which is what the LEFT page of a
201    /// split at `sep` inherits.
202    fn below(mut self, sep: &[u8]) -> Self { self.narrow_upper(sep); self }
203    /// The half at or above `sep` -- the RIGHT page's interval. Both halves
204    /// are SUBintervals of this one, which is why arming either is sound: a
205    /// narrower interval can only refuse keys, never misplace one.
206    fn above(mut self, sep: &[u8]) -> Self { self.narrow_lower(sep); self }
207}
208
209/// One remembered leaf, and the exact interval of keys that may be appended
210/// to it without descending.
211///
212/// The fences are the SEPARATORS the arming descent walked through, not the
213/// leaf's own first and last keys: they are the fences the tree itself would
214/// use to route a key to this leaf.
215#[derive(Clone, Copy)]
216struct TagHint {
217    /// 0 marks a free slot; tree ids start at 1.
218    tree: u16,
219    /// The leading byte of the keys this slot serves -- the keyspace tag (D4).
220    tag: u8,
221    leaf: u32,
222    /// The leaf's `next_leaf` when the hint was armed. A split of THIS leaf
223    /// relinks it to the new right page, so comparing one `u32` tells the
224    /// subdivision of this interval -- the one case a cached fence cannot
225    /// survive -- from the ordinary appends that leave the chain alone. It
226    /// doubles as identity: a recycled page number would have to be relinked
227    /// identically to pass.
228    next: u32,
229    /// Lower fence: the separator below this leaf, absent for the leftmost.
230    /// SELECTION ONLY. Nothing is placed on the strength of it; it exists so
231    /// two ascending runs under the SAME tag (the reverse-edge rows of people
232    /// and of organizations) can hold two slots and each find its own.
233    lower: Fence,
234    /// THE FENCE. Absent means this leaf is the tree's rightmost and has no
235    /// separator above it; otherwise a key may be appended here only if it is
236    /// STRICTLY below this. A key equal to the separator belongs to the next
237    /// leaf: `upper_bound` routes it there, so appending it here would make it
238    /// reachable by a scan and invisible to `get`.
239    upper: Fence,
240    /// When this slot was last used or armed, on the cache's own counter.
241    /// The victim of an eviction is the smallest of these.
242    used: u64,
243}
244
245impl TagHint {
246    const FREE: TagHint = TagHint {
247        tree: 0, tag: 0, leaf: 0, next: 0, lower: Fence::ABSENT, upper: Fence::ABSENT, used: 0,
248    };
249    /// Does this slot claim `key`? Lower fence inclusive, upper exclusive.
250    ///
251    /// `relaxed` makes the upper fence inclusive, which is WRONG -- see
252    /// `fast_path_tag_leaf`. It exists only so the byte-equivalence oracle has
253    /// a negative control: a test that proves two files identical proves
254    /// nothing unless a deliberately different placement makes them differ.
255    /// It is settable under `cfg(test)` alone and is a constant `false`
256    /// everywhere else.
257    fn claims(&self, tree: u16, tag: u8, key: &[u8], relaxed: bool) -> bool {
258        self.tree == tree
259            && self.tag == tag
260            && self.lower.get().is_none_or(|l| key >= l)
261            && self.upper.get().is_none_or(|u| if relaxed { key <= u } else { key < u })
262    }
263}
264
265/// The per-keyspace append hints for one handle.
266///
267/// D9 widened the append SPLIT from "rightmost leaf of the tree" to "rightmost
268/// leaf of the key's own tag". This widens the append HINT the same way, which
269/// is the other half: the split policy was already right for a tag that is not
270/// the tree's last, but every one of those inserts still paid a full
271/// root-to-leaf descent to reach the leaf the policy then acted on.
272///
273/// WHY A CACHED FENCE IS SAFE. `fast_path_leaf`'s `next_leaf() == 0` test is
274/// self-validating -- it reads the truth off the page every time -- and a
275/// cached separator is not. It is exact only while the tree's shape is the one
276/// the arming descent walked, so every way that shape can move is answered:
277///   * a split of the hinted leaf subdivides its interval -- the cached
278///     `next_leaf` changes, so the page-side check catches it, and the split
279///     path forgets the slot as well;
280///   * a neighbour redistribution moves separators BETWEEN siblings without
281///     touching the chain, so it forgets the slots of every page in its
282///     window itself;
283///   * a delete, a graft, a bulk pack, a rollback and a checkpoint can merge
284///     leaves, collapse the root or hand a page back to the allocator, and
285///     each of those clears the whole cache.
286///
287/// SACRIFICE (Law 4): about 1.5 KB of fixed state per handle, and a linear
288/// scan of sixteen slots per insert. Bought: an ascending run that is not the
289/// tree's rightmost -- which is every run but one, in a store where D4 makes
290/// every feature a tag in the same tree -- inserts with one page access
291/// instead of the tree's full height.
292pub struct TagHints {
293    slots: [Cell<TagHint>; TAG_HINTS],
294    /// Monotonic use counter. Evicting the LEAST RECENTLY USED slot, rather
295    /// than the next one round-robin, is what lets a few hot runs share the
296    /// cache with many cold ones: the relationship load writes two ascending
297    /// runs (forward rows, reverse rows over people) beside a HUNDRED cold
298    /// ones (reverse rows, one run per organization), and round-robin handed
299    /// a cold organization the hot forward-edge slot every sixteenth write.
300    clock: Cell<u64>,
301    hits: Cell<u64>,
302    attempts: Cell<u64>,
303    /// Inserts for which no slot claimed the key at all. Separate from a
304    /// refused probe because the two have different costs and different cures:
305    /// a miss here costs nothing but the descent it was going to pay anyway,
306    /// while a refused probe costs one page read on top of it.
307    misses: Cell<u64>,
308    /// Off means the handle descends for every insert, exactly as it did
309    /// before this cache existed. Not a cargo feature: one build has to be
310    /// able to run a workload BOTH ways and compare the files it produced,
311    /// which is the only evidence that a hint changes where a write descends
312    /// from and not where it lands.
313    enabled: Cell<bool>,
314    #[cfg(test)]
315    relaxed_upper: Cell<bool>,
316}
317
318impl Default for TagHints {
319    fn default() -> Self {
320        TagHints {
321            slots: std::array::from_fn(|_| Cell::new(TagHint::FREE)),
322            clock: Cell::new(0),
323            hits: Cell::new(0),
324            attempts: Cell::new(0),
325            misses: Cell::new(0),
326            enabled: Cell::new(true),
327            #[cfg(test)]
328            relaxed_upper: Cell::new(false),
329        }
330    }
331}
332
333impl TagHints {
334    /// The leaf that may hold `key` without a descent, and the `next_leaf` it
335    /// had when armed. Pure memory: the fences are compared here so that a
336    /// miss never costs a page read, and only the winner is opened.
337    fn find(&self, tree: u16, key: &[u8]) -> Option<(u32, u32)> {
338        if !self.enabled.get() { return None; }
339        let &tag = key.first()?;
340        let relaxed = self.relaxed();
341        for slot in &self.slots {
342            let mut h = slot.get();
343            if h.claims(tree, tag, key, relaxed) {
344                h.used = self.tick();
345                slot.set(h);
346                return Some((h.leaf, h.next));
347            }
348        }
349        None
350    }
351
352    fn tick(&self) -> u64 {
353        let now = self.clock.get() + 1;
354        self.clock.set(now);
355        now
356    }
357
358    /// Remember `leaf` for `key`'s tag between the fences a descent found.
359    /// Any slot naming the same leaf, or covering an overlapping interval of
360    /// the same tag, is replaced rather than duplicated: two slots that both
361    /// claim one key would make which leaf gets tried depend on scan order.
362    fn arm(&self, tree: u16, key: &[u8], leaf: u32, next: u32, fences: LeafFences) {
363        if !self.enabled.get() { return; }
364        let Some(&tag) = key.first() else { return };
365        if !fences.armable { return; }
366        let overlaps = |h: &TagHint| {
367            h.tree == tree
368                && (h.leaf == leaf
369                    || (h.tag == tag
370                        && fences.upper.get().is_none_or(|u| h.lower.get().is_none_or(|l| l < u))
371                        && h.upper.get().is_none_or(|u| fences.lower.get().is_none_or(|l| l < u))))
372        };
373        let mut at = None;
374        for (i, slot) in self.slots.iter().enumerate() {
375            if overlaps(&slot.get()) {
376                slot.set(TagHint::FREE);
377                if at.is_none() { at = Some(i); }
378            }
379        }
380        let at = at
381            .or_else(|| self.slots.iter().position(|s| s.get().tree == 0))
382            .unwrap_or_else(|| {
383                let mut victim = 0;
384                let mut oldest = u64::MAX;
385                for (i, slot) in self.slots.iter().enumerate() {
386                    let used = slot.get().used;
387                    if used < oldest { oldest = used; victim = i; }
388                }
389                victim
390            });
391        self.slots[at].set(TagHint {
392            tree, tag, leaf, next, lower: fences.lower, upper: fences.upper, used: self.tick(),
393        });
394    }
395
396    /// Forget one slot after its leaf refused the record. PostgreSQL's
397    /// `_bt_search_insert` disarms on any rejection; this disarms only the
398    /// slot that was wrong, because the other fifteen describe other runs.
399    fn forget_leaf(&self, tree: u16, leaf: u32) {
400        for slot in &self.slots {
401            let h = slot.get();
402            if h.tree == tree && h.leaf == leaf { slot.set(TagHint::FREE); }
403        }
404    }
405
406    /// Forget everything. The answer to every structural change this cache
407    /// does not track page by page.
408    pub fn clear(&self) {
409        for slot in &self.slots { slot.set(TagHint::FREE); }
410    }
411
412    /// Forget one tree's slots, for a tree whose root is being freed or whose
413    /// handle slot is being recycled.
414    pub fn clear_tree(&self, tree: u16) {
415        for slot in &self.slots {
416            if slot.get().tree == tree { slot.set(TagHint::FREE); }
417        }
418    }
419
420    /// Diagnostics: inserts that found a slot, and inserts that then landed on
421    /// its leaf. Counted, not reasoned about -- the budget tests read these.
422    pub fn attempts(&self) -> u64 { self.attempts.get() }
423    pub fn hits(&self) -> u64 { self.hits.get() }
424    /// Inserts no slot claimed.
425    pub fn misses(&self) -> u64 { self.misses.get() }
426
427    /// A cache that is off. Every insert descends, as it did before K1.
428    pub fn disabled() -> Self {
429        let t = TagHints::default();
430        t.set_enabled(false);
431        t
432    }
433
434    /// Turn the cache on or off for this handle. Turning it off forgets
435    /// everything it held, so a handle cannot come back to a stale fence.
436    pub fn set_enabled(&self, on: bool) {
437        self.enabled.set(on);
438        if !on { self.clear(); }
439    }
440
441    pub fn enabled(&self) -> bool { self.enabled.get() }
442
443    /// Negative control for the byte-equivalence oracle; see `TagHint::claims`.
444    #[cfg(test)]
445    pub(crate) fn set_relaxed_upper_fence(&self, on: bool) { self.relaxed_upper.set(on); }
446    #[cfg(test)]
447    fn relaxed(&self) -> bool { self.relaxed_upper.get() }
448    #[cfg(not(test))]
449    fn relaxed(&self) -> bool { false }
450}
451
452pub struct BTree<'p> {
453    pool: &'p BufferPool,
454    tree_id: u16,
455    root: u32,
456    /// The last leaf inserted into, cached the way PostgreSQL caches
457    /// `RelationGetTargetBlock`. Tried before descending on the next insert;
458    /// see `fast_path_leaf`. One `u32` — Law 1 (no allocation proportional to
459    /// the store).
460    ///
461    /// Borrowed, not owned: `Store` builds a fresh `BTree` on every `put`,
462    /// `delete`, `get` and `scan` and drops it immediately (Task 16), so a
463    /// hint owned by the `BTree` itself would reset to `None` on every call
464    /// and the fast path would never fire through `Store`. `Store` owns the
465    /// `Cell` and lends it here for the `BTree`'s lifetime — the equivalent
466    /// of PostgreSQL keeping the hint on the `Relation`, not on a per-insert
467    /// scan state.
468    last_leaf: &'p Cell<Option<u32>>,
469    /// Times `insert` used `last_leaf` instead of a full descent. Borrowed
470    /// the same way as `last_leaf` and for the same reason: a per-`BTree`
471    /// counter would reset every time `Store` opened a fresh one, and the
472    /// new `Store`-level tests need a count that survives across calls.
473    fast_path_hits: &'p Cell<u64>,
474    /// Times `insert` found `last_leaf` armed and called `fast_path_leaf` at
475    /// all, hit or miss. Borrowed the same way as `fast_path_hits`, for the
476    /// same reason (Task 18): a counter owned by the transient `BTree` would
477    /// reset on every `Store::put`, and the disarm test needs a count of
478    /// attempts -- not just hits -- that survives across calls to prove the
479    /// hint stops trying after the first rejection instead of retrying
480    /// forever on a workload it can never satisfy.
481    fast_path_attempts: &'p Cell<u64>,
482    /// The per-keyspace append hints, borrowed from the owning handle for the
483    /// same reason `last_leaf` is: `Store` and `PageWalStore` build a fresh
484    /// `BTree` for every single operation, so a cache owned here would be
485    /// empty on arrival every time. `None` is a tree opened without them --
486    /// every path that can invalidate a hint clears through this same borrow,
487    /// so a handle either passes it everywhere or nowhere.
488    tags: Option<&'p TagHints>,
489}
490
491/// Immutable plan for replacing exactly one live boundary leaf with a
492/// verified candidate subtree. The old leaf's records are copied into the
493/// candidate, so keys outside the empty graft interval are preserved without
494/// being reinserted.
495pub(crate) struct GraftBoundary {
496    leaf: u32,
497    path: Vec<(u32, usize)>,
498    records: Vec<Vec<u8>>,
499    split: usize,
500    pub(crate) old_next: u32,
501    pub(crate) right_page: Option<u32>,
502}
503
504pub(crate) struct GraftCandidate {
505    pub(crate) root: u32,
506    pub(crate) rows: u64,
507    pub(crate) min: Vec<u8>,
508    pub(crate) max: Vec<u8>,
509    pub(crate) last_next: u32,
510}
511
512/// vlen sentinel marking a spilled value. Unambiguous: a real inline value
513/// never exceeds MAX_RECORD_LEN (~4KB), far below 0xFFFF.
514pub(crate) const OVERFLOW_VLEN: u16 = 0xFFFF;
515
516/// The 12-byte body a marker record carries in place of its value:
517/// [total_len u32][head_page u32][crc32 of the FULL value].
518/// The crc is Law 5 for the chain as a WHOLE: each overflow page already
519/// carries its own page checksum, but a chain crossed with another record's
520/// chain (or truncated by a lost page) is made of individually-valid pages --
521/// only a checksum over the assembled value catches that. Blast radius: this
522/// one record.
523pub(crate) fn enc_marker(total: u32, head: u32, crc: u32) -> [u8; 12] {
524    let mut m = [0u8; 12];
525    m[0..4].copy_from_slice(&total.to_le_bytes());
526    m[4..8].copy_from_slice(&head.to_le_bytes());
527    m[8..12].copy_from_slice(&crc.to_le_bytes());
528    m
529}
530
531/// Overflow page layout, after the standard 40-byte page header:
532///   [next_page u32][used u16][content ...]
533/// Ordinary pages: PageKind::Overflow, sealed+checksummed at the pool write
534/// like every other page.
535pub(crate) const OV_NEXT: usize = crate::page::HEADER_LEN;
536pub(crate) const OV_USED: usize = OV_NEXT + 4;
537pub(crate) const OV_DATA: usize = OV_USED + 2;
538pub(crate) const OV_CAP: usize = crate::page::PAGE_SIZE - OV_DATA;
539
540/// Write `val` as a chain, return (head_page, crc). Pages are allocated
541/// forward and linked as built; the LAST page's next is 0.
542pub(crate) fn write_overflow(pool: &BufferPool, val: &[u8]) -> Result<(u32, u32)> {
543    if pool.resource_limits().is_some_and(|l| val.len() > l.record_bytes as usize) {
544        return Err(Error::ResourceLimit("overflow value exceeds record allowance"));
545    }
546    let crc = crc32c::crc32c(val);
547    let mut chunks: Vec<&[u8]> = val.chunks(OV_CAP).collect();
548    if chunks.is_empty() { chunks.push(&[]); }
549    let mut pages = Vec::with_capacity(chunks.len());
550    for _ in &chunks {
551        let w = pool.allocate()?;
552        pages.push(w.page_no());
553        drop(w);
554    }
555    for (i, chunk) in chunks.iter().enumerate() {
556        let mut w = pool.get_mut(pages[i])?;
557        let b = w.bytes_mut();
558        crate::page::PageMut::init(b, crate::page::PageKind::Overflow, 0, pages[i]).finalise(0);
559        let next = if i + 1 < pages.len() { pages[i + 1] } else { 0 };
560        b[OV_NEXT..OV_NEXT + 4].copy_from_slice(&next.to_le_bytes());
561        b[OV_USED..OV_USED + 2].copy_from_slice(&(chunk.len() as u16).to_le_bytes());
562        b[OV_DATA..OV_DATA + chunk.len()].copy_from_slice(chunk);
563    }
564    Ok((pages[0], crc))
565}
566
567/// Assemble a spilled value from its marker. Verifies the whole-value crc and
568/// every structural field before believing anything (Law 5): a wrong page
569/// kind, a length past the page, a chain longer than the file, or a crc
570/// mismatch all refuse the RECORD -- never return partial bytes.
571pub(crate) fn read_overflow(pool: &BufferPool, marker: &[u8]) -> Result<Vec<u8>> {
572    if marker.len() != 12 {
573        return Err(Error::Corrupt { page_no: 0, why: "overflow marker wrong size" });
574    }
575    let total = u32::from_le_bytes(marker[0..4].try_into().unwrap()) as usize;
576    if pool.resource_limits().is_some_and(|l| total > l.record_bytes as usize) {
577        return Err(Error::ResourceLimit("stored overflow value exceeds record allowance"));
578    }
579    let head = u32::from_le_bytes(marker[4..8].try_into().unwrap());
580    let want_crc = u32::from_le_bytes(marker[8..12].try_into().unwrap());
581
582    // Fast path (2e ablation A1): `write_overflow` allocates its pages in one
583    // tight loop off a monotone counter, so a chain's page numbers are always
584    // consecutive -- the whole chain is one speculative pread, verified page
585    // by page, never cached. Falls back to the per-page pool path when any
586    // page is resident (the frame may be dirtier than disk) or when anything
587    // read fails to look like exactly the chain the marker promised. The
588    // fallback re-reads from page 0 of the chain: this path trusts NOTHING it
589    // saw here.
590    let n_pages = total.div_ceil(OV_CAP).max(1);
591    if n_pages <= u32::MAX as usize {
592        let mut buf = Vec::new();
593        if let Ok(true) = pool.read_run_uncached(head, n_pages as u32, &mut buf) {
594            let mut out = Vec::with_capacity(total);
595            let mut contiguous = true;
596            for i in 0..n_pages {
597                let b = &buf[i * crate::page::PAGE_SIZE..(i + 1) * crate::page::PAGE_SIZE];
598                let page_no = head + i as u32;
599                let Ok(p) = PageRef::open_resident(b, page_no) else { contiguous = false; break };
600                if p.kind() != crate::page::PageKind::Overflow { contiguous = false; break; }
601                let next = u32::from_le_bytes(b[OV_NEXT..OV_NEXT + 4].try_into().unwrap());
602                let want_next = if i + 1 < n_pages { page_no + 1 } else { 0 };
603                if next != want_next { contiguous = false; break; }
604                let used = u16::from_le_bytes(b[OV_USED..OV_USED + 2].try_into().unwrap()) as usize;
605                if used > OV_CAP || out.len() + used > total { contiguous = false; break; }
606                out.extend_from_slice(&b[OV_DATA..OV_DATA + used]);
607            }
608            if contiguous && out.len() == total && crc32c::crc32c(&out) == want_crc {
609                return Ok(out);
610            }
611            // else: fall through to the authoritative per-page walk, which
612            // will refuse the record with a precise reason if it is corrupt.
613        }
614    }
615
616    let mut page = head;
617    let mut out = Vec::with_capacity(total);
618    let mut hops = 0u32;
619    let cap = pool.page_count();
620    while page != 0 {
621        hops += 1;
622        if hops > cap {
623            return Err(Error::Corrupt { page_no: page, why: "overflow chain cycles" });
624        }
625        let r = pool.get(page)?;
626        let p = PageRef::open_resident(&r, page)?;
627        if p.kind() != crate::page::PageKind::Overflow {
628            return Err(Error::Corrupt { page_no: page, why: "chain points at a non-overflow page" });
629        }
630        let b = &r[..];
631        let next = u32::from_le_bytes(b[OV_NEXT..OV_NEXT + 4].try_into().unwrap());
632        let used = u16::from_le_bytes(b[OV_USED..OV_USED + 2].try_into().unwrap()) as usize;
633        if used > OV_CAP || out.len() + used > total {
634            return Err(Error::Corrupt { page_no: page, why: "overflow length out of bounds" });
635        }
636        out.extend_from_slice(&b[OV_DATA..OV_DATA + used]);
637        drop(r);
638        page = next;
639    }
640    if out.len() != total || crc32c::crc32c(&out) != want_crc {
641        return Err(Error::Corrupt { page_no: 0, why: "overflow value fails its checksum" });
642    }
643    Ok(out)
644}
645
646/// Validate an old chain completely before allowing its pages onto the
647/// retirement list. No value buffer: one (page, birth-generation) pair per page of
648/// the changed value. The caller removes the marker first, then retires these page IDs
649/// through the existing generation/reader horizon; nothing is freed on an
650/// incomplete or corrupt chain. Cost is proportional to the old large value.
651fn replaced_overflow_pages(pool: &BufferPool, rec: &[u8]) -> Result<Vec<(u32, u64)>> {
652    let (_, marker, overflow) = validated_leaf(rec);
653    if !overflow { return Ok(Vec::new()); }
654    let total = u32::from_le_bytes(marker[..4].try_into().unwrap()) as usize;
655    if pool.resource_limits().is_some_and(|l| total > l.record_bytes as usize) {
656        return Err(Error::ResourceLimit("stored overflow value exceeds record allowance"));
657    }
658    let mut no = u32::from_le_bytes(marker[4..8].try_into().unwrap());
659    let want = u32::from_le_bytes(marker[8..12].try_into().unwrap());
660    let bound = total.div_ceil(OV_CAP).max(1);
661    let mut pages = Vec::new();
662    let (mut bytes, mut crc) = (0usize, 0u32);
663    while no != 0 {
664        if no < 2 || no >= pool.page_count() || pages.len() >= bound {
665            return Err(Error::Corrupt { page_no: no, why: "retired overflow chain bounds" });
666        }
667        let r = pool.get(no)?;
668        let p = PageRef::open_resident(&r, no)?;
669        if p.kind() != PageKind::Overflow || p.tree_id() != 0 {
670            return Err(Error::Corrupt { page_no: no, why: "retired overflow page kind" });
671        }
672        let used = u16::from_le_bytes(r[OV_USED..OV_USED+2].try_into().unwrap()) as usize;
673        // Writer chains fill every non-final page. This also bounds traversal
674        // without a database-sized visited set, even for forged cycles.
675        if used != (total-bytes).min(OV_CAP) {
676            return Err(Error::Corrupt { page_no: no, why: "retired overflow length" });
677        }
678        crc = crc32c::crc32c_append(crc, &r[OV_DATA..OV_DATA+used]);
679        bytes += used;
680        let birth = if pool.is_frozen(no) { p.lsn() } else { pool.stamp_generation() };
681        pages.push((no, birth));
682        no = u32::from_le_bytes(r[OV_NEXT..OV_NEXT+4].try_into().unwrap());
683    }
684    if pages.len() != bound || bytes != total || crc != want {
685        return Err(Error::Corrupt { page_no: 0, why: "retired overflow whole-value checksum or length" });
686    }
687    Ok(pages)
688}
689
690/// A leaf record whose value is an overflow marker: vlen = OVERFLOW_VLEN,
691/// body = enc_marker(). dec_val() on such a record returns the 12 marker
692/// bytes; is_marker() is how readers know to resolve them.
693pub(crate) fn enc_leaf_marker(key: &[u8], marker: &[u8; 12]) -> Vec<u8> {
694    let mut r = Vec::with_capacity(4 + key.len() + 12);
695    r.extend_from_slice(&(key.len() as u16).to_le_bytes());
696    r.extend_from_slice(key);
697    r.extend_from_slice(&OVERFLOW_VLEN.to_le_bytes());
698    r.extend_from_slice(marker);
699    r
700}
701
702/// `compact` is the encoding THIS DATABASE declares (see
703/// `BufferPool::compact_cells`), not what this build was compiled with. A
704/// database that declares the plain encoding gets plain cells even from a
705/// `compact-cells` build, and the other way round; both families decode
706/// everywhere, so a page may legitimately hold a mixture after an encoding
707/// was declared differently at some point in its history.
708pub(crate) fn enc_leaf(key: &[u8], val: &[u8], compact: bool) -> Vec<u8> {
709    if compact && key.first().is_some_and(|b|(0x81..=0x88).contains(b))
710        && key.len()==1+(key[0]-0x80)as usize {
711        // The width-tagged integer key gives its own length; the validated
712        // page slot gives the cell's end. FF + 81..88 cannot be a legal v1
713        // u16 key length in a 4 KiB page. Large values keep overflow markers.
714        let mut r=Vec::with_capacity(1+key.len()+val.len());
715        r.push(0xff);r.extend_from_slice(key);r.extend_from_slice(val);return r;
716    }
717    if compact && key.len() <= 0x0fff {
718        // 0x4xxx cannot be a legal legacy key length in a 4KiB page. The
719        // CRC/bounds-checked slot already provides the value's end, so ordinary
720        // keys need no duplicate u16 value length. Overflow markers stay distinct.
721        let mut r=Vec::with_capacity(2+key.len()+val.len());
722        r.extend_from_slice(&(0x4000|key.len() as u16).to_le_bytes());
723        r.extend_from_slice(key);r.extend_from_slice(val);return r;
724    }
725    let mut r = Vec::with_capacity(4 + key.len() + val.len());
726    r.extend_from_slice(&(key.len() as u16).to_le_bytes());
727    r.extend_from_slice(key);
728    r.extend_from_slice(&(val.len() as u16).to_le_bytes());
729    r.extend_from_slice(val);
730    r
731}
732/// Fast access after `validate_records` accepted the complete page through the
733/// shared fallible decoder. These helpers never receive unvalidated disk bytes:
734/// `open_cached` owns that boundary, and writable-page callers validate before
735/// their first access. Keeping the proof on the buffer residency avoids paying
736/// the same bounds checks again for every row of every hot scan.
737#[inline(always)]
738fn validated_key(rec: &[u8]) -> &[u8] {
739    if rec[0]==0xff && (0x81..=0x88).contains(&rec[1]) {
740        return &rec[1..2+(rec[1]-0x80)as usize];
741    }
742    // Unconditional: the cell family is stored in the bytes, so a build
743    // without the `compact-cells` feature must still read one.
744    if rec[1]&0xf0==0x40 {
745        let k=(u16::from_le_bytes([rec[0],rec[1]])&0x0fff) as usize;
746        return &rec[2..2+k];
747    }
748    let k = u16::from_le_bytes([rec[0], rec[1]]) as usize;
749    &rec[2..2 + k]
750}
751
752#[inline(always)]
753fn validated_leaf(rec: &[u8]) -> (&[u8], &[u8], bool) {
754    if rec[0]==0xff && (0x81..=0x88).contains(&rec[1]) {
755        let end=2+(rec[1]-0x80)as usize;return (&rec[1..end],&rec[end..],false);
756    }
757    if rec[1]&0xf0==0x40 {
758        let end=2+(u16::from_le_bytes([rec[0],rec[1]])&0x0fff) as usize;
759        return (&rec[2..end],&rec[end..],false);
760    }
761    let k = u16::from_le_bytes([rec[0], rec[1]]) as usize;
762    let v = u16::from_le_bytes([rec[2 + k], rec[3 + k]]);
763    let value_len = if v == OVERFLOW_VLEN { 12 } else { v as usize };
764    (&rec[2..2 + k], &rec[4 + k..4 + k + value_len], v == OVERFLOW_VLEN)
765}
766/// Does this leaf hold an overflow marker anywhere? Read ONCE per leaf pin so
767/// a pull cursor can serve each row with one page open and one record decode
768/// instead of asking the page again per row. Only the general record encoding
769/// can carry a marker at all -- the two short forms declare their own value
770/// inline -- so the common leaf costs two comparisons per record and stops
771/// there.
772fn leaf_has_marker(page: &PageRef<'_>) -> bool {
773    for i in 0..page.nentries() {
774        let rec = page.slot(i);
775        if rec[0] == 0xff && (0x81..=0x88).contains(&rec[1]) {
776            continue;
777        }
778        if rec[1] & 0xf0 == 0x40 {
779            continue;
780        }
781        let k = u16::from_le_bytes([rec[0], rec[1]]) as usize;
782        if u16::from_le_bytes([rec[2 + k], rec[3 + k]]) == OVERFLOW_VLEN {
783            return true;
784        }
785    }
786    false
787}
788fn enc_interior(key: &[u8], child: u32) -> Vec<u8> {
789    let mut r = Vec::with_capacity(6 + key.len());
790    r.extend_from_slice(&(key.len() as u16).to_le_bytes());
791    r.extend_from_slice(key);
792    r.extend_from_slice(&child.to_le_bytes());
793    r
794}
795#[inline(always)]
796fn validated_child(rec: &[u8]) -> u32 {
797    let k = u16::from_le_bytes([rec[0], rec[1]]) as usize;
798    u32::from_le_bytes(rec[2 + k..6 + k].try_into().unwrap())
799}
800
801fn child_at(pool: &BufferPool, page: &PageRef<'_>, index: usize) -> Result<u32> {
802    let child = if index == 0 {
803        page.child0()
804    } else {
805        validated_child(page.slot(index - 1))
806    };
807    if child == 0 || child >= pool.page_count() {
808        return Err(Error::Corrupt {
809            page_no: page.page_no(),
810            why: "interior child pointer is outside the data file",
811        });
812    }
813    Ok(child)
814}
815
816/// Build a page's contents in scratch, then commit them to the frame in one copy.
817///
818/// `PageMut::init` zeroes the page. Writing straight into a live frame and then
819/// hitting a fallible `insert_slot` therefore leaves that frame wiped, dirty and
820/// never finalised -- a stale checksum over a blank page, which the next read
821/// refuses, losing every key on it. That is exactly the replace-path defect this
822/// task already fixed once, relocated into the split.
823///
824/// Today `split_point`'s `+4` per-record accounting is the same arithmetic
825/// `insert_slot` checks, so these inserts provably cannot fail. But that is an
826/// invariant spanning two functions with nothing enforcing it, and a change to
827/// `SLOT_LEN` or `HEADER_LEN` would reintroduce total leaf loss with no test
828/// able to catch it. Building in scratch removes the class rather than relying
829/// on the coincidence.
830///
831/// SACRIFICE (Law 4): one page of scratch per split plus one extra copy.
832/// Bought: no error path can destroy a live page.
833pub(crate) fn build_page(
834    kind: PageKind,
835    tree_id: u16,
836    page_no: u32,
837    recs: &[Vec<u8>],
838    child0_or_next: u32,
839) -> Result<Vec<u8>> {
840    let mut scratch = vec![0u8; PAGE_SIZE];
841    build_page_into(&mut scratch, kind, tree_id, page_no, recs.iter().map(|r| r.as_slice()), child0_or_next)?;
842    Ok(scratch)
843}
844
845/// The same page build, into a buffer the caller already owns and over
846/// records the caller does not have to own. This is the ONE inner insert
847/// loop: `build_page` allocates a fresh page for it, the `slotref-split`
848/// path hands it a reusable scratch page and an iterator over records that
849/// are still sitting in their original page images. Keeping one loop is the
850/// point -- two copies of "append every record, then stamp next_leaf" is two
851/// places for the split's byte-for-byte contract to drift.
852pub(crate) fn build_page_into<'r>(
853    dst: &mut [u8],
854    kind: PageKind,
855    tree_id: u16,
856    page_no: u32,
857    recs: impl Iterator<Item = &'r [u8]>,
858    child0_or_next: u32,
859) -> Result<()> {
860    let mut p = PageMut::init(dst, kind, tree_id, page_no);
861    for r in recs {
862        let at = p.nentries_pub();
863        p.insert_slot(at, r)?;
864    }
865    p.set_next_leaf(child0_or_next);
866    p.finalise(0);
867    Ok(())
868}
869
870/// Index of the first entry whose key is >= `key`.
871fn lower_bound(p: &PageRef, key: &[u8]) -> Result<usize> {
872    let (mut lo, mut hi) = (0usize, p.nentries());
873    while lo < hi {
874        let mid = (lo + hi) / 2;
875        if validated_key(p.slot(mid)) < key { lo = mid + 1 } else { hi = mid }
876    }
877    Ok(lo)
878}
879
880/// Index of the first entry whose key is > `key`.
881fn upper_bound(p: &PageRef, key: &[u8]) -> Result<usize> {
882    let (mut lo, mut hi) = (0usize, p.nentries());
883    while lo < hi {
884        let mid = (lo + hi) / 2;
885        if validated_key(p.slot(mid)) <= key { lo = mid + 1 } else { hi = mid }
886    }
887    Ok(lo)
888}
889
890/// Where to cut a page's entries so that **both** halves fit in a page.
891///
892/// Splitting at `len / 2` by COUNT is wrong for variable-length records. So is
893/// cutting at the first prefix to cross half the bytes: that prefix can exceed
894/// a page on its own. Concretely, with a 4056-byte usable page, forty 100-byte
895/// entries and one 4000-byte entry inserted in the middle, the first prefix to
896/// reach half the bytes is 6000 bytes — a page and a half.
897///
898/// So this checks the constraint directly rather than approximating it: among
899/// the cuts where the left side AND the right side each fit, take the most
900/// balanced. Returns `None` when no two-way cut exists, which happens only when
901/// one record is so large that no arrangement of the rest leaves room. The
902/// caller refuses that insert rather than corrupting a page; the real answer is
903/// overflow pages for large values, which the spec defers.
904#[cfg(all(feature = "sqlite-balance", any(test, not(feature = "slotref-split"))))]
905fn neighbor_cell_cuts(sizes: &[usize], usable: usize, existing: usize) -> Option<Vec<usize>> {
906    if sizes.is_empty() || sizes.iter().any(|&v| v==0 || v>usable) { return None; }
907    let n=sizes.len();
908    let mut prefix=vec![0usize];
909    for &size in sizes { prefix.push(prefix.last()?.checked_add(size)?); }
910    // Positive, indivisible cells: the longest fitting prefix minimizes the
911    // number of contiguous pages. Compute every suffix's minimum in O(cells).
912    let mut ends=vec![0;n];let mut end=0;
913    for i in 0..n {
914        while end<n && prefix[end+1]-prefix[i]<=usable { end+=1; }
915        ends[i]=end;
916    }
917    let mut needed=vec![0;n+1];
918    for i in (0..n).rev() { needed[i]=1+needed[ends[i]]; }
919    let groups=existing.max(needed[0]);
920    if groups>existing+1 || n<groups { return None; }
921    let mut cuts=vec![0];
922    for group in 0..groups-1 {
923        let begin=*cuts.last()?;let left=groups-group-1;
924        let target=(prefix[n]-prefix[begin])/(left+1);
925        let mut best=None;
926        for end in begin+1..=ends[begin].min(n-left) {
927            if needed[end]>left { continue; }
928            let delta=(prefix[end]-prefix[begin]).abs_diff(target);
929            if best.is_none_or(|(_,old)|delta<old) { best=Some((end,delta)); }
930        }
931        cuts.push(best?.0);
932    }
933    cuts.push(n);Some(cuts)
934}
935
936#[cfg(all(test,feature="sqlite-balance"))]
937mod neighbor_capacity_tests {
938    use super::neighbor_cell_cuts;
939    #[test]
940    fn full_leaf_and_one_sibling_use_at_most_three_pages() {
941        let sizes=vec![272;29];
942        let cuts=neighbor_cell_cuts(&sizes,4056,2).unwrap();
943        assert_eq!(cuts.len(),4);
944        for w in cuts.windows(2) { assert!((w[1]-w[0])*272<=4056); }
945    }
946    #[test]
947    fn forty_three_indivisible_cells_need_four_pages() {
948        let sizes=vec![272;43];
949        assert!(sizes.iter().sum::<usize>()<3*4056);
950        let cuts=neighbor_cell_cuts(&sizes,4056,3).unwrap();
951        assert_eq!(cuts.len(),5);
952        for w in cuts.windows(2) { assert!((w[1]-w[0])*272<=4056); }
953    }
954    #[test]
955    fn bounded_planner_matches_exhaustive_partition_oracle() {
956        fn possible(s:&[usize],pages:usize)->bool {
957            if pages==0 { return s.is_empty(); }
958            let mut sum=0;
959            for end in 1..=s.len() {
960                sum+=s[end-1];if sum>7 { break; }
961                if possible(&s[end..],pages-1) { return true; }
962            }
963            false
964        }
965        for n in 2..=7 {
966            for mut code in 0..3usize.pow(n as u32) {
967                let s:Vec<_>=(0..n).map(|_| {let v=[1,3,5][code%3];code/=3;v}).collect();
968                let expected=(2..=3).find(|&p|possible(&s,p));
969                let result=neighbor_cell_cuts(&s,7,2);
970                assert_eq!(result.as_ref().map(|v|v.len()-1),expected,"{s:?}");
971                if let Some(cuts)=result {
972                    assert_eq!(*cuts.last().unwrap(),n);
973                    for w in cuts.windows(2) {assert!(w[1]>w[0]);assert!(s[w[0]..w[1]].iter().sum::<usize>()<=7);}
974                }
975            }
976        }
977    }
978}
979
980fn split_point(recs: &[Vec<u8>], usable: usize) -> Option<usize> {
981    let sizes: Vec<usize> = recs.iter().map(|r| r.len() + 4).collect();
982    let total: usize = sizes.iter().sum();
983    let mut acc = 0usize;
984    let mut best: Option<(usize, usize)> = None; // (imbalance, mid)
985    for (j, size) in sizes.iter().enumerate().take(recs.len().saturating_sub(1)) {
986        acc += size;
987        let (left, right) = (acc, total - acc);
988        if left <= usable && right <= usable {
989            let imbalance = left.abs_diff(right);
990            if best.is_none_or(|(b, _)| imbalance < b) {
991                best = Some((imbalance, j + 1));
992            }
993        }
994    }
995    best.map(|(_, mid)| mid)
996}
997
998// ===================================================================== L2.3-2
999// Allocation-free leaf split and redistribution.
1000//
1001// THE FINDING. On a Pi, a typed put spends about 46% of its CPU in the split
1002// path, and malloc+free together are a quarter of all cycles. The reason is
1003// visible in one line of the old split: `(0..p.nentries()).map(|j|
1004// p.slot(j).to_vec())`. Every record on the page becomes its own heap
1005// allocation -- about 86 of them for an ordinary leaf -- and a three-sibling
1006// redistribution does that for three leaves plus the parent's separators,
1007// about 320. None of those copies is needed: the records are already sitting,
1008// contiguous and bounds-checked, in pinned buffer-pool frames.
1009//
1010// THE SHAPE. SQLite's `balance_nonroot` does not copy cells either; it builds
1011// an array of cell POINTERS into the existing page images plus one scratch
1012// allocation, and assembles the new pages from that. `SlotRef` is that
1013// pointer array, written in the terms this file already uses: which page image
1014// (an index into a small `frames` array) and the slot directory's own
1015// (offset, length) pair, taken verbatim, with no decoding at all.
1016//
1017// WHAT DOES NOT CHANGE. Every decision: `split_point`, `neighbor_cell_cuts`,
1018// the `at_point` append test, the window order, the fit probe, the shadowing.
1019// The page images and separator bytes are identical, which is a claim about
1020// output and is therefore tested as one -- see `split_byte_equivalence`.
1021//
1022// SACRIFICE (Law 4): about 44 KiB of scratch, per thread that ever splits,
1023// held for the process's life instead of being allocated and freed per split
1024// (10 reusable pages + two SlotRef arrays that grow to the largest window
1025// seen, bounded by three pages of records). It is RAM proportional to a page,
1026// not to the store, so Law 1 is untouched -- but it is no longer transient,
1027// and that is the cost. Bought: a steady-state split allocates a small
1028// bounded constant instead of one allocation per record on the page.
1029
1030/// A record left where it is. `page_idx` selects one of the `frames` the
1031/// caller passes alongside; `off`/`len` are the slot directory's own numbers.
1032#[cfg(any(test, feature = "slotref-split"))]
1033#[derive(Clone, Copy, Debug)]
1034pub(crate) struct SlotRef { page_idx: u8, off: u16, len: u16 }
1035
1036/// Frame indices. Fixed, because a `SlotRef` built while gathering the
1037/// splitting leaf is later resolved against the redistribution's larger frame
1038/// array, and the two must agree about what index 0 and 1 mean.
1039#[cfg(any(test, feature = "slotref-split"))]
1040mod frame {
1041    pub const LEAF: u8 = 0;   // the splitting leaf's image, copied to scratch
1042    pub const REC: u8 = 1;    // the incoming record
1043    pub const SIB0: u8 = 2;   // first redistribution sibling
1044    pub const SIB1: u8 = 3;   // second redistribution sibling
1045    pub const PARENT: u8 = 4; // the parent's image, copied to scratch
1046    pub const SEPS: u8 = 5;   // separators this redistribution had to rebuild
1047}
1048
1049#[cfg(any(test, feature = "slotref-split"))]
1050#[inline(always)]
1051fn rec_of<'f>(frames: &[&'f [u8]], s: SlotRef) -> &'f [u8] {
1052    let b = frames[s.page_idx as usize];
1053    &b[s.off as usize..s.off as usize + s.len as usize]
1054}
1055
1056/// The reusable buffers a split borrows instead of allocating.
1057///
1058/// Three thread-locals rather than one struct with three fields, because the
1059/// leaf image, the leaf's slot list and everything else are borrowed at the
1060/// same time by different parameters of the same call; splitting them is how
1061/// the borrow checker is told they never alias. A `BTree` is rebuilt per
1062/// operation (`Store::put` opens one and drops it), so the scratch cannot live
1063/// on it; the buffer pool is single-threaded by construction (`RefCell`
1064/// inside), so a thread-local is exactly one scratch per live writer.
1065#[cfg(any(test, feature = "slotref-split"))]
1066pub(crate) struct SplitScratch {
1067    /// Sibling page images, two pages: a redistribution window is at most
1068    /// three leaves and one of them is the splitting leaf itself.
1069    sib: Vec<u8>,
1070    /// The parent's page image.
1071    parent: Vec<u8>,
1072    /// Built page images, four pages: up to three leaves plus the parent.
1073    out: Vec<u8>,
1074    /// Interior records a redistribution had to rebuild (new key, or same key
1075    /// and a new child). Three pages: at most three such records, each at most
1076    /// one page's worth.
1077    seps: Vec<u8>,
1078    /// The window's records, in order, across the whole window.
1079    win: Vec<SlotRef>,
1080    /// The parent's separators as they stand, and as they will stand.
1081    pr0: Vec<SlotRef>,
1082    pr: Vec<SlotRef>,
1083    sizes: Vec<usize>,
1084    prefix: Vec<usize>,
1085    ends: Vec<usize>,
1086    needed: Vec<usize>,
1087    cuts: Vec<usize>,
1088    /// The parent's children as they stand, and after any shadowing.
1089    ids: Vec<u32>,
1090    newids: Vec<u32>,
1091}
1092
1093#[cfg(any(test, feature = "slotref-split"))]
1094impl SplitScratch {
1095    fn new() -> Self {
1096        SplitScratch {
1097            sib: vec![0u8; 2 * PAGE_SIZE],
1098            parent: vec![0u8; PAGE_SIZE],
1099            out: vec![0u8; 4 * PAGE_SIZE],
1100            seps: vec![0u8; 3 * PAGE_SIZE],
1101            win: Vec::with_capacity(2048),
1102            pr0: Vec::with_capacity(1024),
1103            pr: Vec::with_capacity(1024),
1104            sizes: Vec::with_capacity(2048),
1105            prefix: Vec::with_capacity(2049),
1106            ends: Vec::with_capacity(2048),
1107            needed: Vec::with_capacity(2049),
1108            cuts: Vec::with_capacity(8),
1109            ids: Vec::with_capacity(1024),
1110            newids: Vec::with_capacity(1024),
1111        }
1112    }
1113}
1114
1115#[cfg(any(test, feature = "slotref-split"))]
1116thread_local! {
1117    /// The splitting leaf's page image. Copied out of the frame once so the
1118    /// write guard can be handed to `redistribute_neighbors_ref` as `&mut`
1119    /// while its records are still readable -- 4 KiB of memcpy in place of one
1120    /// allocation per record.
1121    static SPLIT_LEAF: std::cell::RefCell<Vec<u8>> =
1122        std::cell::RefCell::new(vec![0u8; PAGE_SIZE]);
1123    /// The splitting leaf's records with the incoming one already in place.
1124    static SPLIT_SLOTS: std::cell::RefCell<Vec<SlotRef>> =
1125        std::cell::RefCell::new(Vec::with_capacity(1024));
1126    static SPLIT_SCRATCH: std::cell::RefCell<SplitScratch> =
1127        std::cell::RefCell::new(SplitScratch::new());
1128}
1129
1130/// Write one interior record -- `[klen u16][key][child u32]`, byte for byte
1131/// what `enc_interior` builds -- into the separator arena, and return the
1132/// `SlotRef` that names it.
1133#[cfg(all(feature = "sqlite-balance", any(test, feature = "slotref-split")))]
1134fn push_sep(seps: &mut [u8], at: &mut usize, key: &[u8], child: u32) -> SlotRef {
1135    let off = *at;
1136    let len = 6 + key.len();
1137    seps[off..off + 2].copy_from_slice(&(key.len() as u16).to_le_bytes());
1138    seps[off + 2..off + 2 + key.len()].copy_from_slice(key);
1139    seps[off + 2 + key.len()..off + len].copy_from_slice(&child.to_le_bytes());
1140    *at = off + len;
1141    SlotRef { page_idx: frame::SEPS, off: off as u16, len: len as u16 }
1142}
1143
1144/// Re-point an arena separator at a different child. `enc_interior` puts the
1145/// child in the record's trailing four bytes, so this is that patch -- the
1146/// same one `patch_child` performs on a live page.
1147#[cfg(all(feature = "sqlite-balance", any(test, feature = "slotref-split")))]
1148fn patch_sep_child(seps: &mut [u8], s: SlotRef, child: u32) {
1149    let end = s.off as usize + s.len as usize;
1150    seps[end - 4..end].copy_from_slice(&child.to_le_bytes());
1151}
1152
1153/// `split_point` over borrowed records. Identical arithmetic: a record costs
1154/// its length plus the four bytes of its slot-directory entry, and among the
1155/// cuts where both sides fit a page the most balanced one wins.
1156#[cfg(any(test, feature = "slotref-split"))]
1157fn split_point_ref(slots: &[SlotRef], usable: usize) -> Option<usize> {
1158    let total: usize = slots.iter().map(|s| s.len as usize + 4).sum();
1159    let mut acc = 0usize;
1160    let mut best: Option<(usize, usize)> = None; // (imbalance, mid)
1161    for (j, s) in slots.iter().enumerate().take(slots.len().saturating_sub(1)) {
1162        acc += s.len as usize + 4;
1163        let (left, right) = (acc, total - acc);
1164        if left <= usable && right <= usable {
1165            let imbalance = left.abs_diff(right);
1166            if best.is_none_or(|(b, _)| imbalance < b) {
1167                best = Some((imbalance, j + 1));
1168            }
1169        }
1170    }
1171    best.map(|(_, mid)| mid)
1172}
1173
1174/// `neighbor_cell_cuts` writing into buffers the caller reuses. Line for line
1175/// the same planner; only the four `vec![]`s became `clear()`s. `cuts` holds
1176/// the answer; `false` is the original's `None`.
1177#[cfg(all(feature = "sqlite-balance", any(test, feature = "slotref-split")))]
1178#[allow(clippy::too_many_arguments)]
1179fn neighbor_cell_cuts_into(
1180    sizes: &[usize], usable: usize, existing: usize,
1181    prefix: &mut Vec<usize>, ends: &mut Vec<usize>, needed: &mut Vec<usize>, cuts: &mut Vec<usize>,
1182) -> bool {
1183    cuts.clear();
1184    if sizes.is_empty() || sizes.iter().any(|&v| v == 0 || v > usable) { return false; }
1185    let n = sizes.len();
1186    prefix.clear();
1187    prefix.push(0);
1188    for &size in sizes {
1189        match prefix.last().and_then(|p| p.checked_add(size)) {
1190            Some(v) => prefix.push(v),
1191            None => return false,
1192        }
1193    }
1194    ends.clear();
1195    ends.resize(n, 0);
1196    let mut end = 0;
1197    for i in 0..n {
1198        while end < n && prefix[end + 1] - prefix[i] <= usable { end += 1; }
1199        ends[i] = end;
1200    }
1201    needed.clear();
1202    needed.resize(n + 1, 0);
1203    for i in (0..n).rev() { needed[i] = 1 + needed[ends[i]]; }
1204    let groups = existing.max(needed[0]);
1205    if groups > existing + 1 || n < groups { return false; }
1206    cuts.push(0);
1207    for group in 0..groups - 1 {
1208        let begin = *cuts.last().expect("cuts always holds its first entry");
1209        let left = groups - group - 1;
1210        let target = (prefix[n] - prefix[begin]) / (left + 1);
1211        let mut best = None;
1212        for end in begin + 1..=ends[begin].min(n - left) {
1213            if needed[end] > left { continue; }
1214            let delta = (prefix[end] - prefix[begin]).abs_diff(target);
1215            if best.is_none_or(|(_, old)| delta < old) { best = Some((end, delta)); }
1216        }
1217        match best {
1218            Some((e, _)) => cuts.push(e),
1219            None => { cuts.clear(); return false; }
1220        }
1221    }
1222    cuts.push(n);
1223    true
1224}
1225
1226pub struct RangeIter<'p> {
1227    pool: &'p BufferPool,
1228    tree_id: u16,
1229    page: u32,
1230    idx: usize,
1231    done: bool,
1232    /// The descent path to the current leaf: (interior page, chosen child
1233    /// index), root first. Child index 0 means child0, k means slot k-1.
1234    ///
1235    /// 2f: scans advance THROUGH THE PARENT instead of following the
1236    /// on-page sibling pointer. Under copy-on-write a modified leaf moves
1237    /// to a fresh page number and its left neighbour's stored `next_leaf`
1238    /// silently names the STALE version -- the neighbour is not on any
1239    /// descent path, so nothing can fix it. The parent path has no such
1240    /// problem, and unlike re-descending from the root per leaf (measured:
1241    /// range_filter 0.53 -> 1.13 ms, rejected under D25) it costs one page
1242    /// open per crossing, the same as the chain did. Path staleness cannot
1243    /// occur: mutating the tree during a scan is already forbidden for the
1244    /// writer, and snapshot readers hold an immutable root. `next_leaf`
1245    /// stays on disk only as the rightmost flag (zero vs nonzero survives
1246    /// shadowing) for the append fast path.
1247    path: Vec<(u32, usize)>,
1248    /// One leaf's records, drained per `next()`, but only once a scan has
1249    /// PROVED long. Per-entry re-pinning cost ~132ns/key (`open_resident`
1250    /// bounds-checks every slot per open -- O(entries^2) per leaf) and made
1251    /// index ranges 7x slower than SQLite; whole-leaf batching fixed that and
1252    /// then cost graph hops 2-3x, because a 3-edge hop copied a ~130-entry
1253    /// leaf. So: the first `SHORT_SCAN` entries are served per-entry (a hop
1254    /// never notices), and batching starts at the threshold (a range scan
1255    /// amortises everything after). Bounded by leaf capacity; Law 1 intact.
1256    buf: std::collections::VecDeque<(Vec<u8>, Vec<u8>, bool)>,
1257    served: u32,
1258    /// Leaves visited, and the ceiling. A sibling chain cannot legitimately visit
1259    /// more leaves than the file has pages, so exceeding it means it cycles.
1260    leaves: u32,
1261    max_leaves: u32,
1262    /// Live pin of `page` for [`RangeIter::peek_at_or_after`]. Held across
1263    /// successive peeks into the same leaf so a lockstep cursor pays one
1264    /// `pool.get` per leaf, not per row. Dropped before `advance()` and
1265    /// before overflow-chain walks. `next()` / `for_each_ref` drop it on
1266    /// entry so their existing pin accounting is unchanged.
1267    pin: Option<PinnedRead<'p>>,
1268    /// The pinned leaf's entry count and whether any of its records is an
1269    /// overflow marker, read ONCE when the pin is taken. A pull cursor asks
1270    /// "am I still standing on a record of this leaf?" once per ROW, and
1271    /// answering it from the page needed a second page open and a second
1272    /// record decode every time. Meaningful only while `pin` is `Some`.
1273    leaf_entries: usize,
1274    leaf_markers: bool,
1275    /// Seeks: how many times this cursor has had to ASK where it is, rather
1276    /// than read the record it was already standing on. One per leaf is the
1277    /// shape a streaming walk should have; one per row is the shape a pull
1278    /// cursor had. Diagnostic, so the property can be asserted rather than
1279    /// timed.
1280    seeks: u64,
1281}
1282
1283/// A descending range cursor. Unlike reversing a forward [`RangeIter`], this
1284/// pins one leaf at a time and never materialises the range it is walking.
1285pub struct ReverseRangeIter<'p> {
1286    pool: &'p BufferPool,
1287    tree_id: u16,
1288    page: u32,
1289    /// Exclusive slot bound in the current leaf; the next slot is `idx - 1`.
1290    idx: usize,
1291    done: bool,
1292    path: Vec<(u32, usize)>,
1293    leaves: u32,
1294    max_leaves: u32,
1295    /// Live pin of `page` for [`ReverseRangeIter::peek_ref`], held across
1296    /// successive peeks into the same leaf exactly as the forward cursor holds
1297    /// its own. `for_each_ref` never sets it and is unaffected.
1298    pin: Option<PinnedRead<'p>>,
1299    /// An overflow record resolved out of its chain. It cannot be borrowed
1300    /// from the pinned leaf, so it is parked here and served from here; the
1301    /// slot index has already stepped past it when it lands.
1302    pending: Option<(Vec<u8>, Vec<u8>)>,
1303}
1304
1305impl<'p> BTree<'p> {
1306    pub fn create(
1307        pool: &'p BufferPool,
1308        tree_id: u16,
1309        last_leaf: &'p Cell<Option<u32>>,
1310        fast_path_hits: &'p Cell<u64>,
1311        fast_path_attempts: &'p Cell<u64>,
1312    ) -> Result<Self> {
1313        let mut w = pool.allocate()?;
1314        let no = w.page_no();
1315        // Page 0 is the superblock, and `next_leaf == 0` is how a leaf says it
1316        // has no sibling. A tree page numbered 0 would make that sentinel
1317        // ambiguous, so the caller must have reserved page 0 before creating any
1318        // tree. This is the bootstrap order `Store::build` follows.
1319        assert_ne!(no, crate::meta::META_PAGE, "page 0 is reserved for the superblock");
1320        let mut p = PageMut::init(w.bytes_mut(), PageKind::Leaf, tree_id, no);
1321        p.finalise(0);
1322        drop(w);
1323        Ok(BTree { pool, tree_id, root: no, last_leaf, fast_path_hits, fast_path_attempts, tags: None })
1324    }
1325
1326    pub fn open(
1327        pool: &'p BufferPool,
1328        tree_id: u16,
1329        root: u32,
1330        last_leaf: &'p Cell<Option<u32>>,
1331        fast_path_hits: &'p Cell<u64>,
1332        fast_path_attempts: &'p Cell<u64>,
1333    ) -> Self {
1334        BTree { pool, tree_id, root, last_leaf, fast_path_hits, fast_path_attempts, tags: None }
1335    }
1336
1337    /// Attach the owning handle's per-keyspace append hints. A handle must do
1338    /// this on EVERY tree it opens, not only the ones it means to speed up:
1339    /// the clearing a split or a delete performs happens through this borrow,
1340    /// so a mutation that arrives on an unattached `BTree` would leave a hint
1341    /// standing over a tree whose shape it no longer describes.
1342    pub fn with_tags(mut self, tags: &'p TagHints) -> Self { self.tags = Some(tags); self }
1343
1344    pub fn root(&self) -> u32 { self.root }
1345
1346    /// Drop every per-keyspace hint. Called wherever a separator can move or a
1347    /// page can be recycled.
1348    fn forget_tag_hints(&self) { if let Some(t) = self.tags { t.clear(); } }
1349
1350    /// Re-arm the per-keyspace hint on the page a SPLIT left the record in.
1351    ///
1352    /// Without this a leaf fill costs the run two descents, not one: the probe
1353    /// is refused for want of room and disarms the slot, the descent splits,
1354    /// and then the NEXT key of that run finds no slot and descends again.
1355    /// Measured on the 200K relationship load, forward-edge rows: 6,383 of
1356    /// 99,840 puts found no slot and each paid about 7.4 page accesses --
1357    /// 1.774 accesses per put against 1.36 with this.
1358    ///
1359    /// Sound because a split's halves are SUBintervals of the interval the
1360    /// descent walked: the left page keeps everything below the new separator
1361    /// and the right page everything at or above it, so naming one of them is
1362    /// a narrowing of what was already exact.
1363    fn arm_after_split(&self, key: &[u8], fences: Option<LeafFences>, page: u32, next: u32,
1364        side: impl FnOnce(LeafFences) -> LeafFences) {
1365        if let (Some(tags), Some(f)) = (self.tags, fences) {
1366            tags.arm(self.tree_id, key, page, next, side(f));
1367        }
1368    }
1369
1370    /// Choose the leaf immediately to the left of `min` (or the leftmost leaf
1371    /// when no predecessor exists), copy its encoded records, and allocate the
1372    /// optional right fragment.  Everything allocated here is unreachable;
1373    /// the standing tree is read only until `install_graft` runs after an
1374    /// independent verification.
1375    pub(crate) fn plan_graft(&self, min: &[u8]) -> Result<GraftBoundary> {
1376        self.plan_graft_inner(min, true)
1377    }
1378
1379    /// Reconstruct only the standing-tree path needed to publish a candidate
1380    /// that was already packed before a crash. Its right fragment is already
1381    /// part of that verified candidate, so allocating it again would leak a
1382    /// fresh page on every resume-of-resume.
1383    pub(crate) fn plan_existing_graft(&self, min: &[u8]) -> Result<GraftBoundary> {
1384        self.plan_graft_inner(min, false)
1385    }
1386
1387    fn plan_graft_inner(&self, min: &[u8], allocate_right: bool) -> Result<GraftBoundary> {
1388        // The boundary must be the leaf whose INTERVAL owns `min` — which is
1389        // exactly where `descend(min)` lands. An earlier draft descended to the
1390        // PREDECESSOR key's leaf instead; the two agree everywhere except when
1391        // an interior separator equals `min` — then the predecessor sits one
1392        // child to the LEFT, and installing there pushes the packed range past
1393        // that child's separator. A rebuild makes this real: deleting a grafted
1394        // range empties its leaf but deliberately keeps the leaf and its
1395        // separator, so the next graft of the same range anchored left of a
1396        // stale separator that still claimed the interval — the tree then held
1397        // keys its own root said could not be there, and every mid-range seek
1398        // (a spatial cover, a btree range) silently skipped them while full
1399        // scans still saw them. An index must change speed, never the answer.
1400        let (leaf, path) = self.descend_with_path(min)?;
1401        let (records, split, old_next) = {
1402            let r = self.pool.get(leaf)?;
1403            let page = open_cached(&r, leaf)?;
1404            if page.kind() != PageKind::Leaf || page.tree_id() != self.tree_id {
1405                return Err(Error::Corrupt { page_no: leaf, why: "graft boundary is not a tree leaf" });
1406            }
1407            let split = lower_bound(&page, min)?;
1408            let records = (0..page.nentries()).map(|i| page.slot(i).to_vec()).collect::<Vec<_>>();
1409            (records, split, page.next_leaf())
1410        };
1411
1412        let right_page = if allocate_right && split < records.len() {
1413            let mut w = self.pool.allocate()?;
1414            let no = w.page_no();
1415            let page = build_page(
1416                PageKind::Leaf,
1417                self.tree_id,
1418                no,
1419                &records[split..],
1420                old_next,
1421            )?;
1422            w.bytes_mut().copy_from_slice(&page);
1423            Some(no)
1424        } else {
1425            None
1426        };
1427
1428        Ok(GraftBoundary { leaf, path, records, split, old_next, right_page })
1429    }
1430
1431    /// Assemble the verified unit that will replace the boundary leaf.  The
1432    /// packed pages themselves are not modified: `pack_range` received the
1433    /// right continuation up front, so every final page is written once.
1434    pub(crate) fn build_graft_candidate(
1435        &self,
1436        boundary: &GraftBoundary,
1437        packed: &crate::bulk::PackedRange,
1438    ) -> Result<GraftCandidate> {
1439        let packed_min = packed.min.as_deref().ok_or(Error::TooLarge)?;
1440        let packed_max = packed.max.as_deref().ok_or(Error::TooLarge)?;
1441        let left = &boundary.records[..boundary.split];
1442        let right = &boundary.records[boundary.split..];
1443
1444        let left_page = if left.is_empty() {
1445            None
1446        } else {
1447            let mut w = self.pool.allocate()?;
1448            let no = w.page_no();
1449            let page = build_page(
1450                PageKind::Leaf,
1451                self.tree_id,
1452                no,
1453                left,
1454                packed.first_leaf,
1455            )?;
1456            w.bytes_mut().copy_from_slice(&page);
1457            Some(no)
1458        };
1459
1460        let root = if left_page.is_none() && boundary.right_page.is_none() {
1461            packed.root
1462        } else {
1463            let child0 = left_page.unwrap_or(packed.root);
1464            let mut entries = Vec::with_capacity(2);
1465            if left_page.is_some() {
1466                entries.push(enc_interior(packed_min, packed.root));
1467            }
1468            if let Some(right_page) = boundary.right_page {
1469                let right_min = validated_key(&right[0]);
1470                entries.push(enc_interior(right_min, right_page));
1471            }
1472            let mut w = self.pool.allocate()?;
1473            let no = w.page_no();
1474            let page = build_page(PageKind::Interior, self.tree_id, no, &entries, child0)?;
1475            w.bytes_mut().copy_from_slice(&page);
1476            no
1477        };
1478
1479        let min = left.first()
1480            .map(|record| validated_key(record).to_vec())
1481            .unwrap_or_else(|| packed_min.to_vec());
1482        let max = right.last()
1483            .map(|record| validated_key(record).to_vec())
1484            .unwrap_or_else(|| packed_max.to_vec());
1485        let rows = packed.rows
1486            .checked_add(boundary.records.len() as u64)
1487            .ok_or(Error::TooLarge)?;
1488
1489        Ok(GraftCandidate {
1490            root,
1491            rows,
1492            min,
1493            max,
1494            last_next: boundary.old_next,
1495        })
1496    }
1497
1498    /// Copy the boundary's parent path bottom-up and patch one child pointer
1499    /// in each fresh page. The only logical mutation is replacing the boundary
1500    /// child with the already-verified candidate; no published page is edited.
1501    pub(crate) fn install_graft(
1502        &mut self,
1503        boundary: &GraftBoundary,
1504        candidate_root: u32,
1505        candidate_min: &[u8],
1506    ) -> Result<Vec<u32>> {
1507        let mut replacement = candidate_root;
1508        let mut expected_child = boundary.leaf;
1509        let mut retired = vec![boundary.leaf];
1510        // With no left record retained, this subtree's minimum becomes the
1511        // packed range's minimum. Carry that change through child0 edges; the
1512        // first non-child0 edge owns the separator that names it.
1513        let mut propagate_min = boundary.split == 0;
1514
1515        for &(parent, child_index) in boundary.path.iter().rev() {
1516            let (child0, mut records) = {
1517                let r = self.pool.get(parent)?;
1518                let page = open_cached(&r, parent)?;
1519                if page.kind() != PageKind::Interior || page.tree_id() != self.tree_id {
1520                    return Err(Error::Corrupt { page_no: parent, why: "graft path reaches a non-interior page" });
1521                }
1522                if child_at(self.pool, &page, child_index)? != expected_child {
1523                    return Err(Error::Corrupt { page_no: parent, why: "graft path child changed before publication" });
1524                }
1525                (page.child0(), (0..page.nentries())
1526                    .map(|i| page.slot(i).to_vec()).collect::<Vec<_>>())
1527            };
1528
1529            let mut w = self.pool.allocate()?;
1530            let no = w.page_no();
1531            let next_child0 = if child_index == 0 {
1532                replacement
1533            } else {
1534                let slot = child_index - 1;
1535                let key = if propagate_min {
1536                    candidate_min
1537                } else {
1538                    validated_key(&records[slot])
1539                };
1540                records[slot] = enc_interior(key, replacement);
1541                child0
1542            };
1543            let page = build_page(PageKind::Interior, self.tree_id, no, &records, next_child0)?;
1544            w.bytes_mut().copy_from_slice(&page);
1545            if child_index != 0 { propagate_min = false; }
1546            replacement = no;
1547            expected_child = parent;
1548            retired.push(parent);
1549        }
1550        self.root = replacement;
1551        Ok(retired)
1552    }
1553
1554    /// Pack a sorted run into full pages and splice it into an EMPTY key
1555    /// interval of this tree, instead of inserting its keys one at a time.
1556    ///
1557    /// This is the shape a `CREATE INDEX` has: every key of a new index shares
1558    /// one contiguous keyspace (`[tag][index id]...`) that holds nothing yet,
1559    /// so the run has no existing neighbours to interleave with. SQLite builds
1560    /// the same run into a separate, empty index B-tree with a sorter and
1561    /// `OP_IdxInsert`; E4 has one tree per database (D1), so the equivalent is
1562    /// to pack the pages and graft them in.
1563    ///
1564    /// The four steps:
1565    ///
1566    /// 1. **Refuse a non-empty interval.** One seek to `min` and one key
1567    ///    comparison. A key in `[min, max]` means the caller's assumption is
1568    ///    wrong, and the graft is refused with `RangeNotEmpty` before anything
1569    ///    is allocated or written.
1570    /// 2. **Plan the boundary.** `plan_graft` descends to the leaf whose
1571    ///    interval owns `min` and splits it at the insertion point: records
1572    ///    below `min` become the copied left fragment, records above it the
1573    ///    copied right fragment. Both are fresh pages; the standing leaf is
1574    ///    read only.
1575    /// 3. **Pack.** `pack_range_pooled` fills leaves to 90% and builds the
1576    ///    run's OWN interior levels bottom-up, allocating every page through
1577    ///    the ordinary buffer pool. The right continuation is known before the
1578    ///    last leaf is written, so the leaf chain is correct on the first and
1579    ///    only write of each page.
1580    /// 4. **Verify, then splice.** The packed subtree is walked
1581    ///    (`verify_range_pool`) before any standing page is touched. Then one
1582    ///    two-or-three-child wrapper joins {left fragment, packed root, right
1583    ///    fragment} and `install_graft` copies the O(height) parent path,
1584    ///    replacing exactly one child pointer. The retired page numbers are
1585    ///    returned for the caller to free.
1586    ///
1587    /// **Interior strategy.** The run brings its own interior levels and
1588    /// enters the standing tree as ONE separator. Inserting one separator per
1589    /// packed leaf was the alternative, and it is O(leaves) interior inserts
1590    /// with their own splits -- the cost this call exists to remove -- so the
1591    /// subtree is grafted whole. The cost is that the grafted subtree's height
1592    /// is independent of the standing tree's, so the tree is no longer
1593    /// uniformly deep; `descend_with_path` already reserves for that.
1594    ///
1595    /// **Page LAYOUT is not preserved, the ENTRY SET is.** Which key sits on
1596    /// which page differs from the one-at-a-time insert path (packed leaves
1597    /// are 90% full; split-built leaves are not), and so do page numbers and
1598    /// tree height. Every persisted key and value is identical, which is what
1599    /// `tests/index_build_equivalence.rs` digests.
1600    ///
1601    /// Nothing here bypasses the log or swaps a root: every page is an
1602    /// ordinary pooled page, so a page-WAL store logs each one as a normal
1603    /// frame and publishes the whole graft with the caller's commit. A crash
1604    /// before that commit leaves the standing tree exactly as it was.
1605    pub fn graft_sorted_range<I>(
1606        &mut self,
1607        sorted: I,
1608        expected_rows: u64,
1609        min: &[u8],
1610        max: &[u8],
1611        scratch_dir: &std::path::Path,
1612    ) -> Result<Vec<u32>>
1613    where I: Iterator<Item = Result<(Vec<u8>, Vec<u8>, bool)>> {
1614        if expected_rows == 0 { return Ok(Vec::new()); }
1615        if min > max { return Err(Error::TooLarge); }
1616        // The bounded probe. `range` seeks once; the first key it returns is
1617        // the smallest at or above `min`, so one comparison settles the whole
1618        // interval.
1619        if let Some(row) = self.range(min)?.next() {
1620            let (key, _) = row?;
1621            if key.as_slice() <= max { return Err(Error::RangeNotEmpty); }
1622        }
1623        let boundary = self.plan_graft(min)?;
1624        let last_next = boundary.right_page.unwrap_or(boundary.old_next);
1625        // Fill factor 1.0, not the whole-tree load's 0.9.
1626        //
1627        // The path this replaces is the per-keyspace APPEND split (D9), which
1628        // leaves each completed leaf of an ascending run essentially full. A
1629        // 90% pack would therefore cost about one leaf in ten MORE than
1630        // inserting the same run key by key, and in a page-WAL store every
1631        // extra page is another logged frame and another page folded at the
1632        // next checkpoint -- measured as a 26% frame increase over the insert
1633        // path at 0.9, which moved a whole checkpoint into the next build
1634        // stage. SQLite's `CREATE INDEX` fills its index pages the same way.
1635        //
1636        // SACRIFICE (Law 4): a packed leaf has no slack, so the first live
1637        // write that lands inside one splits it. That is the ordinary split
1638        // path, once per leaf, and the run was just built from data that was
1639        // already there; the alternative was paying the extra page for every
1640        // leaf up front, whether or not anything ever writes to it.
1641        let packed = crate::bulk::pack_range_pooled(
1642            self.pool, self.tree_id, sorted, 1.0, scratch_dir, last_next)?;
1643        // The stream is the caller's; `pack_range_pooled` recomputes all three
1644        // of these from the bytes it actually packed, and a disagreement means
1645        // the interval that was proved empty is not the interval that was
1646        // packed.
1647        if packed.rows != expected_rows
1648            || packed.min.as_deref() != Some(min)
1649            || packed.max.as_deref() != Some(max) {
1650            return Err(Error::Corrupt { page_no: packed.root,
1651                why: "packed range disagrees with its sorted-stream manifest" });
1652        }
1653        crate::verify::verify_range_pool(self.pool, packed.root, self.tree_id,
1654            packed.rows, min, max, last_next)?;
1655        let candidate = self.build_graft_candidate(&boundary, &packed)?;
1656        let candidate_min = candidate.min.clone();
1657        let retired = self.install_graft(&boundary, candidate.root, &candidate_min)?;
1658        // The append hint names a leaf this graft may have just retired, and a
1659        // retired page number can be handed straight back out by the allocator.
1660        // `fast_path_leaf`'s checks are about shape, not identity, so a
1661        // recycled leaf of the SAME tree could pass all five. The hint has no
1662        // reason to survive a graft; drop it rather than rely on those checks.
1663        self.last_leaf.set(None);
1664        self.forget_tag_hints();
1665        Ok(retired)
1666    }
1667
1668    /// Descend to the leaf that would hold `key`, recording the path.
1669    fn descend(&self, key: &[u8]) -> Result<(u32, Vec<u32>)> {
1670        let mut path = Vec::new();
1671        let mut cur = self.root;
1672        loop {
1673            let r = self.pool.get(cur)?;
1674            let p = open_cached(&r, cur)?;
1675            // PageRef::open proves the page is intact and is the page we asked
1676            // for. It cannot know which TREE we meant, and five trees share this
1677            // file, so a stale root would descend into another tree's pages and
1678            // answer confidently from them.
1679            if p.tree_id() != self.tree_id {
1680                return Err(Error::Corrupt { page_no: cur, why: "page belongs to another tree" });
1681            }
1682            if p.kind() == PageKind::Leaf { return Ok((cur, path)); }
1683            // INTERIOR CONVENTION. Entry i is (min_key_i, child_i), and child_i
1684            // holds keys in [min_key_i, min_key_{i+1}). The LEFTMOST child has
1685            // no minimum and lives in the header as child0, so the slot array
1686            // stays strictly sorted and binary search over it is valid.
1687            //
1688            // A sentinel entry with an empty key placed LAST would NOT be sorted
1689            // — the empty string compares smallest — so binary search would
1690            // silently return the wrong child for any key below the first
1691            // separator. That is why child0 is a header field, not a sentinel.
1692            let i = upper_bound(&p, key)?;
1693            let child = child_at(self.pool, &p, i)?;
1694            path.push(cur);
1695            cur = child;
1696        }
1697    }
1698
1699    /// Descend to the leaf for `key` and, while that leaf is still pinned,
1700    /// answer where in it the scan starts.
1701    ///
1702    /// `range` used to take the path from `descend_with_path` and then pin the
1703    /// SAME leaf a second time purely to run `lower_bound` on it. Every scan in
1704    /// the engine paid that second `pool.get` -- a borrow of the pool's table
1705    /// and a hash lookup -- for a page the descent had open one line earlier.
1706    fn descend_positioned(&self, key: &[u8]) -> Result<(u32, usize, Vec<(u32, usize)>)> {
1707        let mut cur = self.root;
1708        let mut path = Vec::with_capacity(8);
1709        loop {
1710            let r = self.pool.get(cur)?;
1711            let p = open_cached(&r, cur)?;
1712            if p.tree_id() != self.tree_id {
1713                return Err(Error::Corrupt { page_no: cur, why: "page belongs to another tree" });
1714            }
1715            if p.kind() == PageKind::Leaf { return Ok((cur, lower_bound(&p, key)?, path)); }
1716            let i = upper_bound(&p, key)?;
1717            let child = child_at(self.pool, &p, i)?;
1718            path.push((cur, i));
1719            cur = child;
1720        }
1721    }
1722
1723    /// Descend to the leaf for `key`, recording the path as (interior page,
1724    /// chosen child index) pairs for the scan cursor's parent-walk advance.
1725    fn descend_with_path(&self, key: &[u8]) -> Result<(u32, Vec<(u32, usize)>)> {
1726        let mut cur = self.root;
1727        // Grafted subtrees can add a shallow wrapper level. Reserve the
1728        // ordinary maximum up front so a bounded query does not acquire one
1729        // extra heap allocation merely because an index was bulk-built.
1730        let mut path = Vec::with_capacity(8);
1731        loop {
1732            let r = self.pool.get(cur)?;
1733            let p = open_cached(&r, cur)?;
1734            if p.tree_id() != self.tree_id {
1735                return Err(Error::Corrupt { page_no: cur, why: "page belongs to another tree" });
1736            }
1737            if p.kind() == PageKind::Leaf { return Ok((cur, path)); }
1738            let i = upper_bound(&p, key)?;
1739            let child = child_at(self.pool, &p, i)?;
1740            path.push((cur, i));
1741            cur = child;
1742        }
1743    }
1744
1745    pub fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>> {
1746        let (leaf, _) = self.descend(key)?;
1747        let r = self.pool.get(leaf)?;
1748        let p = open_cached(&r, leaf)?;
1749        let i = lower_bound(&p, key)?;
1750        if i < p.nentries() {
1751            let rec = p.slot(i);
1752            let (stored_key, value, overflow) = validated_leaf(rec);
1753            if stored_key != key { return Ok(None); }
1754            if overflow {
1755                let marker = value.to_vec();
1756                drop(r); // release the leaf before walking the chain
1757                return Ok(Some(read_overflow(self.pool, &marker)?));
1758            }
1759            return Ok(Some(value.to_vec()));
1760        }
1761        Ok(None)
1762    }
1763
1764    /// Walk root -> leaf taking READ guards on interior pages, dropping each
1765    /// as we step down (interiors are not modified by an insert), and end
1766    /// holding a single WRITE guard on the leaf. That one guard is carried
1767    /// through `insert_into_leaf` and, if needed, `split_leaf_and_insert`,
1768    /// so a logical insert acquires the leaf exactly once instead of the
1769    /// three separate acquisitions the old `descend` + re-`get`/`get_mut`
1770    /// shape required — each of which made the page evictable in between.
1771    fn descend_for_write(&mut self, key: &[u8]) -> Result<(PinnedWrite<'p>, Vec<u32>)> {
1772        let (w, path, _) = self.descend_for_write_fenced(key)?;
1773        Ok((w, path))
1774    }
1775
1776    /// The same descent, also reporting the leaf's FENCES: the separators
1777    /// immediately below and above the leaf it lands on.
1778    ///
1779    /// They are free here. `upper_bound` already chose a child index at every
1780    /// interior page; the separator at that index is the first key of the
1781    /// subtree to the right, and the one before it is the first key of this
1782    /// subtree. The innermost level where the chosen child was not the last
1783    /// gives the tightest upper fence; where it WAS the last, the fence is
1784    /// inherited from further up, and a path that was last-child the whole way
1785    /// down ends at the tree's rightmost leaf, which has no upper fence at all.
1786    ///
1787    /// This is exactly the interval `upper_bound` will route to this leaf, so a
1788    /// key inside it needs no descent to find its page -- which is what
1789    /// `TagHints` remembers.
1790    fn descend_for_write_fenced(&mut self, key: &[u8])
1791        -> Result<(PinnedWrite<'p>, Vec<u32>, LeafFences)> {
1792        // 2f: this is THE shadowed descent. Every page on the write path is
1793        // relocated to a fresh number if it belongs to the published epoch,
1794        // top-down, so by the time any mutation below runs -- the leaf edit,
1795        // and every `get_mut(parent)` a split performs on the `path` pages --
1796        // it lands on this epoch's copies only. The published root and its
1797        // pages stay byte-identical for snapshot readers.
1798        if self.pool.is_frozen(self.root) {
1799            self.root = shadow_page(self.pool, self.root)?;
1800        }
1801        let mut path = Vec::new();
1802        let mut cur = self.root;
1803        let mut fences = LeafFences::NONE;
1804        loop {
1805            let (is_leaf, frozen_child) = {
1806                let r = self.pool.get(cur)?;
1807                let p = open_cached(&r, cur)?;
1808                if p.tree_id() != self.tree_id {
1809                    return Err(Error::Corrupt { page_no: cur, why: "page belongs to another tree" });
1810                }
1811                if p.kind() == PageKind::Leaf {
1812                    (true, None)
1813                } else {
1814                    let i = upper_bound(&p, key)?;
1815                    // Narrow the fences by this level's separators before
1816                    // stepping down. Child `i` holds the keys between slot
1817                    // `i - 1` (inclusive) and slot `i` (exclusive).
1818                    if i > 0 { fences.narrow_lower(validated_key(p.slot(i - 1))); }
1819                    if i < p.nentries() { fences.narrow_upper(validated_key(p.slot(i))); }
1820                    let child = child_at(self.pool, &p, i)?;
1821                    if self.pool.is_frozen(child) {
1822                        (false, Some((i, child)))
1823                    } else {
1824                        path.push(cur);
1825                        cur = child;
1826                        (false, None)
1827                    }
1828                }
1829            }; // `r` dropped here, before either looping again or taking get_mut
1830            if let Some((i, child)) = frozen_child {
1831                let fresh = shadow_page(self.pool, child)?;
1832                patch_child(self.pool, cur, i, fresh)?;
1833                path.push(cur);
1834                cur = fresh;
1835                continue;
1836            }
1837            if is_leaf {
1838                let w = self.pool.get_mut(cur)?;
1839                return Ok((w, path, fences));
1840            }
1841        }
1842    }
1843
1844    /// Try to append `rec` to a leaf a `TagHints` slot named, skipping the
1845    /// descent. The caller has already established, IN MEMORY, that `key` sits
1846    /// inside the fences recorded when the slot was armed; this establishes the
1847    /// rest against the page itself.
1848    ///
1849    /// THE FENCE RULE, in full. `key` may go into this leaf without a descent
1850    /// when all of these hold:
1851    ///   * ROUTING, established by the caller in memory from the cached
1852    ///     fences: lower fence <= `key` < upper fence, the lower inclusive and
1853    ///     the upper STRICTLY exclusive. Strictly, because `upper_bound`
1854    ///     routes a key equal to a separator to the child on its RIGHT: a
1855    ///     record placed here instead stays visible to a scan and disappears
1856    ///     from every `get`. An absent upper fence means the tree's rightmost
1857    ///     leaf, which has no separator above it.
1858    ///   * IDENTITY: a live (unfrozen) leaf page of THIS tree whose
1859    ///     `next_leaf` is still the one the hint recorded. The link is what
1860    ///     makes a split of this very leaf -- the one event that subdivides
1861    ///     the cached interval -- visible without re-descending, and it is
1862    ///     also the evidence that the page number was not recycled underneath
1863    ///     the hint.
1864    ///   * ROOM for the record and its slot, and the leaf is not empty: an
1865    ///     empty leaf is no evidence a key belongs to it, the trap
1866    ///     `fast_path_leaf`'s check 5 documents.
1867    ///   * `key` IS NOT ALREADY THERE. A replacement has to retire the old
1868    ///     record's overflow pages and reclaim its payload bytes, which is
1869    ///     `insert_into_leaf`'s work, not this one's.
1870    ///
1871    /// AND NOTHING ABOUT POSITION. The record goes in at `lower_bound`'s slot,
1872    /// wherever that is, because the fences alone already say the key belongs
1873    /// to this leaf and nowhere else. Two earlier versions of this check asked
1874    /// for more and both were wrong to:
1875    ///   * "after the leaf's LAST record" never fires on a boundary leaf. One
1876    ///     leaf per keyspace boundary holds the end of one tag and the start
1877    ///     of the next, and for the tag below, that leaf is exactly where its
1878    ///     run ends -- its appends land BEFORE the higher tag's records. The
1879    ///     tag that most needs this hint (0x71 forward edges, with 0x72
1880    ///     reverse edges above them) armed nothing at all: measured 0 hits in
1881    ///     200,000 edge puts, 4.40 page accesses each.
1882    ///   * "after the last record OF ITS OWN TAG" fires on a boundary leaf but
1883    ///     not on a near-ascending run. The reverse-edge rows go to
1884    ///     destinations (i+1) and (i+7): the second is the tag's new maximum,
1885    ///     the first is six keys below it, so two puts in three landed before
1886    ///     a record of their own tag and refused a leaf they belonged in.
1887    ///     Measured: 4.357 page accesses per reverse row, unchanged.
1888    ///
1889    /// Returns the slot to insert at.
1890    fn fast_path_tag_leaf(&self, hint: u32, next: u32, key: &[u8], rec: &[u8])
1891        -> Result<Option<(PinnedWrite<'p>, usize)>> {
1892        if self.pool.is_frozen(hint) { return Ok(None); }
1893        let w = self.pool.get_mut(hint)?;
1894        let at = {
1895            let p = PageRef::open_resident(w.bytes(), hint)?;
1896            validate_records(&p)?;
1897            if p.kind() != PageKind::Leaf
1898                || p.tree_id() != self.tree_id
1899                || p.next_leaf() != next
1900                || p.free_space() < rec.len() + 4
1901                || p.nentries() == 0
1902            {
1903                None
1904            } else {
1905                // `lower_bound` puts `at` at the first record >= `key`, so
1906                // inserting there keeps the leaf sorted, and everything before
1907                // it is already strictly below.
1908                let at = lower_bound(&p, key)?;
1909                let fresh = at == p.nentries() || validated_key(p.slot(at)) != key;
1910                fresh.then_some(at)
1911            }
1912        };
1913        Ok(at.map(|at| (w, at)))
1914    }
1915
1916    /// Try to insert directly into the cached last-written leaf, skipping
1917    /// the descent entirely. Every one of these five checks is load-bearing
1918    /// (Task 15 brief): a wrong hint may cost a fallback descent, but must
1919    /// never place a record wrongly or lose one (Law 3). On any failure the
1920    /// write guard, if taken, is dropped here and `None` is returned so the
1921    /// caller falls back to `descend_for_write`.
1922    ///
1923    /// SACRIFICE (Law 4): the hint can be stale (rightmost leaf changed,
1924    /// wrong tree, no room, key not actually the new maximum). Bought: an
1925    /// ascending-key workload — the common case for autoincrementing IDs and
1926    /// time-ordered writes — inserts with zero interior-page traffic instead
1927    /// of a full root-to-leaf walk per row.
1928    fn fast_path_leaf(&self, hint: u32, key: &[u8], rec: &[u8]) -> Result<Option<PinnedWrite<'p>>> {
1929        // check 0 (2f): a frozen hint belongs to the published epoch; fall
1930        // back to the shadowed descent, which relocates it properly.
1931        if self.pool.is_frozen(hint) { return Ok(None); }
1932        let w = self.pool.get_mut(hint)?;
1933        let fits = {
1934            let p = match PageRef::open_resident(w.bytes(), hint) {
1935                Ok(p) => p,
1936                Err(e) => return Err(e),
1937            };
1938            validate_records(&p)?;
1939            p.kind() == PageKind::Leaf                 // check 1
1940                && p.tree_id() == self.tree_id          // check 2
1941                && p.next_leaf() == 0                   // check 3: rightmost
1942                && p.free_space() >= rec.len() + 4       // check 4: room
1943                // check 5: strictly greater. An EMPTY leaf must NOT pass:
1944                // emptiness is not evidence the key belongs here. Delete every
1945                // key and the rightmost leaf is empty while separators still
1946                // route low keys left of it -- keys appended here become
1947                // reachable by scan but not by get (measured: 27/20,000 lost).
1948                && p.nentries() > 0
1949                && key > validated_key(p.slot(p.nentries() - 1))
1950        };
1951        Ok(if fits { Some(w) } else { None })
1952    }
1953
1954    /// Insert `rec` at the end of an already-validated rightmost leaf. Every
1955    /// `fast_path_leaf` check that ran before this was called guarantees
1956    /// `insert_slot` fits, so there is no split path here.
1957    fn insert_fast(&mut self, mut w: PinnedWrite<'p>, rec: Vec<u8>) -> Result<()> {
1958        #[cfg(feature = "write-trace")]
1959        let trace_started = crate::write_trace::active().then(std::time::Instant::now);
1960        let leaf = w.page_no();
1961        let mut p = PageMut::reopen(w.bytes_mut());
1962        let at = p.nentries_pub();
1963        p.insert_slot(at, &rec)?;
1964        #[cfg(feature = "write-trace")]
1965        if trace_started.is_some() { crate::write_trace::value_copy(); }
1966        p.finalise(0);
1967        drop(p);
1968        self.last_leaf.set(Some(leaf));
1969        #[cfg(feature = "write-trace")]
1970        if let Some(started) = trace_started {
1971            crate::write_trace::add(crate::write_trace::Field::LeafInsert, started.elapsed());
1972        }
1973        Ok(())
1974    }
1975
1976    pub fn insert(&mut self, key: &[u8], val: &[u8]) -> Result<()> {
1977        // The one choke point: put(), WAL replay and vacuum all insert here,
1978        // so spilling here means none of them can disagree about when a value
1979        // overflows. The KEY always stays in the leaf.
1980        #[cfg(feature = "write-trace")]
1981        let encode_started = crate::write_trace::active().then(std::time::Instant::now);
1982        let rec = if 4 + key.len() + val.len() > crate::page::MAX_RECORD_LEN {
1983            let (head, crc) = write_overflow(self.pool, val)?;
1984            enc_leaf_marker(key, &enc_marker(val.len() as u32, head, crc))
1985        } else {
1986            enc_leaf(key, val, self.pool.compact_cells())
1987        };
1988        #[cfg(feature = "write-trace")]
1989        if let Some(started) = encode_started {
1990            crate::write_trace::add(crate::write_trace::Field::RecordEncode, started.elapsed());
1991            if 4 + key.len() + val.len() <= crate::page::MAX_RECORD_LEN {
1992                crate::write_trace::value_copy();
1993            }
1994        }
1995        if rec.len() > crate::page::MAX_RECORD_LEN { return Err(Error::TooLarge); }
1996
1997        // Append fast path: try the last leaf we wrote before paying for a
1998        // root-to-leaf descent (PostgreSQL's `RelationGetTargetBlock`).
1999        #[cfg(feature = "write-trace")]
2000        let descent_started = crate::write_trace::active().then(std::time::Instant::now);
2001        // The per-keyspace hint goes first, and when it claims this key the
2002        // whole-tree hint is not probed at all. Probing both would be worse
2003        // than probing neither: `last_leaf` names the rightmost leaf of the
2004        // TREE, so every insert under a lower tag would fail it, disarm it,
2005        // and force the next insert under the top tag to re-descend -- two
2006        // interleaved ascending runs would knock each other's hint out
2007        // forever. Whichever hint owns the key owns the probe.
2008        if let Some(tags) = self.tags {
2009            let claimed = tags.find(self.tree_id, key);
2010            if claimed.is_none() { tags.misses.set(tags.misses.get() + 1); }
2011            if let Some((hint, next)) = claimed {
2012                tags.attempts.set(tags.attempts.get() + 1);
2013                if let Some((w, at)) = self.fast_path_tag_leaf(hint, next, key, &rec)? {
2014                    tags.hits.set(tags.hits.get() + 1);
2015                    #[cfg(feature = "write-trace")]
2016                    if let Some(started) = descent_started {
2017                        crate::write_trace::add(crate::write_trace::Field::Descent, started.elapsed());
2018                    }
2019                    return self.insert_tag_fast(w, at, rec);
2020                }
2021                // Only THIS slot was wrong (the leaf filled, or a key arrived
2022                // out of order inside its own run). The other fifteen describe
2023                // other runs and stay; the descent below re-arms this one.
2024                tags.forget_leaf(self.tree_id, hint);
2025                let (w, path, fence) = self.descend_for_write_fenced(key)?;
2026                #[cfg(feature = "write-trace")]
2027                if let Some(started) = descent_started {
2028                    crate::write_trace::add(crate::write_trace::Field::Descent, started.elapsed());
2029                }
2030                return self.insert_into_leaf(w, path, key, rec, Some(fence));
2031            }
2032        }
2033        if let Some(hint) = self.last_leaf.get() {
2034            self.fast_path_attempts.set(self.fast_path_attempts.get() + 1);
2035            if let Some(w) = self.fast_path_leaf(hint, key, &rec)? {
2036                self.fast_path_hits.set(self.fast_path_hits.get() + 1);
2037                #[cfg(feature = "write-trace")]
2038                if let Some(started) = descent_started {
2039                    crate::write_trace::add(crate::write_trace::Field::Descent, started.elapsed());
2040                }
2041                return self.insert_fast(w, rec);
2042            }
2043            // PostgreSQL's `_bt_search_insert`, on ANY fast-path rejection,
2044            // calls `RelationSetTargetBlock(rel, InvalidBlockNumber)` --
2045            // forgets the hint rather than retrying it next time. Copy that:
2046            // one failure disarms it, so a random-order workload pays for
2047            // exactly one wasted probe (this one) and then nothing more,
2048            // instead of paying a `get_mut` plus a full CRC verify on every
2049            // single insert forever. It is re-armed below, in
2050            // `insert_into_leaf`, only when an insert actually lands on the
2051            // rightmost leaf.
2052            //
2053            // SACRIFICE (Law 4): a workload that keeps switching between
2054            // append order and random order pays one wasted probe at every
2055            // switch -- the disarm is not free, it is just no longer
2056            // permanent.
2057            self.last_leaf.set(None);
2058        }
2059
2060        let (w, path, fence) = self.descend_for_write_fenced(key)?;
2061        #[cfg(feature = "write-trace")]
2062        if let Some(started) = descent_started {
2063            crate::write_trace::add(crate::write_trace::Field::Descent, started.elapsed());
2064        }
2065        self.insert_into_leaf(w, path, key, rec, Some(fence))
2066    }
2067
2068    /// Place a record in a leaf a tag hint named, at the slot the probe
2069    /// found. The slot already describes this leaf, so nothing is re-armed;
2070    /// unlike `insert_fast` this must NOT touch `last_leaf`, because the leaf
2071    /// is rightmost of its own tag, not of the tree.
2072    fn insert_tag_fast(&mut self, mut w: PinnedWrite<'p>, at: usize, rec: Vec<u8>) -> Result<()> {
2073        #[cfg(feature = "write-trace")]
2074        let trace_started = crate::write_trace::active().then(std::time::Instant::now);
2075        let mut p = PageMut::reopen(w.bytes_mut());
2076        p.insert_slot(at, &rec)?;
2077        #[cfg(feature = "write-trace")]
2078        if trace_started.is_some() { crate::write_trace::value_copy(); }
2079        p.finalise(0);
2080        #[cfg(feature = "write-trace")]
2081        if let Some(started) = trace_started {
2082            crate::write_trace::add(crate::write_trace::Field::LeafInsert, started.elapsed());
2083        }
2084        Ok(())
2085    }
2086
2087    fn insert_into_leaf(&mut self, mut w: PinnedWrite<'p>, path: Vec<u32>, key: &[u8], rec: Vec<u8>,
2088        fences: Option<LeafFences>) -> Result<()> {
2089        let leaf = w.page_no();
2090        #[cfg(feature = "write-trace")]
2091        let search_started = crate::write_trace::active().then(std::time::Instant::now);
2092        let (i, exists, is_rightmost, is_next, retired) = {
2093            // Not "just written": this guard came straight from
2094            // `descend_for_write`'s `get_mut`, which never verifies a CRC.
2095            // This is the one read that must.
2096            let p = PageRef::open_resident(w.bytes(), leaf)?;
2097            validate_records(&p)?;
2098            let i = lower_bound(&p, key)?;
2099            let exists = i < p.nentries()
2100                && validated_key(p.slot(i)) == key;
2101            // `descend_for_write` only ever hands back a write guard for a
2102            // page it already confirmed was a leaf (the interior branch
2103            // never breaks out of its loop), so `next_leaf == 0` here means
2104            // "rightmost leaf", not "leftmost child of an interior page" --
2105            // the ambiguity that field carries on a page whose kind hasn't
2106            // been checked yet. Read once, here, before anything below
2107            // mutates the page: neither `remove_slot`/`compact` nor
2108            // `insert_slot` ever touch this field, so it stays valid for the
2109            // whole function.
2110            let is_next = p.next_leaf();
2111            let is_rightmost = is_next == 0;
2112
2113            let retired = if exists { replaced_overflow_pages(self.pool, p.slot(i))? } else { Vec::new() };
2114            (i, exists, is_rightmost, is_next, retired)
2115        };
2116        #[cfg(feature = "write-trace")]
2117        if let Some(started) = search_started {
2118            crate::write_trace::add(crate::write_trace::Field::LeafSearch, started.elapsed());
2119        }
2120
2121        // Closed operation 1: if the key already exists, remove and compact
2122        // as one step, finalising before we ever ask whether the NEW record
2123        // fits. `compact` reclaims the old entry's payload bytes, not just
2124        // its slot; without this as a separate finalised step, an insert
2125        // that then failed to fit would leave the page dirty with a
2126        // checksum matching neither the old contents (already mutated) nor
2127        // the new (never written) — and the next `PageRef::open` on this
2128        // leaf, for ANY key, refuses the whole page.
2129        if exists {
2130            let mut p = PageMut::reopen(w.bytes_mut());
2131            p.remove_slot(i);
2132            p.compact();
2133            p.finalise(0);
2134            drop(p);
2135            for (no, birth) in retired { self.pool.free_shadow_page(no, birth)?; }
2136        }
2137
2138        // Closed operation 2: decide room from the page's REAL free space —
2139        // not a sum over live entry lengths, which diverges from the truth
2140        // the moment anything has ever been deleted from this page — then
2141        // insert as its own finalised step. `i` is still the correct
2142        // insertion point: if the key existed, entry `i` is exactly what we
2143        // just removed, so nothing before or after it moved. No fresh
2144        // `PageRef::open` here: we either just finalised this page
2145        // ourselves (the `exists` branch) or verified it above and have not
2146        // touched it since — both leave the CRC known-good without paying
2147        // for another ~254ns verification pass.
2148        // remove_slot frees the slot entry, not the payload bytes; only
2149        // compact() recovers them. An emptied leaf can report nentries()==0 AND
2150        // free_space()==0 -- empty and full at once -- sending a single record
2151        // to the split path, which cannot halve one record (TooLarge on an
2152        // empty page). Compact and re-check before concluding there is no room.
2153        let mut room = PageMut::reopen(w.bytes_mut()).free_space() >= rec.len() + 4;
2154        if !room {
2155            let mut p = PageMut::reopen(w.bytes_mut());
2156            p.compact();
2157            p.finalise(0);
2158            drop(p);
2159            room = PageMut::reopen(w.bytes_mut()).free_space() >= rec.len() + 4;
2160        }
2161        if room {
2162            #[cfg(feature = "write-trace")]
2163            let insert_started = crate::write_trace::active().then(std::time::Instant::now);
2164            let mut p = PageMut::reopen(w.bytes_mut());
2165            p.insert_slot(i, &rec)?;
2166            #[cfg(feature = "write-trace")]
2167            if insert_started.is_some() { crate::write_trace::value_copy(); }
2168            p.finalise(0);
2169            // Arm only when this write actually landed on the rightmost
2170            // leaf. A descent can insert into ANY leaf -- most of them not
2171            // rightmost -- and re-arming unconditionally (the pre-Task-18
2172            // bug) is what let a random-order workload thrash forever: every
2173            // wrong hint just gets replaced with another wrong hint instead
2174            // of staying disarmed.
2175            if is_rightmost {
2176                self.last_leaf.set(Some(leaf));
2177            }
2178            // Arm the per-keyspace hint whether or not this leaf is the
2179            // tree's rightmost -- that is the whole point -- and wherever in
2180            // the leaf the record landed. The hint says "this tag's writes
2181            // are currently in this leaf, between these two separators",
2182            // which is true of any insert that did not split; the next key of
2183            // the tag is probed against the FENCES, so a hint armed from a
2184            // mid-leaf insert is not a worse guess than one armed from an
2185            // append. It is only not armed when the insert split, because a
2186            // split moves the separator this leaf sits under.
2187            if let (Some(tags), Some(fences)) = (self.tags, fences) {
2188                tags.arm(self.tree_id, key, leaf, is_next, fences);
2189            }
2190            #[cfg(feature = "write-trace")]
2191            if let Some(started) = insert_started {
2192                crate::write_trace::add(crate::write_trace::Field::LeafInsert, started.elapsed());
2193            }
2194            return Ok(());
2195        }
2196        #[cfg(feature = "write-trace")]
2197        let split_started = crate::write_trace::active().then(std::time::Instant::now);
2198        let result = self.split_leaf_and_insert(w, path, i, rec, key, fences);
2199        #[cfg(feature = "write-trace")]
2200        if let Some(started) = split_started {
2201            crate::write_trace::add(crate::write_trace::Field::Split, started.elapsed());
2202            crate::write_trace::value_copy();
2203        }
2204        result
2205    }
2206
2207    /// Split dispatcher. Both bodies take the same decisions and write the
2208    /// same bytes; they differ only in how the records are MOVED (`_vec`
2209    /// copies every record into its own `Vec<u8>`, `_ref` points at them in
2210    /// place). Both are compiled under `cfg(test)` so the byte-equivalence
2211    /// oracle can run one against the other in a single build.
2212    fn split_leaf_and_insert(&mut self, w: PinnedWrite<'p>, path: Vec<u32>, at: usize, rec: Vec<u8>,
2213        key: &[u8], fences: Option<LeafFences>) -> Result<()> {
2214        // ONLY this leaf's fences. A split subdivides the interval of the leaf
2215        // it splits and inserts the new separator inside it; every other
2216        // leaf's interval is untouched, and a root split merely adds a level
2217        // above separators that do not move. Clearing the whole cache here
2218        // instead was measured at 1.410 page accesses per forward-edge insert
2219        // against 1.037 for this: a 0x40 leaf fills every fifteen documents,
2220        // and each of those splits was knocking out the edge run's hint.
2221        // (The neighbour redistribution inside DOES move other leaves'
2222        // separators, and clears their slots itself.)
2223        if let Some(t) = self.tags { t.forget_leaf(self.tree_id, w.page_no()); }
2224        #[cfg(not(feature = "slotref-split"))]
2225        { self.split_leaf_and_insert_vec(w, path, at, rec, key, fences) }
2226        #[cfg(feature = "slotref-split")]
2227        { self.split_leaf_and_insert_ref(w, path, at, rec, key, fences) }
2228    }
2229
2230    #[cfg(any(test, not(feature = "slotref-split")))]
2231    #[allow(clippy::too_many_arguments)]
2232    fn split_leaf_and_insert_vec(&mut self, mut w: PinnedWrite<'p>, mut path: Vec<u32>, at: usize,
2233        rec: Vec<u8>, key: &[u8], fences: Option<LeafFences>) -> Result<()> {
2234        #[cfg(feature = "write-trace")]
2235        if crate::write_trace::active() { crate::write_trace::leaf_split(); }
2236        let leaf = w.page_no();
2237        // Collect, insert into the collected list, then redistribute. Bounded by
2238        // one page, so this is RAM proportional to the change, not the store.
2239        // One `PageRef::open` reads both the entries and `next_leaf` -- the old
2240        // shape spent a second full acquisition-and-verify on the same page just
2241        // to learn one more field of the header it had already opened.
2242        let (mut recs, old_next): (Vec<Vec<u8>>, u32) = {
2243            let p = PageRef::open_resident(w.bytes(), leaf)?;
2244            validate_records(&p)?;
2245            ((0..p.nentries()).map(|j| p.slot(j).to_vec()).collect(), p.next_leaf())
2246        };
2247        recs.insert(at, rec);
2248        // Split AT the insertion point when the left page ends up well filled.
2249        //
2250        // A 50/50 split of an ascending run abandons every left half at 50%
2251        // forever -- measured 0.458 utilisation, the file ~2x its needed size.
2252        // Splitting at the insertion point (SQLite balance_quick, InnoDB
2253        // sequential-insert) closes each left page full. "Append = last in
2254        // leaf" is too narrow when several keyspaces share the tree: the top
2255        // leaf of one space also holds the next space's keys, so ascending
2256        // inserts land BEFORE them, never last.
2257        //
2258        // The >= 2/3 guard is what separates workloads without detecting them:
2259        // ascending leaves the left ~full, scattered ~half -- and unguarded,
2260        // scattered amplification measurably worsened (2955.8 -> 3511.0
2261        // bytes/row), caught by a pinned test, so scattered keeps the
2262        // balanced split.
2263        let usable = PAGE_SIZE - crate::page::HEADER_LEN;
2264        let bytes = |r: &[Vec<u8>]| r.iter().map(|x| x.len() + 4).sum::<usize>();
2265        let left_bytes = bytes(&recs[..at.min(recs.len())]);
2266        // The unambiguous append signal: the new record is LAST in the leaf and
2267        // the leaf is RIGHTMOST (old_next == 0). Local byte-guards could not
2268        // separate the workloads -- left>=2/3 admitted scattered splits (file
2269        // 156 -> 182 MiB), and a small-right-count guard still left it at 174
2270        // (a random key is a new leaf-local max in ~1/nentries of splits, each
2271        // stranding a nearly-empty right page). Rightmost-append never fires
2272        // mid-tree under random keys, and is exactly SQLite's balance_quick
2273        // condition.
2274        //
2275        // `keyspace-append` widens "rightmost" from the TREE to the KEY TAG.
2276        // One collection is three ascending runs in one tree (D3/D4: vector
2277        // 0x60, row 0x40, external-key mapping 0x20), written one document at a
2278        // time, so only the top tag is ever the tree's rightmost leaf and the
2279        // other two never qualified -- see `keyspace_rightmost` below.
2280        let at_tail = at > 0
2281            && at == recs.len() - 1
2282            && left_bytes <= usable
2283            && bytes(&recs[at..]) <= usable;
2284        let at_point = at_tail
2285            && (old_next == 0 || self.keyspace_rightmost(&path, leaf, old_next, &recs[at])?);
2286        // SQLite's balance_nonroot uses neighboring child pages before growing
2287        // the tree. Reuse capacity in at most three existing leaves before
2288        // splitting. An append we have already decided to take skips it: moving
2289        // a closed run's records between siblings is the work this whole path
2290        // exists to avoid, and it cannot add capacity the append does not need.
2291        #[cfg(feature = "sqlite-balance")]
2292        if !at_point && (old_next != 0 || at + 1 != recs.len()) {
2293            if self.redistribute_neighbors_vec(&mut w, &path, &recs, old_next)? {
2294                return Ok(());
2295            }
2296        }
2297        let sp = if at_point {
2298            at
2299        } else if let Some(sp) = split_point(&recs, usable) {
2300            sp
2301        } else {
2302            // A large indivisible record between two existing runs can need
2303            // THREE leaves even though the total is below two-page capacity.
2304            // Greedy byte packing of one old page plus one new record needs
2305            // at most three groups; every individual cell was already bounded.
2306            let mut cuts=vec![0];let mut used=0;
2307            for (i,r) in recs.iter().enumerate() {
2308                if used+r.len()+4>usable {cuts.push(i);used=0;}
2309                used+=r.len()+4;
2310            }
2311            cuts.push(recs.len());
2312            if cuts.len()!=4 || cuts.windows(2).any(|w|w[0]==w[1]) {return Err(Error::TooLarge);}
2313            let mut middle=self.pool.allocate()?;let mid_no=middle.page_no();
2314            let mut right=self.pool.allocate()?;let right_no=right.page_no();
2315            let left_image=build_page(PageKind::Leaf,self.tree_id,leaf,&recs[..cuts[1]],mid_no)?;
2316            let mid_image=build_page(PageKind::Leaf,self.tree_id,mid_no,&recs[cuts[1]..cuts[2]],right_no)?;
2317            let right_image=build_page(PageKind::Leaf,self.tree_id,right_no,&recs[cuts[2]..],old_next)?;
2318            let mid_sep=validated_key(&recs[cuts[1]]).to_vec();let right_sep=validated_key(&recs[cuts[2]]).to_vec();
2319            middle.bytes_mut().copy_from_slice(&mid_image);right.bytes_mut().copy_from_slice(&right_image);w.bytes_mut().copy_from_slice(&left_image);
2320            drop((middle,right,w));self.last_leaf.set(None);
2321            self.insert_separator(&mut path,&mid_sep,mid_no)?;
2322            // The first fence can split its ancestors, so reacquire the second
2323            // fence's path from the new root instead of reusing stale parents.
2324            let (guard,mut new_path)=self.descend_for_write(&right_sep)?;drop(guard);
2325            self.insert_separator(&mut new_path,&right_sep,right_no)?;
2326            if old_next==0 {self.last_leaf.set(Some(right_no));}
2327            if at<cuts[1] {self.arm_after_split(key,fences,leaf,mid_no,|f|f.below(&mid_sep));}
2328            else if at<cuts[2] {self.arm_after_split(key,fences,mid_no,right_no,
2329                |f|f.above(&mid_sep).below(&right_sep));}
2330            else {self.arm_after_split(key,fences,right_no,old_next,|f|f.above(&right_sep));}
2331            return Ok(());
2332        };
2333        let right_recs = recs.split_off(sp);
2334        let sep = validated_key(&right_recs[0]).to_vec();
2335
2336        let right_no = {
2337            let mut rw = self.pool.allocate()?;
2338            let no = rw.page_no();
2339            let page = build_page(PageKind::Leaf, self.tree_id, no, &right_recs, old_next)?;
2340            rw.bytes_mut().copy_from_slice(&page);
2341            no
2342        };
2343
2344        {
2345            // Built before the frame is overwritten, so a failure cannot
2346            // leave the live leaf wiped.
2347            let page = build_page(PageKind::Leaf, self.tree_id, leaf, &recs, right_no)?;
2348            w.bytes_mut().copy_from_slice(&page);
2349        }
2350        drop(w); // release the leaf pin before recursing into the parent chain
2351
2352        // The append hint: meaningful only when this split happened at the
2353        // tail of the leaf chain (`old_next == 0`), in which case `right_no`
2354        // is now the rightmost leaf and the next ascending insert should try
2355        // it first. A split elsewhere in the tree leaves whatever hint
2356        // already existed exactly as valid, or invalid, as it was — so it is
2357        // left untouched rather than being overwritten with a leaf that
2358        // isn't actually rightmost.
2359        if old_next == 0 {
2360            self.last_leaf.set(Some(right_no));
2361        }
2362
2363        self.insert_separator(&mut path, &sep, right_no)?;
2364        if at < sp {
2365            self.arm_after_split(key, fences, leaf, right_no, |f| f.below(&sep));
2366        } else {
2367            self.arm_after_split(key, fences, right_no, old_next, |f| f.above(&sep));
2368        }
2369        Ok(())
2370    }
2371
2372    /// L2.3-2: the same split, with every record left where it already is.
2373    ///
2374    /// Identical to `split_leaf_and_insert_vec` in every decision it takes and
2375    /// every byte it writes -- see the module comment above `SlotRef` and the
2376    /// `split_byte_equivalence` oracle. The difference is that the leaf's
2377    /// records are named by `SlotRef`s into one 4 KiB copy of the page image
2378    /// instead of being copied into one `Vec<u8>` each.
2379    #[cfg(any(test, feature = "slotref-split"))]
2380    #[allow(clippy::too_many_arguments)]
2381    fn split_leaf_and_insert_ref(&mut self, w: PinnedWrite<'p>, path: Vec<u32>, at: usize,
2382        rec: Vec<u8>, key: &[u8], fences: Option<LeafFences>) -> Result<()> {
2383        // Re-entrancy: nothing `split_core` can reach -- `insert_separator`,
2384        // `descend_for_write`, `shadow_page`, `redistribute_neighbors_ref` --
2385        // splits a LEAF, so these three borrows cannot nest. That is what lets
2386        // the separator key stay a borrow into scratch instead of a `Vec`.
2387        SPLIT_LEAF.with(|li| SPLIT_SLOTS.with(|sl| SPLIT_SCRATCH.with(|ss| {
2388            let mut leaf_img = li.borrow_mut();
2389            let mut slots = sl.borrow_mut();
2390            let mut sc = ss.borrow_mut();
2391            self.split_core(w, path, at, &rec, key, fences, &mut leaf_img, &mut slots, &mut sc)
2392        })))
2393    }
2394
2395    #[cfg(any(test, feature = "slotref-split"))]
2396    #[allow(clippy::too_many_arguments)]
2397    fn split_core(
2398        &mut self,
2399        mut w: PinnedWrite<'p>,
2400        mut path: Vec<u32>,
2401        at: usize,
2402        rec: &[u8],
2403        key: &[u8],
2404        fences: Option<LeafFences>,
2405        leaf_img: &mut [u8],
2406        slots: &mut Vec<SlotRef>,
2407        sc: &mut SplitScratch,
2408    ) -> Result<()> {
2409        const P: usize = PAGE_SIZE;
2410        #[cfg(feature = "write-trace")]
2411        if crate::write_trace::active() { crate::write_trace::leaf_split(); }
2412        let leaf = w.page_no();
2413
2414        // One `PageRef::open` reads the records' addresses and `next_leaf`.
2415        // The image is then copied into scratch: the write pin stays held, but
2416        // its bytes cannot be borrowed across the `&mut w` redistribution
2417        // needs, and one 4 KiB memcpy is cheaper than ~86 allocations.
2418        let old_next = {
2419            let p = PageRef::open_resident(w.bytes(), leaf)?;
2420            validate_records(&p)?;
2421            slots.clear();
2422            for i in 0..p.nentries() {
2423                let (off, len) = p.slot_bounds(i);
2424                slots.push(SlotRef { page_idx: frame::LEAF, off, len });
2425            }
2426            p.next_leaf()
2427        };
2428        leaf_img.copy_from_slice(w.bytes());
2429        slots.insert(at, SlotRef { page_idx: frame::REC, off: 0, len: rec.len() as u16 });
2430        let frames: [&[u8]; 2] = [leaf_img, rec];
2431
2432        let usable = P - crate::page::HEADER_LEN;
2433        let bytes = |s: &[SlotRef]| s.iter().map(|x| x.len as usize + 4).sum::<usize>();
2434        let left_bytes = bytes(&slots[..at.min(slots.len())]);
2435        let at_tail = at > 0
2436            && at == slots.len() - 1
2437            && left_bytes <= usable
2438            && bytes(&slots[at..]) <= usable;
2439        let at_point = at_tail
2440            && (old_next == 0 || self.keyspace_rightmost(&path, leaf, old_next, rec)?);
2441
2442        #[cfg(feature = "sqlite-balance")]
2443        if !at_point && (old_next != 0 || at + 1 != slots.len()) {
2444            if self.redistribute_neighbors_ref(&mut w, &path, leaf_img, rec, slots, old_next, sc)? {
2445                return Ok(());
2446            }
2447        }
2448
2449        let sp = if at_point {
2450            at
2451        } else if let Some(sp) = split_point_ref(slots, usable) {
2452            sp
2453        } else {
2454            // The three-way cut: one indivisible record too large to pair with
2455            // its neighbours. Greedy byte packing, exactly as before; anything
2456            // other than two breaks is a record no arrangement can place.
2457            let mut cuts = [0usize; 4];
2458            let mut breaks = 0usize;
2459            let mut used = 0usize;
2460            for (i, s) in slots.iter().enumerate() {
2461                if used + s.len as usize + 4 > usable {
2462                    breaks += 1;
2463                    if breaks <= 2 { cuts[breaks] = i; }
2464                    used = 0;
2465                }
2466                used += s.len as usize + 4;
2467            }
2468            cuts[3] = slots.len();
2469            if breaks != 2 || cuts.windows(2).any(|c| c[0] == c[1]) { return Err(Error::TooLarge); }
2470            let mut middle = self.pool.allocate()?; let mid_no = middle.page_no();
2471            let mut right = self.pool.allocate()?; let right_no = right.page_no();
2472            build_page_into(&mut sc.out[0..P], PageKind::Leaf, self.tree_id, leaf,
2473                slots[..cuts[1]].iter().map(|x| rec_of(&frames, *x)), mid_no)?;
2474            build_page_into(&mut sc.out[P..2 * P], PageKind::Leaf, self.tree_id, mid_no,
2475                slots[cuts[1]..cuts[2]].iter().map(|x| rec_of(&frames, *x)), right_no)?;
2476            build_page_into(&mut sc.out[2 * P..3 * P], PageKind::Leaf, self.tree_id, right_no,
2477                slots[cuts[2]..].iter().map(|x| rec_of(&frames, *x)), old_next)?;
2478            let mid_sep = validated_key(rec_of(&frames, slots[cuts[1]]));
2479            let right_sep = validated_key(rec_of(&frames, slots[cuts[2]]));
2480            middle.bytes_mut().copy_from_slice(&sc.out[P..2 * P]);
2481            right.bytes_mut().copy_from_slice(&sc.out[2 * P..3 * P]);
2482            w.bytes_mut().copy_from_slice(&sc.out[0..P]);
2483            drop((middle, right, w));
2484            self.last_leaf.set(None);
2485            self.insert_separator(&mut path, mid_sep, mid_no)?;
2486            let (guard, mut new_path) = self.descend_for_write(right_sep)?;
2487            drop(guard);
2488            self.insert_separator(&mut new_path, right_sep, right_no)?;
2489            if old_next == 0 { self.last_leaf.set(Some(right_no)); }
2490            if at < cuts[1] { self.arm_after_split(key, fences, leaf, mid_no, |f| f.below(mid_sep)); }
2491            else if at < cuts[2] { self.arm_after_split(key, fences, mid_no, right_no,
2492                |f| f.above(mid_sep).below(right_sep)); }
2493            else { self.arm_after_split(key, fences, right_no, old_next, |f| f.above(right_sep)); }
2494            return Ok(());
2495        };
2496
2497        let sep = validated_key(rec_of(&frames, slots[sp]));
2498        let right_no = {
2499            let mut rw = self.pool.allocate()?;
2500            let no = rw.page_no();
2501            build_page_into(&mut sc.out[P..2 * P], PageKind::Leaf, self.tree_id, no,
2502                slots[sp..].iter().map(|x| rec_of(&frames, *x)), old_next)?;
2503            rw.bytes_mut().copy_from_slice(&sc.out[P..2 * P]);
2504            no
2505        };
2506        // Built before the frame is overwritten, so a failure cannot leave the
2507        // live leaf wiped (btree.rs's atomicity rule for `build_page`).
2508        build_page_into(&mut sc.out[0..P], PageKind::Leaf, self.tree_id, leaf,
2509            slots[..sp].iter().map(|x| rec_of(&frames, *x)), right_no)?;
2510        w.bytes_mut().copy_from_slice(&sc.out[0..P]);
2511        drop(w);
2512        if old_next == 0 { self.last_leaf.set(Some(right_no)); }
2513        self.insert_separator(&mut path, sep, right_no)?;
2514        if at < sp {
2515            self.arm_after_split(key, fences, leaf, right_no, |f| f.below(sep));
2516        } else {
2517            self.arm_after_split(key, fences, right_no, old_next, |f| f.above(sep));
2518        }
2519        Ok(())
2520    }
2521
2522    /// L2.3-2: `redistribute_neighbors` over borrowed records.
2523    ///
2524    /// Same windows in the same order, same `neighbor_cell_cuts`, same fit
2525    /// probe, same shadowing, same guard-before-install order. What changed:
2526    /// the window's records and the parent's separators are `SlotRef`s rather
2527    /// than `Vec<u8>`s, the four page images are built into reusable scratch,
2528    /// and the two or three separators that actually change are written into a
2529    /// small arena instead of one `Vec` each.
2530    ///
2531    /// PINNING. A `SlotRef` is only valid while the frame behind it is pinned,
2532    /// and `get_mut` below demands those same frames be unpinned. So each
2533    /// sibling is read-pinned ONE AT A TIME, validated, copied into scratch,
2534    /// and released; the splitting leaf's write pin is held throughout and its
2535    /// image was copied by the caller for the same reason.
2536    #[cfg(all(feature = "sqlite-balance", any(test, feature = "slotref-split")))]
2537    #[allow(clippy::too_many_arguments)]
2538    fn redistribute_neighbors_ref(
2539        &mut self,
2540        w: &mut PinnedWrite<'p>,
2541        path: &[u32],
2542        leaf_img: &[u8],
2543        rec: &[u8],
2544        current: &[SlotRef],
2545        current_next: u32,
2546        sc: &mut SplitScratch,
2547    ) -> Result<bool> {
2548        const P: usize = PAGE_SIZE;
2549        let Some(&parent_no) = path.last() else { return Ok(false) };
2550        let leaf = w.page_no();
2551        let SplitScratch {
2552            sib, parent, out, seps, win, pr0, pr, sizes, prefix, ends, needed, cuts, ids, newids,
2553        } = sc;
2554
2555        let pos = {
2556            let r = self.pool.get(parent_no)?;
2557            let p = open_cached(&r, parent_no)?;
2558            if p.kind() != PageKind::Interior || p.tree_id() != self.tree_id {
2559                return Err(Error::Corrupt { page_no: parent_no, why: "redistribution parent identity" });
2560            }
2561            ids.clear();
2562            pr0.clear();
2563            ids.push(p.child0());
2564            for i in 0..p.nentries() {
2565                let (off, len) = p.slot_bounds(i);
2566                pr0.push(SlotRef { page_idx: frame::PARENT, off, len });
2567                ids.push(validated_child(p.slot(i)));
2568            }
2569            parent.copy_from_slice(&r[..]);
2570            ids.iter().position(|id| *id == leaf)
2571                .ok_or(Error::Corrupt { page_no: parent_no, why: "redistribution child missing" })?
2572        };
2573        let nchild = ids.len();
2574
2575        let mut windows = [(0usize, 0usize); 3];
2576        let mut nw = 0;
2577        if nchild >= 3 { windows[nw] = (pos.saturating_sub(1).min(nchild - 3), 3); nw += 1; }
2578        if pos + 1 < nchild { windows[nw] = (pos, 2); nw += 1; }
2579        if pos > 0 { windows[nw] = (pos - 1, 2); nw += 1; }
2580        let usable = P - crate::page::HEADER_LEN;
2581
2582        'windows: for &(start, count) in &windows[..nw] {
2583            // Gather the window. One sibling pinned at a time; its image is
2584            // copied into scratch before the pin is dropped.
2585            win.clear();
2586            let mut next = 0u32;
2587            let mut nsib = 0usize;
2588            for k in start..start + count {
2589                let no = ids[k];
2590                if no == leaf {
2591                    win.extend_from_slice(current);
2592                    next = current_next;
2593                    continue;
2594                }
2595                let r = self.pool.get(no)?;
2596                let p = open_cached(&r, no)?;
2597                if p.tree_id() != self.tree_id {
2598                    return Err(Error::Corrupt { page_no: no, why: "redistribution sibling identity" });
2599                }
2600                if p.kind() != PageKind::Leaf {
2601                // A grafted run enters the tree as a SUBTREE under a single
2602                // separator, so a leaf's sibling under the same parent can be
2603                // an interior page: a tree with a graft in it is no longer
2604                // uniformly deep. Redistribution moves leaf CELLS between
2605                // neighbours and has nothing to say about a subtree, so skip
2606                // this window and let the ordinary split run -- it never
2607                // needed the neighbours. Refusing here turned an ordinary
2608                // insert next to a bulk-built index into `Corrupt`, with
2609                // nothing actually corrupt (kernel/tests/range_graft.rs).
2610                    continue 'windows;
2611                }
2612                let idx = if nsib == 0 { frame::SIB0 } else { frame::SIB1 };
2613                sib[nsib * P..nsib * P + P].copy_from_slice(&r[..]);
2614                for i in 0..p.nentries() {
2615                    let (off, len) = p.slot_bounds(i);
2616                    win.push(SlotRef { page_idx: idx, off, len });
2617                }
2618                next = p.next_leaf();
2619                nsib += 1;
2620            }
2621
2622            sizes.clear();
2623            sizes.extend(win.iter().map(|s| s.len as usize + 4));
2624            if !neighbor_cell_cuts_into(sizes, usable, count, prefix, ends, needed, cuts) { continue; }
2625            let groups = cuts.len() - 1;
2626            if groups > count { continue; }
2627            // `neighbor_cell_cuts` returns `max(existing, needed)` groups and
2628            // the line above rejects more than `existing`, so the window is
2629            // always repacked into exactly as many pages as it already had.
2630            debug_assert_eq!(groups, count);
2631
2632            newids.clear();
2633            newids.extend_from_slice(ids);
2634            pr.clear();
2635            pr.extend_from_slice(pr0);
2636
2637            // The separators this window rewrites, into the arena. Children
2638            // are the pre-shadow page numbers for now: the fit probe below
2639            // depends on the RECORD LENGTHS, which shadowing cannot change,
2640            // and shadowing must not happen before a window can still be
2641            // rejected.
2642            let mut arena = 0usize;
2643            {
2644                let f: [&[u8]; 5] = [leaf_img, rec, &sib[..P], &sib[P..2 * P], parent];
2645                for j in 1..groups {
2646                    let key = validated_key(rec_of(&f, win[cuts[j]]));
2647                    pr[start + j - 1] = push_sep(seps, &mut arena, key, newids[start + j]);
2648                }
2649                if start > 0 {
2650                    // The separator to the window's LEFT keeps its key and is
2651                    // re-pointed at whatever page now starts the window.
2652                    let key = validated_key(rec_of(&f, pr0[start - 1]));
2653                    pr[start - 1] = push_sep(seps, &mut arena, key, newids[start]);
2654                }
2655            }
2656
2657            {
2658                let f: [&[u8]; 6] = [leaf_img, rec, &sib[..P], &sib[P..2 * P], parent, seps];
2659                match build_page_into(&mut out[3 * P..4 * P], PageKind::Interior, self.tree_id,
2660                        parent_no, pr.iter().map(|x| rec_of(&f, *x)), ids[0]) {
2661                    Ok(()) => {}
2662                    Err(Error::TooLarge) => continue,
2663                    Err(e) => return Err(e),
2664                }
2665            }
2666
2667            // Past every `continue`: from here the window is committed, so
2668            // page-allocating side effects are allowed.
2669            for k in start..start + count {
2670                if newids[k] != leaf && self.pool.is_frozen(newids[k]) {
2671                    newids[k] = shadow_page(self.pool, newids[k])?;
2672                }
2673            }
2674            for j in 1..groups { patch_sep_child(seps, pr[start + j - 1], newids[start + j]); }
2675            if start > 0 { patch_sep_child(seps, pr[start - 1], newids[start]); }
2676
2677            {
2678                let f: [&[u8]; 6] = [leaf_img, rec, &sib[..P], &sib[P..2 * P], parent, seps];
2679                for j in 0..groups {
2680                    let no = newids[start + j];
2681                    let nx = if j + 1 < groups { newids[start + j + 1] } else { next };
2682                    build_page_into(&mut out[j * P..(j + 1) * P], PageKind::Leaf, self.tree_id, no,
2683                        win[cuts[j]..cuts[j + 1]].iter().map(|x| rec_of(&f, *x)), nx)?;
2684                }
2685                build_page_into(&mut out[3 * P..4 * P], PageKind::Interior, self.tree_id, parent_no,
2686                    pr.iter().map(|x| rec_of(&f, *x)), newids[0])?;
2687            }
2688
2689            // Acquire every fallible guard before installing any new image.
2690            let mut guards: [Option<(usize, PinnedWrite<'p>)>; 3] = [None, None, None];
2691            let mut g = 0;
2692            for j in 0..groups {
2693                if newids[start + j] != leaf {
2694                    guards[g] = Some((j, self.pool.get_mut(newids[start + j])?));
2695                    g += 1;
2696                }
2697            }
2698            let mut pw = self.pool.get_mut(parent_no)?;
2699            for slot in guards.iter_mut().flatten() {
2700                let j = slot.0;
2701                slot.1.bytes_mut().copy_from_slice(&out[j * P..(j + 1) * P]);
2702            }
2703            let me = pos - start;
2704            w.bytes_mut().copy_from_slice(&out[me * P..(me + 1) * P]);
2705            pw.bytes_mut().copy_from_slice(&out[3 * P..4 * P]);
2706            self.last_leaf.set(None);
2707            // Redistribution rewrote the separators BETWEEN these siblings, so
2708            // a hint on any of them describes an interval that has moved --
2709            // and unlike a split it can leave the sibling chain intact, so the
2710            // page-side check would not notice. Only the window is affected;
2711            // the rest of the cache is still exact.
2712            if let Some(t) = self.tags {
2713                for j in 0..groups { t.forget_leaf(self.tree_id, newids[start + j]); }
2714            }
2715            return Ok(true);
2716        }
2717        Ok(false)
2718    }
2719
2720    /// Is this leaf the rightmost one of the NEW RECORD'S OWN key tag?
2721    ///
2722    /// D9's append split fires only at the tree's rightmost leaf. That is one
2723    /// leaf in the whole file, and D4 makes every feature a key tag inside the
2724    /// same tree, so a store with several ascending runs gets the append split
2725    /// for exactly one of them. `src/collections/mod.rs` writes one document as
2726    /// three keys -- 0x60 vector, 0x40 row, 0x20 mapping -- each ascending in
2727    /// itself; the 0x40 and 0x20 runs always have a higher-tag leaf to their
2728    /// right, so both paid `redistribute_neighbors` (a clone of the parent's
2729    /// records plus up to three siblings' records, then up to four rebuilt page
2730    /// images) on every single leaf fill. A CPU sample of a 100K typed load put
2731    /// 23.3% of load time inside `split_leaf_and_insert`, about 45% of that in
2732    /// malloc/clone/free and 26% in `build_page` + `neighbor_cell_cuts`.
2733    ///
2734    /// The widened signal keeps D9's discipline exactly. The caller has already
2735    /// established that the new record is STRICTLY the largest in this leaf; so
2736    /// if the first key to this leaf's right carries a different tag, there is
2737    /// no key of this tag anywhere to the right, and the insert is an append to
2738    /// its own run in the same unambiguous sense D9 requires. A scattered
2739    /// workload still cannot reach here: a random key is the leaf-local maximum
2740    /// only rarely, and when it is, the tag test is the same one D9 already
2741    /// trusted.
2742    ///
2743    /// THE BOUNDARY LEAF. One leaf per keyspace holds the last key of one tag
2744    /// and the first key of the next. An ascending insert into that leaf lands
2745    /// BEFORE the higher tag's records, so `at == recs.len() - 1` is false and
2746    /// this is never consulted: the boundary leaf keeps the balanced split and
2747    /// the neighbour redistribution, unchanged. The shortcut costs one such
2748    /// leaf per keyspace boundary, which is where it belongs.
2749    ///
2750    /// The separator sitting in the parent ALREADY ON THE DESCENT PATH is this
2751    /// leaf's right neighbour's first key, so the ordinary case reads no page
2752    /// the insert had not already read. Only a leaf that is its parent's last
2753    /// child needs the sibling itself, and that leaf's right neighbour lives
2754    /// under another parent.
2755    ///
2756    /// SACRIFICE (Law 4): one buffer-pool `get` of the parent per split of a
2757    /// full leaf whose new record is its maximum, and for the last child of a
2758    /// parent one `get` of the right sibling, which may be a page read. Splits
2759    /// are one insert in tens; redistribution already opened this same parent.
2760    #[cfg(feature = "keyspace-append")]
2761    fn keyspace_rightmost(&self, path: &[u32], leaf: u32, old_next: u32, rec: &[u8]) -> Result<bool> {
2762        let Some(&tag) = validated_key(rec).first() else { return Ok(false) };
2763        // The parent separator is free: this descent just walked through it.
2764        if let Some(&parent) = path.last() {
2765            let r = self.pool.get(parent)?;
2766            let p = open_cached(&r, parent)?;
2767            if p.kind() == PageKind::Interior && p.tree_id() == self.tree_id {
2768                let n = p.nentries();
2769                let pos = if p.child0() == leaf {
2770                    Some(0)
2771                } else {
2772                    (0..n).find(|&i| validated_child(p.slot(i)) == leaf).map(|i| i + 1)
2773                };
2774                // `pos == n` is the parent's last child: its right neighbour is
2775                // under a different parent, so fall through to the sibling.
2776                if let Some(pos) = pos {
2777                    if pos < n {
2778                        return Ok(validated_key(p.slot(pos)).first() != Some(&tag));
2779                    }
2780                }
2781            }
2782        }
2783        let r = self.pool.get(old_next)?;
2784        let p = open_cached(&r, old_next)?;
2785        if p.kind() != PageKind::Leaf || p.tree_id() != self.tree_id || p.nentries() == 0 {
2786            return Ok(false);
2787        }
2788        Ok(validated_key(p.slot(0)).first() != Some(&tag))
2789    }
2790
2791    #[cfg(not(feature = "keyspace-append"))]
2792    fn keyspace_rightmost(&self, _path: &[u32], _leaf: u32, _next: u32, _rec: &[u8]) -> Result<bool> {
2793        Ok(false)
2794    }
2795
2796    /// Reuse neighboring capacity without adding a leaf to the window. If all
2797    /// neighbors are full, use the ordinary one-to-two split instead. This
2798    /// avoids rewriting unrelated full siblings merely to allocate a new leaf.
2799    /// New images are built before edits; frozen siblings are shadowed before
2800    /// changing their records. Only this parent and its children participate.
2801    #[cfg(feature = "sqlite-balance")]
2802    #[cfg(any(test, not(feature = "slotref-split")))]
2803    fn redistribute_neighbors_vec(&mut self, w: &mut PinnedWrite<'p>, path: &[u32], current: &[Vec<u8>], current_next: u32) -> Result<bool> {
2804        let Some(&parent)=path.last() else{return Ok(false);};
2805        let leaf=w.page_no();
2806        let (parent_recs, child_ids, pos)={
2807            let r=self.pool.get(parent)?;let p=open_cached(&r,parent)?;
2808            if p.kind()!=PageKind::Interior || p.tree_id()!=self.tree_id{return Err(Error::Corrupt{page_no:parent,why:"redistribution parent identity"});}
2809            let recs:Vec<Vec<u8>>=(0..p.nentries()).map(|i|p.slot(i).to_vec()).collect();
2810            let mut ids=vec![p.child0()];ids.extend(recs.iter().map(|r|validated_child(r)));
2811            let pos=ids.iter().position(|id|*id==leaf).ok_or(Error::Corrupt{page_no:parent,why:"redistribution child missing"})?;
2812            (recs,ids,pos)
2813        };
2814        let mut windows=Vec::new();
2815        if child_ids.len()>=3{windows.push((pos.saturating_sub(1).min(child_ids.len()-3),3));}
2816        if pos+1<child_ids.len(){windows.push((pos,2));}
2817        if pos>0{windows.push((pos-1,2));}
2818        let usable=PAGE_SIZE-crate::page::HEADER_LEN;
2819        'windows: for (start,count) in windows{
2820            let mut all=Vec::new();let mut next=0;
2821            for &no in &child_ids[start..start+count]{
2822                if no==leaf{all.extend(current.iter().cloned());next=current_next;}
2823                else{let r=self.pool.get(no)?;let p=open_cached(&r,no)?;
2824                    if p.tree_id()!=self.tree_id{return Err(Error::Corrupt{page_no:no,why:"redistribution sibling identity"});}
2825                    if p.kind()!=PageKind::Leaf{
2826                    // A grafted run enters the tree as a SUBTREE under a
2827                    // single separator, so a leaf's sibling under the same
2828                    // parent can be an interior page: a tree with a graft in
2829                    // it is no longer uniformly deep. Redistribution moves
2830                    // leaf CELLS between neighbours and has nothing to say
2831                    // about a subtree, so skip this window and let the
2832                    // ordinary split run -- it never needed the neighbours.
2833                    // Refusing here turned an ordinary insert next to a
2834                    // bulk-built index into `Corrupt`, with nothing actually
2835                    // corrupt (kernel/tests/range_graft.rs).
2836                        continue 'windows;
2837                    }
2838                    all.extend((0..p.nentries()).map(|i|p.slot(i).to_vec()));next=p.next_leaf();}
2839            }
2840            let sizes:Vec<usize>=all.iter().map(|r|r.len()+4).collect();
2841            let Some(cuts)=neighbor_cell_cuts(&sizes,usable,count) else { continue; };
2842            let groups=cuts.len()-1;
2843            if groups>count { continue; }
2844            let mut ids=child_ids.clone();
2845            let mut pr=parent_recs.clone();
2846            if groups>count{ids.insert(start+count,0);pr.insert(start+count-1,enc_interior(validated_key(&all[cuts[count]]),0));}
2847            for j in 1..groups{pr[start+j-1]=enc_interior(validated_key(&all[cuts[j]]),ids[start+j]);}
2848            match build_page(PageKind::Interior,self.tree_id,parent,&pr,child_ids[0]){Ok(_)=>{},Err(Error::TooLarge)=>continue,Err(e)=>return Err(e)}
2849            if groups>count{let mut fresh=self.pool.allocate()?;let no=fresh.page_no();let mut p=PageMut::init(fresh.bytes_mut(),PageKind::Leaf,self.tree_id,no);p.finalise(0);ids[start+count]=no;}
2850            for id in &mut ids[start..start+count]{if *id!=leaf && self.pool.is_frozen(*id){*id=shadow_page(self.pool,*id)?;}}
2851            let mut images=Vec::new();
2852            for j in 0..groups{
2853                let no=ids[start+j];let next=if j+1<groups{ids[start+j+1]}else{next};
2854                images.push(build_page(PageKind::Leaf,self.tree_id,no,&all[cuts[j]..cuts[j+1]],next)?);
2855                if start+j>0{
2856                    let k=if j>0{validated_key(&all[cuts[j]]).to_vec()}else{validated_key(&pr[start+j-1]).to_vec()};
2857                    pr[start+j-1]=enc_interior(&k,no);
2858                }
2859            }
2860            let parent_image=build_page(PageKind::Interior,self.tree_id,parent,&pr,ids[0])?;
2861            // Acquire every fallible guard before installing any new image.
2862            let mut guards=Vec::new();for j in 0..groups{if ids[start+j]!=leaf{guards.push((j,self.pool.get_mut(ids[start+j])?));}}
2863            let mut pw=self.pool.get_mut(parent)?;
2864            for (j,guard) in &mut guards{guard.bytes_mut().copy_from_slice(&images[*j]);}
2865            w.bytes_mut().copy_from_slice(&images[pos-start]);pw.bytes_mut().copy_from_slice(&parent_image);
2866            self.last_leaf.set(None);
2867            // See `redistribute_neighbors_ref`: the window's separators moved.
2868            if let Some(t)=self.tags {for j in 0..groups {t.forget_leaf(self.tree_id,ids[start+j]);}}
2869            return Ok(true);
2870        }
2871        Ok(false)
2872    }
2873
2874    fn insert_separator(&mut self, path: &mut Vec<u32>, sep: &[u8], right: u32) -> Result<()> {
2875        let Some(parent) = path.pop() else {
2876            // The root split: build a new root above the old one.
2877            let left = self.root;
2878            let mut w = self.pool.allocate()?;
2879            let no = w.page_no();
2880            let mut p = PageMut::init(w.bytes_mut(), PageKind::Interior, self.tree_id, no);
2881            p.set_child0(left);                       // the old root
2882            p.insert_slot(0, &enc_interior(sep, right))?;
2883            p.finalise(0);
2884            drop(w);
2885            self.root = no;
2886            return Ok(());
2887        };
2888
2889        let rec = enc_interior(sep, right);
2890        let (i, room) = {
2891            let r = self.pool.get(parent)?;
2892            let p = PageRef::open_resident(&r, parent)?;
2893            validate_records(&p)?;
2894            let i = upper_bound(&p, sep)?;
2895            // Interior pages never have entries removed in this task, so
2896            // this figure cannot yet have diverged from a summed
2897            // reconstruction — but the real free_space() is what's actually
2898            // true, and using it here keeps both room checks in the file
2899            // computing the same thing the same way.
2900            (i, p.free_space() >= rec.len() + 4)
2901        };
2902
2903        if room {
2904            let mut w = self.pool.get_mut(parent)?;
2905            let mut p = PageMut::reopen(w.bytes_mut());
2906            // (sep, right) slots in ahead of the first entry whose key
2907            // exceeds sep. Whatever pointer already reached the left half still
2908            // reaches it, because the left half kept its page number.
2909            p.insert_slot(i, &rec)?;
2910            p.finalise(0);
2911            return Ok(());
2912        }
2913
2914        // Split the interior page the same way.
2915        #[cfg(feature = "write-trace")]
2916        if crate::write_trace::active() { crate::write_trace::interior_split(); }
2917        let mut recs: Vec<Vec<u8>> = {
2918            let r = self.pool.get(parent)?;
2919            let p = PageRef::open_resident(&r, parent)?;
2920            validate_records(&p)?;
2921            (0..p.nentries()).map(|j| p.slot(j).to_vec()).collect()
2922        };
2923        recs.insert(i, rec);
2924        let old_child0 = {
2925            let r = self.pool.get(parent)?;
2926            let p = PageRef::open_resident(&r, parent)?;
2927            validate_records(&p)?;
2928            p.child0()
2929        };
2930        let sp = split_point(&recs, PAGE_SIZE - crate::page::HEADER_LEN)
2931            .ok_or(Error::TooLarge)?;
2932        let right_recs = recs.split_off(sp);
2933        // The right page's FIRST entry is promoted out of the slot array: its
2934        // key becomes the separator pushed up, and its child becomes the right
2935        // page's child0. This is the standard interior split.
2936        let up = validated_key(&right_recs[0]).to_vec();
2937        let right_child0 = validated_child(&right_recs[0]);
2938
2939        let right_no = {
2940            let mut w = self.pool.allocate()?;
2941            let no = w.page_no();
2942            let page = build_page(
2943                PageKind::Interior, self.tree_id, no, &right_recs[1..], right_child0)?;
2944            w.bytes_mut().copy_from_slice(&page);
2945            no
2946        };
2947        {
2948            let page = build_page(
2949                PageKind::Interior, self.tree_id, parent, &recs, old_child0)?;
2950            let mut w = self.pool.get_mut(parent)?;
2951            w.bytes_mut().copy_from_slice(&page);
2952        }
2953        self.insert_separator(path, &up, right_no)
2954    }
2955
2956    /// Delete one record and maintain sparsely occupied siblings locally.
2957    /// Scratch is bounded by two children plus their parent; published pages
2958    /// remain protected by the ordinary copy-on-write retirement protocol.
2959    pub fn delete(&mut self, key: &[u8]) -> Result<bool> {
2960        // A delete can empty a leaf, merge two, collapse the root, or free a
2961        // page that a later allocation hands to another leaf of this same
2962        // tree. The hinted fences survive none of that, and the page-identity
2963        // checks cannot tell a recycled leaf from the one that was armed.
2964        self.forget_tag_hints();
2965        // Read-only presence probe first, so a miss never shadows anything.
2966        let (leaf, _) = self.descend(key)?;
2967        let retired = {
2968            let r = self.pool.get(leaf)?;
2969            let p = open_cached(&r, leaf)?;
2970            let i = lower_bound(&p, key)?;
2971            if i >= p.nentries()
2972                || validated_key(p.slot(i)) != key
2973            {
2974                return Ok(false);
2975            }
2976            replaced_overflow_pages(self.pool, p.slot(i))?
2977        };
2978        // Hit: take the shadowed write descent (2f) and remove there.
2979        let (mut w, path) = self.descend_for_write(key)?;
2980        let i = {
2981            let pr = PageRef::open_resident_validated(w.bytes(), w.page_no())?;
2982            lower_bound(&pr, key)?
2983        };
2984        let mut p = PageMut::reopen(w.bytes_mut());
2985        p.remove_slot(i);
2986        p.finalise(0);
2987        drop(p);
2988        let leaf = w.page_no();
2989        drop(w);
2990        for (no, birth) in retired { self.pool.free_shadow_page(no, birth)?; }
2991        self.rebalance_after_delete(leaf, path)?;
2992        Ok(true)
2993    }
2994
2995    /// At most two sibling images and their parent per level. Merge when they
2996    /// fit; otherwise redistribute only below one-third occupancy. The gap to
2997    /// half occupancy provides hysteresis for alternating delete/reinsert.
2998    fn rebalance_after_delete(&mut self, mut node: u32, mut path: Vec<u32>) -> Result<()> {
2999        let usable = PAGE_SIZE - crate::page::HEADER_LEN;
3000        loop {
3001            let (kind, used, entries, only_child, node_birth) = {
3002                let r = self.pool.get(node)?; let p = open_cached(&r, node)?;
3003                if p.tree_id() != self.tree_id || !matches!(p.kind(), PageKind::Leaf | PageKind::Interior) {
3004                    return Err(Error::Corrupt { page_no: node, why: "delete maintenance node identity" });
3005                }
3006                (p.kind(), (0..p.nentries()).map(|i|p.slot(i).len()+4).sum::<usize>(), p.nentries(), p.child0(), p.lsn())
3007            };
3008            if node == self.root {
3009                if kind == PageKind::Interior && entries == 0 {
3010                    // Verify the surviving child before unlinking the old root.
3011                    let r = self.pool.get(only_child)?; let child = open_cached(&r, only_child)?;
3012                    if child.tree_id()!=self.tree_id || !matches!(child.kind(),PageKind::Leaf|PageKind::Interior) {
3013                        return Err(Error::Corrupt {page_no:only_child,why:"collapsed root child identity"});
3014                    }
3015                    drop(r);
3016                    let birth=if self.pool.is_frozen(node) {node_birth}else{self.pool.write_generation()};
3017                    self.pool.free_shadow_page(node, birth)?;
3018                    self.root = only_child; self.last_leaf.set(None); node = only_child;
3019                    continue;
3020                }
3021                return Ok(());
3022            }
3023            if used >= usable / 3 { return Ok(()); }
3024            let parent = path.pop().ok_or(Error::Corrupt {page_no:node,why:"delete maintenance missing parent"})?;
3025            let (parent_recs, ids, pos) = {
3026                let r=self.pool.get(parent)?;let p=open_cached(&r,parent)?;
3027                if p.kind()!=PageKind::Interior || p.tree_id()!=self.tree_id {
3028                    return Err(Error::Corrupt {page_no:parent,why:"delete maintenance parent identity"});
3029                }
3030                let recs:Vec<Vec<u8>>=(0..p.nentries()).map(|i|p.slot(i).to_vec()).collect();
3031                let ids:Vec<u32>=(0..=p.nentries()).map(|i|child_at(self.pool,&p,i)).collect::<Result<_>>()?;
3032                let pos=ids.iter().position(|id|*id==node).ok_or(Error::Corrupt {page_no:parent,why:"delete maintenance child missing"})?;
3033                (recs,ids,pos)
3034            };
3035            if ids.len()==1 { node=parent; continue; }
3036            // Prefer a merge in either direction over rewriting two pages.
3037            let mut starts=Vec::with_capacity(2);
3038            if pos>0 {starts.push(pos-1);} if pos+1<ids.len() {starts.push(pos);}
3039            let mut changed=false;
3040            'attempt: for merge_only in [true,false] {
3041                for &start in &starts {
3042                    let left=ids[start];let right=ids[start+1];
3043                    // A grafted run enters the tree as a SUBTREE under one
3044                    // separator, so two children of the same parent need not
3045                    // be the same kind any more: an underfull leaf can sit
3046                    // beside a bulk-built subtree root. Merging or
3047                    // redistributing across that boundary is not defined --
3048                    // their records are not the same shape and they are not
3049                    // the same height -- so this PAIR is declined and the next
3050                    // candidate tried. Refusing outright failed an ordinary
3051                    // delete next to a bulk-built index with `Corrupt` and
3052                    // nothing corrupt; a declined pair only leaves a node
3053                    // underfull, which `!changed` already tolerates.
3054                    let (mut all, left_link) = {
3055                        let r=self.pool.get(left)?;let p=open_cached(&r,left)?;
3056                        if p.tree_id()!=self.tree_id {return Err(Error::Corrupt {page_no:left,why:"delete left sibling identity"});}
3057                        if p.kind()!=kind {continue;}
3058                        ((0..p.nentries()).map(|i|p.slot(i).to_vec()).collect::<Vec<_>>(),p.next_leaf())
3059                    };
3060                    let (right_link, right_birth) = {
3061                        let r=self.pool.get(right)?;let p=open_cached(&r,right)?;
3062                        if p.tree_id()!=self.tree_id {return Err(Error::Corrupt {page_no:right,why:"delete right sibling identity"});}
3063                        if p.kind()!=kind {continue;}
3064                        if kind==PageKind::Interior {all.push(enc_interior(validated_key(&parent_recs[start]),p.child0()));}
3065                        all.extend((0..p.nentries()).map(|i|p.slot(i).to_vec()));(p.next_leaf(),p.lsn())
3066                    };
3067                    let total:usize=all.iter().map(|r|r.len()+4).sum();let merge=total<=usable;
3068                    if merge_only!=merge {continue;}
3069                    let mut pr=parent_recs.clone();let mut children=ids.clone();
3070                    let (left_recs,right_recs,right_child0)=if merge {
3071                        pr.remove(start);children.remove(start+1);(all.clone(),Vec::new(),0)
3072                    } else if kind==PageKind::Leaf {
3073                        let Some(mid)=split_point(&all,usable) else {continue;};
3074                        pr[start]=enc_interior(validated_key(&all[mid]),right);
3075                        (all[..mid].to_vec(),all[mid..].to_vec(),right_link)
3076                    } else {
3077                        let mut prefix=0usize;let mut best=None;
3078                        for (i,rec) in all.iter().enumerate() {
3079                            let suffix=total-prefix-rec.len()-4;
3080                            if prefix<=usable && suffix<=usable {
3081                                let delta=prefix.abs_diff(suffix);
3082                                if best.is_none_or(|(_,old)|delta<old) {best=Some((i,delta));}
3083                            }
3084                            prefix+=rec.len()+4;
3085                        }
3086                        let Some((mid,_))=best else {continue;};
3087                        pr[start]=enc_interior(validated_key(&all[mid]),right);
3088                        (all[..mid].to_vec(),all[mid+1..].to_vec(),validated_child(&all[mid]))
3089                    };
3090                    // A longer replacement fence can overflow a variable-key
3091                    // parent. Decline that redistribution without touching it.
3092                    match build_page(PageKind::Interior,self.tree_id,parent,&pr,children[0]) {
3093                        Ok(_)=>{},Err(Error::TooLarge)=>continue,Err(e)=>return Err(e),
3094                    }
3095                    let left_next=if kind==PageKind::Interior {left_link}else if merge {right_link}else{right};
3096                    let mut left_image=build_page(kind,self.tree_id,left,&left_recs,left_next)?;
3097                    let mut right_image=if merge {None}else{Some(build_page(kind,self.tree_id,right,&right_recs,right_child0)?)};
3098                    // Shadow only pages that will be rewritten; a merged-away
3099                    // frozen sibling is retired directly, never overwritten.
3100                    let fresh_left=if self.pool.is_frozen(left) {shadow_page(self.pool,left)?}else{left};
3101                    let fresh_right=if !merge && self.pool.is_frozen(right) {shadow_page(self.pool,right)?}else{right};
3102                    children[start]=fresh_left;
3103                    if !merge {children[start+1]=fresh_right;}
3104                    for (i,&id) in children.iter().enumerate().skip(1) {
3105                        let key=validated_key(&pr[i-1]).to_vec();pr[i-1]=enc_interior(&key,id);
3106                    }
3107                    left_image[12..16].copy_from_slice(&fresh_left.to_le_bytes());
3108                    if kind==PageKind::Leaf && !merge {left_image[20..24].copy_from_slice(&fresh_right.to_le_bytes());}
3109                    if let Some(image)=right_image.as_mut(){image[12..16].copy_from_slice(&fresh_right.to_le_bytes());}
3110                    let parent_image=build_page(PageKind::Interior,self.tree_id,parent,&pr,children[0])?;
3111                    // Acquire every fallible guard and reserve retirement before
3112                    // installing any replacement image. Store fails closed on
3113                    // any error; no fallible operation follows the image copies.
3114                    let mut lw=self.pool.get_mut(fresh_left)?;
3115                    let mut rw=if merge {None}else{Some(self.pool.get_mut(fresh_right)?)};
3116                    let mut pw=self.pool.get_mut(parent)?;
3117                    if merge {
3118                        let birth=if self.pool.is_frozen(right) {right_birth}else{self.pool.write_generation()};
3119                        self.pool.free_shadow_page(right,birth)?;
3120                    }
3121                    lw.bytes_mut().copy_from_slice(&left_image);
3122                    if let (Some(w),Some(image))=(&mut rw,&right_image){w.bytes_mut().copy_from_slice(image);}
3123                    pw.bytes_mut().copy_from_slice(&parent_image);
3124                    self.last_leaf.set(None);changed=merge;
3125                    break 'attempt;
3126                }
3127            }
3128            if !changed {return Ok(());} node=parent;
3129        }
3130    }
3131
3132    /// Scan from `from` forward, in key order.
3133    ///
3134    /// SINGLE WRITER. The iterator borrows the pool, not `self`, so the borrow
3135    /// checker will happily let you `insert` into this tree while a scan is live.
3136    /// Do not: the iterator re-reads its leaf on every `next`, so an insert that
3137    /// shifts entries in the leaf it is standing on makes it skip or repeat a
3138    /// row, with no compiler or runtime signal. The engine is single-writer by
3139    /// design, and this is where that assumption is cashed.
3140    /// Remove every key with `prefix`. Walks matching leaves via the
3141    /// parent path (a cleared leaf still receives the same descent -- the
3142    /// separators do not change -- so re-descending with the prefix would
3143    /// loop on the first emptied page forever; the advance must go THROUGH
3144    /// the parents, the RangeIter lesson). Each matching leaf is either
3145    /// CLEARED in one page write (every entry matches) or slot-trimmed.
3146    /// Cost: O(matching leaves) page writes + one read-descent each, not
3147    /// O(matching rows) tree operations. Emptied leaves stay allocated
3148    /// (the documented delete posture).
3149    pub fn delete_prefix(&mut self, prefix: &[u8]) -> Result<u64> {
3150        self.forget_tag_hints();
3151        let mut removed = 0u64;
3152        let mut cursor: Vec<u8> = prefix.to_vec();
3153        // 2n: the last leaf KEPT in the chain. Fully-cleared leaves after it
3154        // are unlinked from their parent and freed; the anchor's sibling
3155        // pointer is patched forward over each. The FIRST visited leaf is
3156        // never freed (its left neighbour is unknown), and a parent's last
3157        // child is never detached (no cascade; the leaf stays cleared).
3158        let mut anchor: Option<u32> = None;
3159        'leaves: loop {
3160            // read-descend to the candidate leaf, remembering the path
3161            let (leaf, mut path) = self.descend_with_path(&cursor)?;
3162            let (matches, all, past, retired) = {
3163                let r = self.pool.get(leaf)?;
3164                let p = open_cached(&r, leaf)?;
3165                let n = p.nentries();
3166                let mut idx = Vec::new();
3167                let mut past = false;
3168                let mut retired = Vec::new();
3169                for i in 0..n {
3170                    let k = validated_key(p.slot(i));
3171                    if k.starts_with(prefix) {
3172                        idx.push(i);
3173                        retired.extend(replaced_overflow_pages(self.pool, p.slot(i))?);
3174                    }
3175                    else if k > prefix { past = true; }
3176                }
3177                (idx.clone(), n > 0 && idx.len() == n, past, retired)
3178            };
3179            if !matches.is_empty() {
3180                removed += matches.len() as u64;
3181                let (mut w, wpath) = self.descend_for_write(&cursor)?;
3182                // The write descent may SHADOW the leaf (fresh or recycled
3183                // number) -- the numbers legitimately differ. What the slot
3184                // indices computed from the read descent require is CONTENT
3185                // congruence: the shadow is a byte-identical copy. Only
3186                // check when a shadow actually happened: reading the same
3187                // page while `w` write-pins it is itself a pin conflict.
3188                #[cfg(debug_assertions)]
3189                if w.page_no() != leaf {
3190                    let wn = { let p = PageMut::reopen(w.bytes_mut()); p.nentries_pub() };
3191                    let rn = { let r = self.pool.get(leaf)?; open_cached(&r, leaf)?.nentries() };
3192                    debug_assert_eq!(wn, rn,
3193                        "write-descent leaf diverged from the read-descent leaf");
3194                }
3195                if all {
3196                    // Whole-leaf clear MUST keep the sibling pointer: init
3197                    // resets the full header, and a zeroed next_leaf makes a
3198                    // MIDDLE leaf look rightmost -- the append fast path then
3199                    // writes keys past the leaf's true range and orphans
3200                    // committed subtrees (silent loss: a fold+insert+fold
3201                    // cycle dropped every folded segment; caught by 2n's
3202                    // reuse probe, present since 2h).
3203                    let no = w.page_no();
3204                    let tid = self.tree_id;
3205                    let nl = u32::from_le_bytes(w.bytes_mut()[20..24].try_into().unwrap());
3206                    let mut p = PageMut::init(w.bytes_mut(), PageKind::Leaf, tid, no);
3207                    p.set_next_leaf(nl);
3208                    p.finalise(0);
3209                    drop(w);
3210                    // 2n: detach and free it when safe -- an emptied leaf
3211                    // whose key range never refills (monotonic ids) is
3212                    // otherwise allocated forever. Stale read paths still
3213                    // find it cleared-with-chain, which walks treat as any
3214                    // other empty leaf.
3215                    let freed = match (anchor, wpath.last()) {
3216                        (Some(a), Some(&parent)) if a != no => {
3217                            match unlink_child(self.pool, self.tree_id, parent, no)? {
3218                                Some(pos) => {
3219                                    self.pool.free_page(no)?;
3220                                    let mut aw = self.pool.get_mut(a)?;
3221                                    let mut ap = PageMut::reopen(aw.bytes_mut());
3222                                    ap.set_next_leaf(nl);
3223                                    ap.finalise(0);
3224                                    // later iterations' read path may hold
3225                                    // saved child indices into this SAME
3226                                    // writable parent -- shift them left
3227                                    // past the removed position, or the
3228                                    // advance skips a child (rows survive
3229                                    // the delete; measured, not theorised).
3230                                    for e in path.iter_mut() {
3231                                        if e.0 == parent && e.1 >= pos && e.1 > 0 { e.1 -= 1; }
3232                                    }
3233                                    true
3234                                }
3235                                None => false,
3236                            }
3237                        }
3238                        _ => false,
3239                    };
3240                    if !freed { anchor = Some(no); }
3241                } else {
3242                    let mut p = PageMut::reopen(w.bytes_mut());
3243                    for &i in matches.iter().rev() { p.remove_slot(i); }
3244                    p.finalise(0);
3245                    anchor = Some(w.page_no());
3246                }
3247                for (no, birth) in retired { self.pool.free_shadow_page(no, birth)?; }
3248                self.last_leaf.set(None); // the hint may name a cleared leaf
3249            }
3250            if past { return Ok(removed); }
3251            // advance through the parents to the next leaf's first key
3252            loop {
3253                let (mut cur, mut idx) = loop {
3254                    let Some((page, i)) = path.pop() else { return Ok(removed) };
3255                    let n = {
3256                        let r = self.pool.get(page)?;
3257                        open_cached(&r, page)?.nentries()
3258                    };
3259                    if i < n { break (page, i + 1); }
3260                };
3261                // leftmost spine from the right sibling down to a leaf
3262                let first_key: Option<Vec<u8>> = loop {
3263                    let r = self.pool.get(cur)?;
3264                    let p = open_cached(&r, cur)?;
3265                    if p.kind() == PageKind::Leaf {
3266                        break if p.nentries() == 0 { None }
3267                              else { Some(validated_key(p.slot(0)).to_vec()) };
3268                    }
3269                    let child = child_at(self.pool, &p, idx)?;
3270                    path.push((cur, idx));
3271                    cur = child;
3272                    idx = 0;
3273                };
3274                match first_key {
3275                    Some(k) if k.starts_with(prefix) => { cursor = k; continue 'leaves; }
3276                    Some(_) => return Ok(removed),
3277                    None => continue, // empty leaf: keep advancing
3278                }
3279            }
3280        }
3281    }
3282
3283    pub fn range(&self, from: &[u8]) -> Result<RangeIter<'p>> {
3284        let (leaf, idx, path) = self.descend_positioned(from)?;
3285        Ok(RangeIter {
3286            pool: self.pool, tree_id: self.tree_id, page: leaf, idx,
3287            done: false, leaves: 1, max_leaves: self.pool.page_count(),
3288            buf: std::collections::VecDeque::new(),
3289            served: 0,
3290            path,
3291            pin: None,
3292            leaf_entries: 0,
3293            leaf_markers: true,
3294            seeks: 0,
3295        })
3296    }
3297
3298    /// Scan keys strictly below `to` in descending order.
3299    pub fn range_reverse(&self, to: &[u8]) -> Result<ReverseRangeIter<'p>> {
3300        let (leaf, path) = self.descend_with_path(to)?;
3301        let idx = { let r = self.pool.get(leaf)?; lower_bound(&open_cached(&r, leaf)?, to)? };
3302        Ok(ReverseRangeIter {
3303            pool: self.pool, tree_id: self.tree_id, page: leaf, idx,
3304            done: false, leaves: 1, max_leaves: self.pool.page_count(), path,
3305            pin: None, pending: None,
3306        })
3307    }
3308}
3309
3310impl ReverseRangeIter<'_> {
3311    /// Step to the leaf immediately left of the current one through the saved
3312    /// parent path, then land just past its final slot.
3313    fn retreat(&mut self) -> Result<bool> {
3314        let (mut cur, mut idx_in_parent) = loop {
3315            let Some((page, i)) = self.path.pop() else { return Ok(false) };
3316            if i > 0 { break (page, i - 1); }
3317        };
3318        loop {
3319            let r = self.pool.get(cur)?;
3320            let p = open_cached(&r, cur)?;
3321            if p.tree_id() != self.tree_id {
3322                return Err(Error::Corrupt { page_no: cur, why: "page belongs to another tree" });
3323            }
3324            if p.kind() == PageKind::Leaf {
3325                self.page = cur;
3326                self.idx = p.nentries();
3327                break;
3328            }
3329            let n = p.nentries();
3330            let child_i = if idx_in_parent > n { n } else { idx_in_parent };
3331            let child = child_at(self.pool, &p, child_i)?;
3332            self.path.push((cur, child_i));
3333            cur = child;
3334            idx_in_parent = usize::MAX; // every later descent takes the rightmost child
3335        }
3336        self.leaves += 1;
3337        if self.leaves > self.max_leaves {
3338            return Err(Error::Corrupt {
3339                page_no: self.page, why: "reverse scan visits more leaves than the file holds" });
3340        }
3341        Ok(true)
3342    }
3343
3344    /// The descending record the cursor is parked on, borrowed from the pinned
3345    /// leaf, with no allocation. Paired with [`ReverseRangeIter::step`] this is
3346    /// the PULL cursor the forward iterator already has: peek, use, step, peek.
3347    ///
3348    /// A query executor cannot live inside `for_each_ref`'s callback -- it has
3349    /// to interleave the walk with a heap, a work meter and a cancellation
3350    /// check -- and until this existed, every descending order had to
3351    /// materialise its whole range before it could rank it. Same leaf pin,
3352    /// same tree-id and cycle checks, same overflow resolution as the
3353    /// callback form.
3354    pub fn peek_ref(&mut self) -> Result<Option<(&[u8], &[u8])>> {
3355        self.position()?;
3356        self.current_ref()
3357    }
3358
3359    /// Step past the record the last peek returned. Crossing into the leaf to
3360    /// the LEFT is left to the next peek, which retreats through the saved
3361    /// parent path when the slot index reaches the start of the leaf.
3362    pub fn step(&mut self) {
3363        if self.pending.take().is_some() {
3364            return;
3365        }
3366        self.idx = self.idx.saturating_sub(1);
3367    }
3368
3369    fn position(&mut self) -> Result<()> {
3370        loop {
3371            if self.pending.is_some() || self.done {
3372                return Ok(());
3373            }
3374            if self.pin.is_none() {
3375                self.pin = Some(self.pool.get(self.page)?);
3376            }
3377            enum Step {
3378                Stay,
3379                Overflow { key: Vec<u8>, marker: Vec<u8> },
3380                PreviousLeaf,
3381                Corrupt,
3382            }
3383            let step = {
3384                let pin = self.pin.as_ref().unwrap();
3385                let p = open_cached(pin, self.page)?;
3386                if p.tree_id() != self.tree_id {
3387                    Step::Corrupt
3388                } else if self.idx > 0 {
3389                    let (key, value, is_marker) = validated_leaf(p.slot(self.idx - 1));
3390                    if is_marker {
3391                        Step::Overflow { key: key.to_vec(), marker: value.to_vec() }
3392                    } else {
3393                        Step::Stay
3394                    }
3395                } else {
3396                    Step::PreviousLeaf
3397                }
3398            };
3399            match step {
3400                Step::Corrupt => {
3401                    self.pin = None;
3402                    self.done = true;
3403                    return Err(Error::Corrupt {
3404                        page_no: self.page, why: "reverse-scan page belongs to another tree" });
3405                }
3406                Step::Stay => return Ok(()),
3407                Step::Overflow { key, marker } => {
3408                    // Step past it here: `step()` then only drops `pending`.
3409                    self.idx -= 1;
3410                    self.pin = None;
3411                    let value = read_overflow(self.pool, &marker)?;
3412                    self.pending = Some((key, value));
3413                    return Ok(());
3414                }
3415                Step::PreviousLeaf => {
3416                    self.pin = None;
3417                    if !self.retreat()? {
3418                        self.done = true;
3419                    }
3420                }
3421            }
3422        }
3423    }
3424
3425    fn current_ref(&self) -> Result<Option<(&[u8], &[u8])>> {
3426        if let Some((key, value)) = self.pending.as_ref() {
3427            return Ok(Some((key.as_slice(), value.as_slice())));
3428        }
3429        if self.done {
3430            return Ok(None);
3431        }
3432        let Some(pin) = self.pin.as_ref() else {
3433            return Ok(None);
3434        };
3435        let p = open_cached(pin, self.page)?;
3436        if self.idx == 0 {
3437            return Ok(None);
3438        }
3439        let (key, value, is_marker) = validated_leaf(p.slot(self.idx - 1));
3440        debug_assert!(!is_marker, "peek parks overflow markers in `pending`");
3441        Ok(Some((key, value)))
3442    }
3443
3444    /// Visit descending records as borrows into one pinned leaf. The cursor is
3445    /// bounded by the buffer pool; a caller stopping after `k` entries pays for
3446    /// only the pages containing those entries.
3447    pub fn for_each_ref(mut self, mut f: impl FnMut(&[u8], &[u8]) -> bool) -> Result<()> {
3448        self.pin = None;
3449        if let Some((key, value)) = self.pending.take() {
3450            if !f(&key, &value) { return Ok(()) }
3451        }
3452        loop {
3453            if self.done { return Ok(()) }
3454            let r = self.pool.get(self.page)?;
3455            let p = open_cached(&r, self.page)?;
3456            if p.tree_id() != self.tree_id {
3457                return Err(Error::Corrupt {
3458                    page_no: self.page, why: "reverse-scan page belongs to another tree" });
3459            }
3460            let mut overflow: Option<(Vec<u8>, Vec<u8>)> = None;
3461            while self.idx > 0 {
3462                self.idx -= 1;
3463                let rec = p.slot(self.idx);
3464                let (key, value, is_marker) = validated_leaf(rec);
3465                if is_marker {
3466                    overflow = Some((key.to_vec(), value.to_vec()));
3467                    break;
3468                }
3469                if !f(key, value) { return Ok(()) }
3470            }
3471            drop(r);
3472            if let Some((key, marker)) = overflow {
3473                let val = read_overflow(self.pool, &marker)?;
3474                if !f(&key, &val) { return Ok(()) }
3475                // Re-pin the same leaf at the already-decremented slot.
3476                continue;
3477            }
3478            if self.idx > 0 { continue }
3479            if !self.retreat()? { self.done = true; }
3480        }
3481    }
3482}
3483
3484impl RangeIter<'_> {
3485    /// How many LEAVES this cursor has stepped through since it was opened.
3486    ///
3487    /// `advance` is the cursor's real unit of cost: it climbs the parent path
3488    /// until an ancestor has a child to the right and then re-descends the
3489    /// leftmost spine, so one step is one or more pager accesses and a long
3490    /// reach is many. A caller choosing between stepping this cursor forward
3491    /// and descending the tree afresh is choosing between LEAF STEPS and a
3492    /// tree height, and this is the only half of that it cannot work out for
3493    /// itself -- how far apart two keys are in leaves depends on how wide the
3494    /// records between them are, which is the caller's data and not its plan.
3495    pub fn leaves_stepped(&self) -> u32 {
3496        self.leaves
3497    }
3498
3499    /// 2f: step to the next leaf through the parent path. Climb until an
3500    /// ancestor has a child to the right, step into it, then take the
3501    /// leftmost spine down to its first leaf. Returns false when every
3502    /// ancestor was exhausted -- the finished leaf was rightmost. Typical
3503    /// cost: one parent open and one leaf open, same as the old chain.
3504    fn advance(&mut self) -> Result<bool> {
3505        // Climb: drop exhausted levels.
3506        let (mut cur, mut idx_in_parent) = loop {
3507            let Some((page, i)) = self.path.pop() else { return Ok(false) };
3508            let n = {
3509                let r = self.pool.get(page)?;
3510                open_cached(&r, page)?.nentries()
3511            };
3512            // Children are child0 + one per slot: indices 0..=nentries.
3513            if i < n { break (page, i + 1); }
3514        };
3515        // Step right, then take the leftmost spine down to a leaf. Keep the
3516        // leaf pin so the next for_each_ref / peek does not get the same page
3517        // a second time -- decode each page once.
3518        loop {
3519            let r = self.pool.get(cur)?;
3520            let p = open_cached(&r, cur)?;
3521            if p.tree_id() != self.tree_id {
3522                return Err(Error::Corrupt { page_no: cur, why: "page belongs to another tree" });
3523            }
3524            if p.kind() == PageKind::Leaf {
3525                self.page = cur;
3526                self.idx = 0;
3527                self.leaf_entries = p.nentries();
3528                self.leaf_markers = leaf_has_marker(&p);
3529                drop(p);
3530                self.pin = Some(r);
3531                break;
3532            }
3533            let child = child_at(self.pool, &p, idx_in_parent)?;
3534            self.path.push((cur, idx_in_parent));
3535            cur = child;
3536            idx_in_parent = 0; // leftmost from here down
3537        }
3538        self.leaves += 1;
3539        if self.leaves > self.max_leaves {
3540            return Err(Error::Corrupt { page_no: self.page, why: "scan visits more leaves than the file holds" });
3541        }
3542        Ok(true)
3543    }
3544}
3545
3546impl RangeIter<'_> {
3547    /// Stop permanently, handing back the error that stopped us.
3548    ///
3549    /// Fusing on error is not tidiness. Returning `Some(Err(..))` without it
3550    /// leaves `page` and `idx` unchanged, so the next call retries the identical
3551    /// read and fails identically — forever. A consumer that logs and continues,
3552    /// or uses `filter_map(Result::ok)`, spins for good the first time a scan
3553    /// crosses an unreadable page. Fail loudly once; never fail forever.
3554    fn fail(&mut self, e: Error) -> Option<Result<(Vec<u8>, Vec<u8>)>> {
3555        self.done = true;
3556        Some(Err(e))
3557    }
3558}
3559
3560impl RangeIter<'_> {
3561    /// Visit every remaining record WITHOUT allocating per entry: the callback
3562    /// gets the record's key and value as borrows into the pinned leaf, and
3563    /// iteration stops when it returns false.
3564    ///
3565    /// This exists because the allocating path costs ~34ns/key against
3566    /// SQLite's ~18 on covering scans -- two Vecs per row, paid even by
3567    /// count-only queries. One pin and one validation per leaf, zero allocs,
3568    /// same sibling-chain, cycle and tree-id checks as `next()`.
3569    pub fn for_each_ref(mut self, mut f: impl FnMut(&[u8], &[u8]) -> bool) -> Result<()> {
3570        // Drain anything already buffered by earlier `next()` calls first.
3571        while let Some((k, v, is_marker)) = self.buf.pop_front() {
3572            if is_marker {
3573                let val = read_overflow(self.pool, &v)?;
3574                if !f(&k, &val) { return Ok(()); }
3575                continue;
3576            }
3577            if !f(&k, &v) { return Ok(()); }
3578        }
3579        loop {
3580            if self.done { return Ok(()); }
3581            let r = match self.pin.take() {
3582                Some(pin) => pin,
3583                None => self.pool.get(self.page)?,
3584            };
3585            let p = open_cached(&r, self.page)?;
3586            if p.tree_id() != self.tree_id {
3587                return Err(Error::Corrupt {
3588                    page_no: self.page, why: "sibling page belongs to another tree" });
3589            }
3590            let mut deferred: Vec<(Vec<u8>, Vec<u8>, bool)> = Vec::new();
3591            while self.idx < p.nentries() {
3592                let rec = p.slot(self.idx);
3593                self.idx += 1;
3594                let (key, value, flag) = validated_leaf(rec);
3595                if flag || !deferred.is_empty() {
3596                    // A marker cannot resolve while this leaf is pinned, and
3597                    // everything after it defers too so key order is kept.
3598                    deferred.push((key.to_vec(), value.to_vec(), flag));
3599                    continue;
3600                }
3601                if !f(key, value) { return Ok(()); }
3602            }
3603            drop(r);
3604            for (k, v, is_marker) in deferred {
3605                if is_marker {
3606                    let val = read_overflow(self.pool, &v)?;
3607                    if !f(&k, &val) { return Ok(()); }
3608                } else if !f(&k, &v) { return Ok(()); }
3609            }
3610            if !self.advance()? { return Ok(()); }
3611        }
3612    }
3613
3614    /// Advance to the first remaining key >= `target` and return borrowed
3615    /// slices into the pinned leaf (or into a resolved overflow buffer).
3616    /// The iterator stays on that record so a later, still-greater target
3617    /// can resume without skipping it. `Ok(None)` means the range is empty
3618    /// or every remaining key is below `target`.
3619    ///
3620    /// Does not drain in-leaf records into `Vec`s. Overflow values still
3621    /// allocate, because they do not live in the pinned leaf. Reuses
3622    /// `advance()` and the validated-bit `open_cached` path that
3623    /// `for_each_ref` uses.
3624    pub fn peek_at_or_after(&mut self, target: &[u8]) -> Result<Option<(&[u8], &[u8])>> {
3625        self.position_at_or_after(target)?;
3626        self.current_ref()
3627    }
3628
3629    /// The record the cursor is parked on, borrowed from the pinned leaf, with
3630    /// no seek and no allocation. Paired with [`RangeIter::step`] this makes a
3631    /// PULL cursor -- peek, use, step, peek -- that costs what `for_each_ref`
3632    /// costs while still letting the caller stop and resume. A query executor
3633    /// needs exactly that: it has to interleave the walk with a heap, a work
3634    /// meter and a cancellation check, none of which fit inside a callback.
3635    ///
3636    /// The empty target is below every key, so this is `peek_at_or_after`
3637    /// standing still: same leaf pin, same overflow-marker resolution.
3638    pub fn peek_ref(&mut self) -> Result<Option<(&[u8], &[u8])>> {
3639        // A cursor that is already standing on a record of its pinned leaf
3640        // has nothing to seek for: the empty target is below every key, so
3641        // `position_at_or_after` would open the page a second time and decode
3642        // the record two more times to arrive back where it already is. That
3643        // was 2 page opens and 3 record decodes per ROW where the callback
3644        // walk pays 1 open per LEAF and 1 decode per row.
3645        if !self.parked() {
3646            self.position_at_or_after(&[])?;
3647        }
3648        self.current_ref()
3649    }
3650
3651    /// True when `current_ref` alone is the whole answer: nothing parked in
3652    /// `buf`, the walk is live, a leaf is pinned, `idx` names a record in it,
3653    /// and that leaf holds no overflow marker (a marker's value lives off the
3654    /// page and only the seek path can resolve it). Every term is a field
3655    /// read, and the two leaf facts were recorded when the pin was taken.
3656    fn parked(&self) -> bool {
3657        self.buf.is_empty()
3658            && !self.done
3659            && self.pin.is_some()
3660            && !self.leaf_markers
3661            && self.idx < self.leaf_entries
3662    }
3663
3664    /// How many times this cursor has had to seek -- ask the tree where it
3665    /// is -- rather than read the record it was standing on. A streaming walk
3666    /// should seek about once per leaf; seeking once per row is the defect
3667    /// this counts.
3668    pub fn seeks(&self) -> u64 {
3669        self.seeks
3670    }
3671
3672    /// Step past the record the last peek returned, without materialising it.
3673    /// Crossing a leaf boundary is left to the next peek, which already climbs
3674    /// the parent path when the slot index runs past the end of the leaf.
3675    pub fn step(&mut self) {
3676        if self.buf.pop_front().is_some() {
3677            return;
3678        }
3679        self.idx += 1;
3680    }
3681
3682    fn position_at_or_after(&mut self, target: &[u8]) -> Result<()> {
3683        self.seeks += 1;
3684        loop {
3685            let skip_buf = matches!(self.buf.front(), Some((k, _, _)) if k.as_slice() < target);
3686            if skip_buf {
3687                self.buf.pop_front();
3688                continue;
3689            }
3690            break;
3691        }
3692        if matches!(self.buf.front(), Some((k, _, _)) if k.as_slice() >= target) {
3693            if let Some((_, v, true)) = self.buf.front() {
3694                let marker = v.clone();
3695                let (k, _, _) = self.buf.pop_front().unwrap();
3696                let val = read_overflow(self.pool, &marker)?;
3697                self.buf.push_front((k, val, false));
3698            }
3699            return Ok(());
3700        }
3701
3702        loop {
3703            if self.done {
3704                self.pin = None;
3705                return Ok(());
3706            }
3707            if self.pin.is_none() {
3708                // The ONE place this cursor takes a leaf pin, and so the one
3709                // place the leaf's own facts are read. `page` cannot change
3710                // while the pin is held -- `advance` runs only after the pin
3711                // is dropped -- so what is recorded here stays true for as
3712                // long as `peek_ref` may believe it.
3713                let pin = self.pool.get(self.page)?;
3714                {
3715                    let p = open_cached(&pin, self.page)?;
3716                    self.leaf_entries = p.nentries();
3717                    self.leaf_markers = leaf_has_marker(&p);
3718                }
3719                self.pin = Some(pin);
3720            }
3721
3722            enum Step {
3723                Stay(usize),
3724                Overflow { idx: usize, key: Vec<u8>, marker: Vec<u8> },
3725                NextLeaf,
3726                Corrupt,
3727            }
3728            let step = {
3729                let pin = self.pin.as_ref().unwrap();
3730                let p = open_cached(pin, self.page)?;
3731                if p.tree_id() != self.tree_id {
3732                    Step::Corrupt
3733                } else {
3734                    let n = p.nentries();
3735                    let mut idx = self.idx;
3736                    // The EMPTY target -- what `peek_ref` asks with -- is below
3737                    // every key, so the cursor is never before it and the
3738                    // probe that decides whether to seek can only ever answer
3739                    // no. Deciding that by decoding the record was 2.6% of a
3740                    // key-only enumeration.
3741                    if !target.is_empty() && idx < n {
3742                        let (key0, _, _) = validated_leaf(p.slot(idx));
3743                        if key0 < target {
3744                            idx = lower_bound(&p, target)?.max(idx);
3745                        }
3746                    }
3747                    if idx < n {
3748                        let (key, value, is_marker) = validated_leaf(p.slot(idx));
3749                        if is_marker {
3750                            Step::Overflow { idx, key: key.to_vec(), marker: value.to_vec() }
3751                        } else {
3752                            Step::Stay(idx)
3753                        }
3754                    } else {
3755                        Step::NextLeaf
3756                    }
3757                }
3758            };
3759            match step {
3760                Step::Corrupt => {
3761                    self.pin = None;
3762                    self.done = true;
3763                    return Err(Error::Corrupt {
3764                        page_no: self.page,
3765                        why: "sibling page belongs to another tree",
3766                    });
3767                }
3768                Step::Stay(idx) => {
3769                    self.idx = idx;
3770                    return Ok(());
3771                }
3772                Step::Overflow { idx, key, marker } => {
3773                    self.idx = idx + 1;
3774                    self.pin = None;
3775                    let val = read_overflow(self.pool, &marker)?;
3776                    self.buf.push_front((key, val, false));
3777                    return Ok(());
3778                }
3779                Step::NextLeaf => {
3780                    self.pin = None;
3781                    if !self.advance()? {
3782                        self.done = true;
3783                    }
3784                }
3785            }
3786        }
3787    }
3788
3789    fn current_ref(&self) -> Result<Option<(&[u8], &[u8])>> {
3790        if let Some((k, v, is_marker)) = self.buf.front() {
3791            debug_assert!(!*is_marker, "peek resolves overflow markers before yielding");
3792            return Ok(Some((k.as_slice(), v.as_slice())));
3793        }
3794        if self.done {
3795            return Ok(None);
3796        }
3797        let Some(pin) = self.pin.as_ref() else {
3798            return Ok(None);
3799        };
3800        // This pin was taken through `open_cached`, which either found the
3801        // frame's validated bit set or validated the residency and set it --
3802        // and a pinned frame cannot be evicted, so the bit cannot have been
3803        // cleared since. Asking the pool for it again costs a `RefCell`
3804        // borrow and a frame index PER ROW; `open_cached` is exactly this
3805        // call plus that question.
3806        let p = PageRef::open_resident_validated(pin, self.page)?;
3807        if self.idx >= p.nentries() {
3808            return Ok(None);
3809        }
3810        let (key, value, is_marker) = validated_leaf(p.slot(self.idx));
3811        debug_assert!(!is_marker, "in-leaf overflow must have been parked in buf");
3812        Ok(Some((key, value)))
3813    }
3814}
3815
3816impl Iterator for RangeIter<'_> {
3817    type Item = Result<(Vec<u8>, Vec<u8>)>;
3818    fn next(&mut self) -> Option<Self::Item> {
3819        loop {
3820            // ONE leaf pinned, ever. `advance` now keeps the leaf it landed
3821            // on so a following `for_each_ref`/`peek` does not get the same
3822            // page twice, and this loop is about to get that page itself --
3823            // so the pin is dropped at the TOP of every iteration, not once
3824            // on entry. It is also what keeps the `read_overflow` chain walks
3825            // below off a pinned leaf.
3826            self.pin = None;
3827            if let Some((k, v, is_marker)) = self.buf.pop_front() {
3828                if is_marker {
3829                    let val = match read_overflow(self.pool, &v) {
3830                        Ok(val) => val, Err(e) => return self.fail(e),
3831                    };
3832                    return Some(Ok((k, val)));
3833                }
3834                return Some(Ok((k, v)));
3835            }
3836            if self.done { return None; }
3837            let r = match self.pool.get(self.page) { Ok(r) => r, Err(e) => return self.fail(e) };
3838            let p = match open_cached(&r, self.page) { Ok(p) => p, Err(e) => return self.fail(e) };
3839            // `descend` checks this for the FIRST leaf only. Every later leaf is
3840            // reached by following a sibling pointer, and a well-formed page from
3841            // another tree passes its checksum perfectly well — five trees share
3842            // this file. Without this the scan decodes another tree's rows and
3843            // returns them as ours.
3844            if p.tree_id() != self.tree_id {
3845                let e = Error::Corrupt {
3846                    page_no: self.page, why: "sibling page belongs to another tree" };
3847                return self.fail(e);
3848            }
3849            const SHORT_SCAN: u32 = 8;
3850            if self.served < SHORT_SCAN {
3851                if self.idx < p.nentries() {
3852                    let rec = p.slot(self.idx);
3853                    self.idx += 1;
3854                    self.served += 1;
3855                    let (key, value, is_marker) = validated_leaf(rec);
3856                    let kv = (key.to_vec(), value.to_vec());
3857                    if is_marker {
3858                        drop(r);
3859                        let v = match read_overflow(self.pool, &kv.1) {
3860                            Ok(v) => v, Err(e) => return self.fail(e),
3861                        };
3862                        return Some(Ok((kv.0, v)));
3863                    }
3864                    return Some(Ok(kv));
3865                }
3866            } else {
3867                while self.idx < p.nentries() {
3868                    let rec = p.slot(self.idx);
3869                    self.idx += 1;
3870                    // markers buffered as markers (explicit flag -- an in-band
3871                    // tag would collide with real values of the same shape),
3872                    // resolved on pop: the chain walk must not run while this
3873                    // leaf is pinned
3874                    let (key, value, flag) = validated_leaf(rec);
3875                    self.buf.push_back((key.to_vec(), value.to_vec(), flag));
3876                }
3877            }
3878            drop(r);   // released BEFORE re-descending: one leaf pinned, ever
3879            // done is a STATE, not an exit: the buffer may hold this final
3880            // leaf's records, and returning here dropped them -- 163 keys of a
3881            // 20,000-key scan, caught by the ordering test within seconds of
3882            // the batching change.
3883            match self.advance() {
3884                Ok(true) => {}
3885                Ok(false) => { self.done = true; }
3886                Err(e) => return self.fail(e),
3887            }
3888        }
3889    }
3890}
3891
3892#[cfg(test)]
3893mod tests {
3894    use super::*;
3895    use crate::test_support::scratch_pool;
3896
3897    #[test]
3898    fn a_key_written_is_a_key_found() {
3899        let (pool, _d) = scratch_pool(64);
3900        let last_leaf = Cell::new(None);
3901        let fast_path_hits = Cell::new(0);
3902        let fast_path_attempts = Cell::new(0);
3903        let mut t = BTree::create(&pool, 1, &last_leaf, &fast_path_hits, &fast_path_attempts).unwrap();
3904        t.insert(b"alpha", b"1").unwrap();
3905        t.insert(b"beta", b"2").unwrap();
3906        assert_eq!(t.get(b"alpha").unwrap().as_deref(), Some(&b"1"[..]));
3907        assert_eq!(t.get(b"beta").unwrap().as_deref(), Some(&b"2"[..]));
3908        assert_eq!(t.get(b"gamma").unwrap(), None);
3909    }
3910
3911    #[test]
3912    fn a_later_write_replaces_an_earlier_one() {
3913        let (pool, _d) = scratch_pool(64);
3914        let last_leaf = Cell::new(None);
3915        let fast_path_hits = Cell::new(0);
3916        let fast_path_attempts = Cell::new(0);
3917        let mut t = BTree::create(&pool, 1, &last_leaf, &fast_path_hits, &fast_path_attempts).unwrap();
3918        t.insert(b"k", b"first").unwrap();
3919        t.insert(b"k", b"second").unwrap();
3920        assert_eq!(t.get(b"k").unwrap().as_deref(), Some(&b"second"[..]));
3921    }
3922
3923    /// Splitting is the whole point: more keys than one page can hold, in an
3924    /// order that guarantees splits, with a pool far smaller than the tree.
3925    #[test]
3926    fn fifty_thousand_scattered_keys_are_all_findable() {
3927        let (pool, _d) = scratch_pool(16);
3928        let last_leaf = Cell::new(None);
3929        let fast_path_hits = Cell::new(0);
3930        let fast_path_attempts = Cell::new(0);
3931        let mut t = BTree::create(&pool, 1, &last_leaf, &fast_path_hits, &fast_path_attempts).unwrap();
3932        let n = 50_000u64;
3933        // A multiplicative hash scatters insert order without needing rand.
3934        let scatter = |i: u64| i.wrapping_mul(0x9E37_79B9_7F4A_7C15);
3935        for i in 0..n { t.insert(&scatter(i).to_be_bytes(), &i.to_le_bytes()).unwrap(); }
3936        for i in 0..n {
3937            let got = t.get(&scatter(i).to_be_bytes()).unwrap();
3938            assert_eq!(got.as_deref(), Some(&i.to_le_bytes()[..]), "key {i} missing");
3939        }
3940    }
3941
3942    #[test]
3943    fn a_value_too_large_for_a_page_is_refused_not_panicked() {
3944        let (pool, _d) = scratch_pool(16);
3945        let last_leaf = Cell::new(None);
3946        let fast_path_hits = Cell::new(0);
3947        let fast_path_attempts = Cell::new(0);
3948        let mut t = BTree::create(&pool, 1, &last_leaf, &fast_path_hits, &fast_path_attempts).unwrap();
3949        // An oversized VALUE now spills to an overflow chain and round-trips
3950        // exactly -- the refusal this test used to assert became a feature.
3951        let big = vec![7u8; 5000];
3952        t.insert(b"k", &big).unwrap();
3953        assert_eq!(t.get(b"k").unwrap().as_deref(), Some(big.as_slice()));
3954        // What must STILL be refused is a key that cannot fit a leaf record
3955        // even as a marker: keys never spill.
3956        let huge_key = vec![b'k'; crate::page::MAX_RECORD_LEN];
3957        assert!(matches!(t.insert(&huge_key, b"v"), Err(crate::Error::TooLarge)));
3958    }
3959
3960    /// The exact path that used to corrupt a leaf: fill one leaf as tightly
3961    /// as this record shape allows without splitting, then replace one
3962    /// entry's value with a longer one whose extra bytes only fit once the
3963    /// old entry's payload is actually reclaimed (not just its slot). With
3964    /// the old `exists || room` shape this replace would remove the entry,
3965    /// fail to fit the new one, and never finalise — leaving the whole leaf
3966    /// unreadable. With the fix, this is an ordinary in-place growth.
3967    #[test]
3968    fn replacing_a_value_with_a_longer_one_on_a_full_leaf_keeps_every_key() {
3969        let (pool, _d) = scratch_pool(8);
3970        let last_leaf = Cell::new(None);
3971        let fast_path_hits = Cell::new(0);
3972        let fast_path_attempts = Cell::new(0);
3973        let mut t = BTree::create(&pool, 1, &last_leaf, &fast_path_hits, &fast_path_attempts).unwrap();
3974
3975        let capacity = crate::page::PAGE_SIZE - crate::page::HEADER_LEN;
3976        let key_len = 4usize;
3977        let small_val_len = 8usize;
3978        // Page-budget cost of one record: 4 (slot directory) + 2 (klen) +
3979        // key + 2 (vlen) + value.
3980        let small_cost = 8 + key_len + small_val_len;
3981        let n = capacity / small_cost;
3982
3983        for i in 0..n as u32 {
3984            t.insert(&i.to_be_bytes(), &vec![0xABu8; small_val_len]).unwrap();
3985        }
3986        let root_before = t.root();
3987
3988        // Grow the replacement so it exactly consumes what's left after the
3989        // other (n - 1) entries plus the reclaimed cost of the one being
3990        // replaced — the boundary the bug lived on.
3991        let free_after_fill = capacity - n * small_cost;
3992        let big_val_len = free_after_fill + small_cost - 8 - key_len;
3993        let big_val = vec![0xCDu8; big_val_len];
3994        t.insert(&0u32.to_be_bytes(), &big_val).unwrap();
3995
3996        assert_eq!(t.root(), root_before, "an in-place replace must not split the leaf");
3997        assert_eq!(t.get(&0u32.to_be_bytes()).unwrap().as_deref(), Some(&big_val[..]));
3998        for i in 1..n as u32 {
3999            assert_eq!(
4000                t.get(&i.to_be_bytes()).unwrap().as_deref(),
4001                Some(&vec![0xABu8; small_val_len][..]),
4002                "key {i} missing after replacing an unrelated key"
4003            );
4004        }
4005    }
4006
4007    /// 200 replacements of ONE key must never split its leaf. If `compact`
4008    /// were decorative (or absent), each replace would abandon its old
4009    /// payload without reclaiming it, `free_ptr` would march toward zero
4010    /// every time regardless of how much is actually live, and the leaf
4011    /// would exhaust its free space and split long before 200 iterations —
4012    /// even though at most one entry is ever live at once. A stable root is
4013    /// what proves compaction is actually reclaiming space, not just
4014    /// present in the source.
4015    #[test]
4016    fn repeated_replacement_of_one_key_does_not_exhaust_its_leaf() {
4017        let (pool, _d) = scratch_pool(8);
4018        let last_leaf = Cell::new(None);
4019        let fast_path_hits = Cell::new(0);
4020        let fast_path_attempts = Cell::new(0);
4021        let mut t = BTree::create(&pool, 1, &last_leaf, &fast_path_hits, &fast_path_attempts).unwrap();
4022        t.insert(b"k", &[0u8; 20]).unwrap();
4023        let root_before = t.root();
4024
4025        for i in 0..200u32 {
4026            let val = vec![i as u8; 20];
4027            t.insert(b"k", &val).unwrap();
4028        }
4029
4030        assert_eq!(t.root(), root_before, "200 replacements of one key must not split its leaf");
4031        assert_eq!(t.get(b"k").unwrap().as_deref(), Some(&vec![199u8; 20][..]));
4032    }
4033
4034    /// The pathological shape the byte-crossing heuristic got wrong: many small
4035    /// records plus one very large one landing near the cut. Cutting at the first
4036    /// prefix to cross half the bytes puts more than a page on the left.
4037    ///
4038    /// The big value's size (2500, not the 3000 originally specified) was
4039    /// picked by checking the arithmetic directly rather than by guessing: with
4040    /// 21 preceding 76-byte-cost small entries (1596 bytes) and 19 following
4041    /// (1444 bytes), a 3000-byte value costs 3016 in page-budget terms, and
4042    /// 1596 + 3016 = 4612 exceeds the 4056-byte usable page on EVERY possible
4043    /// cut, before or after the big record — no two-way split exists at all for
4044    /// that size, so `split_point` correctly returns `None` and the insert is
4045    /// correctly refused, which is not what this test is trying to demonstrate.
4046    /// 2500 costs 2516, low enough that a valid cut exists right before the big
4047    /// record (1596 / 3960, both under 4056), while still being large enough
4048    /// that the OLD "first prefix to cross half the bytes" heuristic picks the
4049    /// cut AFTER the big record instead (1596 + 2516 = 4112, over capacity) —
4050    /// so this size is still squarely in the region the fix is for.
4051    #[test]
4052    fn a_split_with_one_huge_record_among_many_small_ones_keeps_every_key() {
4053        let (pool, _d) = scratch_pool(32);
4054        let last_leaf = Cell::new(None);
4055        let fast_path_hits = Cell::new(0);
4056        let fast_path_attempts = Cell::new(0);
4057        let mut t = BTree::create(&pool, 1, &last_leaf, &fast_path_hits, &fast_path_attempts).unwrap();
4058        let mut keys: Vec<[u8; 8]> = Vec::new();
4059        for i in 0..40u64 {
4060            let k = (i * 2).to_be_bytes();
4061            t.insert(&k, &[b'a'; 60]).unwrap();
4062            keys.push(k);
4063        }
4064        // A large value keyed to land in the middle of the run. The size is
4065        // derived, not chosen: see the doc comment above.
4066        let big = 41u64.to_be_bytes();
4067        t.insert(&big, &vec![b'Z'; 2500]).unwrap();
4068        keys.push(big);
4069
4070        for k in &keys[..40] {
4071            let got = t.get(k).unwrap();
4072            assert!(got.is_some(), "key {k:?} lost across the split");
4073            // Length AND content: outright loss is not the only way a split can
4074            // damage a neighbour.
4075            assert_eq!(got.unwrap(), vec![b'a'; 60], "key {k:?} was corrupted by the split");
4076        }
4077        assert_eq!(t.get(&big).unwrap().unwrap(), vec![b'Z'; 2500]);
4078    }
4079
4080    #[test]
4081    fn a_range_scan_returns_keys_in_order_across_leaf_boundaries() {
4082        let (pool, _d) = scratch_pool(8);
4083        let last_leaf = Cell::new(None);
4084        let fast_path_hits = Cell::new(0);
4085        let fast_path_attempts = Cell::new(0);
4086        let mut t = BTree::create(&pool, 1, &last_leaf, &fast_path_hits, &fast_path_attempts).unwrap();
4087        let n = 20_000u64;
4088        let scatter = |i: u64| i.wrapping_mul(0x9E37_79B9_7F4A_7C15);
4089        for i in 0..n { t.insert(&scatter(i).to_be_bytes(), b"v").unwrap(); }
4090
4091        let mut expect: Vec<[u8; 8]> = (0..n).map(|i| scatter(i).to_be_bytes()).collect();
4092        expect.sort();
4093
4094        let got: Vec<Vec<u8>> = t.range(&[]).unwrap()
4095            .map(|r| r.unwrap().0).collect();
4096        assert_eq!(got.len(), expect.len());
4097        assert!(got.iter().zip(&expect).all(|(a, b)| a.as_slice() == b.as_slice()),
4098                "scan order must equal sorted order");
4099    }
4100
4101    #[test]
4102    fn a_range_scan_from_a_midpoint_starts_there() {
4103        let (pool, _d) = scratch_pool(8);
4104        let last_leaf = Cell::new(None);
4105        let fast_path_hits = Cell::new(0);
4106        let fast_path_attempts = Cell::new(0);
4107        let mut t = BTree::create(&pool, 1, &last_leaf, &fast_path_hits, &fast_path_attempts).unwrap();
4108        for i in 0..1000u64 { t.insert(&i.to_be_bytes(), b"v").unwrap(); }
4109        let first = t.range(&500u64.to_be_bytes()).unwrap().next().unwrap().unwrap().0;
4110        assert_eq!(first, 500u64.to_be_bytes().to_vec());
4111    }
4112
4113    /// Law 1 for the scan path: peak memory is one leaf, whatever the result size.
4114    ///
4115    /// Assert on `peak_pins`, NOT on `frames_total`. `frames_total` is the pool's
4116    /// fixed capacity, set once at construction and never mutated, so an
4117    /// assertion that it did not change is true before the scan, true after it,
4118    /// and would still be true if the iterator pinned every leaf at once. It
4119    /// cannot fail. `peak_pins` is a real measurement.
4120    #[test]
4121    fn a_full_scan_pins_one_leaf_at_a_time() {
4122        let (pool, _d) = scratch_pool(8);
4123        let last_leaf = Cell::new(None);
4124        let fast_path_hits = Cell::new(0);
4125        let fast_path_attempts = Cell::new(0);
4126        let mut t = BTree::create(&pool, 1, &last_leaf, &fast_path_hits, &fast_path_attempts).unwrap();
4127        for i in 0..50_000u64 { t.insert(&i.to_be_bytes(), b"v").unwrap(); }
4128
4129        pool.reset_peak_pins();
4130        let before = pool.stats();
4131        let n = t.range(&[]).unwrap().count();
4132        let after = pool.stats();
4133
4134        assert_eq!(n, 50_000);
4135        assert_eq!(after.peak_pins, 1, "a scan must hold exactly one leaf at a time");
4136        assert!(after.evictions > before.evictions, "50k keys over 8 frames must evict");
4137    }
4138
4139    /// A PULL cursor -- peek, use, step -- is how the query executor walks a
4140    /// scan, and it must cost what the callback walk costs. `peek_ref` used
4141    /// to run the whole `position_at_or_after` seek EVERY time, which is a
4142    /// second page open and two more record decodes per row to arrive back
4143    /// at the record the cursor was already standing on. Counted, not timed:
4144    /// a walk of 50,000 keys must seek about once per leaf, and there are
4145    /// nowhere near 50,000 leaves.
4146    #[test]
4147    fn a_pull_cursor_seeks_once_per_leaf_not_once_per_row() {
4148        let (pool, _d) = scratch_pool(64);
4149        let last_leaf = Cell::new(None);
4150        let fast_path_hits = Cell::new(0);
4151        let fast_path_attempts = Cell::new(0);
4152        let mut t = BTree::create(&pool, 1, &last_leaf, &fast_path_hits, &fast_path_attempts).unwrap();
4153        for i in 0..50_000u64 {
4154            t.insert(&i.to_be_bytes(), b"v").unwrap();
4155        }
4156        let mut iter = t.range(&[]).unwrap();
4157        let mut rows = 0u64;
4158        while iter.peek_ref().unwrap().is_some() {
4159            iter.step();
4160            rows += 1;
4161        }
4162        assert_eq!(rows, 50_000);
4163        let leaves = u64::from(iter.leaves_stepped());
4164        assert!(
4165            iter.seeks() <= leaves + 2,
4166            "a pull walk of {rows} rows over {leaves} leaves seeked {} times;              one seek per leaf (plus the first and the last) is the shape",
4167            iter.seeks()
4168        );
4169    }
4170
4171    /// The fast path above must never serve an overflow marker's 12-byte
4172    /// locator where the value belongs. A leaf holding one is excluded
4173    /// wholesale, so every record of it goes the seek way.
4174    #[test]
4175    fn a_pull_cursor_still_resolves_overflow_values() {
4176        let (pool, _d) = scratch_pool(64);
4177        let last_leaf = Cell::new(None);
4178        let fast_path_hits = Cell::new(0);
4179        let fast_path_attempts = Cell::new(0);
4180        let mut t = BTree::create(&pool, 1, &last_leaf, &fast_path_hits, &fast_path_attempts).unwrap();
4181        let big = vec![7u8; 9_000];
4182        for i in 0..64u64 {
4183            if i % 8 == 0 {
4184                t.insert(&i.to_be_bytes(), &big).unwrap();
4185            } else {
4186                t.insert(&i.to_be_bytes(), b"v").unwrap();
4187            }
4188        }
4189        let mut iter = t.range(&[]).unwrap();
4190        let mut seen = 0u64;
4191        loop {
4192            let (key, value) = {
4193                let Some((key, value)) = iter.peek_ref().unwrap() else { break };
4194                (key.to_vec(), value.to_vec())
4195            };
4196            let i = u64::from_be_bytes(key.as_slice().try_into().unwrap());
4197            if i % 8 == 0 {
4198                assert_eq!(value, big, "key {i} must read back its overflow value");
4199            } else {
4200                assert_eq!(value, b"v", "key {i} must read back its inline value");
4201            }
4202            iter.step();
4203            seen += 1;
4204        }
4205        assert_eq!(seen, 64);
4206    }
4207
4208    #[test]
4209    fn a_scan_starting_past_the_last_key_yields_nothing() {
4210        let (pool, _d) = scratch_pool(8);
4211        let last_leaf = Cell::new(None);
4212        let fast_path_hits = Cell::new(0);
4213        let fast_path_attempts = Cell::new(0);
4214        let mut t = BTree::create(&pool, 1, &last_leaf, &fast_path_hits, &fast_path_attempts).unwrap();
4215        for i in 0..1000u64 { t.insert(&i.to_be_bytes(), b"v").unwrap(); }
4216        assert_eq!(t.range(&u64::MAX.to_be_bytes()).unwrap().count(), 0);
4217    }
4218
4219    #[test]
4220    fn a_scan_of_an_empty_tree_yields_nothing() {
4221        let (pool, _d) = scratch_pool(8);
4222        let last_leaf = Cell::new(None);
4223        let fast_path_hits = Cell::new(0);
4224        let fast_path_attempts = Cell::new(0);
4225        let t = BTree::create(&pool, 1, &last_leaf, &fast_path_hits, &fast_path_attempts).unwrap();
4226        assert_eq!(t.range(&[]).unwrap().count(), 0);
4227        assert_eq!(t.range(&42u64.to_be_bytes()).unwrap().count(), 0);
4228    }
4229
4230    fn peek_owned(iter: &mut RangeIter<'_>, target: &[u8]) -> Option<(Vec<u8>, Vec<u8>)> {
4231        iter.peek_at_or_after(target)
4232            .unwrap()
4233            .map(|(k, v)| (k.to_vec(), v.to_vec()))
4234    }
4235
4236    fn collect_next(t: &BTree<'_>) -> Vec<(Vec<u8>, Vec<u8>)> {
4237        t.range(&[]).unwrap().collect::<Result<Vec<_>>>().unwrap()
4238    }
4239
4240    /// `peek_at_or_after` on a sequence of ascending targets must return the
4241    /// same key/value as the first allocating `next()` record with key >= target.
4242    #[test]
4243    fn peek_at_or_after_matches_allocating_iterator_on_random_trees() {
4244        let (pool, _d) = scratch_pool(32);
4245        let last_leaf = Cell::new(None);
4246        let hits = Cell::new(0);
4247        let tries = Cell::new(0);
4248        let mut t = BTree::create(&pool, 1, &last_leaf, &hits, &tries).unwrap();
4249        let n = 800u64;
4250        let scatter = |i: u64| i.wrapping_mul(0x9E37_79B9_7F4A_7C15).to_be_bytes().to_vec();
4251        for i in 0..n {
4252            t.insert(&scatter(i), &i.to_le_bytes()).unwrap();
4253        }
4254        let all = collect_next(&t);
4255        assert_eq!(all.len(), n as usize);
4256
4257        let mut targets: Vec<Vec<u8>> = Vec::new();
4258        targets.push(Vec::new());
4259        for (i, (k, _)) in all.iter().enumerate() {
4260            targets.push(k.clone());
4261            if i + 1 < all.len() {
4262                let mut mid = k.clone();
4263                if let Some(last) = mid.last_mut() {
4264                    *last = last.saturating_add(1);
4265                }
4266                if mid.as_slice() < all[i + 1].0.as_slice() {
4267                    targets.push(mid);
4268                }
4269            }
4270        }
4271        targets.push(vec![0xff; 16]);
4272        targets.sort();
4273        targets.dedup();
4274
4275        let mut peek = t.range(&[]).unwrap();
4276        for target in &targets {
4277            let expected = all
4278                .iter()
4279                .find(|(k, _)| k.as_slice() >= target.as_slice())
4280                .cloned();
4281            assert_eq!(peek_owned(&mut peek, target), expected, "target {target:?}");
4282        }
4283    }
4284
4285    #[test]
4286    fn peek_at_or_after_crosses_leaf_boundaries() {
4287        let (pool, _d) = scratch_pool(16);
4288        let last_leaf = Cell::new(None);
4289        let hits = Cell::new(0);
4290        let tries = Cell::new(0);
4291        let mut t = BTree::create(&pool, 1, &last_leaf, &hits, &tries).unwrap();
4292        let n = 2_000u64;
4293        for i in 0..n {
4294            t.insert(&i.to_be_bytes(), &i.to_le_bytes()).unwrap();
4295        }
4296        let all = collect_next(&t);
4297        assert_eq!(all.len(), n as usize);
4298
4299        let mut peek = t.range(&[]).unwrap();
4300        for (k, v) in &all {
4301            assert_eq!(peek_owned(&mut peek, k).as_ref(), Some(&(k.clone(), v.clone())));
4302        }
4303        // Jumping onto a key that is not first in its leaf, then walking to the end.
4304        let mid = &all[all.len() / 2].0;
4305        let mut peek = t.range(&[]).unwrap();
4306        let got = peek_owned(&mut peek, mid);
4307        assert_eq!(got.as_ref(), Some(&all[all.len() / 2]));
4308        let last = &all[all.len() - 1].0;
4309        assert_eq!(peek_owned(&mut peek, last).as_ref(), Some(&all[all.len() - 1]));
4310    }
4311
4312    #[test]
4313    fn peek_at_or_after_target_beyond_the_end_is_none() {
4314        let (pool, _d) = scratch_pool(8);
4315        let last_leaf = Cell::new(None);
4316        let hits = Cell::new(0);
4317        let tries = Cell::new(0);
4318        let mut t = BTree::create(&pool, 1, &last_leaf, &hits, &tries).unwrap();
4319        for i in 0..200u64 {
4320            t.insert(&i.to_be_bytes(), b"v").unwrap();
4321        }
4322        let mut peek = t.range(&[]).unwrap();
4323        assert!(peek_owned(&mut peek, &u64::MAX.to_be_bytes()).is_none());
4324        assert!(peek_owned(&mut peek, &u64::MAX.to_be_bytes()).is_none());
4325        let mut peek = t.range(&500u64.to_be_bytes()).unwrap();
4326        assert!(peek_owned(&mut peek, &u64::MAX.to_be_bytes()).is_none());
4327    }
4328
4329    #[test]
4330    fn peek_at_or_after_on_an_empty_range_is_none() {
4331        let (pool, _d) = scratch_pool(8);
4332        let last_leaf = Cell::new(None);
4333        let hits = Cell::new(0);
4334        let tries = Cell::new(0);
4335        let t = BTree::create(&pool, 1, &last_leaf, &hits, &tries).unwrap();
4336        let mut peek = t.range(&[]).unwrap();
4337        assert!(peek_owned(&mut peek, b"").is_none());
4338        assert!(peek_owned(&mut peek, b"z").is_none());
4339        let mut peek = t.range(b"mid").unwrap();
4340        assert!(peek_owned(&mut peek, b"mid").is_none());
4341    }
4342
4343    /// Sequential peeks of every key must pin each leaf once, not once per row.
4344    #[test]
4345    fn peek_at_or_after_pool_gets_are_per_leaf_not_per_row() {
4346        let (pool, _d) = scratch_pool(32);
4347        let last_leaf = Cell::new(None);
4348        let hits = Cell::new(0);
4349        let tries = Cell::new(0);
4350        let mut t = BTree::create(&pool, 1, &last_leaf, &hits, &tries).unwrap();
4351        let n = 3_000u64;
4352        for i in 0..n {
4353            t.insert(&i.to_be_bytes(), &i.to_le_bytes()).unwrap();
4354        }
4355        let keys: Vec<_> = collect_next(&t).into_iter().map(|(k, _)| k).collect();
4356        let before = pool.stats();
4357        let mut peek = t.range(&[]).unwrap();
4358        for k in &keys {
4359            assert!(peek_owned(&mut peek, k).is_some());
4360        }
4361        let after = pool.stats();
4362        let gets = (after.hits + after.misses) - (before.hits + before.misses);
4363        println!("peek_at_or_after over {n} rows: {gets} pool gets");
4364        assert!(
4365            gets < n / 8,
4366            "peek over {n} rows charged {gets} pool gets; expected O(leaves)"
4367        );
4368    }
4369
4370    #[test]
4371    fn peek_at_or_after_does_not_allocate_per_row() {
4372        let (pool, _d) = scratch_pool(32);
4373        let last_leaf = Cell::new(None);
4374        let hits = Cell::new(0);
4375        let tries = Cell::new(0);
4376        let mut t = BTree::create(&pool, 1, &last_leaf, &hits, &tries).unwrap();
4377        let n = 2_000u64;
4378        for i in 0..n {
4379            t.insert(&i.to_be_bytes(), &i.to_le_bytes()).unwrap();
4380        }
4381        let (_, allocs, bytes) = crate::test_alloc::measured(|| {
4382            let mut peek = t.range(&[]).unwrap();
4383            for i in 0..n {
4384                let key = i.to_be_bytes();
4385                let hit = peek.peek_at_or_after(&key).unwrap();
4386                let (k, v) = hit.expect("key present");
4387                assert_eq!(k, key);
4388                assert_eq!(v, i.to_le_bytes());
4389            }
4390        });
4391        println!("peek_at_or_after over {n} rows: {allocs} allocations, {bytes} bytes");
4392        assert!(
4393            allocs < 32,
4394            "peek over {n} in-leaf rows allocated {allocs} times; expected O(1)"
4395        );
4396    }
4397
4398    #[test]
4399    fn deleted_keys_are_gone_and_their_neighbours_are_not() {
4400        let (pool, _d) = scratch_pool(8);
4401        let last_leaf = Cell::new(None);
4402        let fast_path_hits = Cell::new(0);
4403        let fast_path_attempts = Cell::new(0);
4404        let mut t = BTree::create(&pool, 1, &last_leaf, &fast_path_hits, &fast_path_attempts).unwrap();
4405        let n = 10_000u64;
4406        for i in 0..n { t.insert(&i.to_be_bytes(), &i.to_le_bytes()).unwrap(); }
4407        for i in (0..n).step_by(2) { assert!(t.delete(&i.to_be_bytes()).unwrap()); }
4408
4409        for i in 0..n {
4410            let got = t.get(&i.to_be_bytes()).unwrap();
4411            if i % 2 == 0 { assert!(got.is_none(), "{i} should be gone"); }
4412            else { assert_eq!(got.as_deref(), Some(&i.to_le_bytes()[..]), "{i} was collateral"); }
4413        }
4414        // And the scan agrees with the point lookups.
4415        let scanned = t.range(&[]).unwrap().count();
4416        assert_eq!(scanned as u64, n / 2);
4417    }
4418
4419    #[test]
4420    fn deleting_a_key_that_is_not_there_reports_so() {
4421        let (pool, _d) = scratch_pool(8);
4422        let last_leaf = Cell::new(None);
4423        let fast_path_hits = Cell::new(0);
4424        let fast_path_attempts = Cell::new(0);
4425        let mut t = BTree::create(&pool, 1, &last_leaf, &fast_path_hits, &fast_path_attempts).unwrap();
4426        t.insert(b"a", b"1").unwrap();
4427        assert!(!t.delete(b"zzz").unwrap());
4428    }
4429
4430    /// Deletion must leave the sibling chain walkable. A leaf emptied by
4431    /// deletion is not unlinked — it stays in the chain with zero entries — so
4432    /// the scan has to pass through it rather than stopping there.
4433    #[test]
4434    fn a_scan_still_walks_leaves_that_deletion_emptied() {
4435        let (pool, _d) = scratch_pool(16);
4436        let last_leaf = Cell::new(None);
4437        let fast_path_hits = Cell::new(0);
4438        let fast_path_attempts = Cell::new(0);
4439        let mut t = BTree::create(&pool, 1, &last_leaf, &fast_path_hits, &fast_path_attempts).unwrap();
4440        for i in 0..5_000u64 { t.insert(&i.to_be_bytes(), b"v").unwrap(); }
4441
4442        // Delete a long contiguous run, which should empty at least one leaf
4443        // outright while leaving keys on both sides of it.
4444        for i in 1_000..3_000u64 { assert!(t.delete(&i.to_be_bytes()).unwrap()); }
4445
4446        let seen: Vec<u64> = t.range(&[]).unwrap()
4447            .map(|r| u64::from_be_bytes(r.unwrap().0.try_into().unwrap()))
4448            .collect();
4449        assert_eq!(seen.len(), 3_000, "keys on the far side of an emptied leaf must survive");
4450        assert_eq!(seen.first().copied(), Some(0));
4451        assert_eq!(seen.last().copied(), Some(4_999));
4452        assert!(seen.windows(2).all(|w| w[0] < w[1]), "scan order must still be sorted");
4453    }
4454
4455    /// The cycle guard, actually exercised. Without it this scan never returns.
4456    ///
4457    /// The guard is the one path in the scan with no other coverage: the two
4458    /// boundary tests are about where a scan starts, not about how it refuses to
4459    /// run forever. An unbounded loop on a damaged sibling pointer is how the
4460    /// predecessor engine died, so the guard needs a test that would notice its
4461    /// removal.
4462    #[test]
4463    fn a_cyclic_sibling_chain_is_inert_because_scans_descend() {
4464        let (pool, _d) = scratch_pool(16);
4465        let last_leaf = Cell::new(None);
4466        let fast_path_hits = Cell::new(0);
4467        let fast_path_attempts = Cell::new(0);
4468        let mut t = BTree::create(&pool, 1, &last_leaf, &fast_path_hits, &fast_path_attempts).unwrap();
4469        for i in 0..5000u64 { t.insert(&i.to_be_bytes(), b"v").unwrap(); }
4470
4471        // The leftmost leaf, and the one it points at.
4472        let first = { let (leaf, _) = t.descend(&[]).unwrap(); leaf };
4473        let second = {
4474            let r = pool.get(first).unwrap();
4475            PageRef::open_resident(&r, first).unwrap().next_leaf()
4476        };
4477        assert_ne!(second, 0, "this fixture needs at least two leaves");
4478
4479        // Point the second leaf back at the first: A -> B -> A.
4480        {
4481            let mut w = pool.get_mut(second).unwrap();
4482            let mut p = PageMut::reopen(w.bytes_mut());
4483            p.set_next_leaf(first);
4484            p.finalise(0);
4485        }
4486
4487        // 2f: scans advance by separator re-descent, never by the sibling
4488        // pointer, so the planted cycle must be INERT -- the scan completes,
4489        // terminates, and serves every key exactly once. (Before 2f this
4490        // fixture asserted the cycle was detected and refused; now the walk
4491        // that could meet it no longer exists. The leaves-vs-file-size guard
4492        // in `advance` still bounds a corrupt-interior descent loop.)
4493        let mut n = 0u64;
4494        for item in t.range(&[]).unwrap() {
4495            let (k, _) = item.expect("cycle in the DEAD sibling chain must not affect the scan");
4496            assert_eq!(k, n.to_be_bytes().to_vec(), "keys in order, exactly once");
4497            n += 1;
4498        }
4499        assert_eq!(n, 5000, "every key served exactly once despite the cycle");
4500    }
4501
4502    // -- Task 15: one write guard per insert, and the append fast path --
4503
4504    /// Ascending keys never require a split within this run (100 tiny
4505    /// records fit easily in one 4056-byte-usable leaf), so only the very
4506    /// first insert -- before `last_leaf` is ever set -- pays for a
4507    /// descent. Every insert after it satisfies all five fast-path checks:
4508    /// same leaf, same tree, rightmost (no split ever occurs), room, and a
4509    /// strictly increasing key. Asserting the exact count (not `> 0`) is
4510    /// what would catch a fast path that only fires sometimes.
4511    #[test]
4512    fn insert_ascending_uses_fast_path() {
4513        let (pool, _d) = scratch_pool(4);
4514        let last_leaf = Cell::new(None);
4515        let fast_path_hits = Cell::new(0);
4516        let fast_path_attempts = Cell::new(0);
4517        let mut t = BTree::create(&pool, 1, &last_leaf, &fast_path_hits, &fast_path_attempts).unwrap();
4518        let n = 100u64;
4519        for i in 0..n { t.insert(&i.to_be_bytes(), b"v").unwrap(); }
4520        assert_eq!(
4521            t.fast_path_hits.get(), n - 1,
4522            "every insert but the first must hit the append fast path"
4523        );
4524    }
4525
4526    /// Descending keys can never satisfy "strictly greater than the last
4527    /// key on the page" -- each new key is smaller than everything already
4528    /// there -- so the fast path must never fire, not even once after the
4529    /// first insert sets the hint.
4530    #[test]
4531    fn insert_descending_never_uses_fast_path() {
4532        let (pool, _d) = scratch_pool(4);
4533        let last_leaf = Cell::new(None);
4534        let fast_path_hits = Cell::new(0);
4535        let fast_path_attempts = Cell::new(0);
4536        let mut t = BTree::create(&pool, 1, &last_leaf, &fast_path_hits, &fast_path_attempts).unwrap();
4537        let n = 100u64;
4538        for i in (0..n).rev() { t.insert(&i.to_be_bytes(), b"v").unwrap(); }
4539        assert_eq!(
4540            t.fast_path_hits.get(), 0,
4541            "a descending insert order must never satisfy the strictly-greater check"
4542        );
4543    }
4544
4545    /// A hint left pointing at another tree's leaf must be refused by the
4546    /// tree_id check, not used -- five trees can share one file, and a page
4547    /// from another tree parses as a perfectly valid page. Build two trees
4548    /// in one store, force tree 1's hint onto tree 2's leaf, and confirm
4549    /// both trees come out uncorrupted.
4550    #[test]
4551    fn fast_path_rejects_wrong_tree() {
4552        let (pool, _d) = scratch_pool(8);
4553        let last_leaf1 = Cell::new(None);
4554        let fast_path_hits1 = Cell::new(0);
4555        let fast_path_attempts1 = Cell::new(0);
4556        let last_leaf2 = Cell::new(None);
4557        let fast_path_hits2 = Cell::new(0);
4558        let fast_path_attempts2 = Cell::new(0);
4559        let mut t1 = BTree::create(&pool, 1, &last_leaf1, &fast_path_hits1, &fast_path_attempts1).unwrap();
4560        let mut t2 = BTree::create(&pool, 2, &last_leaf2, &fast_path_hits2, &fast_path_attempts2).unwrap();
4561        t1.insert(b"a", b"1").unwrap();
4562        t2.insert(b"z", b"2").unwrap();
4563
4564        // Force t1's hint onto t2's leaf, as a stale/corrupted hint would.
4565        let t2_leaf = t2.root();
4566        t1.last_leaf.set(Some(t2_leaf));
4567
4568        // "zz" sorts after "z", t2's only (and last) key -- so the
4569        // ordering check alone would let this through. Only the tree_id
4570        // check stops it from landing on t2's leaf.
4571        t1.insert(b"zz", b"3").unwrap();
4572
4573        assert_eq!(
4574            t1.fast_path_hits.get(), 0,
4575            "a hint pointing into another tree must fall back, not be used"
4576        );
4577        assert_eq!(t1.get(b"a").unwrap().as_deref(), Some(&b"1"[..]));
4578        assert_eq!(t1.get(b"zz").unwrap().as_deref(), Some(&b"3"[..]));
4579        assert_eq!(
4580            t2.get(b"z").unwrap().as_deref(), Some(&b"2"[..]),
4581            "t2 must be untouched by t1's misdirected hint"
4582        );
4583        assert_eq!(
4584            t2.range(&[]).unwrap().count(), 1,
4585            "t2 must not have gained a record that belonged to t1"
4586        );
4587    }
4588
4589    /// After a split, the leaf that used to be rightmost has a right
4590    /// sibling and must never be used as an append target again, even for
4591    /// a key that legitimately belongs on it. Force the hint back onto that
4592    /// now-interior-ish (non-rightmost) leaf and confirm the insert still
4593    /// lands correctly, via fallback, with nothing else disturbed.
4594    #[test]
4595    fn fast_path_rejects_non_rightmost() {
4596        let (pool, _d) = scratch_pool(8);
4597        let last_leaf = Cell::new(None);
4598        let fast_path_hits = Cell::new(0);
4599        let fast_path_attempts = Cell::new(0);
4600        let mut t = BTree::create(&pool, 1, &last_leaf, &fast_path_hits, &fast_path_attempts).unwrap();
4601
4602        // Even keys only, so odd keys are legitimate "new, in-range" keys
4603        // with no split-order ambiguity. Enough of them (400, at ~17 bytes
4604        // of page-budget cost each) to force at least one split of the
4605        // single starting leaf.
4606        let n = 400u64;
4607        for i in 0..n { t.insert(&(i * 2).to_be_bytes(), b"v").unwrap(); }
4608
4609        let (left_leaf, _) = t.descend(&0u64.to_be_bytes()).unwrap();
4610        let last_in_left = {
4611            let r = pool.get(left_leaf).unwrap();
4612            let p = PageRef::open_resident(&r, left_leaf).unwrap();
4613            assert_ne!(p.next_leaf(), 0, "fixture needs the left leaf to have a right sibling");
4614            u64::from_be_bytes(
4615                validated_key(p.slot(p.nentries() - 1))
4616                    .try_into()
4617                    .unwrap(),
4618            )
4619        };
4620
4621        t.last_leaf.set(Some(left_leaf));
4622        let hits_before = t.fast_path_hits.get();
4623
4624        // The odd key immediately after the left leaf's own last key: this
4625        // is the one candidate that is BOTH strictly greater than the last
4626        // key on the (stale) hinted leaf -- satisfying the ordering check
4627        // on its own -- AND still genuinely in-range for that leaf (it
4628        // sorts below the separator, since the next even key belongs to the
4629        // right leaf). Only the rightmost check can still catch it.
4630        let candidate = last_in_left + 1;
4631        t.insert(&candidate.to_be_bytes(), b"new").unwrap();
4632
4633        assert_eq!(
4634            t.fast_path_hits.get(), hits_before,
4635            "a non-rightmost hint must never be used, even for a key that is both \
4636             in-range and greater than the hinted leaf's last key"
4637        );
4638        assert_eq!(
4639            t.get(&candidate.to_be_bytes()).unwrap().as_deref(), Some(&b"new"[..]),
4640            "the insert must still land correctly via fallback"
4641        );
4642        for i in 0..n {
4643            assert_eq!(
4644                t.get(&(i * 2).to_be_bytes()).unwrap().as_deref(), Some(&b"v"[..]),
4645                "key {i} corrupted by the stale hint"
4646            );
4647        }
4648    }
4649
4650    /// The correctness net for both changes at once: the same 20k keys,
4651    /// inserted once in ascending order (exercising the fast path
4652    /// constantly) and once in a scattered order (exercising
4653    /// `descend_for_write` and splits constantly, since a scattered key is
4654    /// essentially never greater than the rightmost leaf's last key), must
4655    /// produce byte-identical range scans.
4656    #[test]
4657    fn random_order_matches_sequential() {
4658        let n = 20_000u64;
4659        let scatter = |i: u64| i.wrapping_mul(0x9E37_79B9_7F4A_7C15);
4660
4661        let (pool_a, _da) = scratch_pool(16);
4662        let last_leaf_a = Cell::new(None);
4663        let fast_path_hits_a = Cell::new(0);
4664        let fast_path_attempts_a = Cell::new(0);
4665        let mut a = BTree::create(&pool_a, 1, &last_leaf_a, &fast_path_hits_a, &fast_path_attempts_a).unwrap();
4666        for i in 0..n { a.insert(&i.to_be_bytes(), &i.to_le_bytes()).unwrap(); }
4667
4668        let (pool_b, _db) = scratch_pool(16);
4669        let last_leaf_b = Cell::new(None);
4670        let fast_path_hits_b = Cell::new(0);
4671        let fast_path_attempts_b = Cell::new(0);
4672        let mut b = BTree::create(&pool_b, 1, &last_leaf_b, &fast_path_hits_b, &fast_path_attempts_b).unwrap();
4673        let mut order: Vec<u64> = (0..n).collect();
4674        order.sort_by_key(|&i| scatter(i));
4675        for &i in &order { b.insert(&i.to_be_bytes(), &i.to_le_bytes()).unwrap(); }
4676
4677        let seq_a: Vec<(Vec<u8>, Vec<u8>)> = a.range(&[]).unwrap().map(|r| r.unwrap()).collect();
4678        let seq_b: Vec<(Vec<u8>, Vec<u8>)> = b.range(&[]).unwrap().map(|r| r.unwrap()).collect();
4679        assert_eq!(seq_a.len(), n as usize);
4680        assert_eq!(
4681            seq_a, seq_b,
4682            "ascending vs scattered insertion order must produce identical range scans"
4683        );
4684    }
4685
4686    // ---------------------------------------------------------- D9 per-keyspace append
4687    //
4688    // One tree holds several key TAGS (D3/D4: vectors, rows and external-key
4689    // mappings are keyspaces, not files). `src/collections/mod.rs` writes one
4690    // document as three keys -- 0x60 vector, 0x40 row, 0x20 mapping -- and each
4691    // of those three runs is ASCENDING in itself. Only the highest tag is ever
4692    // the TREE's rightmost leaf, so D9's `next_leaf() == 0` guard fires for
4693    // 0x60 and never for 0x40 or 0x20.
4694    //
4695    // What that costs is WORK, not space: `redistribute_neighbors` keeps the
4696    // lower two runs densely packed (measured below), and pays for it with a
4697    // clone of the parent's records plus up to three siblings' records and up
4698    // to four rebuilt page images on every leaf fill, forever.
4699
4700    /// `src/collections/mod.rs`'s width-tagged big-endian integer component.
4701    fn ordered(n: u64) -> Vec<u8> {
4702        let b = n.to_be_bytes();
4703        let start = b.iter().position(|x| *x != 0).unwrap_or(7);
4704        let mut k = vec![0x80 + (8 - start) as u8];
4705        k.extend_from_slice(&b[start..]);
4706        k
4707    }
4708
4709    /// One document's key for `tag`, ascending in `i`. Shaped exactly like
4710    /// `src/collections/mod.rs`: `[tag][ordered(collection)][...]`.
4711    fn tagged(tag: u8, i: u64) -> Vec<u8> {
4712        let mut k = vec![tag];
4713        k.extend(ordered(1));
4714        match tag {
4715            // mapping: the caller's external key, a string
4716            0x20 => k.extend_from_slice(format!("key-{i:012}").as_bytes()),
4717            // row: the entity sequence
4718            0x40 => k.extend(ordered(i + 1)),
4719            // vector: the row key plus a field ordinal
4720            _ => {
4721                k.extend(ordered(i + 1));
4722                k.extend(ordered(0));
4723            }
4724        }
4725        k
4726    }
4727
4728    /// The three keyspaces store very differently sized values, and that is the
4729    /// point: a mapping row is a few bytes, a document row a few hundred.
4730    fn tagged_value(tag: u8, i: u64) -> Vec<u8> {
4731        match tag {
4732            0x20 => ordered(i + 1),
4733            // A document, varying in length the way real documents do.
4734            0x40 => vec![b'd'; 200 + (i % 97) as usize * 2],
4735            // Eight f32 lanes.
4736            _ => vec![b'f'; 32],
4737        }
4738    }
4739
4740    /// Insert `docs` documents, each as one key per tag in `tags`, in that
4741    /// order -- `src/collections/mod.rs` writes vector, then row, then mapping.
4742    fn load_interleaved(t: &mut BTree<'_>, tags: &[u8], docs: u64) {
4743        for i in 0..docs {
4744            for tag in tags {
4745                t.insert(&tagged(*tag, i), &tagged_value(*tag, i)).unwrap();
4746            }
4747        }
4748    }
4749
4750    /// Every leaf of the tree, as (tag of its first key, payload bytes used,
4751    /// whether the leaf mixes tags). Walks THROUGH THE PARENTS rather than the
4752    /// sibling chain, which is the only walk this file trusts.
4753    fn leaves(pool: &BufferPool, page_no: u32, out: &mut Vec<(u8, usize, bool)>) {
4754        let kids = {
4755            let r = pool.get(page_no).unwrap();
4756            let p = PageRef::open_resident(&r, page_no).unwrap();
4757            match p.kind() {
4758                PageKind::Leaf => {
4759                    if p.nentries() > 0 {
4760                        let tag = validated_key(p.slot(0))[0];
4761                        let mixed = (0..p.nentries()).any(|i| validated_key(p.slot(i))[0] != tag);
4762                        let used: usize = (0..p.nentries()).map(|i| p.slot(i).len() + 4).sum();
4763                        out.push((tag, used, mixed));
4764                    }
4765                    return;
4766                }
4767                PageKind::Interior => {
4768                    let mut kids = vec![p.child0()];
4769                    kids.extend((0..p.nentries()).map(|i| validated_child(p.slot(i))));
4770                    kids
4771                }
4772                other => panic!("unexpected page kind in the tree: {other:?}"),
4773            }
4774        };
4775        for k in kids {
4776            leaves(pool, k, out);
4777        }
4778    }
4779
4780    /// Mean fill of the leaves whose keys all carry `tag`, and how many there
4781    /// are. Mixed-tag leaves are excluded: one per keyspace boundary exists by
4782    /// construction and it belongs to no single run.
4783    fn fill_of(all: &[(u8, usize, bool)], tag: u8) -> (f64, usize) {
4784        let usable = (PAGE_SIZE - crate::page::HEADER_LEN) as f64;
4785        let mine: Vec<usize> = all
4786            .iter()
4787            .filter(|(t, _, mixed)| *t == tag && !*mixed)
4788            .map(|(_, used, _)| *used)
4789            .collect();
4790        assert!(!mine.is_empty(), "no pure leaves for tag {tag:#04x}");
4791        (mine.iter().sum::<usize>() as f64 / mine.len() as f64 / usable, mine.len())
4792    }
4793
4794    /// Buffer-pool page accesses per insert for one interleaved load.
4795    ///
4796    /// No `TagHints` attached, deliberately: this is D9's own measurement and
4797    /// the number below is its control. K1's per-keyspace HINT is measured
4798    /// separately, by `accesses_for_tag`, on a tree that has one.
4799    ///
4800    /// Page accesses are what this repo measures cost in (D14's 1.1 reads per
4801    /// hop, D13's 4.2 vs 90.3 reads per trace query). They are exact, they are
4802    /// already instrumented, and they do not move with the machine.
4803    fn accesses_per_insert(tags: &[u8], docs: u64) -> f64 {
4804        let (pool, _d) = scratch_pool(512);
4805        let last_leaf = Cell::new(None);
4806        let hits = Cell::new(0);
4807        let attempts = Cell::new(0);
4808        let mut t = BTree::create(&pool, 1, &last_leaf, &hits, &attempts).unwrap();
4809        load_interleaved(&mut t, tags, docs);
4810        let s = pool.stats();
4811        (s.hits + s.misses) as f64 / (docs * tags.len() as u64) as f64
4812    }
4813
4814    /// THE COST PROPERTY. An ascending run's per-insert page work must not
4815    /// depend on how many other keyspaces happen to sit to its right.
4816    ///
4817    /// Each of these three runs, loaded on its own, costs about one page access
4818    /// per insert. Interleaved into one tree they cost several, and most of the
4819    /// difference is not the deeper tree: it is `redistribute_neighbors`
4820    /// re-reading and rewriting a parent and up to three siblings on every fill
4821    /// of a 0x40 or 0x20 leaf, because neither run is the tree's rightmost.
4822    ///
4823    /// Measured on this fixture, 5,000 documents = 15,000 inserts:
4824    ///   `compact-cells,sqlite-balance`                  71,007 accesses = 4.734/insert
4825    ///   `compact-cells,sqlite-balance,keyspace-append`  60,882 accesses = 4.059/insert
4826    /// The single-tag arms are byte-identical between the two builds, which is
4827    /// the guard that nothing D9 already covered was disturbed.
4828    ///
4829    /// The bound below sits between those two figures. It is an absolute number
4830    /// because this fixture is deterministic -- no randomness, no timing -- and
4831    /// because the quantity it bounds is the one the CPU sample of the 100K
4832    /// typed load pointed at: 23.3% of load time inside `split_leaf_and_insert`.
4833    #[test]
4834    fn sharing_a_tree_must_not_multiply_an_ascending_runs_page_work() {
4835        const DOCS: u64 = 5_000;
4836        let mixed = accesses_per_insert(&[0x60, 0x40, 0x20], DOCS);
4837        let alone: Vec<f64> = [0x60u8, 0x40, 0x20]
4838            .iter()
4839            .map(|t| accesses_per_insert(std::slice::from_ref(t), DOCS))
4840            .collect();
4841        eprintln!("interleaved {mixed:.3} accesses/insert; alone {alone:.3?}");
4842
4843        // Each run on its own already gets D9 and the append hint. This is the
4844        // control arm: it must not move, in either build.
4845        for (tag, a) in [0x60u8, 0x40, 0x20].iter().zip(&alone) {
4846            assert!(*a <= 1.50, "tag {tag:#04x} alone costs {a:.3} accesses/insert");
4847        }
4848        // This is the append optimization's acceptance bound. The balancing
4849        // control deliberately lacks it (4.734 in the measurements above).
4850        // Keep the standalone controls in every build.
4851        if cfg!(feature = "keyspace-append") {
4852            assert!(
4853                mixed <= 4.30,
4854                "interleaved keyspaces cost {mixed:.3} page accesses per insert, against {alone:.3?} \
4855                 for the same three runs loaded separately"
4856            );
4857        }
4858    }
4859
4860    /// The density guard. `redistribute_neighbors` already packs these runs to
4861    /// ~0.96-0.99 of a page, so the append shortcut must not buy its cheaper
4862    /// splits with a fatter file -- which is the exact trade D9's own 2/3-fill
4863    /// discipline exists to refuse.
4864    #[cfg(feature = "sqlite-balance")]
4865    #[test]
4866    fn every_keyspace_packs_its_own_ascending_run() {
4867        const DOCS: u64 = 3_000;
4868        let (pool, _d) = scratch_pool(16);
4869        let last_leaf = Cell::new(None);
4870        let hits = Cell::new(0);
4871        let attempts = Cell::new(0);
4872        let mut t = BTree::create(&pool, 1, &last_leaf, &hits, &attempts).unwrap();
4873        load_interleaved(&mut t, &[0x60, 0x40, 0x20], DOCS);
4874
4875        let mut all = Vec::new();
4876        leaves(&pool, t.root, &mut all);
4877        let usable = PAGE_SIZE - crate::page::HEADER_LEN;
4878        let payload: usize = all.iter().map(|(_, used, _)| *used).sum();
4879        // The floor no policy can beat: the records themselves, wall to wall.
4880        let floor = payload.div_ceil(usable);
4881        let (top, top_n) = fill_of(&all, 0x60);
4882        let (row, row_n) = fill_of(&all, 0x40);
4883        let (map, map_n) = fill_of(&all, 0x20);
4884        eprintln!(
4885            "fill 0x20 {map:.3} ({map_n})  0x40 {row:.3} ({row_n})  0x60 {top:.3} ({top_n})  \
4886             leaves {} floor {floor}",
4887            all.len()
4888        );
4889
4890        for (tag, f) in [(0x20u8, map), (0x40, row), (0x60, top)] {
4891            assert!(f >= 0.90, "tag {tag:#04x} packs its leaves only {f:.3} full");
4892        }
4893        // Measured: 292 leaves against a 281-leaf floor without the shortcut,
4894        // 294 with it. 6% is the room that leaves, and no more.
4895        assert!(
4896            (all.len() as f64) <= floor as f64 * 1.06,
4897            "{} leaves for a {floor}-leaf payload",
4898            all.len()
4899        );
4900    }
4901
4902    // ------------------------------------------------ K1 per-keyspace append HINT
4903    //
4904    // D9 widened the append SPLIT to a key's own tag. The HINT stayed narrow:
4905    // `fast_path_leaf` believes a leaf only when `next_leaf() == 0`, the
4906    // rightmost leaf of the whole TREE, so a run behind a higher tag paid a
4907    // full root-to-leaf descent for every single row. Measured on the
4908    // relationship load (200K rows, 99,840 edges): 4.40 page accesses to write
4909    // one forward edge row and 4.35 to write its reverse, 95% of what was left
4910    // of the per-edge write once the endpoint and preflight reads were gone.
4911    //
4912    // `TagHints` remembers the leaf per (tree, tag) together with the FENCES
4913    // the arming descent walked, so the same append lands with one access.
4914
4915    /// A tree that keeps per-keyspace hints, and the state they borrow.
4916    /// The `Cell`s must outlive the tree, so the caller owns them.
4917    struct Hinted {
4918        last_leaf: Cell<Option<u32>>,
4919        hits: Cell<u64>,
4920        attempts: Cell<u64>,
4921        tags: TagHints,
4922    }
4923    impl Hinted {
4924        fn new() -> Self {
4925            Hinted { last_leaf: Cell::new(None), hits: Cell::new(0), attempts: Cell::new(0),
4926                     tags: TagHints::default() }
4927        }
4928        fn tree<'p>(&'p self, pool: &'p BufferPool) -> BTree<'p> {
4929            BTree::create(pool, 1, &self.last_leaf, &self.hits, &self.attempts)
4930                .unwrap()
4931                .with_tags(&self.tags)
4932        }
4933    }
4934
4935    /// Page accesses per insert for the run under `measured`, with the other
4936    /// tags interleaved between them, counted only after `warm` documents so
4937    /// the tree has its final height and the hint has been armed at least once.
4938    fn accesses_for_tag(tags: &[u8], measured: u8, docs: u64, warm: u64) -> f64 {
4939        let (pool, _d) = scratch_pool(512);
4940        let h = Hinted::new();
4941        let mut t = h.tree(&pool);
4942        let at = |p: &BufferPool| { let s = p.stats(); s.hits + s.misses };
4943        let (mut spent, mut counted) = (0u64, 0u64);
4944        for i in 0..docs {
4945            for tag in tags {
4946                let before = at(&pool);
4947                t.insert(&tagged(*tag, i), &tagged_value(*tag, i)).unwrap();
4948                if *tag == measured && i >= warm {
4949                    spent += at(&pool) - before;
4950                    counted += 1;
4951                }
4952            }
4953        }
4954        spent as f64 / counted.max(1) as f64
4955    }
4956
4957    /// THE BUDGET. An ascending run under a tag that is NOT the tree's last
4958    /// must reach its leaf without descending.
4959    ///
4960    /// 0x71 is the forward-edge tag; 0x40 (rows) and 0x60 (vectors) are written
4961    /// between its keys exactly as the multimodel load writes them, and 0x71
4962    /// sorts after both, so nothing about it is the tree's rightmost leaf.
4963    ///
4964    /// One page access is the floor: the leaf itself has to be read. Splits
4965    /// happen about once per leaf-full of rows and cost a descent each, so the
4966    /// bound is a little above the floor rather than at it.
4967    #[test]
4968    fn an_ascending_run_behind_other_keyspaces_reaches_its_leaf_without_descending() {
4969        const DOCS: u64 = 1_000;
4970        let edges = accesses_for_tag(&[0x40, 0x60, 0x71], 0x71, DOCS, DOCS / 5);
4971        eprintln!("tag 0x71 behind 0x40 and 0x60: {edges:.3} page accesses per insert");
4972        assert!(
4973            edges <= 1.20,
4974            "an ascending run behind two other keyspaces costs {edges:.3} page accesses per \
4975             insert; before the per-keyspace hint it was the tree's full height"
4976        );
4977    }
4978
4979    /// THE PLACEMENT PROPERTY. A key that does NOT fall inside the hinted
4980    /// leaf's fences must still land where a descent would put it.
4981    ///
4982    /// The tree is loaded with three ascending runs so every tag holds a live
4983    /// hint, then written scattered over the SAME tags -- every one of those
4984    /// keys finds a hint armed and almost none of them belongs to its leaf. A
4985    /// `BTreeMap` is the oracle: the full scan must equal it exactly, and every
4986    /// key must also be reachable by `get`, which is the half a wrong fence
4987    /// breaks first. A record appended past its separator stays visible to the
4988    /// scan and disappears from the descent.
4989    #[test]
4990    fn a_key_outside_the_hinted_leaf_still_lands_where_a_descent_would_put_it() {
4991        const ASCENDING: u64 = 1_500;
4992        const SCATTERED: u64 = 1_500;
4993        let (pool, _d) = scratch_pool(16);
4994        let h = Hinted::new();
4995        let mut t = h.tree(&pool);
4996        let mut model: std::collections::BTreeMap<Vec<u8>, Vec<u8>> = Default::default();
4997        let value = |tag: u8, i: u64| {
4998            let mut v = tagged_value(tag, i);
4999            v.extend_from_slice(format!("|{tag:#04x}/{i}").as_bytes());
5000            v
5001        };
5002        let tags = [0x20u8, 0x40, 0x60, 0x71, 0x72];
5003        for i in 0..ASCENDING {
5004            for tag in tags {
5005                let (k, v) = (tagged(tag, i), value(tag, i));
5006                t.insert(&k, &v).unwrap();
5007                model.insert(k, v);
5008            }
5009        }
5010        // Scattered, but INSIDE the range the ascending runs already cover, so
5011        // the hinted leaf is a plausible-looking neighbour rather than an
5012        // obvious miss -- and every third key is deliberately aimed just above
5013        // whatever leaf the hint currently names.
5014        for j in 0..SCATTERED {
5015            let i = j.wrapping_mul(0x9E37_79B9_7F4A_7C15) % ASCENDING;
5016            for tag in tags {
5017                let (k, v) = (tagged(tag, i), value(tag, i + 1));
5018                t.insert(&k, &v).unwrap();
5019                model.insert(k, v);
5020            }
5021        }
5022        let seen: Vec<(Vec<u8>, Vec<u8>)> = t.range(&[]).unwrap().map(|r| r.unwrap()).collect();
5023        let expect: Vec<(Vec<u8>, Vec<u8>)> = model.into_iter().collect();
5024        assert_eq!(seen.len(), expect.len(), "the scan returned a different number of keys");
5025        assert_eq!(seen, expect, "the scan is not every key exactly once in byte order");
5026        for (k, v) in &expect {
5027            assert_eq!(
5028                t.get(k).unwrap().as_deref(), Some(&v[..]),
5029                "a key the scan can see is not reachable by a descent: {k:?}"
5030            );
5031        }
5032    }
5033
5034    /// THE SHAPE PROPERTY. Splitting under the hint must leave an ordinary
5035    /// tree: every leaf reachable through its parents, every leaf non-empty,
5036    /// keys strictly ascending across the whole walk, and the sibling chain
5037    /// agreeing with the parent walk. `insert_tag_fast` never splits -- it is
5038    /// the fallback descent that does -- so this is the guard that the hint
5039    /// does not quietly push records onto a page the split policy then
5040    /// mis-cuts.
5041    #[test]
5042    fn splitting_under_the_per_keyspace_hint_leaves_an_ordinary_tree() {
5043        const DOCS: u64 = 4_000;
5044        let (pool, _d) = scratch_pool(64);
5045        let h = Hinted::new();
5046        let mut t = h.tree(&pool);
5047        for i in 0..DOCS {
5048            for tag in [0x40u8, 0x60, 0x71] {
5049                t.insert(&tagged(tag, i), &tagged_value(tag, i)).unwrap();
5050            }
5051        }
5052        let mut all = Vec::new();
5053        leaves(&pool, t.root, &mut all);
5054        assert!(all.len() > 8, "the fixture did not split enough to test anything");
5055
5056        // The parent walk and the sibling chain must describe the same tree.
5057        let scanned: Vec<Vec<u8>> = t.range(&[]).unwrap().map(|r| r.unwrap().0).collect();
5058        assert_eq!(scanned.len() as u64, DOCS * 3, "the walk lost or duplicated keys");
5059        for w in scanned.windows(2) {
5060            assert!(w[0] < w[1], "the walk is not strictly ascending at {:?}", w[0]);
5061        }
5062        for k in &scanned {
5063            assert!(t.get(k).unwrap().is_some(), "a walked key is not reachable by a descent");
5064        }
5065    }
5066
5067    /// Every leaf of the tree, through its parents, as
5068    /// (page, first key, last key, `next_leaf`, free space).
5069    fn leaf_details(pool: &BufferPool, page_no: u32,
5070        out: &mut Vec<(u32, Vec<u8>, Vec<u8>, u32, usize)>) {
5071        let kids = {
5072            let r = pool.get(page_no).unwrap();
5073            let p = PageRef::open_resident(&r, page_no).unwrap();
5074            match p.kind() {
5075                PageKind::Leaf => {
5076                    if p.nentries() > 0 {
5077                        out.push((page_no,
5078                            validated_key(p.slot(0)).to_vec(),
5079                            validated_key(p.slot(p.nentries() - 1)).to_vec(),
5080                            p.next_leaf(), p.free_space()));
5081                    }
5082                    return;
5083                }
5084                PageKind::Interior => {
5085                    let mut kids = vec![p.child0()];
5086                    kids.extend((0..p.nentries()).map(|i| validated_child(p.slot(i))));
5087                    kids
5088                }
5089                other => panic!("unexpected page kind in the tree: {other:?}"),
5090            }
5091        };
5092        for k in kids { leaf_details(pool, k, out); }
5093    }
5094
5095    /// THE FENCE IS EXCLUSIVE AT THE TOP. A key EQUAL to the separator above
5096    /// the hinted leaf belongs to the NEXT leaf, and the hint must refuse it.
5097    ///
5098    /// This is the one boundary a cached fence can get wrong in the silent
5099    /// direction. `upper_bound` routes a key equal to a separator to the child
5100    /// on its RIGHT, so a separator key is always the first key of the leaf to
5101    /// the right -- it already exists there. Accepting it on the hinted leaf
5102    /// appends a SECOND copy: the scan returns the key twice, and `get` keeps
5103    /// answering with the old value forever. Relaxing the comparison in
5104    /// `TagHints::find` from `key < upper` to `key <= upper` fails this test
5105    /// with "the key is in the tree twice".
5106    ///
5107    /// The hint is forced onto a chosen leaf the way the stale-hint tests above
5108    /// force `last_leaf`: a real workload reaches this state rarely, and a test
5109    /// that waits for it to happen by chance is testing nothing.
5110    #[test]
5111    fn a_key_equal_to_the_fence_belongs_to_the_next_leaf() {
5112        const DOCS: u64 = 3_000;
5113        let (pool, _d) = scratch_pool(64);
5114        let h = Hinted::new();
5115        let mut t = h.tree(&pool);
5116        // The 0x71 run is written SCATTERED here on purpose: an append split
5117        // closes each left page nearly full, and this test needs a leaf with
5118        // room in it. The hint is armed by hand below, so the write order of
5119        // the fixture decides nothing else.
5120        for i in 0..DOCS {
5121            let scattered = i.wrapping_mul(0x9E37_79B9_7F4A_7C15) % DOCS;
5122            t.insert(&tagged(0x40, i), &tagged_value(0x40, i)).unwrap();
5123            t.insert(&tagged(0x71, scattered), &tagged_value(0x71, scattered)).unwrap();
5124        }
5125        let mut all = Vec::new();
5126        leaf_details(&pool, t.root, &mut all);
5127
5128        // A 0x71 leaf with room, whose right neighbour is also a 0x71 leaf.
5129        let by_page: std::collections::HashMap<u32, usize> =
5130            all.iter().enumerate().map(|(i, l)| (l.0, i)).collect();
5131        let chosen = all.iter().find(|l| {
5132            l.1[0] == 0x71 && l.2[0] == 0x71 && l.4 >= 64
5133                && by_page.get(&l.3).is_some_and(|&j| all[j].1[0] == 0x71)
5134        }).expect("the fixture has no 0x71 leaf with room beside another");
5135        let right = &all[by_page[&chosen.3]];
5136        let separator = right.1.clone();
5137        assert!(separator > chosen.2, "the fixture's leaves are not in order");
5138
5139        // Arm the hint exactly as the arming descent would have, then hand it
5140        // the separator key.
5141        let mut fences = LeafFences::NONE;
5142        fences.narrow_lower(&chosen.1);
5143        fences.narrow_upper(&separator);
5144        assert!(fences.armable, "the fixture's separators must fit the inline fence");
5145        h.tags.arm(1, &chosen.2, chosen.0, chosen.3, fences);
5146        t.insert(&separator, b"replaced").unwrap();
5147
5148        let seen: Vec<Vec<u8>> = t.range(&[]).unwrap().map(|r| r.unwrap().0).collect();
5149        assert_eq!(
5150            seen.iter().filter(|k| **k == separator).count(), 1,
5151            "the key is in the tree twice: the hint appended it below its own fence"
5152        );
5153        for w in seen.windows(2) {
5154            assert!(w[0] < w[1], "the scan is not strictly ascending at {:?}", w[0]);
5155        }
5156        assert_eq!(
5157            t.get(&separator).unwrap().as_deref(), Some(&b"replaced"[..]),
5158            "the descent still answers with the old value, so the new record went elsewhere"
5159        );
5160    }
5161
5162    /// THE INVALIDATION PROPERTY. A delete can merge leaves, collapse the root
5163    /// and hand a freed page straight back to the allocator, so the fences a
5164    /// hint cached describe a shape that no longer exists. Writing the same
5165    /// ascending run again afterwards must place every key correctly.
5166    #[test]
5167    fn a_delete_forgets_the_fences_it_invalidated() {
5168        const DOCS: u64 = 1_200;
5169        let (pool, _d) = scratch_pool(16);
5170        let h = Hinted::new();
5171        let mut t = h.tree(&pool);
5172        let mut model: std::collections::BTreeMap<Vec<u8>, Vec<u8>> = Default::default();
5173        for i in 0..DOCS {
5174            for tag in [0x40u8, 0x71] {
5175                let (k, v) = (tagged(tag, i), tagged_value(tag, i));
5176                t.insert(&k, &v).unwrap();
5177                model.insert(k, v);
5178            }
5179        }
5180        // Empty most of the forward-edge run, which is where the hint sits.
5181        for i in 0..DOCS * 3 / 4 {
5182            let k = tagged(0x71, i);
5183            assert!(t.delete(&k).unwrap());
5184            model.remove(&k);
5185        }
5186        // And write it straight back, ascending, through whatever the delete
5187        // left behind.
5188        for i in 0..DOCS * 3 / 4 {
5189            let (k, v) = (tagged(0x71, i), tagged_value(0x71, i + 7));
5190            t.insert(&k, &v).unwrap();
5191            model.insert(k, v);
5192        }
5193        let seen: Vec<(Vec<u8>, Vec<u8>)> = t.range(&[]).unwrap().map(|r| r.unwrap()).collect();
5194        assert_eq!(seen, model.into_iter().collect::<Vec<_>>(),
5195            "the tree written after a delete is not the tree the model describes");
5196        for (k, v) in &seen {
5197            assert_eq!(t.get(k).unwrap().as_deref(), Some(&v[..]),
5198                "a key is in the scan but not reachable by a descent: {k:?}");
5199        }
5200    }
5201
5202    // ------------------------------- K1 byte-equivalence: placement is unchanged
5203    //
5204    // Phase 1's rule for a speed change in the split/append path is that the
5205    // page IMAGES do not move (`split_byte_equivalence` below). The same rule
5206    // applies here, and more sharply: this cache decides which leaf a write
5207    // reaches WITHOUT descending, so if it ever reaches a different one than
5208    // the descent would, the file says so.
5209    //
5210    // The claim under test: the per-keyspace hint changes where a write
5211    // descends FROM, never where it lands. Run one deterministic multimodel
5212    // workload twice into two stores -- once with the cache, once with it
5213    // switched off -- and the two data files must be equal byte for byte,
5214    // every page image, after the final checkpoint.
5215
5216    use crate::store::{Config, Store, SyncMode};
5217    use crate::io::IoMode;
5218
5219    fn cfg() -> Config {
5220        Config { budget_bytes: 32 << 20, io: IoMode::Buffered, sync: SyncMode::Off }
5221    }
5222
5223    /// `[tag][collection][sequence]` -- a data row, `src/collections/mod.rs`'s shape.
5224    fn row_key(i: u64) -> Vec<u8> {
5225        let mut k = vec![0x40u8, 1];
5226        k.extend_from_slice(&i.to_be_bytes());
5227        k
5228    }
5229    /// `[tag][collection][sequence][field]` -- an embedding row.
5230    fn vec_key(i: u64) -> Vec<u8> {
5231        let mut k = vec![0x60u8, 1];
5232        k.extend_from_slice(&i.to_be_bytes());
5233        k.push(0);
5234        k
5235    }
5236    /// `[tag][first collection][first seq][ctx][type][last collection][last seq]`
5237    /// -- `src/index/graph/mod.rs`'s edge key, narrowed to one byte per
5238    /// small identity. 0x71 is the forward row (first = source), 0x72 the
5239    /// reverse (first = destination). Organizations live in collection 2, so
5240    /// their rows sort ABOVE every person's, exactly as in the bench.
5241    fn edge_key(tag: u8, a: (u8, u64), ty: u8, b: (u8, u64)) -> Vec<u8> {
5242        let mut k = vec![tag, a.0];
5243        k.extend_from_slice(&a.1.to_be_bytes());
5244        k.extend_from_slice(&[0, ty, b.0]);
5245        k.extend_from_slice(&b.1.to_be_bytes());
5246        k
5247    }
5248
5249    /// THE WORKLOAD. Deterministic, no randomness, no timing: per document it
5250    /// writes a row, an embedding, three forward-edge rows and their three
5251    /// reverse rows, and every so often deletes one of each. The destinations
5252    /// reproduce the relationship load's shape -- `(i+1)` and `(i+7)` make two
5253    /// near-ascending reverse runs six keys apart, and `i % 100` makes a
5254    /// hundred hot organizations whose reverse rows are a hundred separate
5255    /// cold runs. 5,000 documents is 40,000 puts, far past the volume at which
5256    /// every tag's run splits and redistributes across the keyspace boundary
5257    /// it shares with the tag above it.
5258    fn multimodel_workload(s: &mut Store, docs: u64) {
5259        for i in 0..docs {
5260            s.put(&row_key(i), &vec![b'd'; 200 + (i % 97) as usize * 2]).unwrap();
5261            s.put(&vec_key(i), &vec![b'f'; 32]).unwrap();
5262            let people = [(1u8, (i + 1) % docs), (1, (i + 7) % docs)];
5263            for (n, dst) in people.iter().enumerate() {
5264                s.put(&edge_key(0x71, (1, i), 1, *dst), &[b'e', n as u8]).unwrap();
5265                s.put(&edge_key(0x72, *dst, 1, (1, i)), &[]).unwrap();
5266            }
5267            let org = (2u8, i % 100 + 1);
5268            s.put(&edge_key(0x71, (1, i), 2, org), b"m").unwrap();
5269            s.put(&edge_key(0x72, org, 2, (1, i)), &[]).unwrap();
5270
5271            // Interleaved deletes: a merge, a freed page and a collapsed root
5272            // are what a cached fence cannot survive, so the oracle has to
5273            // contain them or it is not testing the invalidation at all.
5274            if i % 37 == 36 && i > 40 {
5275                s.delete(&row_key(i - 20)).unwrap();
5276                s.delete(&edge_key(0x71, (1, i - 13), 1, (1, (i - 12) % docs))).unwrap();
5277                s.delete(&edge_key(0x72, (1, (i - 12) % docs), 1, (1, i - 13))).unwrap();
5278            }
5279            if i % 256 == 255 { s.commit().unwrap(); }
5280            if i % 1024 == 1023 { s.checkpoint().unwrap(); }
5281        }
5282        s.commit().unwrap();
5283        s.checkpoint().unwrap();
5284
5285        // THE UPDATE SWEEP, and it is here for the negative control. A key
5286        // EQUAL to a hinted leaf's upper fence is the one placement error a
5287        // cached fence can make silently, and it only ever arrives as a
5288        // REWRITE: the separator key already lives in the leaf to the right.
5289        // Rewriting every key in ascending order walks the cache onto each
5290        // leaf in turn and then hands it exactly that leaf's separator, at
5291        // every boundary in the tree. The values are shorter than the
5292        // originals so the leaf has the room a wrong placement would need --
5293        // a control that cannot fit its record proves nothing either.
5294        for i in 0..docs {
5295            s.put(&row_key(i), &vec![b'u'; 48]).unwrap();
5296            s.put(&vec_key(i), b"u").unwrap();
5297            s.put(&edge_key(0x71, (1, i), 1, (1, (i + 1) % docs)), b"U").unwrap();
5298            s.put(&edge_key(0x72, (1, (i + 1) % docs), 1, (1, i)), &[]).unwrap();
5299            if i % 256 == 255 { s.commit().unwrap(); }
5300        }
5301        s.commit().unwrap();
5302        s.checkpoint().unwrap();
5303    }
5304
5305    /// Run the workload into a fresh store and return its data file, page by
5306    /// page, plus the rows a full scan sees.
5307    fn workload_file(docs: u64, hints: bool, relaxed: bool)
5308        -> (Vec<Vec<u8>>, Vec<(Vec<u8>, Vec<u8>)>, u64) {
5309        let d = tempfile::tempdir().unwrap();
5310        let mut s = Store::create(&d.path().join("s"), cfg()).unwrap();
5311        s.tag_hints().set_enabled(hints);
5312        s.tag_hints().set_relaxed_upper_fence(relaxed);
5313        multimodel_workload(&mut s, docs);
5314        let mut rows = Vec::new();
5315        s.scan(&[]).unwrap()
5316            .for_each_ref(|k, v| { rows.push((k.to_vec(), v.to_vec())); true })
5317            .unwrap();
5318        let served = s.tag_hints().hits();
5319        drop(s);
5320        let bytes = std::fs::read(d.path().join("s").join("data")).unwrap();
5321        (bytes.chunks(PAGE_SIZE).map(<[u8]>::to_vec).collect(), rows, served)
5322    }
5323
5324    /// Report the first page and SLOT at which two files disagree, in the
5325    /// terms a placement bug is stated in: which leaf, which slot, which key,
5326    /// and how the two sides differ there.
5327    fn first_difference(a: &[Vec<u8>], b: &[Vec<u8>]) -> Option<String> {
5328        if a.len() != b.len() {
5329            return Some(format!("page COUNT differs: {} pages with the hint, {} without",
5330                a.len(), b.len()));
5331        }
5332        let records = |page: &[u8], no: u32| -> std::result::Result<Vec<Vec<u8>>, String> {
5333            let p = PageRef::open(page, no).map_err(|e| format!("unreadable: {e:?}"))?;
5334            Ok((0..p.nentries()).map(|i| p.slot(i).to_vec()).collect())
5335        };
5336        for (no, (pa, pb)) in a.iter().zip(b).enumerate() {
5337            if pa == pb { continue; }
5338            let no = no as u32;
5339            let (ra, rb) = match (records(pa, no), records(pb, no)) {
5340                (Ok(x), Ok(y)) => (x, y),
5341                (x, y) => return Some(format!("page {no}: {x:?} vs {y:?}")),
5342            };
5343            let header = pa[..crate::page::HEADER_LEN] != pb[..crate::page::HEADER_LEN];
5344            if ra == rb {
5345                return Some(format!(
5346                    "page {no} holds the same {} records but differs in its bytes \
5347                     (header differs: {header}) -- a difference that is NOT placement",
5348                    ra.len()));
5349            }
5350            let slot = (0..ra.len().max(rb.len()))
5351                .find(|&i| ra.get(i) != rb.get(i)).unwrap_or(0);
5352            let show = |r: Option<&Vec<u8>>| match r {
5353                Some(rec) => format!("key {:02x?} ({} bytes)", validated_key(rec), rec.len()),
5354                None => "no record".into(),
5355            };
5356            return Some(format!(
5357                "page {no} (a {:?}) first differs at SLOT {slot}: {} records with the hint, \
5358                 {} without\n  with the hint: {}\n  without:      {}",
5359                PageRef::open(pa, no).map(|p| p.kind()).unwrap(),
5360                ra.len(), rb.len(), show(ra.get(slot)), show(rb.get(slot))));
5361        }
5362        None
5363    }
5364
5365    /// THE PROOF. Two stores, one workload, one difference: whether the
5366    /// per-keyspace cache was on. A hinted placement has to land in the same
5367    /// leaf AND the same slot a descent would have chosen, and a split that
5368    /// re-arms the cache afterwards must not change which half a record went
5369    /// to -- either would move a byte, and every byte is compared.
5370    #[test]
5371    fn the_hint_changes_where_a_write_descends_from_not_where_it_lands() {
5372        const DOCS: u64 = 5_000;   // 40,000 puts
5373        let (hinted, hinted_rows, served) = workload_file(DOCS, true, false);
5374        let (plain, plain_rows, none) = workload_file(DOCS, false, false);
5375        eprintln!("{} pages with the hint and {} without; the cache served {served} writes \
5376                   with it on and {none} with it off", hinted.len(), plain.len());
5377        // Neither arm may be vacuous: the control must really have the cache
5378        // off, and the candidate must really be using it.
5379        assert_eq!(none, 0, "the switch did not turn the cache off");
5380        assert!(served > DOCS * 4, "the cache served only {served} of this workload's writes");
5381        assert_eq!(hinted_rows, plain_rows, "the two stores do not hold the same rows");
5382        if let Some(why) = first_difference(&hinted, &plain) {
5383            panic!("the per-keyspace hint moved a record:\n{why}");
5384        }
5385
5386        // THE NEGATIVE CONTROL. Without this the assertion above could pass
5387        // because the workload never armed a hint at all. Relaxing the upper
5388        // fence from `key < separator` to `key <= separator` is the smallest
5389        // real placement error this cache can make: the separator key already
5390        // lives in the NEXT leaf, so the relaxed rule appends a second copy
5391        // here. It must make the files differ.
5392        let (relaxed, relaxed_rows, _) = workload_file(DOCS, true, true);
5393        let moved = first_difference(&hinted, &relaxed);
5394        assert!(
5395            moved.is_some() || relaxed_rows != hinted_rows,
5396            "relaxing the upper fence changed nothing, so this workload never \
5397             exercised the fence and the comparison above is vacuous"
5398        );
5399        eprintln!("negative control: {}", moved.unwrap_or_else(|| "rows differ".into()));
5400    }
5401
5402    /// The ordering oracle that rejected pair-packing and scattered-packing
5403    /// (docs/PAIR_PACKING.md, docs/SCATTERED_PACKING.md): a cheaper split policy
5404    /// is worth nothing if the tree stops answering exactly. Interleaved
5405    /// ascending runs FIRST, then a scattered phase over the same keyspaces, so
5406    /// the append shortcut and the balanced split both run against one tree.
5407    #[test]
5408    fn interleaved_then_scattered_keeps_every_key_exactly_once_in_order() {
5409        const DOCS: u64 = 2_000;
5410        const SCATTER: u64 = 2_000;
5411        let (pool, _d) = scratch_pool(16);
5412        let last_leaf = Cell::new(None);
5413        let hits = Cell::new(0);
5414        let attempts = Cell::new(0);
5415        let mut t = BTree::create(&pool, 1, &last_leaf, &hits, &attempts).unwrap();
5416
5417        let mut expected: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
5418        let value = |tag: u8, i: u64| {
5419            let mut v = tagged_value(tag, i);
5420            v.extend_from_slice(format!("|{tag:#04x}/{i}").as_bytes());
5421            v
5422        };
5423        for i in 0..DOCS {
5424            for tag in [0x60u8, 0x40, 0x20] {
5425                let (k, v) = (tagged(tag, i), value(tag, i));
5426                t.insert(&k, &v).unwrap();
5427                expected.push((k, v));
5428            }
5429        }
5430        // A multiplicative hash scatters insert order without needing rand.
5431        for j in 0..SCATTER {
5432            let i = DOCS + j.wrapping_mul(0x9E37_79B9_7F4A_7C15) % 1_000_000;
5433            for tag in [0x20u8, 0x60, 0x40] {
5434                let (k, v) = (tagged(tag, i), value(tag, i));
5435                t.insert(&k, &v).unwrap();
5436                expected.push((k, v));
5437            }
5438        }
5439        expected.sort();
5440        expected.dedup_by(|a, b| a.0 == b.0);
5441
5442        let seen: Vec<(Vec<u8>, Vec<u8>)> = t.range(&[]).unwrap().map(|r| r.unwrap()).collect();
5443        assert_eq!(seen.len(), expected.len(), "scan returned a different number of keys");
5444        assert_eq!(seen, expected, "full scan is not every key exactly once in byte order");
5445        for w in seen.windows(2) {
5446            assert!(w[0].0 < w[1].0, "scan is not strictly ascending at {:?}", w[0].0);
5447        }
5448        for (k, v) in &expected {
5449            assert_eq!(t.get(k).unwrap().as_deref(), Some(&v[..]), "point get disagrees for {k:?}");
5450        }
5451    }
5452}
5453
5454/// L2.3-2 -- the byte-equivalence oracle for the allocation-free split.
5455///
5456/// The candidate changes only HOW a split moves bytes, never WHICH bytes it
5457/// writes: same `split_point`, same `neighbor_cell_cuts`, same `at_point`
5458/// decision, same page images, same separators. That is a claim about output,
5459/// so it is tested as one: every corpus case is run through
5460/// `split_leaf_and_insert_vec` (the record-per-`Vec` implementation) and
5461/// through `split_leaf_and_insert_ref` (the `SlotRef` one) in two independent
5462/// stores, and EVERY page of the two files is compared byte for byte -- which
5463/// covers separator keys too, since a separator is bytes on a parent page.
5464///
5465/// Both implementations are compiled under `cfg(test)` precisely so this can
5466/// run in ONE build; in a shipping build the `slotref-split` feature selects
5467/// one of them and the other is not compiled at all.
5468#[cfg(test)]
5469mod split_byte_equivalence {
5470    use super::*;
5471    use crate::test_support::scratch_pool;
5472
5473    #[derive(Clone, Copy, PartialEq, Debug)]
5474    enum Impl { Vec, Ref }
5475
5476    struct Case {
5477        name: &'static str,
5478        prefix: Vec<(Vec<u8>, Vec<u8>)>,
5479        last: (Vec<u8>, Vec<u8>),
5480    }
5481
5482    /// `src/collections/mod.rs`'s width-tagged big-endian integer component --
5483    /// the shape that produces `compact-cells` 0xff cells.
5484    fn ordered(n: u64) -> Vec<u8> {
5485        let b = n.to_be_bytes();
5486        let start = b.iter().position(|x| *x != 0).unwrap_or(7);
5487        let mut k = vec![0x80 + (8 - start) as u8];
5488        k.extend_from_slice(&b[start..]);
5489        k
5490    }
5491
5492    fn tagged(tag: u8, i: u64) -> Vec<u8> {
5493        let mut k = vec![tag];
5494        k.extend(ordered(1));
5495        match tag {
5496            0x20 => k.extend_from_slice(format!("key-{i:012}").as_bytes()),
5497            0x40 => k.extend(ordered(i + 1)),
5498            _ => { k.extend(ordered(i + 1)); k.extend(ordered(0)); }
5499        }
5500        k
5501    }
5502
5503    fn tagged_value(tag: u8, i: u64) -> Vec<u8> {
5504        match tag {
5505            0x20 => ordered(i + 1),
5506            0x40 => vec![b'd'; 200 + (i % 97) as usize * 2],
5507            _ => vec![b'f'; 32],
5508        }
5509    }
5510
5511    fn scatter(i: u64) -> [u8; 8] { i.wrapping_mul(0x9E37_79B9_7F4A_7C15).to_be_bytes() }
5512
5513    /// Load the prefix, then force the LAST record through the split path --
5514    /// whichever implementation is named. Returns every page of the resulting
5515    /// file, the root, and the full key/value sequence a scan sees.
5516    fn run(case: &Case, which: Impl) -> (Vec<Vec<u8>>, u32, Vec<(Vec<u8>, Vec<u8>)>, u32) {
5517        let (pool, _d) = scratch_pool(192);
5518        let last_leaf = Cell::new(None);
5519        let hits = Cell::new(0);
5520        let tries = Cell::new(0);
5521        let mut t = BTree::create(&pool, 1, &last_leaf, &hits, &tries).unwrap();
5522        for (k, v) in &case.prefix { t.insert(k, v).unwrap(); }
5523
5524        let (k, v) = &case.last;
5525        let before = pool.page_count();
5526        let rec = enc_leaf(k, v, pool.compact_cells());
5527        let (w, path) = t.descend_for_write(k).unwrap();
5528        let leaf = w.page_no();
5529        let at = {
5530            let p = PageRef::open_resident(w.bytes(), leaf).unwrap();
5531            validate_records(&p).unwrap();
5532            lower_bound(&p, k).unwrap()
5533        };
5534        match which {
5535            Impl::Vec => t.split_leaf_and_insert_vec(w, path, at, rec, k, None).unwrap(),
5536            Impl::Ref => t.split_leaf_and_insert_ref(w, path, at, rec, k, None).unwrap(),
5537        }
5538
5539        let root = t.root();
5540        let mut rows = Vec::new();
5541        t.range(&[]).unwrap()
5542            .for_each_ref(|k, v| { rows.push((k.to_vec(), v.to_vec())); true })
5543            .unwrap();
5544        let pages: Vec<Vec<u8>> = (0..pool.page_count())
5545            .map(|n| pool.get(n).unwrap().to_vec())
5546            .collect();
5547        (pages, root, rows, before)
5548    }
5549
5550    /// The corpus. Each entry names one shape the split path has to handle:
5551    /// which branch it takes, what its records look like, and where in the
5552    /// tree the splitting leaf sits.
5553    fn corpus() -> Vec<Case> {
5554        let mut cases = Vec::new();
5555
5556        // 1. Ascending into the TREE's rightmost leaf: D9's append split
5557        //    (at_point), left page closed full, right page holds one record.
5558        cases.push(Case {
5559            name: "ascending append at the tree's rightmost leaf",
5560            prefix: (0..64u64).map(|i| (tagged(0x40, i), tagged_value(0x40, i))).collect(),
5561            last: (tagged(0x40, 64), tagged_value(0x40, 64)),
5562        });
5563
5564        // 2. Three keyspaces in one tree, written one document at a time --
5565        //    `src/collections/mod.rs`'s exact shape. The 0x40 run is ascending
5566        //    but never at the tree's rightmost leaf, so this is the case
5567        //    `keyspace-append` widens and `redistribute_neighbors` owned
5568        //    before it.
5569        let mut prefix = Vec::new();
5570        for i in 0..90u64 {
5571            for tag in [0x60u8, 0x40, 0x20] { prefix.push((tagged(tag, i), tagged_value(tag, i))); }
5572        }
5573        cases.push(Case {
5574            name: "ascending row run under a higher keyspace",
5575            prefix,
5576            last: (tagged(0x40, 90), tagged_value(0x40, 90)),
5577        });
5578
5579        // 3. Scattered keys: the balanced `split_point` cut, and the
5580        //    neighbour redistribution that fires when siblings have room.
5581        cases.push(Case {
5582            name: "scattered keys, balanced cut",
5583            prefix: (0..400u64).map(|i| (scatter(i).to_vec(), vec![b'x'; 90])).collect(),
5584            last: (scatter(400).to_vec(), vec![b'x'; 90]),
5585        });
5586
5587        // 4. The new record lands FIRST in its leaf (at == 0).
5588        let mut prefix: Vec<(Vec<u8>, Vec<u8>)> =
5589            (10..70u64).map(|i| ((i * 4).to_be_bytes().to_vec(), vec![b'a'; 120])).collect();
5590        prefix.push((0u64.to_be_bytes().to_vec(), vec![b'a'; 120]));
5591        cases.push(Case {
5592            name: "new record first in its leaf",
5593            prefix,
5594            last: (1u64.to_be_bytes().to_vec(), vec![b'b'; 120]),
5595        });
5596
5597        // 5. The boundary leaf: one leaf holding the top of tag 0x40 and the
5598        //    bottom of tag 0x60, so an ascending 0x40 insert is NOT last in
5599        //    the leaf and the append shortcut is never consulted.
5600        let mut prefix = Vec::new();
5601        for i in 0..40u64 { prefix.push((tagged(0x40, i), tagged_value(0x40, i))); }
5602        for i in 0..40u64 { prefix.push((tagged(0x60, i), tagged_value(0x60, i))); }
5603        cases.push(Case {
5604            name: "mixed-tag boundary leaf",
5605            prefix,
5606            last: (tagged(0x40, 40), tagged_value(0x40, 40)),
5607        });
5608
5609        // 6. One indivisible record too large to pair with its neighbours:
5610        //    the three-way cut that allocates TWO pages and pushes TWO
5611        //    separators. Sizes taken from the pinned huge-record test.
5612        let mut prefix: Vec<(Vec<u8>, Vec<u8>)> =
5613            (0..40u64).map(|i| ((i * 2).to_be_bytes().to_vec(), vec![b'a'; 60])).collect();
5614        prefix.push((41u64.to_be_bytes().to_vec(), vec![b'Z'; 2500]));
5615        cases.push(Case {
5616            name: "one huge record among small ones",
5617            prefix,
5618            last: (42u64.to_be_bytes().to_vec(), vec![b'Y'; 2400]),
5619        });
5620
5621        // 6b. The three-way cut actually taken. A ROOT leaf has no parent, so
5622        //     the neighbour redistribution cannot absorb the insert, and the
5623        //     sizes are chosen so that no single cut leaves both halves inside
5624        //     a page: 44 records of 74 page-bytes each with a 2514-byte record
5625        //     landing exactly in the middle. Left-of-big and right-of-big are
5626        //     each 1628 bytes, so big + either side is 4142 > 4056.
5627        let mut prefix: Vec<(Vec<u8>, Vec<u8>)> =
5628            (0..44u64).map(|i| ((i * 2).to_be_bytes().to_vec(), vec![b'a'; 60])).collect();
5629        prefix.sort();
5630        cases.push(Case {
5631            name: "three-way cut on a root leaf",
5632            prefix,
5633            last: (43u64.to_be_bytes().to_vec(), vec![b'Z'; 2500]),
5634        });
5635
5636        // 7. Records close to the per-record limit: two or three to a leaf,
5637        //    so the cut has almost no freedom and every byte of slack shows.
5638        cases.push(Case {
5639            name: "records near the size limit",
5640            prefix: (0..12u64).map(|i| (i.to_be_bytes().to_vec(), vec![b'L'; 1300])).collect(),
5641            last: (12u64.to_be_bytes().to_vec(), vec![b'L'; 1300]),
5642        });
5643
5644        // 8. Compact integer cells (the 0xff + width-tag encoding) with tiny
5645        //    values: hundreds of very short records in one leaf.
5646        cases.push(Case {
5647            name: "compact integer cells, many per leaf",
5648            prefix: (0..600u64).map(|i| (ordered(i + 1), vec![b'v'; 4])).collect(),
5649            last: (ordered(601), vec![b'v'; 4]),
5650        });
5651
5652        // 9. A leaf that is its PARENT's last child: its right neighbour
5653        //    lives under a different parent, which is the branch
5654        //    `keyspace_rightmost` falls through to and the window
5655        //    `redistribute_neighbors` has to clamp.
5656        let mut prefix: Vec<(Vec<u8>, Vec<u8>)> =
5657            (0..900u64).map(|i| (scatter(i).to_vec(), vec![b'p'; 40])).collect();
5658        prefix.extend((0..300u64).map(|i| (tagged(0x60, i), tagged_value(0x60, i))));
5659        cases.push(Case {
5660            name: "deep tree, last child of a parent",
5661            prefix,
5662            last: (scatter(901).to_vec(), vec![b'p'; 40]),
5663        });
5664
5665        cases
5666    }
5667
5668    #[test]
5669    fn both_split_implementations_write_identical_pages_for_every_corpus_state() {
5670        let mut reached = [false; 3];
5671        for case in corpus() {
5672            let (a, root_a, rows_a, before) = run(&case, Impl::Vec);
5673            let (b, root_b, rows_b, _) = run(&case, Impl::Ref);
5674            // Which branch a case reached shows in how many pages the split
5675            // added: 0 = the neighbour redistribution absorbed it, 1 = an
5676            // ordinary or append split into one new leaf, more = the
5677            // three-way cut (two leaves, and a new root when the leaf that
5678            // split WAS the root). A corpus that stops reaching one of the
5679            // three stops testing it, silently -- hence the tally below.
5680            let added = a.len() as u32 - before;
5681            println!("{}: {added} pages added", case.name);
5682            reached[(added as usize).min(2)] = true;
5683            assert_eq!(root_a, root_b, "{}: root page differs", case.name);
5684            assert_eq!(rows_a, rows_b, "{}: visible rows differ", case.name);
5685            assert_eq!(a.len(), b.len(), "{}: page count differs", case.name);
5686            for (i, (x, y)) in a.iter().zip(b.iter()).enumerate() {
5687                assert!(x == y, "{}: page {i} differs byte for byte", case.name);
5688            }
5689        }
5690        // Redistribution does not exist in a build without sqlite-balance.
5691        // Byte identity and the remaining split coverage apply in every mode.
5692        assert_eq!(reached[0], cfg!(feature = "sqlite-balance"),
5693            "neighbour redistribution coverage must match the compiled policy");
5694        assert!(reached[1], "no corpus case reached the one-new-leaf split");
5695        assert!(reached[2], "no corpus case reached the three-way cut");
5696    }
5697
5698    /// The comparison above is worthless unless it can FAIL. Feed the same
5699    /// harness two states that genuinely differ -- one byte of one value --
5700    /// and require it to notice.
5701    #[test]
5702    fn the_oracle_notices_a_one_byte_difference() {
5703        let mut a = corpus().remove(2);
5704        let mut b = corpus().remove(2);
5705        b.last.1[0] = b'z';
5706        assert_ne!(a.last.1, b.last.1);
5707        let pages_a = run(&a, Impl::Vec).0;
5708        let pages_b = run(&b, Impl::Vec).0;
5709        assert_ne!(pages_a, pages_b, "the oracle cannot tell two different splits apart");
5710        // ... and identical input through the same implementation must be
5711        // identical, or the harness itself is not deterministic.
5712        a.name = "determinism";
5713        assert_eq!(run(&a, Impl::Vec).0, run(&a, Impl::Vec).0);
5714    }
5715}
5716
5717/// L2.3-2 -- how much the split path allocates, counted rather than argued.
5718///
5719/// The finding this candidate answers: on a Pi, a typed put spends ~46% of its
5720/// CPU in the split path, and a quarter of all cycles in malloc/free. A plain
5721/// leaf split copied every record on the page into its own `Vec<u8>`
5722/// (`p.slot(j).to_vec()`); a three-sibling redistribution did that for three
5723/// pages plus the parent's records. So the number to watch is not a timing, it
5724/// is an ALLOCATION COUNT, and it is measured here with the kernel's
5725/// `cfg(test)` counting allocator.
5726///
5727/// Both figures are printed, so the test doubles as the record of the before.
5728#[cfg(test)]
5729mod split_allocations {
5730    use super::*;
5731    use crate::test_alloc::measured;
5732    use crate::test_support::scratch_pool;
5733
5734    /// Allocations inside ONE steady-state split -- not the first one. The
5735    /// first split of a process warms allocator free lists and (with the
5736    /// feature on) creates the one reusable scratch; a steady-state split is
5737    /// what a load actually spends its time in.
5738    ///
5739    /// The split that gets measured is a REAL one: keys are inserted through
5740    /// the ordinary path until one of them genuinely does not fit its leaf,
5741    /// and only that record is handed to the split directly, so the leaf is as
5742    /// full as it would be in a running store rather than as full as the test
5743    /// happened to leave it.
5744    fn split_allocations(scattered: bool, slotref: bool) -> (usize, usize) {
5745        let (pool, _d) = scratch_pool(192);
5746        let last_leaf = Cell::new(None);
5747        let hits = Cell::new(0);
5748        let tries = Cell::new(0);
5749        let mut t = BTree::create(&pool, 1, &last_leaf, &hits, &tries).unwrap();
5750        let val = vec![b'v'; 120];
5751        let mut i = 0u64;
5752        let mut next = move || {
5753            i += 1;
5754            if scattered { i.wrapping_mul(0x9E37_79B9_7F4A_7C15).to_be_bytes().to_vec() }
5755            else { i.to_be_bytes().to_vec() }
5756        };
5757        for _ in 0..2000 { let k = next(); t.insert(&k, &val).unwrap(); }
5758
5759        /// Insert until a record does not fit its leaf; return that record's key.
5760        fn until_split(t: &mut BTree<'_>, next: &mut impl FnMut() -> Vec<u8>, val: &[u8]) -> Vec<u8> {
5761            loop {
5762                let k = next();
5763                let rec = enc_leaf(&k, val, t.pool.compact_cells());
5764                let (leaf, _) = t.descend(&k).unwrap();
5765                let room = {
5766                    let r = t.pool.get(leaf).unwrap();
5767                    let p = open_cached(&r, leaf).unwrap();
5768                    p.free_space() >= rec.len() + 4
5769                };
5770                if !room { return k; }
5771                t.insert(&k, val).unwrap();
5772            }
5773        }
5774
5775        fn once<'p>(t: &mut BTree<'p>, k: &[u8], val: &[u8], slotref: bool, measure: bool) -> (usize, usize) {
5776            let rec = enc_leaf(k, val, t.pool.compact_cells());
5777            let (w, path) = t.descend_for_write(k).unwrap();
5778            let leaf = w.page_no();
5779            let at = {
5780                let p = PageRef::open_resident(w.bytes(), leaf).unwrap();
5781                validate_records(&p).unwrap();
5782                lower_bound(&p, k).unwrap()
5783            };
5784            let run = |t: &mut BTree<'p>| {
5785                if slotref { t.split_leaf_and_insert_ref(w, path, at, rec, k, None) }
5786                else { t.split_leaf_and_insert_vec(w, path, at, rec, k, None) }.unwrap()
5787            };
5788            if measure {
5789                let (_, count, bytes) = measured(|| run(t));
5790                (count, bytes)
5791            } else {
5792                run(t);
5793                (0, 0)
5794            }
5795        }
5796        // The first split warms; the second is the measurement.
5797        let warm = until_split(&mut t, &mut next, &val);
5798        once(&mut t, &warm, &val, slotref, false);
5799        let hot = until_split(&mut t, &mut next, &val);
5800        once(&mut t, &hot, &val, slotref, true)
5801    }
5802
5803    #[test]
5804    fn a_steady_state_split_allocates_a_bounded_constant() {
5805        for scattered in [false, true] {
5806            let (n_vec, b_vec) = split_allocations(scattered, false);
5807            let (n_ref, b_ref) = split_allocations(scattered, true);
5808            println!(
5809                "{} split: record-per-Vec {n_vec} allocations / {b_vec} bytes; \
5810                 SlotRef {n_ref} allocations / {b_ref} bytes",
5811                if scattered { "scattered" } else { "ascending" }
5812            );
5813            // The before. A split of a leaf holding ~30 records of this size
5814            // allocates one Vec per record plus the page images; a
5815            // redistribution does it for a whole window.
5816            assert!(n_vec >= 20, "the record-per-Vec split should allocate per record, saw {n_vec}");
5817            // The after, and the residue is NAMED rather than waved at. A
5818            // redistribution allocates nothing at all (measured: 0). A split
5819            // that actually adds a page allocates exactly one thing: the
5820            // interior record `insert_separator` pushes into the parent
5821            // (`enc_interior`, key + 6 bytes). That belongs to the separator
5822            // promotion, not to moving the leaf's records, and it does not
5823            // grow with the number of records on the page -- which is the
5824            // property under test. `BufferPool::allocate` itself allocated
5825            // nothing here; if its page tables ever grow inside a measured
5826            // split this bound is where it will show.
5827            assert!(
5828                n_ref <= 1,
5829                "the SlotRef split should allocate at most the promoted separator, saw {n_ref}"
5830            );
5831        }
5832    }
5833}