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