Skip to main content

lsm_tree/tree/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2024-present, fjall-rs
3// Copyright (c) 2026-present, Dmitry Prudnikov
4
5#[cfg(feature = "columnar")]
6pub(crate) mod columnar_scan;
7mod data_stage;
8pub(crate) mod ingest;
9pub(crate) mod inner;
10mod level_resolve;
11mod level_stages;
12mod multi_get_read;
13pub(crate) mod read_job;
14pub mod resumable;
15pub(crate) mod sealed;
16mod tables_read;
17
18use crate::path::Path;
19use crate::{
20    AbstractTree, Checksum, KvPair, SeqNo, SequenceNumberCounter, TableId, UserKey, UserValue,
21    ValueType,
22    compaction::{CompactionStrategy, state::CompactionState},
23    config::Config,
24    format_version::FormatVersion,
25    fs::Fs,
26    iter_guard::{IterGuard, IterGuardImpl},
27    key::InternalKey,
28    manifest::Manifest,
29    memtable::Memtable,
30    range_tombstone::RangeTombstone,
31    scan_since::ScanSinceEvent,
32    slice::Slice,
33    table::{Table, probe_stats::PlanCounts},
34    value::InternalValue,
35    version::{SuperVersion, SuperVersions, Version, recovery::recover},
36    vlog::BlobFile,
37};
38use alloc::sync::Arc;
39#[cfg(not(feature = "std"))]
40use alloc::{boxed::Box, string::ToString, vec::Vec};
41use core::ops::{Bound, RangeBounds};
42use inner::{FlushGuard, TreeId, TreeInner, VersionsWriteGuard};
43// no-std: spin mirrors parking_lot's Mutex/RwLock API without an allocator.
44// parking_lot wins on the std hot path, so keep it for std.
45#[cfg(feature = "std")]
46use parking_lot::{Mutex, RwLock};
47#[cfg(not(feature = "std"))]
48use spin::{Mutex, RwLock};
49
50#[cfg(feature = "metrics")]
51use crate::metrics::Metrics;
52
53/// Floor for the storage-admission reserved headroom band (see
54/// [`Tree::compute_write_admission`]). Even with an empty active memtable the
55/// gate keeps at least this much room below the budget so the next writes and a
56/// space-reclaiming compaction have somewhere to land. 1 MiB.
57pub(crate) const MIN_RESERVED_HEADROOM: u64 = 1024 * 1024;
58
59/// How long a cached disk-free sample stays valid before the admission gate
60/// re-probes. Bounds how stale the physical free-space figure can be when the
61/// filesystem fills from another process between flushes, without issuing a
62/// `statfs`/`statvfs` syscall on every gated write. 1 second.
63const ADMISSION_DISK_FREE_TTL: core::time::Duration = core::time::Duration::from_secs(1);
64
65/// Iterator value guard
66pub struct Guard(crate::Result<(UserKey, UserValue)>);
67
68impl IterGuard for Guard {
69    fn into_inner_if(
70        self,
71        pred: impl Fn(&UserKey) -> bool,
72    ) -> crate::Result<(UserKey, Option<UserValue>)> {
73        let (k, v) = self.0?;
74
75        if pred(&k) {
76            Ok((k, Some(v)))
77        } else {
78            Ok((k, None))
79        }
80    }
81
82    fn key(self) -> crate::Result<UserKey> {
83        self.0.map(|(k, _)| k)
84    }
85
86    fn size(self) -> crate::Result<u32> {
87        #[expect(clippy::cast_possible_truncation, reason = "values are u32 length max")]
88        self.into_inner().map(|(_, v)| v.len() as u32)
89    }
90
91    fn into_inner(self) -> crate::Result<(UserKey, UserValue)> {
92        self.0
93    }
94}
95
96/// Trait for monomorphized table point-read results.
97///
98/// Allows `find_in_tables` to operate generically over `InternalValue` (for
99/// `get`) and `(InternalValue, Block)` (for `get_pinned`), generating optimal
100/// code for each path without runtime dispatch or extra refcount overhead.
101trait TablePointLookup: Sized {
102    fn lookup(
103        table: &Table,
104        key: &[u8],
105        seqno: SeqNo,
106        key_hash: u64,
107    ) -> crate::Result<Option<Self>>;
108    fn entry_seqno(&self) -> SeqNo;
109    fn filter_tombstone(self) -> Option<Self>;
110}
111
112/// Lookup result for standard `get()` — entry only, no block retained.
113type TableEntry = InternalValue;
114
115/// One covered key in a batched run resolution: `(input index, key hash,
116/// resolved item)`. Aliased to keep `resolve_run_batched`'s return readable.
117type CoveredKey = (usize, u64, Option<InternalValue>);
118
119/// `(miss_keys, duplicates)` from [`Tree::dedup_sorted_miss_keys`]: `miss_keys`
120/// is `(key_index, bloom_hash)` for the strictly-sorted-unique batched resolver,
121/// `duplicates` is `(duplicate_index, representative_index)` for the fan-out.
122type DedupedMissKeys = (Vec<(usize, u64)>, Vec<(usize, usize)>);
123
124/// The outcome of resolving a key batch against one run (see `resolve_run_batched`).
125struct RunResolve {
126    /// Covered, non-skipped keys with their resolved item, in input order.
127    covered: Vec<CoveredKey>,
128    /// Keys this run does not cover, in input order, for the next run or level.
129    not_covered: Vec<(usize, u64)>,
130}
131
132/// One data block the chunked `multi_get` resolver will read (see
133/// `resolve_level_chunked`): the block, the SST it lives in, the table-local
134/// read seqno, whether it needs the special load path (Page-ECC / columnar),
135/// and the ORIGINAL key indices that fall in this block, each with its key's
136/// hash. The table's file is opened when the block's chunk is read, so a
137/// level holds open only the files of the chunk it is reading.
138struct BlockTask<'a> {
139    table: &'a crate::Table,
140    /// The table's place in the version, by which a job names it.
141    at: read_job::TableAt,
142    handle: crate::table::BlockHandle,
143    table_seqno: SeqNo,
144    special: bool,
145    keys: Vec<(usize, u64)>,
146}
147
148/// A level's block tasks, and per table the filter probes planning them took,
149/// not yet counted.
150type LevelTasks<'a> = (Vec<BlockTask<'a>>, Vec<(&'a Table, PlanCounts)>);
151
152/// One table of a level being planned for a key batch: the keys it covers,
153/// and how it is read.
154struct LevelTable<'a> {
155    table: &'a Table,
156    /// The table's place in the version, by which a job names it.
157    at: read_job::TableAt,
158    /// Its keys: a span of the level's batch, which every run of the level
159    /// walks in the same order, so no table holds a copy of its keys.
160    keys: core::ops::Range<usize>,
161    read: LevelRead<'a>,
162    /// The file the first stage opened, held for every later stage: on a
163    /// level wider than the descriptor cache, the cache has evicted it by
164    /// then, and opening it again is a cold open per stage. It is let go once
165    /// the table's stages are over.
166    file: Option<Arc<dyn crate::fs::FsFile>>,
167}
168
169/// How a table of a level is read for its keys.
170enum LevelRead<'a> {
171    /// Not begun: nothing past its span of keys is built until it has a
172    /// place to read in, so a level wider than the descriptor cache holds the
173    /// key positions of no more tables than it holds files.
174    Pending,
175    /// Read in stages.
176    Staged(crate::table::staged::StagedRead<'a>),
177    /// To be planned serially: a Page-ECC table, whose every block needs the
178    /// load path's recovery, or a table a stage of which failed.
179    Serial,
180    /// Planned serially: its read seqno, the blocks it reads with the
181    /// positions of their keys in its span, and the filter probes planning
182    /// took.
183    Planned(
184        SeqNo,
185        Vec<(crate::table::BlockHandle, Vec<usize>)>,
186        PlanCounts,
187    ),
188    /// No key of the batch reads anything in it.
189    Nothing,
190}
191
192impl LevelRead<'_> {
193    /// Whether the table needs no more stages: planned, or left to the
194    /// serial planner, or with nothing to read.
195    fn is_over(&self) -> bool {
196        match self {
197            Self::Pending => false,
198            Self::Staged(read) => read.is_done(),
199            Self::Serial | Self::Planned(..) | Self::Nothing => true,
200        }
201    }
202}
203
204/// A data block task's block before its chunk is read.
205enum TaskBlock {
206    /// At hand: taken from the cache.
207    Held(crate::table::DataBlock),
208    /// To be read in the chunk's batch.
209    Read,
210    /// To be loaded when its chunk is read, through a Page-ECC or columnar
211    /// table's own path, which heals or reconstructs it; held only while its
212    /// keys are read.
213    Load,
214}
215
216/// The read queue of one backend a level's tables were opened through.
217struct LevelQueue<'a> {
218    fs: &'a Arc<dyn crate::fs::Fs>,
219    queue: Box<dyn crate::fs::ReadQueue + 'a>,
220    /// Whether the queue wakes the level's [`LevelWake`] as its reads come
221    /// back, so the level can sleep on all its queues at once.
222    wakes: bool,
223}
224
225/// Where the blocking driver takes a read's asks: a block read straight into
226/// the queue of its backend, so no list stands between the read and the
227/// queue; a job until the pass that asked for it is over, since running it
228/// hands its result back to the read.
229struct DriveSink<'a> {
230    /// One queue per backend, opened when a table first asks it for a block,
231    /// so a read answered from the cache opens none.
232    queues: Vec<LevelQueue<'a>>,
233    #[cfg(feature = "std")]
234    wake: Arc<LevelWake>,
235    jobs: Vec<read_job::Job>,
236    /// The cache of the first table a block was read for, which the waits
237    /// on the queues are timed into for the estimate that decides whether
238    /// misses share loads.
239    cache: Option<&'a crate::cache::Cache>,
240}
241
242impl<'a> read_job::ReadSink<'a> for DriveSink<'a> {
243    fn job(&mut self, job: read_job::Job) {
244        self.jobs.push(job);
245    }
246
247    /// The blocking driver completes every read itself, on the calling
248    /// thread, and runs its jobs there too.
249    fn leads_loads(&self) -> bool {
250        crate::inflight::batches_may_lead()
251    }
252
253    #[expect(clippy::indexing_slicing, reason = "a slot is a position in `queues`")]
254    fn read(&mut self, read: read_job::BlockRequest<'a>) {
255        if self.cache.is_none() {
256            self.cache = Some(read.cache);
257        }
258        let slot = if let Some(slot) = self.queues.iter().position(|q| Arc::ptr_eq(q.fs, read.fs)) {
259            slot
260        } else {
261            #[cfg_attr(not(feature = "std"), expect(unused_mut))]
262            let mut queue = read.fs.read_queue();
263            #[cfg(feature = "std")]
264            let wakes = queue.set_wake(Arc::clone(&self.wake) as Arc<dyn crate::fs::ReadWake>);
265            #[cfg(not(feature = "std"))]
266            let wakes = false;
267            self.queues.push(LevelQueue {
268                fs: read.fs,
269                queue,
270                wakes,
271            });
272            self.queues.len() - 1
273        };
274        self.queues[slot].queue.submit(crate::fs::QueuedRead {
275            tag: read.tag,
276            file: read.file,
277            offset: read.offset,
278            buf: read.buf,
279        });
280    }
281}
282
283/// The filter and index bytes each table of a staged level read holds, and
284/// their sum: the buffers of its blocks in flight, at the size they are read
285/// as, and the blocks its read holds, at their decoded size.
286struct MetaHeld {
287    in_flight: Vec<u64>,
288    held: Vec<u64>,
289    total: u64,
290}
291
292impl MetaHeld {
293    fn new(tables: usize) -> Self {
294        Self {
295            in_flight: vec![0; tables],
296            held: vec![0; tables],
297            total: 0,
298        }
299    }
300
301    /// Counts the table at `at` anew, its read holding `read_held` bytes.
302    #[expect(
303        clippy::indexing_slicing,
304        reason = "`at` indexes the level's tables, which both vectors are sized to"
305    )]
306    fn settle(&mut self, at: usize, read_held: u64) {
307        let now = self.in_flight[at] + read_held;
308        // `total` sums `held`, so it is at least the share replaced here.
309        self.total = self.total - self.held[at] + now;
310        self.held[at] = now;
311    }
312}
313
314/// Woken by any queue of a level when one of its reads is back: each wake
315/// moves the generation on, so a wake landing between the driver's look at
316/// the queues and its park is not lost, and unparks the driving thread only
317/// while it sleeps on the wake. A driver waiting inside one queue is parked
318/// by that queue's own channel, which an unpark of ours would only wake
319/// spuriously, a context switch per read for nothing.
320// no-std: without a thread to park, the level waits on one queue at a time.
321#[cfg(feature = "std")]
322struct LevelWake {
323    generation: core::sync::atomic::AtomicU64,
324    /// Whether the driver sleeps on this wake. Set before it looks at the
325    /// generation and read after a wake moves it, both sequentially
326    /// consistent: either the driver sees the new generation and does not
327    /// park, or the wake sees it asleep and unparks it.
328    sleeping: core::sync::atomic::AtomicBool,
329    thread: std::thread::Thread,
330}
331
332#[cfg(feature = "std")]
333impl crate::fs::ReadWake for LevelWake {
334    fn wake(&self) {
335        self.generation
336            .fetch_add(1, core::sync::atomic::Ordering::SeqCst);
337        if self.sleeping.load(core::sync::atomic::Ordering::SeqCst) {
338            self.thread.unpark();
339        }
340    }
341}
342
343impl TablePointLookup for TableEntry {
344    fn lookup(
345        table: &Table,
346        key: &[u8],
347        seqno: SeqNo,
348        key_hash: u64,
349    ) -> crate::Result<Option<Self>> {
350        table.get(key, seqno, key_hash)
351    }
352
353    fn entry_seqno(&self) -> SeqNo {
354        self.key.seqno
355    }
356
357    fn filter_tombstone(self) -> Option<Self> {
358        ignore_tombstone_value(self)
359    }
360}
361
362/// Lookup result for `get_pinned()` — entry + block for zero-copy pinning.
363type TableEntryWithBlock = (InternalValue, crate::table::Block);
364
365impl TablePointLookup for TableEntryWithBlock {
366    fn lookup(
367        table: &Table,
368        key: &[u8],
369        seqno: SeqNo,
370        key_hash: u64,
371    ) -> crate::Result<Option<Self>> {
372        table.get_with_block(key, seqno, key_hash)
373    }
374
375    fn entry_seqno(&self) -> SeqNo {
376        self.0.key.seqno
377    }
378
379    fn filter_tombstone(self) -> Option<Self> {
380        ignore_tombstone_value(self.0).map(|iv| (iv, self.1))
381    }
382}
383
384/// Lookup result for the value-returning `get()` path: `(value_type, seqno,
385/// value)`, no key reconstruction (the caller has the needle).
386type TableValue = (ValueType, SeqNo, crate::Slice);
387
388impl TablePointLookup for TableValue {
389    fn lookup(
390        table: &Table,
391        key: &[u8],
392        seqno: SeqNo,
393        key_hash: u64,
394    ) -> crate::Result<Option<Self>> {
395        table.get_value(key, seqno, key_hash)
396    }
397
398    fn entry_seqno(&self) -> SeqNo {
399        self.1
400    }
401
402    fn filter_tombstone(self) -> Option<Self> {
403        if self.0.is_tombstone() {
404            None
405        } else {
406            Some(self)
407        }
408    }
409}
410
411fn ignore_tombstone_value(item: InternalValue) -> Option<InternalValue> {
412    if item.is_tombstone() {
413        None
414    } else {
415        Some(item)
416    }
417}
418
419/// A log-structured merge tree (LSM-tree/LSMT)
420///
421/// Clones share one tree. Background work of the tree (the table file pool,
422/// the file deleter) may still touch its directory after a handle drops; to
423/// remove the directory, [`close`](Self::close) the tree and wait for the
424/// returned [`TreeClose`](crate::TreeClose): once it fires, the tree writes
425/// nothing into its directory any more.
426#[derive(Clone)]
427pub struct Tree(#[doc(hidden)] pub Arc<TreeInner>);
428
429impl core::ops::Deref for Tree {
430    type Target = TreeInner;
431
432    fn deref(&self) -> &Self::Target {
433        &self.0
434    }
435}
436
437impl crate::abstract_tree::sealed::Sealed for Tree {}
438
439impl Tree {
440    /// A handle that does not keep the tree open; see [`WeakTree`](crate::WeakTree).
441    #[must_use]
442    pub fn downgrade(&self) -> crate::WeakTree {
443        crate::WeakTree::of(self, None)
444    }
445
446    /// Gives up this handle and returns the signal of the tree being closed.
447    ///
448    /// The tree is closed once nothing of it owns a file of its directory or
449    /// works in it any more: other strong handles, iterators, snapshots and
450    /// pins keep it open until they drop, and the table file pool and the
451    /// background deleter finish what they started. Once closed, the tree
452    /// writes nothing into its directory and removing the directory is safe.
453    ///
454    /// From this call on no [`WeakTree`](crate::WeakTree) raises the tree
455    /// again; strong handles already held keep working until dropped.
456    ///
457    /// # Examples
458    ///
459    /// ```
460    /// # let folder = tempfile::tempdir()?;
461    /// use lsm_tree::{AbstractTree, AnyTree};
462    ///
463    /// let AnyTree::Standard(tree) = lsm_tree::Config::new(
464    ///     folder.path(),
465    ///     lsm_tree::SequenceNumberCounter::default(),
466    ///     lsm_tree::SequenceNumberCounter::default(),
467    /// )
468    /// .open()?
469    /// else {
470    ///     unreachable!("no key-value separation configured");
471    /// };
472    /// let monitor = tree.downgrade();
473    ///
474    /// let closed = tree.close();
475    /// assert!(monitor.upgrade().is_none());
476    /// closed.wait();
477    /// std::fs::remove_dir_all(folder.path())?;
478    /// # Ok::<(), Box<dyn std::error::Error>>(())
479    /// ```
480    pub fn close(self) -> crate::TreeClose {
481        crate::close::close(self)
482    }
483}
484
485/// Maps a raw merge-pipeline item into a standard-tree iterator guard.
486fn standard_guard(item: crate::Result<InternalValue>) -> IterGuardImpl {
487    IterGuardImpl::Standard(Guard(item.map(|iv| (iv.key.user_key, iv.value))))
488}
489
490/// A guard carrying only an error: the single item an iterator surface yields
491/// when it fails before it can open a snapshot (no row, no version to bind a
492/// blob guard to), so the failure reaches the consumer through the same
493/// `Result` it already handles per row.
494pub(crate) fn error_guard(e: crate::Error) -> IterGuardImpl {
495    IterGuardImpl::Standard(Guard(Err(e)))
496}
497
498/// The bytes an install keeps, for each counter set they go to: the set of
499/// the backend each file was written through (see
500/// [`Fs::write_io_stats`]), so a route on
501/// another medium is charged to that medium's set. Files on a backend that
502/// counts nothing are not looked at.
503///
504/// Read before the install publishes the files, recorded once it has
505/// ([`Self::charge`]): an install that fails keeps nothing.
506#[derive(Default)]
507pub(crate) struct InstalledCharge(Vec<(Arc<crate::fs::WriteIoStats>, crate::fs::WriteOrigin, u64)>);
508
509impl InstalledCharge {
510    /// Adds the files of `tables`, written for `table_origin`, and of
511    /// `blob_files`, written for `blob_origin`.
512    ///
513    /// # Errors
514    ///
515    /// A counted blob file whose length cannot be read.
516    pub(crate) fn add(
517        &mut self,
518        tables: &[Table],
519        blob_files: &[crate::BlobFile],
520        table_origin: crate::fs::WriteOrigin,
521        blob_origin: crate::fs::WriteOrigin,
522    ) -> crate::Result<()> {
523        for table in tables {
524            if let Some(stats) = table.fs.write_io_stats() {
525                self.push(stats, table_origin, table.file_len());
526            }
527        }
528        for blob_file in blob_files {
529            if let Some(stats) = blob_file.0.fs.write_io_stats() {
530                self.push(stats, blob_origin, blob_file.physical_size()?);
531            }
532        }
533        Ok(())
534    }
535
536    fn push(
537        &mut self,
538        stats: &Arc<crate::fs::WriteIoStats>,
539        origin: crate::fs::WriteOrigin,
540        bytes: u64,
541    ) {
542        for (set, slot, sum) in &mut self.0 {
543            if *slot == origin && Arc::ptr_eq(set, stats) {
544                *sum += bytes;
545                return;
546            }
547        }
548        self.0.push((Arc::clone(stats), origin, bytes));
549    }
550
551    /// Records the bytes, once the install that keeps them is published.
552    pub(crate) fn charge(self) {
553        for (stats, origin, bytes) in self.0 {
554            stats.installed(origin, bytes);
555        }
556    }
557}
558
559/// The highest seqno the memtables of `snapshot` hold, active and sealed.
560fn highest_memtable_seqno(snapshot: &SuperVersion) -> Option<SeqNo> {
561    let sealed = snapshot
562        .sealed_memtables
563        .iter()
564        .map(|mt| mt.get_highest_seqno())
565        .max()
566        .flatten();
567    snapshot.active_memtable.get_highest_seqno().max(sealed)
568}
569
570/// Extract owned user-key bounds from any range.
571pub(crate) fn range_to_user_bounds<K: AsRef<[u8]>, R: RangeBounds<K>>(
572    range: &R,
573) -> (Bound<UserKey>, Bound<UserKey>) {
574    use core::ops::Bound::{Excluded, Included, Unbounded};
575    let lo = match range.start_bound() {
576        Included(x) => Included(x.as_ref().into()),
577        Excluded(x) => Excluded(x.as_ref().into()),
578        Unbounded => Unbounded,
579    };
580    let hi = match range.end_bound() {
581        Included(x) => Included(x.as_ref().into()),
582        Excluded(x) => Excluded(x.as_ref().into()),
583        Unbounded => Unbounded,
584    };
585    (lo, hi)
586}
587
588/// Wraps a [`SeekableTreeIter`](crate::range::SeekableTreeIter) so a standard
589/// tree can expose it as a [`SeekableGuardIter`](crate::iter_guard::SeekableGuardIter).
590struct StandardSeekable {
591    inner: crate::range::SeekableTreeIter,
592}
593
594impl Iterator for StandardSeekable {
595    type Item = IterGuardImpl;
596
597    fn next(&mut self) -> Option<Self::Item> {
598        self.inner.next().map(standard_guard)
599    }
600}
601
602impl DoubleEndedIterator for StandardSeekable {
603    fn next_back(&mut self) -> Option<Self::Item> {
604        self.inner.next_back().map(standard_guard)
605    }
606}
607
608impl crate::iter_guard::SeekableGuardIter for StandardSeekable {
609    fn seek_to(&mut self, key: &[u8]) {
610        self.inner.seek_to(key);
611    }
612
613    fn seek_to_for_prev(&mut self, key: &[u8]) {
614        self.inner.seek_to_for_prev(key);
615    }
616
617    fn peek_key(&mut self) -> Option<crate::Result<crate::UserKey>> {
618        self.inner.peek_key()
619    }
620}
621
622impl AbstractTree for Tree {
623    fn table_file_cache_size(&self) -> usize {
624        self.config
625            .descriptor_table
626            .as_ref()
627            .map_or(0, |dt| dt.len())
628    }
629
630    fn get_version_history_lock(&self) -> VersionsWriteGuard<'_> {
631        self.version_history.write()
632    }
633
634    fn persist_flushes(&self) -> crate::Result<()> {
635        self.0.persist_flushes()
636    }
637
638    fn next_table_id(&self) -> TableId {
639        self.0.table_id_counter.get()
640    }
641
642    fn id(&self) -> TreeId {
643        self.id
644    }
645
646    fn blob_file_count(&self) -> usize {
647        0
648    }
649
650    #[cfg(feature = "std")]
651    fn create_checkpoint(
652        &self,
653        target_path: &crate::path::Path,
654    ) -> crate::Result<crate::CheckpointInfo> {
655        crate::checkpoint::run_checkpoint(
656            self,
657            &crate::checkpoint::CheckpointParams {
658                target_root: target_path,
659                target_fs: &self.config.fs,
660                src_root: &self.config.path,
661                src_fs: &self.config.fs,
662                deletion_pause: &self.deletion_pause,
663                visible_seqno: &self.config.visible_seqno,
664                include_blobs: false,
665                runtime_config: self.0.runtime_config.load_full(),
666                encryption: self.0.config.encryption.clone(),
667                source: &self.0,
668            },
669        )
670    }
671
672    #[cfg(feature = "std")]
673    fn pin(&self) -> crate::PinnedVersion {
674        crate::PinnedVersion::take(self)
675    }
676
677    #[cfg(feature = "std")]
678    fn acknowledge_repair_report(&self, id: u128) -> crate::Result<bool> {
679        Self::acknowledge_repair_report(self, id)
680    }
681
682    fn print_trace(&self, key: &[u8]) -> crate::Result<()> {
683        let super_version = self.version_history.read().latest_version();
684
685        let key = Slice::from(key);
686
687        for kv in super_version.active_memtable.range_internal((
688            Bound::Included(InternalKey::bound(
689                key.clone(),
690                SeqNo::MAX,
691                ValueType::Value,
692            )),
693            Bound::Unbounded,
694        )) {
695            log::info!("[Active] {kv:?}");
696        }
697
698        for mt in super_version.sealed_memtables.iter().rev() {
699            for kv in mt.range_internal((
700                Bound::Included(InternalKey::bound(
701                    key.clone(),
702                    SeqNo::MAX,
703                    ValueType::Value,
704                )),
705                Bound::Unbounded,
706            )) {
707                log::info!("[Sealed #{}] {kv:?}", mt.id());
708            }
709        }
710
711        for table in super_version
712            .version
713            .iter_levels()
714            .flat_map(|lvl| lvl.iter())
715            .filter_map(|run| run.get_for_key_cmp(&key, self.config.comparator.as_ref()))
716        {
717            for kv in table.range(..) {
718                let kv = kv?;
719
720                if kv.key.user_key != key {
721                    break;
722                }
723
724                log::info!("[Table #{}] {kv:?}", table.id());
725            }
726        }
727
728        Ok(())
729    }
730
731    fn get_internal_entry(&self, key: &[u8], seqno: SeqNo) -> crate::Result<Option<InternalValue>> {
732        let super_version = self.snapshot_for_read(seqno)?;
733        // Counted where a caller asks, not in the lookup, which a read that
734        // counts itself makes as well.
735        super_version.version.read_mix().note_points(1);
736
737        Self::get_internal_entry_from_version(
738            &super_version,
739            key,
740            seqno,
741            self.config.comparator.as_ref(),
742        )
743    }
744
745    fn current_version(&self) -> Version {
746        self.version_history
747            .read()
748            .latest_version_ref()
749            .version
750            .clone()
751    }
752
753    #[cfg(feature = "std")]
754    fn refresh_table_checksum(
755        &self,
756        table_id: TableId,
757        checksum: crate::checksum::Checksum,
758        expected_restriction: Option<&crate::UserKey>,
759    ) -> crate::Result<crate::abstract_tree::ChecksumRefreshOutcome> {
760        use crate::abstract_tree::ChecksumRefreshOutcome;
761        // Same lock order as flush / compaction version installs: compaction
762        // state first, then the version history write lock. But the caller (the
763        // patrol reconcile) holds this table's HEAL LOCK across this call, and a
764        // concurrent tight-space compaction acquires that heal lock WHILE holding
765        // `compaction_state`. Blocking on `compaction_state` here would invert the
766        // order (heal_lock -> compaction_state on this path vs
767        // compaction_state -> heal_lock on the compaction path) and deadlock
768        // permanently. `try_lock` instead: a failed acquire means a compaction is
769        // mid-install, so skip this refresh — but report the skip as CONTENDED,
770        // not as a benign no-op: the healed bytes are durable while the manifest
771        // digest stays stale, so a "clean" report would mislead a later
772        // integrity check / checkpoint. The caller keeps the attestation and
773        // surfaces a finding; the next patrol retries once the compaction
774        // releases the state.
775        let Some(mut _compaction_state) = self.compaction_state.try_lock() else {
776            return Ok(ChecksumRefreshOutcome::Contended);
777        };
778        let mut version_lock = self.version_history.write();
779
780        // Under the install lock, resolve the CURRENT view of this table and
781        // reject the refresh if either it is gone (compacted away — nothing to
782        // refresh) or its restriction no longer matches the one `checksum` was
783        // computed for. A tight-space compaction can swap the captured view for a
784        // restricted same-id view (punching its prefix) between the caller's read
785        // and this lock; installing the caller's digest against a different
786        // restriction would record one the punched file can never match. Skipping
787        // leaves the current view's own (compaction-installed) digest in place for
788        // the next patrol to reconcile.
789        let restriction_matches = version_lock
790            .latest_version_ref()
791            .version
792            .iter_tables()
793            .find(|t| t.id() == table_id)
794            .is_some_and(|t| t.restrict_lower_bound() == expected_restriction);
795        if !restriction_matches {
796            // No-op: the manifest digest is unchanged, so the caller must keep the
797            // attestation for the next patrol.
798            return Ok(ChecksumRefreshOutcome::Stale);
799        }
800
801        version_lock
802            .upgrade_version(
803                &self.config.path,
804                |current| {
805                    let mut copy = current.clone();
806                    if let Some(next) = copy
807                        .version
808                        .with_refreshed_table_checksum(table_id, checksum)
809                    {
810                        copy.version = next;
811                    }
812                    Ok(copy)
813                },
814                &self.config.seqno,
815                &self.config.visible_seqno,
816                &*self.config.fs,
817                self.0.runtime_config.load_full(),
818                self.0.config.encryption.clone(),
819                crate::version::RetentionEffect::Keep,
820            )
821            .map(|()| ChecksumRefreshOutcome::Refreshed)
822    }
823
824    fn sync_mode(&self) -> crate::fs::SyncMode {
825        self.config.sync_mode
826    }
827
828    fn wait_for_table_file_refills(&self) {
829        self.table_files.wait_for_refills();
830    }
831
832    fn prefix_extractor(&self) -> Option<alloc::sync::Arc<dyn crate::prefix::PrefixExtractor>> {
833        self.config.prefix_extractor.clone()
834    }
835
836    fn storage_stats(&self) -> crate::Result<crate::StorageStats> {
837        // One version snapshot reused for the footprint and the full-compaction
838        // estimate below: a second `current_version()` could race a concurrent
839        // flush / compaction and mix two snapshots.
840        let version = self.current_version();
841        // Standard tree: SST values ARE user values (no KV separation).
842        let mut stats =
843            crate::storage_stats::compute_storage_stats(&version, self.is_compacting(), true)?;
844        // Fill the disk-aware capacity figures (quota + free-space probe) the
845        // version-only computation can't know.
846        let (capacity, available, compaction_possible) = self.admission_capacity(stats.used_bytes);
847        stats.capacity_bytes = capacity;
848        stats.available_bytes = available;
849        stats.compaction_possible = compaction_possible;
850        // When admission gating is active and a compaction is not already
851        // running, surface whether a full compaction has working room through the
852        // SAME two-layer check the compaction space gate enforces (logical quota +
853        // physical free per destination volume), so the reported status matches
854        // what the gate will admit. With gating off the gate never runs, so the
855        // status stays `Healthy` even though the backend can report a finite
856        // capacity.
857        if self.storage_admission_enabled()
858            && capacity.is_some()
859            && stats.status == crate::StorageStatus::Healthy
860        {
861            // A full compaction's transient output is bounded by the largest
862            // level's on-disk size, but it LANDS in the last configured level's
863            // volume (`level_count - 1`), which under tiered routing can be a
864            // different filesystem than the largest level. A standard tree has no
865            // blob relocation. Using the per-volume gate (not `available >=
866            // full_compaction_bytes` against the min-volume free) keeps the status
867            // from reporting tight when a routed merge would actually be admitted.
868            let sst_need = crate::storage_stats::full_compaction_demand_bytes(&version)?;
869            // `saturating_sub`: `level_count >= 1` always, so this is the last
870            // level index; the clamp only guards a degenerate zero-level config.
871            let sst_dest_level = self.0.config.level_count.saturating_sub(1);
872            let quota_headroom = self.quota_headroom(stats.used_bytes);
873            let full_fits = crate::compaction::worker::merge_would_fit(
874                &self.0.compaction_capacity,
875                self.0.id,
876                &self.0.config,
877                quota_headroom,
878                (sst_need, 0),
879                sst_dest_level,
880            );
881            stats.status = if full_fits {
882                crate::StorageStatus::FullCompactionAvailable
883            } else {
884                crate::StorageStatus::TightCompactionAvailable
885            };
886        }
887        // A closed admission gate is the operator-actionable state, so it takes
888        // precedence over the others (a read-only tree may well be compacting to
889        // reclaim space).
890        if self.is_read_only() {
891            stats.status = crate::StorageStatus::ReadOnlyOutOfSpace;
892        }
893        Ok(stats)
894    }
895
896    fn write_admission(&self) -> crate::Result<()> {
897        self.compute_write_admission()
898    }
899
900    fn write_backpressure(
901        &self,
902        strategy: &dyn crate::compaction::CompactionStrategy,
903    ) -> crate::Backpressure {
904        // Copy the thresholds out (BackpressureThresholds is Copy) so the
905        // arc-swap guard drops immediately; the off check short-circuits before
906        // touching the version, keeping the disabled path free.
907        let thresholds = self.0.runtime_config.load().backpressure;
908        if thresholds.is_off() {
909            return crate::Backpressure::None;
910        }
911        let version = self.current_version();
912        let signals = crate::BackpressureSignals {
913            // L0's run count is what a read pays there and what the leveled
914            // trigger counts.
915            l0_runs: version.l0().run_count(),
916            pending_compaction_bytes: strategy.pending_compaction_bytes(&version),
917            // Summed over a shared budget's members, so read only when an
918            // axis compares it.
919            unflushed_memtable_bytes: if thresholds.memtable_slowdown.is_some()
920                || thresholds.memtable_stop.is_some()
921            {
922                self.unflushed_memtable_bytes()
923            } else {
924                0
925            },
926        };
927        crate::Backpressure::compute(&signals, &thresholds)
928    }
929
930    fn get_flush_lock(&self) -> FlushGuard<'_> {
931        self.flush_lock.lock()
932    }
933
934    #[cfg(feature = "metrics")]
935    fn metrics(&self) -> &Arc<crate::Metrics> {
936        &self.0.metrics
937    }
938
939    #[cfg(feature = "metrics")]
940    fn cache_stats(&self) -> crate::CacheStats {
941        let cache = &self.0.config.cache;
942        self.metrics().cache_stats(cache.size(), cache.capacity())
943    }
944
945    fn prefix<K: AsRef<[u8]>>(
946        &self,
947        prefix: K,
948        seqno: SeqNo,
949        index: Option<(Arc<Memtable>, SeqNo)>,
950    ) -> Box<dyn DoubleEndedIterator<Item = IterGuardImpl> + Send + 'static> {
951        match self.create_prefix(&prefix, seqno, index) {
952            Ok(iter) => Box::new(iter.map(|kv| IterGuardImpl::Standard(Guard(kv)))),
953            Err(e) => Box::new(core::iter::once(error_guard(e))),
954        }
955    }
956
957    fn range<K: AsRef<[u8]>, R: RangeBounds<K>>(
958        &self,
959        range: R,
960        seqno: SeqNo,
961        index: Option<(Arc<Memtable>, SeqNo)>,
962    ) -> Box<dyn DoubleEndedIterator<Item = IterGuardImpl> + Send + 'static> {
963        match self.create_range(&range, seqno, index) {
964            Ok(iter) => Box::new(iter.map(|kv| IterGuardImpl::Standard(Guard(kv)))),
965            Err(e) => Box::new(core::iter::once(error_guard(e))),
966        }
967    }
968
969    fn range_seekable<K: AsRef<[u8]>, R: RangeBounds<K>>(
970        &self,
971        range: R,
972        seqno: SeqNo,
973        index: Option<(Arc<Memtable>, SeqNo)>,
974    ) -> Box<dyn crate::iter_guard::SeekableGuardIter + 'static> {
975        let (lo, hi) = range_to_user_bounds(&range);
976        match self.create_seekable_range_bounds(lo, hi, seqno, index) {
977            Ok(inner) => Box::new(StandardSeekable { inner }),
978            Err(e) => Box::new(crate::iter_guard::FailedSeekable::new(e)),
979        }
980    }
981
982    fn batch_range_scan<K: AsRef<[u8]>, R: RangeBounds<K> + 'static, I: IntoIterator<Item = R>>(
983        &self,
984        intervals: I,
985        seqno: SeqNo,
986        index: Option<(Arc<Memtable>, SeqNo)>,
987    ) -> Box<dyn Iterator<Item = IterGuardImpl> + Send + 'static>
988    where
989        I::IntoIter: Send + 'static,
990    {
991        // Open the seekable iterator over the whole keyspace once; each interval
992        // is served by repositioning it (single per-SST setup, amortized).
993        let inner = match self.create_seekable_range_bounds(
994            Bound::Unbounded,
995            Bound::Unbounded,
996            seqno,
997            index,
998        ) {
999            Ok(inner) => inner,
1000            Err(e) => return Box::new(core::iter::once(error_guard(e))),
1001        };
1002        let intervals = intervals.into_iter().map(|r| range_to_user_bounds(&r));
1003        Box::new(crate::range::BatchRangeScan::new(inner, intervals).map(standard_guard))
1004    }
1005
1006    /// Returns the number of tombstones in the tree.
1007    fn tombstone_count(&self) -> u64 {
1008        self.current_version()
1009            .iter_tables()
1010            .map(Table::tombstone_count)
1011            .sum()
1012    }
1013
1014    /// Returns the number of weak tombstones (single deletes) in the tree.
1015    fn weak_tombstone_count(&self) -> u64 {
1016        self.current_version()
1017            .iter_tables()
1018            .map(Table::weak_tombstone_count)
1019            .sum()
1020    }
1021
1022    /// Returns the number of value entries that become reclaimable once weak tombstones can be GC'd.
1023    fn weak_tombstone_reclaimable_count(&self) -> u64 {
1024        self.current_version()
1025            .iter_tables()
1026            .map(Table::weak_tombstone_reclaimable)
1027            .sum()
1028    }
1029
1030    fn clear(&self) -> crate::Result<()> {
1031        let config = self.tree_config();
1032        let mut versions = self.get_version_history_lock();
1033
1034        // Pre-clear snapshot: every table + blob file it references becomes
1035        // garbage the moment the new empty version is installed.
1036        let prior = versions.latest_version();
1037
1038        #[cfg(zstd_any)]
1039        let still_written = self.write_referenced_dict_ids();
1040        #[cfg(not(zstd_any))]
1041        let still_written: Vec<crate::file::DictId> = Vec::new();
1042
1043        versions.upgrade_version(
1044            &config.path,
1045            |v| {
1046                let mut copy = v.clone();
1047                copy.active_memtable = Arc::new(Memtable::new(
1048                    self.memtable_id_counter.next(),
1049                    self.config.comparator.clone(),
1050                ));
1051                copy.sealed_memtables = Arc::default();
1052                copy.version = v.version.cleared(&still_written);
1053                Ok(copy)
1054            },
1055            &config.seqno,
1056            &config.visible_seqno,
1057            &*config.fs,
1058            self.0.runtime_config.load_full(),
1059            self.0.config.encryption.clone(),
1060            // Every table goes: no snapshot up to this install is servable.
1061            crate::version::RetentionEffect::DropsData,
1062        )?;
1063        // `prior` still holds the replaced version, so nothing reaches refcount
1064        // zero yet.
1065        drop(versions); // release the version-history lock before any fs work
1066
1067        // Mark every obsolete table / blob file deleted so the file is
1068        // reclaimed (Inner::Drop) once its last reference is released. A
1069        // concurrent reader still holding the pre-clear snapshot keeps its own
1070        // clone alive, deferring physical deletion until it finishes — the
1071        // version-history Arc refcount is the MVCC guard, so reclaim never
1072        // races a live read. Tables with no other live reference are reclaimed
1073        // as `prior` drops at the end of this call.
1074        for table in prior.version.iter_tables() {
1075            table.mark_as_deleted();
1076        }
1077        for blob_file in prior.version.blob_files.iter() {
1078            blob_file.mark_as_deleted();
1079        }
1080
1081        Ok(())
1082    }
1083
1084    #[doc(hidden)]
1085    fn major_compact(
1086        &self,
1087        target_size: u64,
1088        gc_watermark: SeqNo,
1089    ) -> crate::Result<crate::compaction::CompactionResult> {
1090        let strategy = Arc::new(crate::compaction::major::Strategy::new(target_size));
1091
1092        // IMPORTANT: Write lock so we can be the only compaction going on
1093        let _lock = self.0.major_compaction_lock.write();
1094
1095        log::info!("Starting major compaction");
1096        self.inner_compact(strategy, gc_watermark)
1097    }
1098
1099    fn l0_run_count(&self) -> usize {
1100        self.current_version()
1101            .level(0)
1102            .map(|x| x.run_count())
1103            .unwrap_or_default()
1104    }
1105
1106    fn size_of<K: AsRef<[u8]>>(&self, key: K, seqno: SeqNo) -> crate::Result<Option<u32>> {
1107        #[expect(clippy::cast_possible_truncation, reason = "values are u32 length max")]
1108        Ok(self.get(key, seqno)?.map(|x| x.len() as u32))
1109    }
1110
1111    fn filter_size(&self) -> u64 {
1112        self.current_version()
1113            .iter_tables()
1114            .map(Table::filter_size)
1115            .map(u64::from)
1116            .sum()
1117    }
1118
1119    fn pinned_filter_size(&self) -> usize {
1120        self.current_version()
1121            .iter_tables()
1122            .map(Table::pinned_filter_size)
1123            .sum()
1124    }
1125
1126    fn filter_memory(&self) -> crate::FilterMemory {
1127        let version = self.current_version();
1128        let serialised_bytes = version
1129            .iter_tables()
1130            .map(|table| u64::from(table.filter_size()))
1131            .sum();
1132        let budget_bytes = self
1133            .config
1134            .filter_advisor
1135            .as_ref()
1136            .map(crate::config::FilterAdvisor::budget_bytes);
1137        crate::FilterMemory {
1138            serialised_bytes,
1139            resident_bytes: version
1140                .iter_tables()
1141                .map(Table::resident_filter_bytes)
1142                .sum(),
1143            budget_bytes,
1144            over_budget: budget_bytes.is_some_and(|budget| serialised_bytes > budget),
1145        }
1146    }
1147
1148    fn pinned_block_index_size(&self) -> usize {
1149        self.current_version()
1150            .iter_tables()
1151            .map(Table::pinned_block_index_size)
1152            .sum()
1153    }
1154
1155    fn sealed_memtable_count(&self) -> usize {
1156        self.version_history
1157            .read()
1158            .latest_version()
1159            .sealed_memtables
1160            .len()
1161    }
1162
1163    fn flush_to_tables_with_rt(
1164        &self,
1165        stream: impl Iterator<Item = crate::Result<InternalValue>>,
1166        range_tombstones: Vec<crate::range_tombstone::RangeTombstone>,
1167        keys: u64,
1168        hashes: u64,
1169        // A standard tree separates no values, so it has nothing to place.
1170        _overwritten: Option<&crate::blob_tree::overwritten::OverwrittenKeys>,
1171    ) -> crate::Result<
1172        Option<(
1173            Vec<Table>,
1174            Option<Vec<BlobFile>>,
1175            crate::runtime_config::WritePin,
1176        )>,
1177    > {
1178        use crate::table::multi_writer::MultiWriter;
1179        use crate::time::Instant;
1180
1181        let start = Instant::now();
1182
1183        let (folder, level_fs) = self.config.tables_folder_for_level(0);
1184
1185        let data_block_size = self.config.data_block_size_policy.get(0);
1186
1187        let data_block_restart_interval = self.config.data_block_restart_interval_policy.get(0);
1188        let index_block_restart_interval = self.config.index_block_restart_interval_policy.get(0);
1189
1190        // One runtime-config snapshot for the whole flush writer setup. The
1191        // compression policies, the index spill threshold, `seqno_in_index`,
1192        // and the per-KV checksum policy are all live (toggleable via
1193        // `update_runtime_config`); reading `load_full()` per field could
1194        // straddle a concurrent update and mix two snapshots into one SST.
1195        // Compaction is the migration mechanism, so a toggle takes effect on
1196        // the next flush / compaction.
1197        let rc = self.0.runtime_config.load_full();
1198
1199        let data_block_compression = rc.data_block_compression_policy.get(0);
1200        let index_block_compression = rc.index_block_compression_policy.get(0);
1201
1202        let data_block_hash_ratio = self.config.data_block_hash_ratio_policy.get(0);
1203
1204        let index_partitioning = self.config.index_block_partitioning_policy.get(0);
1205        let filter_partitioning = self.config.filter_block_partitioning_policy.get(0);
1206
1207        log::debug!(
1208            "Flushing memtable(s) to {}, data_block_restart_interval={data_block_restart_interval}, index_block_restart_interval={index_block_restart_interval}, data_block_size={data_block_size}, data_block_compression={data_block_compression:?}, index_block_compression={index_block_compression:?}",
1209            folder.display(),
1210        );
1211
1212        let table_writer = MultiWriter::from_pool(
1213            folder.clone(),
1214            Arc::clone(&self.table_files),
1215            None,
1216            self.config.table_target_size,
1217            0,
1218            level_fs.clone(),
1219            crate::fs::WriteOrigin::FlushTable,
1220        )?;
1221        // The flush's L0 recency, taken once its first file is: flushes are
1222        // serialized, so it is above every table already flushed and below
1223        // every one flushed later. A pooled file's id was taken when its batch
1224        // was made and says nothing about when the table was written; a file
1225        // made now carries an id taken now, which serves.
1226        let recency = table_writer
1227            .first_made_now()
1228            .unwrap_or_else(|| self.table_id_counter.next());
1229        let mut table_writer = table_writer
1230            .set_comparator(self.config.comparator.clone())
1231            .use_recency(Some(recency))
1232            .use_data_block_restart_interval(data_block_restart_interval)
1233            .use_index_block_restart_interval(index_block_restart_interval)
1234            .use_data_block_compression(data_block_compression)
1235            .use_index_block_compression(index_block_compression)
1236            .use_data_block_size(data_block_size)
1237            .use_index_partition_size(self.config.index_block_partition_size_policy.get(0))
1238            .use_filter_partition_size(self.config.filter_block_partition_size_policy.get(0))
1239            .use_row_group_size(self.config.columnar_row_group_size_policy.get(0))
1240            .use_columnar_page_size(self.config.columnar_page_size_policy.get(0))
1241            .use_column_encoding(self.config.column_encoding_policy.get(0))
1242            .use_data_block_hash_ratio(data_block_hash_ratio);
1243
1244        let bloom_policy = {
1245            use crate::config::FilterPolicyEntry::{Bloom, None};
1246            use crate::table::filter::BloomConstructionPolicy;
1247
1248            match self.config.filter_policy.get(0) {
1249                Bloom(policy) => policy,
1250                None => BloomConstructionPolicy::BitsPerKey(0.0),
1251            }
1252        };
1253        table_writer = table_writer.use_bloom_policy(bloom_policy);
1254        let filter_sizing = self.new_data_filter_sizing(
1255            0,
1256            bloom_policy,
1257            crate::filter_budget::FilterCount { keys, hashes },
1258            rc.ecc_scheme,
1259        );
1260        table_writer = table_writer.use_filter_sizing(filter_sizing.clone());
1261
1262        if index_partitioning {
1263            // Size-adaptive: single-level index for small SSTs (where pinning
1264            // the whole index is cheap and a two-level lookup is pure overhead),
1265            // spilling to a partitioned index only once the index grows past the
1266            // threshold. Recovers the point-read cost of an unconditional
1267            // two-level index on small/medium SSTs.
1268            table_writer = table_writer.use_adaptive_index(rc.index_partition_spill_threshold);
1269        }
1270        if filter_partitioning {
1271            table_writer = table_writer.use_partitioned_filter();
1272        }
1273
1274        table_writer = table_writer.use_prefix_extractor(self.config.prefix_extractor.clone());
1275        table_writer = table_writer.use_key_domains(self.config.key_domains.clone());
1276        table_writer = table_writer.use_encryption(self.config.encryption.clone());
1277        table_writer = table_writer.use_clock(self.config.clock.clone());
1278        // ECC scheme from the live runtime snapshot (same as `seqno_in_index`
1279        // / `kv_checksums` below), so a flush after a scheme change writes the
1280        // SST with the current scheme rather than the startup one.
1281        table_writer = table_writer.use_page_ecc(self.config.page_ecc, rc.ecc_scheme);
1282        table_writer = table_writer
1283            .use_sync_mode(self.config.sync_mode)
1284            .use_durability(self.config.flush_durability)
1285            .use_writeback_bytes(self.config.writeback_bytes);
1286
1287        table_writer = table_writer.use_seqno_in_index(rc.seqno_in_index);
1288        table_writer = table_writer.use_zone_map(rc.zone_map);
1289        table_writer = table_writer.use_columnar(rc.columnar);
1290        table_writer = table_writer.use_disable_cow_on_sst(rc.disable_cow_on_sst_files);
1291        // `Off` (default) emits no per-KV footer and leaves the data-block
1292        // payload encoding unchanged (the V5 header carries a block_flags byte
1293        // and the meta block a descriptor key regardless, so the on-disk bytes
1294        // are not identical to a pre-V5 table).
1295        table_writer = table_writer.use_kv_checksums(rc.kv_checksums, rc.kv_checksum_algo);
1296        // Flush writes level 0; resolve that level's locator policy entry.
1297        table_writer = table_writer.use_locator(self.config.locator_policy.get(0));
1298
1299        // The dictionary THIS snapshot's policy names. The flush holds the
1300        // snapshot until its tables are installed: a collection spares what a
1301        // held snapshot names, however the policy has changed since.
1302        #[cfg(zstd_any)]
1303        {
1304            table_writer = table_writer
1305                .use_zstd_dictionary(
1306                    self.config
1307                        .current_zstd_dictionaries()
1308                        .for_compression(data_block_compression)?,
1309                )
1310                .use_zstd_two_pass_seed(rc.zstd_two_pass_seed);
1311        }
1312        let write_pin = crate::runtime_config::WritePin::new(&rc).with_filter_sizing(filter_sizing);
1313
1314        // Parallel block compression for the flush writer, on the same pool the
1315        // compaction writers use. Engaged only when the per-block transform does
1316        // real CPU work (a codec, encryption, or page ECC): with the identity
1317        // transform the pipeline's owned buffer + queue hop per block buys
1318        // nothing over the serial reusable-buffer path. Safe wherever the host
1319        // runs this flush — even on a thread of an injected pool — because the
1320        // pipeline's help-first drain executes queued jobs inline instead of
1321        // waiting on a saturated pool (see `parallel_compressor`).
1322        #[cfg(feature = "std")]
1323        {
1324            let transform_does_work = data_block_compression != crate::CompressionType::None
1325                || self.config.encryption.is_some()
1326                || self.config.page_ecc;
1327            if transform_does_work {
1328                table_writer =
1329                    table_writer.use_parallel_compression(self.config.parallel_compression());
1330            }
1331        }
1332
1333        // Set range tombstones BEFORE writing KV items so that if MultiWriter
1334        // rotates to a new table during the write loop, earlier tables already
1335        // carry the RT metadata.
1336        table_writer.set_range_tombstones(range_tombstones);
1337
1338        for item in stream {
1339            table_writer.write(item?)?;
1340        }
1341
1342        let written = table_writer.finish()?;
1343        let entries_durable = written.entries_durable;
1344
1345        log::debug!("Flushed memtable(s) in {:?}", start.elapsed());
1346
1347        let pin_filter = self.config.filter_block_pinning_policy.get(0);
1348        let pin_index = self.config.index_block_pinning_policy.get(0);
1349
1350        // Load tables
1351        let tables = written
1352            .tables
1353            .into_iter()
1354            .map(|(table_id, checksum)| -> crate::Result<Table> {
1355                let mut params = crate::table::RecoverParams::new(
1356                    folder.join(table_id.to_string()),
1357                    checksum,
1358                    table_id,
1359                    level_fs.clone(),
1360                    self.config.comparator.clone(),
1361                    self.config.cache.clone(),
1362                );
1363                params.tree_id = self.id;
1364                params
1365                    .descriptor_table
1366                    .clone_from(&self.config.descriptor_table);
1367                params.pin_filter = pin_filter;
1368                params.pin_index = pin_index;
1369                params.encryption.clone_from(&self.config.encryption);
1370                params
1371                    .prefix_extractor
1372                    .clone_from(&self.config.prefix_extractor);
1373                #[cfg(zstd_any)]
1374                {
1375                    params.zstd_dictionaries = self.config.current_zstd_dictionaries();
1376                }
1377                #[cfg(feature = "metrics")]
1378                {
1379                    params.metrics = self.metrics.clone();
1380                }
1381                let table = Table::recover(params)?;
1382                if entries_durable {
1383                    table.mark_entry_durable();
1384                }
1385                Ok(table)
1386            })
1387            .collect::<crate::Result<Vec<_>>>()?;
1388
1389        // Return Some even when tables is empty (RT-only flush): the caller
1390        // (AbstractTree::flush) handles empty tables by re-inserting RTs into
1391        // the active memtable and still needs to delete sealed memtables.
1392        Ok(Some((tables, None, write_pin)))
1393    }
1394
1395    #[expect(clippy::significant_drop_tightening)]
1396    fn register_tables(
1397        &self,
1398        tables: &[Table],
1399        blob_files: Option<&[BlobFile]>,
1400        frag_map: Option<crate::blob_tree::FragmentationMap>,
1401        sealed_memtables_to_delete: &[crate::tree::inner::MemtableId],
1402        gc_watermark: SeqNo,
1403        collected_below_watermark: bool,
1404    ) -> crate::Result<()> {
1405        log::trace!(
1406            "Registering {} tables, {} blob files",
1407            tables.len(),
1408            blob_files.map(<[BlobFile]>::len).unwrap_or_default(),
1409        );
1410
1411        // Wire the tree-wide deletion pause into every fresh table / blob
1412        // file so an in-flight checkpoint defers their cleanup if they
1413        // later get marked `is_deleted` by compaction.
1414        let sinks = crate::table::TableSinks {
1415            deletion_pause: &self.deletion_pause,
1416            heal_hints: &self.heal_hints,
1417            read_budget: self.config.columnar_read_budget,
1418            #[cfg(feature = "std")]
1419            background_deleter: &self.background_deleter,
1420            track_filter_probes: self.config.filter_advisor.is_some(),
1421        };
1422        for table in tables {
1423            table.bind_to_tree(&sinks);
1424        }
1425        for bf in blob_files.unwrap_or(&[]) {
1426            bf.bind_to_tree(&sinks);
1427        }
1428
1429        let mut installed = InstalledCharge::default();
1430        installed.add(
1431            tables,
1432            blob_files.unwrap_or(&[]),
1433            crate::fs::WriteOrigin::FlushTable,
1434            crate::fs::WriteOrigin::FlushBlob,
1435        )?;
1436
1437        let mut _compaction_state = self.compaction_state.lock();
1438        let mut version_lock = self.version_history.write();
1439
1440        // A sealed memtable that left the version while it was flushed was
1441        // removed with its data (a `clear`): the tables written from it hold
1442        // that data, and are dropped rather than installed.
1443        let sealed = &version_lock.latest_version_ref().sealed_memtables;
1444        if !sealed_memtables_to_delete
1445            .iter()
1446            .all(|&id| sealed.iter().any(|mt| mt.id() == id))
1447        {
1448            log::warn!(
1449                "Flushed memtables left the version before the install, declining the flush"
1450            );
1451            drop(version_lock);
1452            for table in tables {
1453                table.mark_as_deleted();
1454            }
1455            for blob_file in blob_files.unwrap_or(&[]) {
1456                blob_file.mark_as_deleted();
1457            }
1458            return Ok(());
1459        }
1460
1461        // A deferred flush wrote its files unsynced: installed in memory only,
1462        // they are synced and written to the manifest by the next persist.
1463        let deferred = (self.config.flush_durability == crate::config::FlushDurability::Deferred)
1464            .then(|| {
1465                tables
1466                    .iter()
1467                    .cloned()
1468                    .map(crate::version::UnsyncedFile::Table)
1469                    .chain(
1470                        blob_files
1471                            .unwrap_or(&[])
1472                            .iter()
1473                            .cloned()
1474                            .map(crate::version::UnsyncedFile::Blob),
1475                    )
1476                    .collect()
1477            });
1478        version_lock.upgrade_version_deferring(
1479            &self.config.path,
1480            |current| {
1481                let mut copy = current.clone();
1482
1483                let ctx = crate::version::TransformContext::new(self.config.comparator.as_ref());
1484                copy.version = copy.version.with_new_l0_run(
1485                    tables,
1486                    blob_files,
1487                    frag_map.filter(|x| !x.is_empty()),
1488                    &ctx,
1489                );
1490
1491                for &table_id in sealed_memtables_to_delete {
1492                    log::trace!("releasing sealed memtable #{table_id}");
1493                    copy.sealed_memtables = Arc::new(copy.sealed_memtables.remove(table_id));
1494                }
1495
1496                Ok(copy)
1497            },
1498            &self.config.seqno,
1499            &self.config.visible_seqno,
1500            &*self.config.fs,
1501            self.0.runtime_config.load_full(),
1502            self.0.config.encryption.clone(),
1503            // A flush adds a run, but it does not only add: `AbstractTree::flush`
1504            // feeds the sealed memtables through the same `CompactionStream`
1505            // with this same watermark, so it collects the versions below it
1506            // exactly as a compaction does, and owes older snapshots the same
1507            // accounting. Same rule, evaluated in the same place, because a
1508            // flush and a compaction disagreeing about it is how a floor came
1509            // to promise data the output no longer held.
1510            //
1511            // `false` for the filter: the flush stream carries no user
1512            // compaction filter, so `DropsData` is unreachable here.
1513            crate::version::RetentionEffect::of_run(false, collected_below_watermark, gc_watermark),
1514            deferred,
1515        )?;
1516        installed.charge();
1517
1518        Ok(())
1519    }
1520
1521    fn clear_active_memtable(&self) {
1522        use crate::tree::sealed::SealedMemtables;
1523
1524        let mut version_history_lock = self.version_history.write();
1525        let super_version = version_history_lock.latest_version();
1526
1527        if super_version.active_memtable.is_empty() {
1528            return;
1529        }
1530
1531        let mut copy = version_history_lock.latest_version();
1532        copy.active_memtable = Arc::new(Memtable::new(
1533            self.memtable_id_counter.next(),
1534            self.config.comparator.clone(),
1535        ));
1536        copy.sealed_memtables = Arc::new(SealedMemtables::default());
1537
1538        // Rotate does not modify the memtable, so it cannot break snapshots
1539        copy.seqno = super_version.seqno;
1540
1541        version_history_lock.replace_latest_version(copy);
1542
1543        log::trace!("cleared active memtable");
1544    }
1545
1546    fn compact(
1547        &self,
1548        strategy: Arc<dyn CompactionStrategy>,
1549        gc_watermark: SeqNo,
1550    ) -> crate::Result<crate::compaction::CompactionResult> {
1551        // NOTE: Read lock major compaction lock
1552        // That way, if a major compaction is running, we cannot proceed
1553        // But in general, parallel (non-major) compactions can occur
1554        let _lock = self.0.major_compaction_lock.read();
1555
1556        self.inner_compact(strategy, gc_watermark)
1557    }
1558
1559    fn get_next_table_id(&self) -> TableId {
1560        self.0.get_next_table_id()
1561    }
1562
1563    fn tree_config(&self) -> &Config {
1564        &self.config
1565    }
1566
1567    fn active_memtable(&self) -> Arc<Memtable> {
1568        self.version_history
1569            .read()
1570            .latest_version_ref()
1571            .active_memtable
1572            .clone()
1573    }
1574
1575    // Its size says so without the range-tombstone lock `Memtable::is_empty`
1576    // takes, which a tombstone insert holds across the tree's comparator.
1577    #[cfg(feature = "std")]
1578    fn active_memtable_is_empty(&self) -> bool {
1579        self.latest_super_version.load().active_memtable.size() == 0
1580    }
1581
1582    #[cfg(not(feature = "std"))]
1583    fn active_memtable_is_empty(&self) -> bool {
1584        self.version_history
1585            .read()
1586            .latest_version_ref()
1587            .active_memtable
1588            .size()
1589            == 0
1590    }
1591
1592    fn rotate_memtable(&self) -> Option<Arc<Memtable>> {
1593        self.rotate_memtable_locked(&mut self.version_history.write())
1594    }
1595
1596    fn table_count(&self) -> usize {
1597        self.current_version().table_count()
1598    }
1599
1600    fn level_table_count(&self, idx: usize) -> Option<usize> {
1601        self.current_version().level(idx).map(|x| x.table_count())
1602    }
1603
1604    fn approximate_len(&self) -> usize {
1605        let super_version = self.version_history.read().latest_version();
1606
1607        let tables_item_count = self
1608            .current_version()
1609            .iter_tables()
1610            .map(|x| x.metadata.item_count)
1611            .sum::<u64>();
1612
1613        let memtable_count = super_version.active_memtable.len() as u64;
1614        let sealed_count = super_version
1615            .sealed_memtables
1616            .iter()
1617            .map(|mt| mt.len())
1618            .sum::<usize>() as u64;
1619
1620        #[expect(clippy::expect_used, reason = "result should fit into usize")]
1621        (memtable_count + sealed_count + tables_item_count)
1622            .try_into()
1623            .expect("approximate_len too large for usize")
1624    }
1625
1626    fn disk_space(&self) -> u64 {
1627        self.current_version()
1628            .iter_levels()
1629            .map(super::version::Level::size)
1630            .sum()
1631    }
1632
1633    fn approximate_range_stats<K: AsRef<[u8]>, R: core::ops::RangeBounds<K>>(
1634        &self,
1635        range: R,
1636        seqno: SeqNo,
1637    ) -> crate::Result<crate::ApproximateRangeStats> {
1638        use core::ops::Bound;
1639
1640        // A query planner asks for this estimate as part of the query it
1641        // plans, so the index walks below are the query's reads and stay
1642        // counted. A monitoring report's reads are the ones kept out of the
1643        // counters (see `Table::live_item_count`).
1644        let lo: Bound<&[u8]> = match range.start_bound() {
1645            Bound::Included(k) => Bound::Included(k.as_ref()),
1646            Bound::Excluded(k) => Bound::Excluded(k.as_ref()),
1647            Bound::Unbounded => Bound::Unbounded,
1648        };
1649        let hi: Bound<&[u8]> = match range.end_bound() {
1650            Bound::Included(k) => Bound::Included(k.as_ref()),
1651            Bound::Excluded(k) => Bound::Excluded(k.as_ref()),
1652            Bound::Unbounded => Bound::Unbounded,
1653        };
1654        let bounds = (lo, hi);
1655
1656        let mut bytes: u64 = 0;
1657        let mut key_count: u64 = 0;
1658
1659        // Use ONE snapshot at the requested seqno for both the SST and memtable
1660        // contributions, so the estimate is taken against a read's view at
1661        // `seqno`: one consistent TABLE set even during a concurrent flush or
1662        // compaction, and memtable entries filtered by the snapshot. Neither
1663        // half is exact, and the two paragraphs below say how.
1664        //
1665        // What "the same visibility as a read" does and does not mean here.
1666        //
1667        // It uses the FILE SET a read at `seqno` resolves to, which is the
1668        // current one for every snapshot above the floor, and filters the
1669        // memtables by `seqno`. It is not a per-row mask: a table whose seqno
1670        // range straddles `seqno` is classified `Partial` and then apportioned
1671        // whole by byte offsets, so its share can include rows the snapshot
1672        // cannot read. The estimate is an estimate, and this is one of the ways
1673        // it is approximate.
1674        //
1675        // Nor does resolving once freeze the tree. The clone pins the version's
1676        // tables, but the active memtable behind it is the live one every
1677        // writer mutates, so a write landing mid-call can be counted by the
1678        // memtable half after the table half was read. And a compaction
1679        // installing after the clone is what the NEXT read at that seqno
1680        // resolves to: the layout described here is one compaction is free to
1681        // change the moment this returns.
1682        let comparator = self.config.comparator.as_ref();
1683        let super_version = self.get_version_for_snapshot(seqno)?;
1684
1685        // SST contribution: interpolate data-block offsets at the boundaries
1686        // (block granularity), no data-block reads. For a KV-separated SST the
1687        // referenced blob bytes are apportioned by the same in-range fraction.
1688        for table in super_version.version.iter_tables() {
1689            // Comparator-aware overlap: a custom user comparator orders keys
1690            // differently from raw bytes, so use the same comparison the read
1691            // path does instead of default byte ordering.
1692            if !table
1693                .metadata
1694                .key_range
1695                .overlaps_with_bounds_cmp(&bounds, comparator)
1696            {
1697                continue;
1698            }
1699            // The block index is keyed by the table-LOCAL seqno; a bulk-ingested
1700            // table carries a non-zero global seqno, so translate the snapshot
1701            // seqno the same way the read path does before seeking it. A snapshot
1702            // below the table's base means the table postdates it and contributes
1703            // nothing to the estimate, so skip it (`checked_sub` yields `None`).
1704            let Some(table_seqno) = seqno.checked_sub(table.global_seqno()) else {
1705                continue;
1706            };
1707            // The translation alone is not the visibility rule: a table with
1708            // `global_seqno == 0` passes it whatever its entries hold, so one
1709            // holding nothing but seqno 100 would charge rows and bytes to a
1710            // query at seqno 50 that reads none of them. Ask the same
1711            // classification the read path uses.
1712            if table.seqno_visibility(seqno) == crate::table::SeqnoVisibility::None {
1713                continue;
1714            }
1715
1716            // A restricted view's punched-out prefix is served by its
1717            // replacement table, so the span starts past it and the prefix is
1718            // not double-counted (matching how scans skip it).
1719            let Some(span) = table.data_span(bounds, table_seqno, crate::table::SpanEdge::Whole)?
1720            else {
1721                continue;
1722            };
1723            let (idx_bytes, data_end) = (span.covered, span.data_end);
1724            if idx_bytes == 0 {
1725                continue;
1726            }
1727
1728            // fraction = idx_bytes / data_end, in u128 to avoid overflow. For a
1729            // standard tree `idx_bytes` already includes the inline values. For a
1730            // KV-separated SST it covers only the key + pointer bytes, so the
1731            // SST's referenced blob bytes (recorded per-SST at both flush and
1732            // compaction) are apportioned by the same in-range fraction; blob
1733            // files are not key-indexed, so this fraction is the finest estimate
1734            // possible without reading data blocks.
1735            let num = u128::from(idx_bytes);
1736            let den = u128::from(data_end);
1737            let blob_bytes = table.referenced_blob_bytes()?;
1738            // `num <= den` (both offsets are bounded by `data_end`), so
1739            // `x * num / den <= x` and the u128 -> u64 narrowing is total —
1740            // no fallback value to mask a range error with.
1741            #[expect(
1742                clippy::cast_possible_truncation,
1743                reason = "num <= den, so the quotient never exceeds the u64 input"
1744            )]
1745            let sst_blob = (u128::from(blob_bytes) * num / den) as u64;
1746            // Round up to at least one entry: a non-empty byte span over a
1747            // non-empty SST always covers at least one row, so a narrow range
1748            // never reports bytes with a zero key count.
1749            #[expect(
1750                clippy::cast_possible_truncation,
1751                reason = "num <= den, so the quotient never exceeds the u64 input"
1752            )]
1753            let in_range_entries =
1754                ((u128::from(table.metadata.item_count) * num / den) as u64).max(1);
1755            bytes = bytes.saturating_add(idx_bytes).saturating_add(sst_blob);
1756            key_count = key_count.saturating_add(in_range_entries);
1757        }
1758
1759        // Memtable contribution: the in-range fraction of each memtable's
1760        // approximate size. Built from the SAME snapshot and the SAME
1761        // `range_internal` + internal-key bounds the read path uses (range.rs),
1762        // so the counted slice matches what a read at `seqno` would traverse.
1763        let mt_range =
1764            crate::range::internal_bounds(&lo.map(UserKey::from), &hi.map(UserKey::from));
1765        let estimate = |mt: &crate::Memtable| -> (u64, u64) {
1766            let total = mt.len() as u64;
1767            if total == 0 {
1768                return (0, 0);
1769            }
1770            // Count only entries visible at the snapshot (the same seqno cutoff
1771            // reads apply), so the estimate excludes writes newer than `seqno`.
1772            let count = mt
1773                .range_internal(mt_range.clone())
1774                .filter(|kv| kv.key.seqno < seqno)
1775                .count() as u64;
1776            if count == 0 {
1777                return (0, 0);
1778            }
1779            // `count <= total` (the counted entries are a filtered subset of
1780            // the memtable), so the quotient never exceeds the u64 `size()`
1781            // and the narrowing is total.
1782            #[expect(
1783                clippy::cast_possible_truncation,
1784                reason = "count <= total, so the quotient never exceeds the u64 input"
1785            )]
1786            let mt_bytes = (u128::from(mt.size()) * u128::from(count) / u128::from(total)) as u64;
1787            (mt_bytes, count)
1788        };
1789        let (b, c) = estimate(&super_version.active_memtable);
1790        bytes = bytes.saturating_add(b);
1791        key_count = key_count.saturating_add(c);
1792        for mt in super_version.sealed_memtables.iter() {
1793            let (b, c) = estimate(mt);
1794            bytes = bytes.saturating_add(b);
1795            key_count = key_count.saturating_add(c);
1796        }
1797
1798        Ok(crate::ApproximateRangeStats { bytes, key_count })
1799    }
1800
1801    fn approximate_range_cardinality<K: AsRef<[u8]>, R: core::ops::RangeBounds<K>>(
1802        &self,
1803        range: R,
1804        seqno: SeqNo,
1805    ) -> crate::Result<crate::RangeCardinality> {
1806        use crate::table::block_index::BlockIndex;
1807        use core::cmp::Ordering;
1808        use core::ops::Bound;
1809
1810        let lo: Bound<&[u8]> = match range.start_bound() {
1811            Bound::Included(k) => Bound::Included(k.as_ref()),
1812            Bound::Excluded(k) => Bound::Excluded(k.as_ref()),
1813            Bound::Unbounded => Bound::Unbounded,
1814        };
1815        let hi: Bound<&[u8]> = match range.end_bound() {
1816            Bound::Included(k) => Bound::Included(k.as_ref()),
1817            Bound::Excluded(k) => Bound::Excluded(k.as_ref()),
1818            Bound::Unbounded => Bound::Unbounded,
1819        };
1820        let bounds = (lo, hi);
1821        let comparator = self.config.comparator.as_ref();
1822        let super_version = self.get_version_for_snapshot(seqno)?;
1823
1824        let mut rows: u64 = 0;
1825        let mut total_rows: u64 = 0;
1826
1827        for table in super_version.version.iter_tables() {
1828            // Snapshot visibility is settled BEFORE the denominator grows. A
1829            // table no read at `seqno` can see is not part of the dataset the
1830            // selectivity describes, and counting it there while excluding it
1831            // from `rows` reports a full-keyspace query as selecting half the
1832            // tree. Out-of-RANGE tables do belong in the denominator, which is
1833            // why that check stays below it.
1834            //
1835            // A snapshot below the table's base means the table postdates it
1836            // (`checked_sub` yields `None`).
1837            let Some(table_seqno) = seqno.checked_sub(table.global_seqno()) else {
1838                continue;
1839            };
1840            // Wholly above the snapshot: invisible to a read at `seqno`, so it
1841            // contributes nothing here either (mirrors approximate_range_stats).
1842            if table.seqno_visibility(seqno) == crate::table::SeqnoVisibility::None {
1843                continue;
1844            }
1845            // What this VIEW serves, not what the file holds: a tight-space
1846            // restricted table's metadata still counts the punched-out prefix
1847            // its replacement now owns, while the numerator below starts at the
1848            // restriction — so charging the prefix here would report a
1849            // full-keyspace query as selecting a fraction of the tree.
1850            // The planner's estimate is part of the query it plans, so the
1851            // reads it makes are the query's and are counted, like the index
1852            // walks below.
1853            total_rows = total_rows
1854                .saturating_add(table.live_item_count(crate::table::util::ReadCharge::Foreground)?);
1855            if !table
1856                .metadata
1857                .key_range
1858                .overlaps_with_bounds_cmp(&bounds, comparator)
1859            {
1860                continue;
1861            }
1862            // Honor a tight-space restricted view: keys below
1863            // `restrict_lower_bound()` are the punched-out prefix served by the
1864            // replacement table, so raise this table's effective lower bound to
1865            // it (mirrors approximate_range_stats) and never charge that prefix.
1866            let eff_lo = effective_lower_bound(
1867                lo,
1868                table.restrict_lower_bound().map(AsRef::as_ref),
1869                comparator,
1870            );
1871            let zone_map = &table.zone_map;
1872            // A COLUMNAR block's per-column bounds are recorded in BYTE order,
1873            // which is the only ordering a value column has. Comparing them
1874            // with a non-lexicographic user comparator reads the recorded
1875            // minimum as a comparator maximum, so the walk stops before blocks
1876            // that overlap the query: the shortcut is skipped for such trees
1877            // and the byte-fraction estimate below answers instead. A row
1878            // block's bounds are the block's first and last key, already in
1879            // comparator order, so the default path is unaffected.
1880            let zone_bounds_ordered = comparator.is_lexicographic() || !table.metadata.columnar;
1881            if !zone_map.is_empty() && zone_bounds_ordered {
1882                // Zone map present: sum the per-block row counts of blocks whose
1883                // key range overlaps the query. A block is past the range once its
1884                // minimum key is above the upper bound; the boundary block at the
1885                // effective lower bound is counted in full (block granularity). A
1886                // range that lands in a key-space gap legitimately yields zero, so
1887                // this path is authoritative and never falls back to the byte fraction.
1888                let reader = match eff_lo {
1889                    Bound::Included(k) | Bound::Excluded(k) => {
1890                        table.block_index.forward_reader(k, table_seqno)
1891                    }
1892                    Bound::Unbounded => Some(table.block_index.iter()),
1893                };
1894                if let Some(reader) = reader {
1895                    for handle in reader {
1896                        let handle = handle?;
1897                        let Some(col) = zone_map
1898                            .columns_for(*handle.offset())
1899                            .and_then(|c| c.first())
1900                        else {
1901                            continue;
1902                        };
1903                        let above_hi = match hi {
1904                            Bound::Included(hk) => {
1905                                comparator.compare(&col.min, hk) == Ordering::Greater
1906                            }
1907                            Bound::Excluded(hk) => {
1908                                comparator.compare(&col.min, hk) != Ordering::Less
1909                            }
1910                            Bound::Unbounded => false,
1911                        };
1912                        if above_hi {
1913                            break;
1914                        }
1915                        rows = rows.saturating_add(u64::from(col.row_count));
1916                    }
1917                }
1918            } else if let Some(last) = table.block_index.iter().next_back() {
1919                // No zone map: apportion item_count by the in-range
1920                // data-block byte fraction, mirroring approximate_range_stats.
1921                let last = last?;
1922                let data_end = *last.offset() + u64::from(last.size());
1923                if data_end > 0 {
1924                    let off = |key: &[u8], end: bool| -> crate::Result<u64> {
1925                        match table.block_index.forward_reader(key, table_seqno) {
1926                            Some(mut it) => match it.next() {
1927                                Some(h) => {
1928                                    let h = h?;
1929                                    Ok(if end {
1930                                        (*h.offset() + u64::from(h.size())).min(data_end)
1931                                    } else {
1932                                        *h.offset()
1933                                    })
1934                                }
1935                                None => Ok(data_end),
1936                            },
1937                            None => Ok(data_end),
1938                        }
1939                    };
1940                    let off_lo = match eff_lo {
1941                        Bound::Included(k) | Bound::Excluded(k) => off(k, false)?,
1942                        Bound::Unbounded => 0,
1943                    };
1944                    let off_hi = match hi {
1945                        Bound::Included(k) | Bound::Excluded(k) => off(k, true)?,
1946                        Bound::Unbounded => data_end,
1947                    };
1948                    let idx_bytes = off_hi.saturating_sub(off_lo);
1949                    if idx_bytes > 0 {
1950                        // `idx_bytes <= data_end` (both offsets are bounded by
1951                        // `data_end`), so the quotient never exceeds the u64
1952                        // `item_count` and the narrowing is total.
1953                        #[expect(
1954                            clippy::cast_possible_truncation,
1955                            reason = "idx_bytes <= data_end, so the quotient never exceeds the u64 input"
1956                        )]
1957                        let est = ((u128::from(table.metadata.item_count) * u128::from(idx_bytes)
1958                            / u128::from(data_end)) as u64)
1959                            .max(1);
1960                        rows = rows.saturating_add(est);
1961                    }
1962                }
1963            }
1964        }
1965
1966        // Memtables: count the in-range, snapshot-visible entries and add them to
1967        // both the matched rows and the total (matching the SST accounting).
1968        let mt_range =
1969            crate::range::internal_bounds(&lo.map(UserKey::from), &hi.map(UserKey::from));
1970        let mut add_memtable = |mt: &crate::Memtable| {
1971            // The denominator counts what a read at this snapshot can SEE, the
1972            // same rule the SST loop applies: an entry at or above `seqno` is
1973            // filtered out of the numerator below, so counting it here would
1974            // report a full-keyspace query as selecting half of what it sees.
1975            let visible = mt.iter().filter(|kv| kv.key.seqno < seqno).count() as u64;
1976            total_rows = total_rows.saturating_add(visible);
1977            let in_range = mt
1978                .range_internal(mt_range.clone())
1979                .filter(|kv| kv.key.seqno < seqno)
1980                .count() as u64;
1981            rows = rows.saturating_add(in_range);
1982        };
1983        add_memtable(&super_version.active_memtable);
1984        for mt in super_version.sealed_memtables.iter() {
1985            add_memtable(mt);
1986        }
1987
1988        // selectivity is an approximate ratio; u64 row counts are well within
1989        // f64's exact-integer range (2^52) for any realistic table.
1990        #[expect(
1991            clippy::cast_precision_loss,
1992            reason = "row counts never approach 2^52; the ratio is approximate anyway"
1993        )]
1994        let selectivity = if total_rows == 0 {
1995            0.0
1996        } else {
1997            (rows.min(total_rows) as f64) / (total_rows as f64)
1998        };
1999        Ok(crate::RangeCardinality { rows, selectivity })
2000    }
2001
2002    fn get_highest_memtable_seqno(&self) -> Option<SeqNo> {
2003        highest_memtable_seqno(&self.version_history.read().latest_version())
2004    }
2005
2006    fn get_highest_seqno(&self) -> Option<SeqNo> {
2007        // One snapshot for the tables and the memtables: a flush installed
2008        // between two reads would leave its writes in neither. Its current
2009        // version, not the one on disk: a deferred flush moves its writes out
2010        // of the memtable before any persist names them.
2011        let snapshot = self.version_history.read().latest_version();
2012        let tables = snapshot
2013            .version
2014            .iter_tables()
2015            .map(Table::get_highest_seqno)
2016            .max();
2017        highest_memtable_seqno(&snapshot).max(tables)
2018    }
2019
2020    fn get_highest_persisted_seqno(&self) -> Option<SeqNo> {
2021        // The version on disk, not the current one: a deferred flush is not
2022        // persisted until a persist writes it, and a caller trimming its log
2023        // by this seqno must keep what such a flush holds.
2024        self.version_history
2025            .read()
2026            .persisted_version()
2027            .iter_tables()
2028            .map(Table::get_highest_seqno)
2029            .max()
2030    }
2031
2032    fn oldest_retained_seqno(&self) -> SeqNo {
2033        // The read boundary is the persisted floor, live and after a reopen.
2034        self.retention_floor()
2035    }
2036
2037    fn retention_floor(&self) -> SeqNo {
2038        self.version_history
2039            .read()
2040            .latest_version_ref()
2041            .version
2042            .retention_floor()
2043    }
2044
2045    fn lower_retention_floor(&self, floor: SeqNo) -> crate::Result<()> {
2046        let config = self.tree_config();
2047        let mut versions = self.get_version_history_lock();
2048        // Checked under the lock the install takes, so no install raising the
2049        // floor can land between the check and the edit.
2050        let current = versions.latest_version_ref().version.retention_floor();
2051        if floor > current {
2052            return Err(crate::Error::RetentionFloorRaise {
2053                current,
2054                requested: floor,
2055            });
2056        }
2057        if floor == current {
2058            return Ok(());
2059        }
2060        // A counter reset on reopen can stand below the current floor.
2061        let seqno = config.seqno.get();
2062        if floor > seqno {
2063            return Err(crate::Error::RetentionFloorAboveSeqno {
2064                seqno,
2065                requested: floor,
2066            });
2067        }
2068        versions.upgrade_version(
2069            &config.path,
2070            |v| {
2071                let mut copy = v.clone();
2072                copy.version = v.version.with_lowered_retention_floor(floor);
2073                Ok(copy)
2074            },
2075            &config.seqno,
2076            &config.visible_seqno,
2077            &*config.fs,
2078            self.0.runtime_config.load_full(),
2079            self.0.config.encryption.clone(),
2080            // The floor is the mutator's: `Keep` lets the install raise none.
2081            crate::version::RetentionEffect::Keep,
2082        )
2083    }
2084
2085    fn compaction_rate_limiter(&self) -> Arc<crate::rate_limiter::RateLimiter> {
2086        Arc::clone(&self.compaction_rate_limiter)
2087    }
2088
2089    fn write_rate_limiter(&self) -> Arc<crate::rate_limiter::RateLimiter> {
2090        Arc::clone(&self.write_rate_limiter)
2091    }
2092
2093    fn get<K: AsRef<[u8]>>(&self, key: K, seqno: SeqNo) -> crate::Result<Option<UserValue>> {
2094        let key = key.as_ref();
2095
2096        let super_version = self.snapshot_for_read(seqno)?;
2097        // Counted here, not in the lookup: a columnar scan resolving merge
2098        // operands looks keys up too, as part of the scan it counts.
2099        super_version.version.read_mix().note_points(1);
2100
2101        Self::resolve_or_passthrough(
2102            &super_version,
2103            key,
2104            seqno,
2105            self.config.merge_operator.as_ref(),
2106            self.config.comparator.as_ref(),
2107            || self.blob_source(),
2108        )
2109    }
2110
2111    // Existence needs the newest visible version, not its value: a merge
2112    // chain always resolves to some value, so its operands are not merged and
2113    // a separated base is not read.
2114    fn contains_key<K: AsRef<[u8]>>(&self, key: K, seqno: SeqNo) -> crate::Result<bool> {
2115        let super_version = self.snapshot_for_read(seqno)?;
2116        super_version.version.read_mix().note_points(1);
2117        Ok(Self::get_value(
2118            &super_version,
2119            key.as_ref(),
2120            seqno,
2121            self.config.comparator.as_ref(),
2122        )?
2123        .is_some())
2124    }
2125
2126    // Key-only like `contains_key`: the scan runs without the merge operator,
2127    // which yields a chain's newest operand in place of its merged value, the
2128    // same key either way.
2129    fn contains_prefix<K: AsRef<[u8]>>(
2130        &self,
2131        prefix: K,
2132        seqno: SeqNo,
2133        index: Option<(Arc<Memtable>, SeqNo)>,
2134    ) -> crate::Result<bool> {
2135        Ok(self
2136            .create_internal_prefix(prefix.as_ref(), seqno, index, None, None)?
2137            .next()
2138            .transpose()?
2139            .is_some())
2140    }
2141
2142    fn is_empty(&self, seqno: SeqNo, index: Option<(Arc<Memtable>, SeqNo)>) -> crate::Result<bool> {
2143        Ok(self
2144            .create_key_only_iter(seqno, index)?
2145            .next()
2146            .transpose()?
2147            .is_none())
2148    }
2149
2150    fn len(&self, seqno: SeqNo, index: Option<(Arc<Memtable>, SeqNo)>) -> crate::Result<usize> {
2151        let mut count = 0;
2152        for item in self.create_key_only_iter(seqno, index)? {
2153            item?;
2154            count += 1;
2155        }
2156        Ok(count)
2157    }
2158
2159    fn get_pinned<K: AsRef<[u8]>>(
2160        &self,
2161        key: K,
2162        seqno: SeqNo,
2163    ) -> crate::Result<Option<crate::PinnableSlice>> {
2164        let key = key.as_ref();
2165
2166        let super_version = self.snapshot_for_read(seqno)?;
2167        super_version.version.read_mix().note_points(1);
2168
2169        Self::resolve_or_passthrough_pinned(
2170            &super_version,
2171            key,
2172            seqno,
2173            self.config.merge_operator.as_ref(),
2174            self.config.comparator.as_ref(),
2175            || self.blob_source(),
2176        )
2177    }
2178
2179    fn multi_get<K: AsRef<[u8]>>(
2180        &self,
2181        keys: impl IntoIterator<Item = K>,
2182        seqno: SeqNo,
2183    ) -> crate::Result<Vec<Option<UserValue>>> {
2184        let super_version = self.snapshot_for_read(seqno)?;
2185        let comparator = self.config.comparator.as_ref();
2186        let merge_operator = self.config.merge_operator.as_ref();
2187
2188        let keys: Vec<_> = keys.into_iter().collect();
2189        let n = keys.len();
2190        if n == 0 {
2191            return Ok(Vec::new());
2192        }
2193
2194        // For small batches, use the simple per-key path
2195        if n <= 2 {
2196            super_version
2197                .version
2198                .read_mix()
2199                .note_points(u64::try_from(n).unwrap_or(u64::MAX));
2200            // One blob source for every key of the batch, taken when a merge
2201            // first needs it, so both keys read under one fill policy.
2202            let blob_source = core::cell::OnceCell::new();
2203            return keys
2204                .iter()
2205                .map(|key| {
2206                    Self::resolve_or_passthrough(
2207                        &super_version,
2208                        key.as_ref(),
2209                        seqno,
2210                        merge_operator,
2211                        comparator,
2212                        || blob_source.get_or_init(|| self.blob_source()).clone(),
2213                    )
2214                })
2215                .collect();
2216        }
2217
2218        Self::read_many(&read_job::ReadCtx {
2219            super_version,
2220            keys,
2221            seqno,
2222            comparator: Arc::clone(&self.config.comparator),
2223            merge_operator: self.config.merge_operator.clone(),
2224            merge_base: self.blob_source(),
2225            values: read_job::Values::Inline,
2226            metadata_budget: self.config.multi_get_metadata_budget,
2227        })
2228    }
2229
2230    fn apply_batch(&self, batch: crate::WriteBatch, seqno: SeqNo) -> crate::Result<(u64, u64)> {
2231        if batch.is_empty() {
2232            return Ok((0, self.active_memtable().size()));
2233        }
2234        Ok(self.append_batch(batch.materialize(seqno)?))
2235    }
2236
2237    fn insert<K: Into<UserKey>, V: Into<UserValue>>(
2238        &self,
2239        key: K,
2240        value: V,
2241        seqno: SeqNo,
2242    ) -> (u64, u64) {
2243        let value = InternalValue::from_components(key, value, seqno, ValueType::Value);
2244        self.append_entry(value)
2245    }
2246
2247    fn merge<K: Into<UserKey>, V: Into<UserValue>>(
2248        &self,
2249        key: K,
2250        operand: V,
2251        seqno: SeqNo,
2252    ) -> (u64, u64) {
2253        let value = InternalValue::new_merge_operand(key, operand, seqno);
2254        self.append_entry(value)
2255    }
2256
2257    fn remove<K: Into<UserKey>>(&self, key: K, seqno: SeqNo) -> (u64, u64) {
2258        let value = InternalValue::new_tombstone(key, seqno);
2259        self.append_entry(value)
2260    }
2261
2262    fn remove_weak<K: Into<UserKey>>(&self, key: K, seqno: SeqNo) -> (u64, u64) {
2263        let value = InternalValue::new_weak_tombstone(key, seqno);
2264        self.append_entry(value)
2265    }
2266
2267    fn remove_range<K: Into<UserKey>>(&self, start: K, end: K, seqno: SeqNo) -> u64 {
2268        let (start, end) = (start.into(), end.into());
2269        #[cfg(feature = "metrics")]
2270        self.0
2271            .metrics
2272            .accepted_range_deletion(start.len() + end.len());
2273        // The read guard is held through the insert, like `append_entry`: the
2274        // CDC scan's capture (write side of this lock) must exclude every
2275        // in-flight memtable write, or a backdated range deletion could land
2276        // after the capture yet below the returned watermark and be lost; the
2277        // guard also keeps a concurrent `rotate_memtable()` from sealing the
2278        // memtable mid-insert.
2279        let history = self.version_history.read();
2280
2281        #[cfg(test)]
2282        inner::TestHooks::fire(&self.test_hooks.range_write);
2283
2284        history
2285            .latest_version_ref()
2286            .active_memtable
2287            .insert_range_tombstone(start, end, seqno)
2288    }
2289}
2290
2291impl Tree {
2292    /// The filter plan of new data written into tables under the policies of
2293    /// `level`, as a flush or an ingestion writes it: at most `count` keys and
2294    /// filter hashes (zero when unknown) under `bloom_policy`. `None` without
2295    /// an advisor.
2296    /// New data has no probe history of its own: its filters are sized by the
2297    /// load the live tables draw per key, and room is kept for every table the
2298    /// write fills. The plan is held until the tables are installed (see
2299    /// `WritePin`).
2300    ///
2301    /// `ecc_scheme` is the one the write's runtime snapshot frames its blocks
2302    /// under.
2303    pub(crate) fn new_data_filter_sizing(
2304        &self,
2305        level: usize,
2306        bloom_policy: crate::table::filter::BloomConstructionPolicy,
2307        count: crate::filter_budget::FilterCount,
2308        ecc_scheme: crate::runtime_config::EccScheme,
2309    ) -> Option<crate::filter_budget::FilterPlan> {
2310        let advisor = self.config.filter_advisor.as_ref()?;
2311        let version = self.current_version();
2312        crate::filter_budget::plan(
2313            advisor,
2314            &self.filter_budget,
2315            &crate::filter_budget::live(&version),
2316            crate::filter_budget::Rewrite {
2317                count,
2318                framing: crate::filter_budget::Framing {
2319                    encryption: self.config.encryption.clone(),
2320                    ecc: crate::table::writer::resolve_ecc(self.config.page_ecc, ecc_scheme),
2321                },
2322                ..crate::filter_budget::Rewrite::default()
2323            },
2324            bloom_policy,
2325            self.config
2326                .filter_block_partitioning_policy
2327                .get(level)
2328                .then(|| self.config.filter_block_partition_size_policy.get(level)),
2329        )
2330    }
2331
2332    /// The encoded report of a repair the tree still owes, if any.
2333    #[cfg(feature = "std")]
2334    pub(crate) fn owed_repair_report(&self) -> Option<alloc::sync::Arc<[u8]>> {
2335        self.version_history
2336            .read()
2337            .latest_version_ref()
2338            .version
2339            .repair_report()
2340            .map(alloc::sync::Arc::from)
2341    }
2342
2343    /// See [`AbstractTree::acknowledge_repair_report`].
2344    #[cfg(feature = "std")]
2345    pub(crate) fn acknowledge_repair_report(&self, id: u128) -> crate::Result<bool> {
2346        let mut version_lock = self.version_history.write();
2347        let owes_it = version_lock
2348            .latest_version_ref()
2349            .version
2350            .repair_report()
2351            .is_some_and(|owed| crate::repair::owed_report_id(owed) == id);
2352        if !owes_it {
2353            return Ok(false);
2354        }
2355        let installed = version_lock.upgrade_version(
2356            &self.config.path,
2357            |current| {
2358                let mut copy = current.clone();
2359                copy.version = copy.version.without_repair_report();
2360                Ok(copy)
2361            },
2362            &self.config.seqno,
2363            &self.config.visible_seqno,
2364            &*self.config.fs,
2365            self.0.runtime_config.load_full(),
2366            self.0.config.encryption.clone(),
2367            // Only the report goes: every snapshot reads as before.
2368            crate::version::RetentionEffect::Keep,
2369        );
2370        drop(version_lock);
2371        installed.map(|()| true)
2372    }
2373
2374    /// Stores `dict` in the tree and records it in the current version, so it
2375    /// resolves after a reopen with no dictionary supplied in the config.
2376    ///
2377    /// Additive: tables already written keep resolving to the dictionary they
2378    /// were written against, which is what makes it safe to introduce a
2379    /// dictionary on a tree that already holds data, and to replace one (both
2380    /// are this same operation). Registering an id the tree already holds is a
2381    /// no-op, since the id is derived from the bytes.
2382    ///
2383    /// The file is written BEFORE the version edit that references it. The
2384    /// other order would publish an id whose bytes a crash could still lose,
2385    /// and the tables written against it would then fail to open; this order
2386    /// can only leave an unreferenced file, which the next collection removes.
2387    ///
2388    /// Both steps happen under the version lock, which is what serializes this
2389    /// against [`Self::collect_unreferenced_dictionaries`]. Taking the lock only
2390    /// for the edit would leave the file published in the window the collector
2391    /// scans, and it would be unlinked as an orphan before the edit that names
2392    /// it ever landed.
2393    ///
2394    /// # Errors
2395    ///
2396    /// Propagates the store's write failures and the version install's.
2397    #[cfg(zstd_any)]
2398    pub fn register_zstd_dictionary(
2399        &self,
2400        dict: alloc::sync::Arc<crate::compression::ZstdDictionary>,
2401    ) -> crate::Result<()> {
2402        let id = dict.id();
2403        let mut version_lock = self.version_history.write();
2404
2405        let folder = self.config.path.join(crate::file::DICTS_FOLDER);
2406        crate::dicts::write(
2407            &*self.config.fs,
2408            &folder,
2409            &dict,
2410            self.config.encryption.as_deref(),
2411            self.config.sync_mode,
2412        )?;
2413
2414        // Resolvable from this moment on rather than from the next open: the
2415        // tables this registration was made for are written right after it, and
2416        // reading them back needs the dictionary in the LIVE set.
2417        let dicts = self.config.current_zstd_dictionaries().with(dict);
2418        self.config
2419            .zstd_dictionaries
2420            .store(alloc::sync::Arc::new(dicts));
2421
2422        if version_lock
2423            .latest_version_ref()
2424            .version
2425            .dicts()
2426            .contains(&id)
2427        {
2428            return Ok(());
2429        }
2430        version_lock.upgrade_version(
2431            &self.config.path,
2432            |current| {
2433                let mut copy = current.clone();
2434                copy.version = copy.version.with_dict(id);
2435                Ok(copy)
2436            },
2437            &self.config.seqno,
2438            &self.config.visible_seqno,
2439            &*self.config.fs,
2440            self.0.runtime_config.load_full(),
2441            self.0.config.encryption.clone(),
2442            // Registering a dictionary adds a file and drops nothing, so no
2443            // snapshot loses anything it could read before.
2444            crate::version::RetentionEffect::Keep,
2445        )
2446    }
2447
2448    /// The dictionaries this tree can decompress against, as of this call.
2449    #[cfg(zstd_any)]
2450    #[must_use]
2451    pub fn zstd_dictionaries(&self) -> crate::compression::ZstdDictionaries {
2452        self.config.current_zstd_dictionaries()
2453    }
2454
2455    /// The dictionary ids new files may still be written against: every one
2456    /// the live snapshot's policies name, and every one a replaced snapshot a
2457    /// writer still holds names (see `TreeInner::retired_write_snapshots`).
2458    #[cfg(zstd_any)]
2459    pub(crate) fn write_referenced_dict_ids(&self) -> Vec<crate::file::DictId> {
2460        let kv_separated = self.config.kv_separation_opts.is_some();
2461        let mut ids: Vec<crate::file::DictId> = self
2462            .0
2463            .runtime_config
2464            .load()
2465            .write_dict_ids(kv_separated)
2466            .collect();
2467        self.0.retired_write_snapshots.lock().retain(|snapshot| {
2468            let Some(held) = snapshot.upgrade() else {
2469                return false;
2470            };
2471            ids.extend(held.write_dict_ids(kv_separated));
2472            true
2473        });
2474        ids.sort_unstable();
2475        ids.dedup();
2476        ids
2477    }
2478
2479    /// Drops every dictionary nothing references any more, returning how many
2480    /// files were removed.
2481    ///
2482    /// The current version first stops REGISTERING an id nothing references
2483    /// (a version edit, costing an install and nothing on disk), and the file
2484    /// goes in the same pass once the installed version no longer names it. A
2485    /// reader still holding an older version is unaffected: its tables and
2486    /// blob files pinned their dictionaries when they were opened.
2487    ///
2488    /// REFERENCED means both directions: the files that already exist (tables
2489    /// and blob files alike), and the ids the compression policies name for
2490    /// what is written next. Counting only the first would unregister a
2491    /// dictionary before its first table is written, and nothing puts the id
2492    /// back afterwards, so a later pass would unlink the file while live
2493    /// tables are compressed against it. What is written next includes the
2494    /// writers already running: a flush or compaction started under a policy
2495    /// [`Self::update_runtime_config`] has since replaced still writes against
2496    /// the dictionary that policy named, so it is spared until their files are
2497    /// installed and name it themselves.
2498    ///
2499    /// The whole pass holds the version lock. Registration takes the same lock
2500    /// before it writes anything, so the two cannot interleave: without that, a
2501    /// registration's file could land between this scan and its removals and be
2502    /// unlinked as an orphan while the edit naming it was still to come.
2503    ///
2504    /// # Errors
2505    ///
2506    /// Propagates the version install and the store's removal failures.
2507    #[cfg(zstd_any)]
2508    pub fn collect_unreferenced_dictionaries(&self) -> crate::Result<usize> {
2509        let mut version_lock = self.version_history.write();
2510
2511        // Unregister what neither the current version's tables nor the write
2512        // policy needs.
2513        let latest = version_lock.latest_version_ref().version.clone();
2514        let mut referenced = latest.referenced_dicts();
2515        referenced.extend(self.write_referenced_dict_ids());
2516        let stale: Vec<crate::file::DictId> = latest
2517            .dicts()
2518            .iter()
2519            .copied()
2520            .filter(|id| !referenced.contains(id))
2521            .collect();
2522
2523        if !stale.is_empty() {
2524            version_lock.upgrade_version(
2525                &self.config.path,
2526                |current| {
2527                    let mut copy = current.clone();
2528                    copy.version = copy.version.without_dicts(&stale);
2529                    Ok(copy)
2530                },
2531                &self.config.seqno,
2532                &self.config.visible_seqno,
2533                &*self.config.fs,
2534                self.0.runtime_config.load_full(),
2535                self.0.config.encryption.clone(),
2536                // Dropping a dictionary nothing reads takes away no row a
2537                // snapshot could observe: the current version no longer holds
2538                // a table that used it.
2539                crate::version::RetentionEffect::Keep,
2540            )?;
2541        }
2542
2543        // Unlink the files the current version no longer names. Still under
2544        // the version lock, so no registration can publish a file into the
2545        // window between the listing and the removals.
2546        let still_owed = version_lock.registered_dicts();
2547
2548        let folder = self.config.path.join(crate::file::DICTS_FOLDER);
2549        let mut removed = 0;
2550        for id in crate::dicts::list_ids(&*self.config.fs, &folder)? {
2551            if still_owed.contains(&id) || referenced.contains(&id) {
2552                continue;
2553            }
2554            // A held deletion pause SKIPS the dictionary entirely: it is not
2555            // queued for later. A checkpoint holds its captured version as a
2556            // LOCAL clone, so a concurrent install can replace that version
2557            // while the checkpoint is still going to link the dictionaries it
2558            // names; removing one now would fail
2559            // `link_dictionaries` on a missing file and abort a checkpoint that
2560            // was otherwise valid.
2561            //
2562            // Skipping rather than DEFERRING (which is what tables and blob
2563            // files do) because a queued removal fires unconditionally when the
2564            // pause drains, and by then the id may have been registered again:
2565            // registration finds the file already there, makes its write a
2566            // no-op, durably records the id, and the drain then deletes the
2567            // dictionary that registration just published. A table's removal
2568            // cannot be undone by re-creating the table, so deferring is right
2569            // for it; a dictionary collection is an explicit operation that can
2570            // simply be run again, so waiting for the next pass costs nothing
2571            // and leaves no queued action to fire against a re-registered file.
2572            if self.deletion_pause.is_active() {
2573                continue;
2574            }
2575            // A pin lists the dictionary for its copy: skipped the same way,
2576            // and the next pass after the pin drops removes it. Pins register
2577            // under the version lock held here, so none can slip in between.
2578            #[cfg(feature = "std")]
2579            if self
2580                .deletion_pause
2581                .is_pinned(&crate::dicts::path_of(&folder, id))
2582            {
2583                continue;
2584            }
2585            crate::dicts::remove(&*self.config.fs, &folder, id, self.config.sync_mode)?;
2586
2587            // Out of the LIVE set too, not just off the disk. The registry holds
2588            // each dictionary's raw bytes and its prepared decoder state, so a
2589            // tree that rotates dictionaries over a long life would otherwise
2590            // accumulate every generation it ever held until it is dropped,
2591            // and `zstd_dictionaries()` would keep naming ids the tree no
2592            // longer owns.
2593            self.config.zstd_dictionaries.store(alloc::sync::Arc::new(
2594                self.config.current_zstd_dictionaries().without(id),
2595            ));
2596            removed += 1;
2597        }
2598        // Explicitly, and only here: the guard is what excludes a concurrent
2599        // registration from the removals above, so releasing it at its last READ
2600        // (which is what tightening the scope would do) is precisely the race.
2601        //
2602        // This drop is also what satisfies `clippy::significant_drop_tightening`
2603        // — it makes the guard's last use its own release, so the lint does not
2604        // fire and the function needs no expectation. Adding one anyway is a
2605        // BUILD FAILURE here, not dead weight: `unfulfilled_lint_expectations`
2606        // is an error under the crate's `-D warnings`.
2607        drop(version_lock);
2608        Ok(removed)
2609    }
2610
2611    /// Maps a raw internal entry to its change-data-capture event, routing
2612    /// `Indirection` (KV-separated) values through `resolve_indirection`.
2613    ///
2614    /// A standard tree never stores `Indirection` and supplies a resolver that
2615    /// errors; the blob-tree scan path supplies one that reads the blob and
2616    /// returns an [`ScanSinceEvent::Insert`].
2617    fn map_event<F>(
2618        entry: InternalValue,
2619        version: &Version,
2620        resolve_indirection: &F,
2621    ) -> crate::Result<ScanSinceEvent>
2622    where
2623        F: Fn(&Version, InternalValue) -> crate::Result<ScanSinceEvent>,
2624    {
2625        // A cell row may reference blob objects, so it resolves where an
2626        // indirection does.
2627        if matches!(
2628            entry.key.value_type,
2629            ValueType::Indirection | ValueType::CellRow
2630        ) {
2631            return resolve_indirection(version, entry);
2632        }
2633        let seqno = entry.key.seqno;
2634        let key = entry.key.user_key;
2635        Ok(match entry.key.value_type {
2636            ValueType::Value => ScanSinceEvent::Insert {
2637                key,
2638                value: entry.value,
2639                seqno,
2640            },
2641            ValueType::MergeOperand => ScanSinceEvent::MergeOperand {
2642                key,
2643                operand: entry.value,
2644                seqno,
2645            },
2646            // Weak (single-delete) tombstones keep their own event kind: a
2647            // weak tombstone annihilates exactly its matching put during
2648            // compaction and can then expose an older value, while a regular
2649            // tombstone keeps hiding it — collapsing both into one event
2650            // would make a replica replay a weak delete as a full delete and
2651            // diverge from the source.
2652            ValueType::Tombstone => ScanSinceEvent::PointTombstone { key, seqno },
2653            ValueType::WeakTombstone => ScanSinceEvent::WeakTombstone { key, seqno },
2654            ValueType::Indirection | ValueType::CellRow => {
2655                unreachable!("Indirection and CellRow handled above")
2656            }
2657        })
2658    }
2659
2660    /// Shared CDC aggregation behind [`Self::scan_since_seqno`] and the
2661    /// blob-tree scan path: gathers qualifying entries (`seqno >= target`) plus
2662    /// range tombstones from the active + sealed memtables and every SST (with
2663    /// block-skip), maps each entry to a [`ScanSinceEvent`] — routing
2664    /// `Indirection` values through `resolve_indirection` against the same
2665    /// version snapshot — and returns them in increasing seqno order.
2666    ///
2667    /// # Panics
2668    ///
2669    /// Panics if the internal version-history lock is poisoned.
2670    ///
2671    /// # Errors
2672    ///
2673    /// Returns `Err` if reading the index or a data block fails, or if
2674    /// `resolve_indirection` errors.
2675    pub(crate) fn scan_since_seqno_with<F>(
2676        &self,
2677        target_seqno: SeqNo,
2678        block_skip: bool,
2679        resolve_indirection: F,
2680    ) -> crate::Result<alloc::vec::IntoIter<ScanSinceEvent>>
2681    where
2682        F: Fn(&Version, InternalValue) -> crate::Result<ScanSinceEvent>,
2683    {
2684        self.scan_since_seqno_scoped(target_seqno, block_skip, resolve_indirection, None)
2685    }
2686
2687    /// As [`Self::scan_since_seqno_with`], optionally scoped to a key range
2688    /// (in the tree comparator's order): point events are delivered only for
2689    /// keys INSIDE the bounds, range tombstones when their span OVERLAPS them
2690    /// (a tombstone reaching into the range affects replay within it), and
2691    /// SSTs whose key range cannot intersect the bounds are skipped without
2692    /// being read — which is what makes a post-repair reconciliation over
2693    /// [`RepairReport::lost_coverage`](crate::RepairReport) affordable.
2694    pub(crate) fn scan_since_seqno_scoped<F>(
2695        &self,
2696        target_seqno: SeqNo,
2697        block_skip: bool,
2698        resolve_indirection: F,
2699        key_range: Option<&(Bound<UserKey>, Bound<UserKey>)>,
2700    ) -> crate::Result<alloc::vec::IntoIter<ScanSinceEvent>>
2701    where
2702        F: Fn(&Version, InternalValue) -> crate::Result<ScanSinceEvent>,
2703    {
2704        use core::cmp::Ordering;
2705
2706        let cmp = self.config.comparator.clone();
2707        let in_key_range = |key: &[u8]| -> bool {
2708            let Some((lo, hi)) = key_range else {
2709                return true;
2710            };
2711            (match lo {
2712                Bound::Included(b) => cmp.compare(key, b.as_ref()) != Ordering::Less,
2713                Bound::Excluded(b) => cmp.compare(key, b.as_ref()) == Ordering::Greater,
2714                Bound::Unbounded => true,
2715            }) && (match hi {
2716                Bound::Included(b) => cmp.compare(key, b.as_ref()) != Ordering::Greater,
2717                Bound::Excluded(b) => cmp.compare(key, b.as_ref()) == Ordering::Less,
2718                Bound::Unbounded => true,
2719            })
2720        };
2721        // A range tombstone covers `[start, end)`; it is delivered when that
2722        // span overlaps the scope, since a deletion reaching into the range
2723        // affects replay within it.
2724        let rt_in_range = |rt: &RangeTombstone| -> bool {
2725            let Some((lo, hi)) = key_range else {
2726                return true;
2727            };
2728            (match lo {
2729                // `rt.end` is EXCLUSIVE: the tombstone reaches keys strictly
2730                // below it, so it clears the lower bound only when its end is
2731                // ABOVE the bound key (for an excluded bound this over-includes
2732                // the touching case, which is harmless: replaying an extra
2733                // idempotent deletion event cannot corrupt a consumer).
2734                Bound::Included(b) | Bound::Excluded(b) => {
2735                    cmp.compare(rt.end.as_ref(), b.as_ref()) == Ordering::Greater
2736                }
2737                Bound::Unbounded => true,
2738            }) && (match hi {
2739                Bound::Included(b) => {
2740                    cmp.compare(rt.start.as_ref(), b.as_ref()) != Ordering::Greater
2741                }
2742                Bound::Excluded(b) => cmp.compare(rt.start.as_ref(), b.as_ref()) == Ordering::Less,
2743                Bound::Unbounded => true,
2744            })
2745        };
2746        // The active memtable is the one source a writer can still change, and
2747        // the seqno cap alone does not exclude that: a caller may commit with an
2748        // explicit seqno at or BELOW the cap (`apply_batch` takes the seqno from
2749        // the caller), and a live lock-free walk would then see that write or
2750        // miss it depending on where the node lands relative to the cursor —
2751        // even splitting one batch. A consumer that advanced past the returned
2752        // watermark would lose the change for good.
2753        //
2754        // So freeze it: a batch or range write holds the version-history READ
2755        // guard for its whole insert, and a single-entry insert holds its shard
2756        // of the insert gate instead, so taking the WRITE guard and closing the
2757        // gate excludes both, in the order a version change takes them. The cap
2758        // and the active memtable's raw entries are captured under them;
2759        // everything else (sealed memtables, tables) is immutable and needs
2760        // no coordination.
2761        //
2762        // Mapping runs AFTER the guard drops: it resolves blob indirections,
2763        // which reads a blob file, and no I/O may happen with writers blocked.
2764        // The memtables are walked over the scope only, as a range read walks
2765        // them.
2766        let memtable_keys = key_range.map_or(
2767            (Bound::Unbounded, Bound::Unbounded),
2768            crate::range::user_to_internal_bounds,
2769        );
2770        let (super_version, end_seqno, active_entries, active_range_tombstones) = {
2771            let guard = self.version_history.write();
2772            #[cfg(feature = "std")]
2773            let gate = self.insert_gate.close();
2774            let super_version = guard.latest_version();
2775            #[cfg(test)]
2776            inner::TestHooks::fire(&self.test_hooks.scan_freeze);
2777            let end_seqno = {
2778                let active = super_version.active_memtable.get_highest_seqno();
2779                let sealed = super_version
2780                    .sealed_memtables
2781                    .iter()
2782                    .map(|mt| mt.get_highest_seqno())
2783                    .max()
2784                    .flatten();
2785                let tables = super_version
2786                    .version
2787                    .iter_tables()
2788                    .map(Table::get_highest_seqno)
2789                    .max();
2790                active.max(sealed).max(tables)
2791            };
2792            let entries: Vec<InternalValue> = end_seqno.map_or_else(Vec::new, |cap| {
2793                super_version
2794                    .active_memtable
2795                    .range_internal(memtable_keys.clone())
2796                    .filter(|e| e.key.seqno >= target_seqno && e.key.seqno <= cap)
2797                    .collect()
2798            });
2799            let rts = super_version.active_memtable.range_tombstones_sorted();
2800            // Explicit: the gate and the guard must outlive the capture above,
2801            // and writers resume the moment they go.
2802            #[cfg(feature = "std")]
2803            drop(gate);
2804            drop(guard);
2805            (super_version, end_seqno, entries, rts)
2806        };
2807        let version = &super_version.version;
2808        // No entries anywhere ⇒ nothing qualifies, regardless of target.
2809        let Some(end_seqno) = end_seqno else {
2810            return Ok(Vec::new().into_iter());
2811        };
2812
2813        // Events are gathered PER SOURCE, not into one flat list: copies of a
2814        // change across two sources are the same change and collapse, but a
2815        // single source may legitimately hold a byte-identical event more than
2816        // once (a write batch may carry the same merge operand for a key
2817        // twice; both are stored under the batch's shared seqno and both are
2818        // applied on read). See `merge_source_events`.
2819        let mut sources: Vec<Vec<ScanSinceEvent>> = Vec::new();
2820
2821        let in_window = |seqno: SeqNo| seqno >= target_seqno && seqno <= end_seqno;
2822        let range_tombstone_event = |rt: &RangeTombstone| {
2823            in_window(rt.seqno).then(|| ScanSinceEvent::RangeTombstone {
2824                start_key: rt.start.clone(),
2825                end_key: rt.end.clone(),
2826                seqno: rt.seqno,
2827            })
2828        };
2829
2830        // The scope bounds as borrowed slices, for SST key-range pruning.
2831        fn as_ref_bound(b: &Bound<UserKey>) -> Bound<&[u8]> {
2832            match b {
2833                Bound::Included(k) => Bound::Included(k.as_ref()),
2834                Bound::Excluded(k) => Bound::Excluded(k.as_ref()),
2835                Bound::Unbounded => Bound::Unbounded,
2836            }
2837        }
2838        let ref_bounds = key_range.map(|(lo, hi)| (as_ref_bound(lo), as_ref_bound(hi)));
2839
2840        // Active memtable — mapped from the frozen capture above, not walked
2841        // again: a second walk would reintroduce exactly the race the freeze
2842        // closed.
2843        let mut source = Vec::new();
2844        for entry in active_entries {
2845            if !in_key_range(&entry.key.user_key) {
2846                continue;
2847            }
2848            source.push(Self::map_event(entry, version, &resolve_indirection)?);
2849        }
2850        for rt in active_range_tombstones {
2851            if rt_in_range(&rt) {
2852                source.extend(range_tombstone_event(&rt));
2853            }
2854        }
2855        sources.push(source);
2856
2857        // Sealed memtables, NEWEST first: the list is kept in seal order, and
2858        // every source below must be older than the one before it, because the
2859        // merge derives replay precedence from that position.
2860        for memtable in super_version.sealed_memtables.iter().rev() {
2861            let mut source = Vec::new();
2862            // The walk is bounded by internal keys; the user bounds decide at
2863            // their own key, whatever version sits there.
2864            for entry in memtable.range_internal(memtable_keys.clone()) {
2865                if in_window(entry.key.seqno) && in_key_range(&entry.key.user_key) {
2866                    source.push(Self::map_event(entry, version, &resolve_indirection)?);
2867                }
2868            }
2869            for rt in memtable.range_tombstones_sorted() {
2870                if rt_in_range(&rt) {
2871                    source.extend(range_tombstone_event(&rt));
2872                }
2873            }
2874            sources.push(source);
2875        }
2876
2877        // SSTs. A table whose key range cannot intersect the scope is not read
2878        // — the point of the scoped variant. Its tombstones still are: the key
2879        // range does not bound them, since a compaction cuts its outputs'
2880        // tombstones at the next output's first key without widening the
2881        // range up to it.
2882        for table in version.iter_tables() {
2883            let keys_in_scope = ref_bounds.as_ref().is_none_or(|bounds| {
2884                table
2885                    .metadata
2886                    .key_range
2887                    .overlaps_with_bounds_cmp(bounds, cmp.as_ref())
2888            });
2889            if !keys_in_scope {
2890                let mut source = Vec::new();
2891                for rt in table.visible_range_tombstones() {
2892                    if rt_in_range(&rt) {
2893                        source.extend(range_tombstone_event(&rt));
2894                    }
2895                }
2896                if !source.is_empty() {
2897                    sources.push(source);
2898                }
2899                continue;
2900            }
2901            // An RT-only table's synthetic weak-tombstone sentinel (the
2902            // writer's `finish`) is deliberately NOT filtered out here. It is
2903            // a real on-disk entry the READ path sees: at a seqno TIE between
2904            // the range deletion and an older source's write at the range's
2905            // start key, the sentinel is what makes the read converge to a
2906            // deletion — so the event stream must carry it too, or a consumer
2907            // replaying the stream keeps a value the tree itself does not
2908            // serve (this stream's one rule is mirroring the tree's reads,
2909            // see `merge_source_events`). It surfaces as the weak-tombstone
2910            // event it is on disk; away from that tie a replayed weak delete
2911            // at the range's start under the range deletion's own seqno is a
2912            // no-op.
2913            let mut source = Vec::new();
2914            let table_keys = ref_bounds.unwrap_or((Bound::Unbounded, Bound::Unbounded));
2915            for entry in table.scan_seqno_range(target_seqno, end_seqno, block_skip, table_keys)? {
2916                if !in_key_range(&entry.key.user_key) {
2917                    continue;
2918                }
2919                source.push(Self::map_event(entry, version, &resolve_indirection)?);
2920            }
2921            // Clamped to the view's tight-space restriction: the punched
2922            // prefix's deletions are re-emitted by the slice output that
2923            // superseded it, so the raw list would duplicate those events.
2924            for rt in table.visible_range_tombstones() {
2925                if rt_in_range(&rt) {
2926                    source.extend(range_tombstone_event(&rt));
2927                }
2928            }
2929            sources.push(source);
2930        }
2931
2932        Ok(Self::merge_source_events(sources).into_iter())
2933    }
2934
2935    /// Merge per-source event lists into one replay-ordered stream, `sources`
2936    /// ordered NEWEST first.
2937    ///
2938    /// Replay order is increasing seqno, then — for events sharing one seqno —
2939    /// increasing source AGE reversed, so the newest source's event is applied
2940    /// LAST. That matters because two sources can hold different values for one
2941    /// key at one seqno ([`AbstractTree::apply_batch`] takes a caller-chosen
2942    /// seqno and does not require it to be unique), the tree serves the newer
2943    /// one, and a consumer keeps whatever it applies last. Sorting such ties by
2944    /// payload instead would decide precedence by byte order.
2945    ///
2946    /// What happens to byte-identical copies follows ONE rule: the stream must
2947    /// mirror what a read of the same tree does, because a consumer replaying
2948    /// it has to reach the state the tree itself serves.
2949    ///
2950    /// - **Merge operands are all kept.** The read path collects every
2951    ///   physically stored operand for a key and applies them in order — it
2952    ///   never deduplicates by seqno — so two operands are two applications
2953    ///   whether they sit in one source or in two. Seqnos do not disambiguate
2954    ///   here: [`AbstractTree::apply_batch`] takes a caller-chosen seqno and
2955    ///   does not require it to be unique, and one batch may carry the same
2956    ///   operand for a key twice.
2957    /// - **Idempotent events collapse across sources.** A write, a deletion or
2958    ///   a range deletion replayed twice reaches the same state, and the read
2959    ///   path shadows the copies by seqno rather than compounding them. One
2960    ///   committed change can physically live in two published tables — a
2961    ///   manifest-loss repair publishes every surviving SST as its own L0 run,
2962    ///   including both the inputs and the outputs of a compaction that crashed
2963    ///   before deleting its inputs — and delivering it twice would be noise.
2964    ///   Repeats WITHIN one source are still kept: they are separate entries
2965    ///   the source genuinely holds.
2966    fn merge_source_events(sources: Vec<Vec<ScanSinceEvent>>) -> Vec<ScanSinceEvent> {
2967        // Per source, collapse equal neighbours into runs, each tagged with
2968        // its source's recency (0 = newest) and the position of EVERY copy it
2969        // carries.
2970        //
2971        // The POSITIONS are what keep an order-sensitive merge operator
2972        // correct: a source applies the operands of one batch in the order
2973        // they were added, all at one seqno, and grouping them by payload
2974        // would replay them in a different order — an append or a list push
2975        // then converges somewhere the tree never was. Grouping still SORTS
2976        // (equal events have to meet), so each run remembers where each of its
2977        // members sat: one shared position would fold a repeated operand's
2978        // copies onto its twin's slot and replay `B, A, B` as `A, B, B`.
2979        struct Run {
2980            event: ScanSinceEvent,
2981            recency: usize,
2982            /// Original scan position of every copy, ascending.
2983            positions: Vec<usize>,
2984        }
2985        let mut runs: Vec<Run> = Vec::new();
2986        for (recency, source) in sources.into_iter().enumerate() {
2987            let mut indexed: Vec<(usize, ScanSinceEvent)> =
2988                source.into_iter().enumerate().collect();
2989            indexed.sort_by(|(_, a), (_, b)| ScanSinceEvent::grouping_order(a, b));
2990            let mut iter = indexed.into_iter();
2991            let Some((position, mut current)) = iter.next() else {
2992                continue;
2993            };
2994            let mut positions = alloc::vec![position];
2995            for (index, event) in iter {
2996                if event == current {
2997                    positions.push(index);
2998                } else {
2999                    positions.sort_unstable();
3000                    runs.push(Run {
3001                        event: core::mem::replace(&mut current, event),
3002                        recency,
3003                        positions: core::mem::replace(&mut positions, alloc::vec![index]),
3004                    });
3005                }
3006            }
3007            positions.sort_unstable();
3008            runs.push(Run {
3009                event: current,
3010                recency,
3011                positions,
3012            });
3013        }
3014
3015        // Merge the per-source runs. Operands are never collapsed — every copy
3016        // is an application the read path makes, so each keeps ITS source's
3017        // recency and ITS scan position, and the replay order below (oldest
3018        // source first, then position) applies them exactly as the tree does.
3019        // Idempotent events collapse across sources to the count a single
3020        // source holds; the collapsed event keeps the recency of the NEWEST
3021        // source holding it (and that source's positions), which is the slot
3022        // the tree's own precedence gives it.
3023        //
3024        // WEAK tombstones belong to the idempotent class even though a weak
3025        // delete annihilates one put at compaction: its documented contract
3026        // pairs it with a key written at most once, byte-identical copies
3027        // meeting in one compaction stream drain to a single survivor, and a
3028        // consumer cannot materialize multiplicity anyway — replaying the
3029        // same `remove_weak` twice at one seqno lands on ONE internal key in
3030        // its memtable, so a preserved duplicate would replay to the same
3031        // physical state the collapsed stream does.
3032        runs.sort_by(|a, b| ScanSinceEvent::grouping_order(&a.event, &b.event));
3033        let mut merged: Vec<(ScanSinceEvent, usize, usize)> = Vec::new();
3034        let mut iter = runs.into_iter().peekable();
3035        while let Some(run) = iter.next() {
3036            if matches!(run.event, ScanSinceEvent::MergeOperand { .. }) {
3037                // Equal operand runs from OTHER sources follow as their own
3038                // iterations and emit their own copies — no draining here.
3039                for &position in &run.positions {
3040                    merged.push((run.event.clone(), run.recency, position));
3041                }
3042                continue;
3043            }
3044            let Run {
3045                event,
3046                mut recency,
3047                mut positions,
3048            } = run;
3049            let mut count = positions.len();
3050            while let Some(other) = iter.next_if(|next| next.event == event) {
3051                debug_assert_eq!(other.event, event, "next_if matched the same event");
3052                count = count.max(other.positions.len());
3053                if other.recency < recency {
3054                    recency = other.recency;
3055                    positions = other.positions;
3056                }
3057            }
3058            // The winning source may hold fewer copies than the count another
3059            // source did; the extras repeat its last position (they are
3060            // byte-identical, so their relative order carries no information).
3061            while positions.len() < count {
3062                let last = positions.last().copied().unwrap_or_default();
3063                positions.push(last);
3064            }
3065            for &position in positions.iter().take(count) {
3066                merged.push((event.clone(), recency, position));
3067            }
3068        }
3069
3070        // Finally, replay order: seqno, then range deletions, then oldest source
3071        // first, then the position that source gave the event — which is how an
3072        // order-sensitive merge operator converges to what the tree serves.
3073        //
3074        // The range-deletion step comes BEFORE source recency, not after: a tied
3075        // deletion does not suppress the writes it spans (suppression is
3076        // strictly `entry.seqno < tombstone.seqno`), so the tree keeps them, and
3077        // a replay that applied the deletion last would drop them. Ordering by
3078        // recency first would do exactly that whenever the deletion sits in the
3079        // newer source.
3080        merged.sort_by(|(a, a_recency, a_pos), (b, b_recency, b_pos)| {
3081            fn deletion_first(e: &ScanSinceEvent) -> u8 {
3082                u8::from(!matches!(e, ScanSinceEvent::RangeTombstone { .. }))
3083            }
3084            a.seqno()
3085                .cmp(&b.seqno())
3086                .then_with(|| deletion_first(a).cmp(&deletion_first(b)))
3087                .then_with(|| b_recency.cmp(a_recency))
3088                .then_with(|| {
3089                    // Within one source and one seqno, a scan yields a key's
3090                    // versions NEWEST first, and the read path reverses that run
3091                    // to apply them chronologically. Mirror it: same key ⇒ the
3092                    // later scan position replays FIRST. Distinct keys at one
3093                    // seqno touch different state, so their relative order is
3094                    // free — keep it deterministic by scan position.
3095                    //
3096                    // Identity is the engine's one relation (byte equality, see
3097                    // `same_user_key`), which is what the read path this mirrors
3098                    // uses to group a key's versions. Asking the comparator here
3099                    // would answer the same question — its contract makes
3100                    // `Equal` imply byte equality — while letting the two paths
3101                    // disagree the moment a comparator broke that.
3102                    if crate::comparator::same_user_key(a.key(), b.key()) {
3103                        b_pos.cmp(a_pos)
3104                    } else {
3105                        a_pos.cmp(b_pos)
3106                    }
3107                })
3108        });
3109        merged.into_iter().map(|(event, ..)| event).collect()
3110    }
3111
3112    /// Iterate change events with `seqno >= target_seqno`.
3113    ///
3114    /// Returns every change committed at or after `target_seqno` as a stream
3115    /// of [`ScanSinceEvent`]s in increasing seqno order. This is the canonical
3116    /// change-data-capture primitive: a downstream consumer (replica, Kafka
3117    /// connector, Debezium-style pipeline) replays the events in order to
3118    /// reconstruct the source's history. Superseded versions are not collapsed
3119    /// (a key written three times after the target yields three events).
3120    ///
3121    /// # Concurrency
3122    ///
3123    /// The result is a snapshot of the tree as of the call: the active memtable
3124    /// is captured with writers excluded, so a batch committed concurrently is
3125    /// either wholly in the result or wholly absent, never split across it. This
3126    /// holds even for a caller-chosen sequence number at or below the reported
3127    /// watermark, which the seqno bound alone would not exclude. Writers are
3128    /// blocked only while that capture runs — the sealed memtables and SSTs the
3129    /// scan then reads are immutable.
3130    ///
3131    /// # History retention
3132    ///
3133    /// The stream carries what the tree still PHYSICALLY HOLDS. A compaction
3134    /// run with a GC watermark (the `gc_watermark` passed to
3135    /// [`compact`](crate::AbstractTree::compact) /
3136    /// [`major_compact`](crate::AbstractTree::major_compact)) drops shadowed
3137    /// versions and evicted tombstones below that watermark and may fold
3138    /// merge chains and zero bottommost seqnos — history a later scan cannot
3139    /// resurrect. The result is therefore complete only for a `target_seqno`
3140    /// at or above the highest GC watermark ever applied: a deployment
3141    /// replaying this stream (an external-WAL consumer, a CDC replica) must
3142    /// keep its compaction watermark at or below the lowest cursor it may
3143    /// still rewind to, exactly as `docs/external-wal.md` section 4's
3144    /// GC-coordination rules require. The watermark is caller-supplied and
3145    /// not persisted, so this method cannot detect a violation for you.
3146    ///
3147    /// # Block-skip
3148    ///
3149    /// On SSTs written with the `seqno_bounds` section (`seqno_in_index`), data
3150    /// blocks whose bounds cannot overlap the target window are skipped without
3151    /// being read; SSTs without the section are read and filtered per entry, so
3152    /// mixed trees are handled transparently.
3153    ///
3154    /// # KV-separation
3155    ///
3156    /// Standard trees never store blob-indirected values. On the inner tree of
3157    /// a KV-separated (blob) tree this returns an `Err` for indirected entries:
3158    /// blob resolution into [`ScanSinceEvent::Insert`] is provided by the
3159    /// blob-tree scan path, which owns the blob files.
3160    ///
3161    /// # Corruption resilience
3162    ///
3163    /// The per-block seqno-bounds used for skipping live in the optional
3164    /// `seqno_bounds` SST section, a Block covered by XXH3-128 (+ optional Page
3165    /// ECC) and verified when it is loaded at open, plus a decode that rejects
3166    /// non-ascending offsets and inverted bounds, so a corrupted bound is caught
3167    /// rather than trusted. Even in the impossible case of a fault bypassing
3168    /// those checks, a bad bound can only cause a *missed* record, never a wrong
3169    /// one. Callers who want defense against that hypothetical can use
3170    /// [`Self::scan_since_seqno_full_scan`], which reads every block (slower, no
3171    /// skip).
3172    ///
3173    /// # Panics
3174    ///
3175    /// Panics if the internal version-history lock is poisoned.
3176    ///
3177    /// # Errors
3178    ///
3179    /// Returns `Err` if reading the index or a data block fails, or if an entry
3180    /// is a KV-separated value (see above).
3181    pub fn scan_since_seqno(
3182        &self,
3183        target_seqno: SeqNo,
3184    ) -> crate::Result<impl Iterator<Item = ScanSinceEvent> + use<>> {
3185        // A standard tree never stores blob-indirected values; the resolver
3186        // errors so an indirected entry (only reachable via a blob tree's inner
3187        // index) surfaces as a clear error rather than a wrong event.
3188        self.scan_since_seqno_with(target_seqno, true, |_version, _entry| {
3189            Err(crate::Error::FeatureUnsupported(
3190                "scan_since_seqno on KV-separated values requires the blob-tree scan path",
3191            ))
3192        })
3193    }
3194
3195    /// Paranoid variant of [`Self::scan_since_seqno`] that disables the
3196    /// per-block seqno-bounds skip: every data block is read and filtered per
3197    /// entry, even on seqno-indexed SSTs.
3198    ///
3199    /// # When to use
3200    ///
3201    /// The fast [`Self::scan_since_seqno`] trusts each block's recorded
3202    /// `[seqno_min, seqno_max]` to skip blocks that cannot hold a qualifying
3203    /// record. Those bounds live in the `seqno_bounds` SST section, a Block
3204    /// covered by XXH3-128 (and optional Page ECC) and verified at open, so
3205    /// on-disk corruption is caught, not silently trusted. This method exists
3206    /// for callers who
3207    /// want defense even against a fault that somehow bypassed those checks: a
3208    /// corrupted `seqno_max` can only ever cause a *missed* record (never a
3209    /// wrong one), and a full scan cannot miss. It is slower (no skip), so
3210    /// prefer [`Self::scan_since_seqno`] unless you specifically need this
3211    /// guarantee.
3212    ///
3213    /// # Panics
3214    ///
3215    /// Panics if the internal version-history lock is poisoned.
3216    ///
3217    /// # Errors
3218    ///
3219    /// Same as [`Self::scan_since_seqno`].
3220    pub fn scan_since_seqno_full_scan(
3221        &self,
3222        target_seqno: SeqNo,
3223    ) -> crate::Result<impl Iterator<Item = ScanSinceEvent> + use<>> {
3224        self.scan_since_seqno_with(target_seqno, false, |_version, _entry| {
3225            Err(crate::Error::FeatureUnsupported(
3226                "scan_since_seqno on KV-separated values requires the blob-tree scan path",
3227            ))
3228        })
3229    }
3230
3231    /// Range-scoped variant of [`Self::scan_since_seqno`]: delivers only
3232    /// events whose key falls within `range` (in the tree comparator's
3233    /// order); range-deletion events are delivered when their span OVERLAPS
3234    /// it, since a tombstone reaching into the range affects replay within
3235    /// it. SSTs whose key range cannot intersect the bounds are skipped
3236    /// without a single block read.
3237    ///
3238    /// This is the presence-check primitive for reconciling an external
3239    /// write-ahead log after a repair: [`RepairReport::lost_coverage`] names
3240    /// the affected key ranges, and deciding which retained WAL records to
3241    /// re-apply (in particular, which merge operands SURVIVED and must not be
3242    /// folded twice) only needs the events inside those ranges. See
3243    /// `docs/external-wal.md` § Replay after repair.
3244    ///
3245    /// The history-retention caveat of [`Self::scan_since_seqno`] applies
3246    /// unchanged: the stream is complete only for a `target_seqno` at or
3247    /// above the highest compaction GC watermark ever applied.
3248    ///
3249    /// [`RepairReport::lost_coverage`]: crate::RepairReport::lost_coverage
3250    ///
3251    /// # Panics
3252    ///
3253    /// Panics if the internal version-history lock is poisoned.
3254    ///
3255    /// # Errors
3256    ///
3257    /// Same as [`Self::scan_since_seqno`].
3258    pub fn scan_since_seqno_in_range<K: AsRef<[u8]>, R: RangeBounds<K>>(
3259        &self,
3260        target_seqno: SeqNo,
3261        range: R,
3262    ) -> crate::Result<impl Iterator<Item = ScanSinceEvent> + use<K, R>> {
3263        let bounds = range_to_user_bounds(&range);
3264        self.scan_since_seqno_scoped(
3265            target_seqno,
3266            true,
3267            |_version, _entry| {
3268                Err(crate::Error::FeatureUnsupported(
3269                    "scan_since_seqno on KV-separated values requires the blob-tree scan path",
3270                ))
3271            },
3272            Some(&bounds),
3273        )
3274    }
3275
3276    /// Update the live [`crate::runtime_config::RuntimeConfig`].
3277    ///
3278    /// Mutator runs on a clone of the current snapshot; the new snapshot
3279    /// is then atomically swapped in. Subsequent calls to
3280    /// [`Self::runtime_config`] observe the new snapshot.
3281    ///
3282    /// ## Semantics
3283    ///
3284    /// - Write paths (flush, compaction, ingestion) take one snapshot when
3285    ///   they start and use it throughout, so a change applies to the next
3286    ///   one and never splits a single file between two configurations.
3287    /// - Existing on-disk data remains in its original format and reads
3288    ///   transparently: every block, manifest and blob file is
3289    ///   self-describing via its own header or descriptor. That includes
3290    ///   the compression policies, so changing one rewrites nothing.
3291    /// - Compaction acts as the live-migration mechanism: source blocks
3292    ///   are rewritten per the current snapshot over subsequent cycles,
3293    ///   so all data converges to the current settings without
3294    ///   stop-the-world coordination.
3295    ///
3296    /// ## Concurrency
3297    ///
3298    /// **Reader atomicity:** concurrent readers observe either the old
3299    /// or the new snapshot, never a torn intermediate state.
3300    ///
3301    /// **Writer semantics: last-writer-wins.** Two `update` calls racing
3302    /// from the same starting snapshot will have the second `store`
3303    /// overwrite the first — the first writer's mutation is lost. There
3304    /// is no CAS / RCU merge. Callers that need lost-update avoidance
3305    /// (e.g. two threads concurrently toggling different fields) MUST
3306    /// serialize their `update_runtime_config` calls, typically via a
3307    /// `Mutex` around the call site.
3308    /// # Errors
3309    ///
3310    /// Returns [`crate::Error::PageEccUnsupported`] when the mutator
3311    /// leaves `page_ecc = true` on a binary built without the
3312    /// `page_ecc` cargo feature, and
3313    /// [`crate::Error::ZstdDictMismatch`] when a compression policy names a
3314    /// dictionary the tree does not hold (register it first with
3315    /// `Self::register_zstd_dictionary`). The live snapshot stays at its
3316    /// pre-mutation value on error.
3317    pub fn update_runtime_config<F>(&self, mutator: F) -> crate::Result<()>
3318    where
3319        F: FnOnce(&mut crate::runtime_config::RuntimeConfig),
3320    {
3321        // The mutator is the caller's code and may read the tree, so it runs
3322        // before any lock is taken: under the version lock a read of the
3323        // version history would wait on this thread forever.
3324        let mut next = (*self.0.runtime_config.load_full()).clone();
3325        mutator(&mut next);
3326
3327        // Under the version lock, which a dictionary collection holds for its
3328        // whole pass: checking a policy against the tree's dictionaries and
3329        // publishing it must not interleave with a collection that takes the
3330        // one it names.
3331        #[cfg(zstd_any)]
3332        let version_lock = self.version_history.write();
3333        #[cfg(zstd_any)]
3334        next.check_dictionaries(
3335            self.config.kv_separation_opts.is_some(),
3336            &self.config.current_zstd_dictionaries(),
3337        )?;
3338        // What the swap below replaces, read under the lock every update takes,
3339        // so it is the snapshot a writer may still hold, not the one the
3340        // mutator started from.
3341        #[cfg(zstd_any)]
3342        let replaced = self.0.runtime_config.load_full();
3343        // The heal gate below follows exactly the config THIS call commits,
3344        // rather than a separate `load_full()` that could observe a different
3345        // concurrent update's value. Concurrent `update_runtime_config` calls
3346        // must be serialized by the caller (see the last-writer-wins note
3347        // above); under that contract the gate and the committed config stay in
3348        // sync. On a validation error nothing is committed and `?` returns
3349        // before the gate is touched, so it keeps tracking the unchanged config.
3350        let auto_heal = next.auto_heal;
3351        // Route through the validating handle path so an invalid mutation
3352        // (e.g. `page_ecc = true` on a non-`page_ecc` build) is rejected at
3353        // update time, not silently swallowed at the next manifest write.
3354        self.0.runtime_config.try_update(|c| *c = next)?;
3355
3356        // A writer that took `replaced` before this swap may still be about to
3357        // resolve the dictionary it names; see `retired_write_snapshots`.
3358        #[cfg(zstd_any)]
3359        {
3360            let kv_separated = self.config.kv_separation_opts.is_some();
3361            if replaced.write_dict_ids(kv_separated).next().is_some() {
3362                let mut retired = self.0.retired_write_snapshots.lock();
3363                retired.retain(|snapshot| snapshot.strong_count() > 0);
3364                retired.push(Arc::downgrade(&replaced));
3365            }
3366            drop(replaced);
3367            drop(version_lock);
3368        }
3369        self.0.heal_hints.set_enabled(auto_heal);
3370        // Mirror the insert-time digest gate for the write hot path (see
3371        // `TreeInner::kv_digest_at_insert`). Relaxed: a toggle taking effect
3372        // on the next inserts is the documented contract (mixed inserts are
3373        // supported), so no ordering against other memory is needed.
3374        let gate = inner::kv_digest_at_insert_gate(&self.0.runtime_config.load());
3375        self.0
3376            .kv_digest_at_insert
3377            .store(gate, core::sync::atomic::Ordering::Relaxed);
3378        // Drop the cached admission footprint so the next check re-probes
3379        // disk-free: an operator who just raised the budget (or freed disk)
3380        // should see it promptly, not at the next flush.
3381        *self.0.admission_used_cache.lock() = None;
3382        Ok(())
3383    }
3384
3385    /// Snapshot of the current runtime config. Cheap atomic load —
3386    /// safe to call on hot paths.
3387    #[must_use]
3388    pub fn runtime_config(&self) -> Arc<crate::runtime_config::RuntimeConfig> {
3389        self.0.runtime_config.load_full()
3390    }
3391
3392    /// Shared handle to this tree's ECC heal-hint queue.
3393    ///
3394    /// A read that recovers a block from Page-ECC parity records the owning SST
3395    /// here (when the on-disk fault is confirmed persistent). Pass the handle to
3396    /// [`compaction::EccHeal`](crate::compaction::EccHeal) and run that strategy
3397    /// via [`Tree::compact`](crate::AbstractTree::compact) — leader-only in a
3398    /// clustered deployment — to rewrite the flagged SSTs clean. Check
3399    /// [`HealHints::is_empty`](crate::heal_hints::HealHints::is_empty) to skip
3400    /// the pass when nothing is queued.
3401    ///
3402    /// # Examples
3403    ///
3404    /// ```no_run
3405    /// use lsm_tree::{AbstractTree, AnyTree, Config, SequenceNumberCounter, compaction::EccHeal};
3406    /// use std::sync::Arc;
3407    /// # fn main() -> lsm_tree::Result<()> {
3408    /// let AnyTree::Standard(tree) = Config::new(
3409    ///     "/tmp/db",
3410    ///     SequenceNumberCounter::default(),
3411    ///     SequenceNumberCounter::default(),
3412    /// )
3413    /// .open()?
3414    /// else {
3415    ///     return Ok(());
3416    /// };
3417    ///
3418    /// // Opt into rewrite scheduling; reads that recover a block from parity now
3419    /// // flag its SST for healing.
3420    /// tree.update_runtime_config(|c| c.auto_heal = true)?;
3421    ///
3422    /// // Drain the queue, rewriting each flagged SST clean (leader-only in a
3423    /// // clustered deployment).
3424    /// let hints = tree.heal_hints();
3425    /// while !hints.is_empty() {
3426    ///     tree.compact(Arc::new(EccHeal::new(tree.heal_hints(), u64::MAX)), 0)?;
3427    /// }
3428    /// # Ok(())
3429    /// # }
3430    /// ```
3431    #[must_use]
3432    pub fn heal_hints(&self) -> Arc<crate::heal_hints::HealHints> {
3433        Arc::clone(&self.0.heal_hints)
3434    }
3435
3436    /// Starts a multi-get of `keys` at `seqno` that its caller drives: the
3437    /// read hands out its block reads and jobs instead of carrying them out,
3438    /// so the calling thread is never parked inside it. It answers what
3439    /// [`AbstractTree::multi_get`] answers at the moment it starts: what is
3440    /// written while it is suspended is not part of its answer, even at a
3441    /// ceiling such as [`SeqNo::MAX`](crate::SeqNo).
3442    ///
3443    /// # Errors
3444    ///
3445    /// Returns [`crate::Error::SnapshotBelowRetention`] when `seqno` is below
3446    /// what the tree retains.
3447    ///
3448    /// # Examples
3449    ///
3450    /// Driving the read on the calling thread:
3451    ///
3452    /// ```
3453    /// use lsm_tree::resumable::Step;
3454    /// use lsm_tree::{AbstractTree, Config, SequenceNumberCounter};
3455    ///
3456    /// # let folder = tempfile::tempdir()?;
3457    /// let tree = Config::new(&folder, SequenceNumberCounter::default(), SequenceNumberCounter::default()).open()?;
3458    /// tree.insert("a", "1", 0);
3459    /// tree.flush_active_memtable(0)?;
3460    ///
3461    /// let mut step = tree.start_multi_get(["a", "b"], 1)?;
3462    /// let values = loop {
3463    ///     let mut read = match step {
3464    ///         Step::Done(values) => break values?,
3465    ///         Step::Pending(read) => read,
3466    ///     };
3467    ///     for job in read.take_jobs() {
3468    ///         let outcome = job.run();
3469    ///         read.complete_job(outcome);
3470    ///     }
3471    ///     for mut block in read.take_reads() {
3472    ///         let result = block.file.read_at(&mut block.buf, block.offset).and_then(|n| {
3473    ///             if n == block.buf.len() { Ok(()) } else { Err(lsm_tree::io::ErrorKind::UnexpectedEof.into()) }
3474    ///         });
3475    ///         read.complete_read(block.tag, result, block.buf);
3476    ///     }
3477    ///     step = read.resume();
3478    /// };
3479    /// assert_eq!(values, [Some("1".as_bytes().into()), None]);
3480    /// # Ok::<(), lsm_tree::Error>(())
3481    /// ```
3482    pub fn start_multi_get<K: Into<UserKey>>(
3483        &self,
3484        keys: impl IntoIterator<Item = K>,
3485        seqno: SeqNo,
3486    ) -> crate::Result<resumable::Step> {
3487        let snapshot = self.snapshot_for_read(seqno)?;
3488        let seqno = Self::read_ceiling(&snapshot, seqno);
3489        let keys: Vec<UserKey> = keys.into_iter().map(Into::into).collect();
3490        // Each key a point lookup, counted once as the read starts, not on
3491        // every resume.
3492        snapshot
3493            .version
3494            .read_mix()
3495            .note_points(u64::try_from(keys.len()).unwrap_or(u64::MAX));
3496        Ok(resumable::ResumableMultiGet::start(read_job::ReadCtx {
3497            super_version: crate::version::SnapshotRef::Owned((*snapshot).clone()),
3498            keys,
3499            seqno,
3500            comparator: Arc::clone(&self.config.comparator),
3501            merge_operator: self.config.merge_operator.clone(),
3502            merge_base: self.blob_source(),
3503            values: read_job::Values::Inline,
3504            metadata_budget: self.config.multi_get_metadata_budget,
3505        }))
3506    }
3507
3508    /// The seqno a read started at `seqno` on `snapshot` reads at for its whole
3509    /// life: at most one past the newest seqno the snapshot holds. The active
3510    /// memtable the snapshot shares keeps taking writes, and a read that is
3511    /// suspended still checks range deletions and merges against it after
3512    /// it started; what lands there at a higher seqno is then not part of its
3513    /// answer, which stays what the tree held when it started.
3514    pub(crate) fn read_ceiling(snapshot: &SuperVersion, seqno: SeqNo) -> SeqNo {
3515        // Past the newest seqno; nothing is newer than the largest one.
3516        let next = |newest: SeqNo| newest.checked_add(1).unwrap_or(SeqNo::MAX);
3517        let memtables = snapshot.active_memtable.get_highest_seqno().max(
3518            snapshot
3519                .sealed_memtables
3520                .iter()
3521                .map(|mt| mt.get_highest_seqno())
3522                .max()
3523                .flatten(),
3524        );
3525        // A ceiling the memtables already reach admits nothing newer: the
3526        // tables need not be looked at.
3527        if memtables.is_some_and(|newest| next(newest) >= seqno) {
3528            return seqno;
3529        }
3530        let tables = snapshot
3531            .version
3532            .iter_tables()
3533            .map(Table::get_highest_seqno)
3534            .max();
3535        // An empty snapshot holds nothing a read could see.
3536        memtables
3537            .max(tables)
3538            .map_or(0, |newest| seqno.min(next(newest)))
3539    }
3540
3541    /// Shared point-read logic for `get()` and `multi_get()`: finds the newest
3542    /// entry, applies merge resolution or RT suppression, and returns the value.
3543    ///
3544    /// `blob_source` is asked for only by a merge, for a base kept in the
3545    /// value log.
3546    fn resolve_or_passthrough(
3547        super_version: &SuperVersion,
3548        key: &[u8],
3549        seqno: SeqNo,
3550        merge_operator: Option<&Arc<dyn crate::merge_operator::MergeOperator>>,
3551        comparator: &dyn crate::comparator::UserComparator,
3552        blob_source: impl FnOnce() -> Option<crate::blob_tree::BlobSource>,
3553    ) -> crate::Result<Option<UserValue>> {
3554        let entry = Self::get_value(super_version, key, seqno, comparator)?;
3555
3556        match entry {
3557            Some((ValueType::MergeOperand, entry_seqno, value)) => {
3558                if let Some(merge_op) = merge_operator {
3559                    Self::resolve_point_merge(
3560                        super_version,
3561                        key,
3562                        seqno,
3563                        merge_op,
3564                        blob_source().as_ref(),
3565                    )
3566                } else if Self::is_suppressed_by_range_tombstones(
3567                    super_version,
3568                    key,
3569                    entry_seqno,
3570                    seqno,
3571                    comparator,
3572                ) {
3573                    Ok(None)
3574                } else {
3575                    Ok(Some(value))
3576                }
3577            }
3578            Some((_, _, value)) => Ok(Some(value)),
3579            None => Ok(None),
3580        }
3581    }
3582
3583    /// Shared post-lookup resolution for `get_pinned` and `multi_get`:
3584    /// tombstone filter, range-tombstone suppression, merge operand resolution.
3585    /// Returns `None` if entry is tombstoned or suppressed.
3586    #[expect(
3587        clippy::too_many_arguments,
3588        reason = "each is a separate input of the one resolution"
3589    )]
3590    fn resolve_pinned_entry(
3591        super_version: &SuperVersion,
3592        key: &[u8],
3593        entry: InternalValue,
3594        seqno: SeqNo,
3595        merge_operator: Option<&Arc<dyn crate::merge_operator::MergeOperator>>,
3596        comparator: &dyn crate::comparator::UserComparator,
3597        wrap: impl FnOnce(UserValue) -> crate::PinnableSlice,
3598        blob_source: impl FnOnce() -> Option<crate::blob_tree::BlobSource>,
3599    ) -> crate::Result<Option<crate::PinnableSlice>> {
3600        use crate::PinnableSlice;
3601
3602        let Some(entry) = ignore_tombstone_value(entry) else {
3603            return Ok(None);
3604        };
3605        if Self::is_suppressed_by_range_tombstones(
3606            super_version,
3607            key,
3608            entry.key.seqno,
3609            seqno,
3610            comparator,
3611        ) {
3612            return Ok(None);
3613        }
3614        if entry.key.value_type == ValueType::MergeOperand
3615            && let Some(merge_op) = merge_operator
3616        {
3617            // A merged value is built anew, so it is Owned.
3618            return Self::resolve_point_merge(
3619                super_version,
3620                key,
3621                seqno,
3622                merge_op,
3623                blob_source().as_ref(),
3624            )
3625            .map(|opt| opt.map(PinnableSlice::owned));
3626        }
3627        Ok(Some(wrap(entry.value)))
3628    }
3629
3630    /// Like [`Tree::resolve_or_passthrough`], but returns a [`PinnableSlice`](crate::PinnableSlice)
3631    /// that may keep the decompressed block buffer alive.
3632    fn resolve_or_passthrough_pinned(
3633        super_version: &SuperVersion,
3634        key: &[u8],
3635        seqno: SeqNo,
3636        merge_operator: Option<&Arc<dyn crate::merge_operator::MergeOperator>>,
3637        comparator: &dyn crate::comparator::UserComparator,
3638        blob_source: impl FnOnce() -> Option<crate::blob_tree::BlobSource>,
3639    ) -> crate::Result<Option<crate::PinnableSlice>> {
3640        use crate::PinnableSlice;
3641
3642        // Check memtables first — always Owned
3643        if let Some(entry) = super_version.active_memtable.get(key, seqno) {
3644            return Self::resolve_pinned_entry(
3645                super_version,
3646                key,
3647                entry,
3648                seqno,
3649                merge_operator,
3650                comparator,
3651                PinnableSlice::owned,
3652                blob_source,
3653            );
3654        }
3655
3656        // Sealed memtables — always Owned
3657        if let Some(entry) =
3658            Self::get_internal_entry_from_sealed_memtables(super_version, key, seqno)
3659        {
3660            return Self::resolve_pinned_entry(
3661                super_version,
3662                key,
3663                entry,
3664                seqno,
3665                merge_operator,
3666                comparator,
3667                PinnableSlice::owned,
3668                blob_source,
3669            );
3670        }
3671
3672        // Tables — Pinned (value shares decompressed block buffer)
3673        let key_hash = crate::hash::hash64(key);
3674
3675        if let Some((entry, block)) = Self::get_internal_entry_with_block_from_tables(
3676            &super_version.version,
3677            key,
3678            seqno,
3679            key_hash,
3680            comparator,
3681        )? {
3682            return Self::resolve_pinned_entry(
3683                super_version,
3684                key,
3685                entry,
3686                seqno,
3687                merge_operator,
3688                comparator,
3689                |value| PinnableSlice::pinned(block, value),
3690                blob_source,
3691            );
3692        }
3693
3694        Ok(None)
3695    }
3696
3697    /// Like [`Tree::get_internal_entry_from_tables`], but returns the block
3698    /// along with the entry for pinned zero-copy access.
3699    fn get_internal_entry_with_block_from_tables(
3700        version: &Version,
3701        key: &[u8],
3702        seqno: SeqNo,
3703        key_hash: u64,
3704        comparator: &dyn crate::comparator::UserComparator,
3705    ) -> crate::Result<Option<(InternalValue, crate::table::Block)>> {
3706        Self::find_in_tables::<TableEntryWithBlock>(version, key, seqno, key_hash, comparator)
3707    }
3708
3709    /// Where this tree's values kept in the value log are read from; only the
3710    /// index of a blob tree has any.
3711    pub(crate) fn blob_source(&self) -> Option<crate::blob_tree::BlobSource> {
3712        self.config
3713            .kv_separation_opts
3714            .is_some()
3715            .then(|| crate::blob_tree::BlobSource {
3716                tree_id: self.id,
3717                cache: Arc::clone(&self.config.cache),
3718                fill: self.runtime_config.load().blob_cache_fill,
3719                #[cfg(feature = "metrics")]
3720                metrics: Arc::clone(&self.0.metrics),
3721            })
3722    }
3723
3724    /// Resolves the merge of `key` for a point read whose newest visible
3725    /// version is an operand: reads the sources newest first and stops at the
3726    /// first base, so a table below it is never opened.
3727    ///
3728    /// `blob_source` reads a base kept in the value log, which only a blob
3729    /// tree's index holds.
3730    pub(crate) fn resolve_point_merge(
3731        super_version: &SuperVersion,
3732        key: &[u8],
3733        seqno: SeqNo,
3734        merge_operator: &Arc<dyn crate::merge_operator::MergeOperator>,
3735        blob_source: Option<&crate::blob_tree::BlobSource>,
3736    ) -> crate::Result<Option<UserValue>> {
3737        Ok(crate::range::resolve_point_merge(
3738            super_version,
3739            key,
3740            seqno,
3741            merge_operator,
3742            blob_source,
3743        )?
3744        .map(|entry| entry.value))
3745    }
3746
3747    #[doc(hidden)]
3748    pub fn create_internal_range<'a, K: AsRef<[u8]> + 'a, R: RangeBounds<K> + 'a>(
3749        version: SuperVersion,
3750        range: &'a R,
3751        seqno: SeqNo,
3752        ephemeral: Option<(Arc<Memtable>, SeqNo)>,
3753        merge_operator: Option<Arc<dyn crate::merge_operator::MergeOperator>>,
3754        comparator: crate::comparator::SharedComparator,
3755    ) -> impl DoubleEndedIterator<Item = crate::Result<InternalValue>> + 'static {
3756        Self::create_internal_range_with_prefix_hash(
3757            version,
3758            range,
3759            seqno,
3760            ephemeral,
3761            merge_operator,
3762            comparator,
3763            None,
3764            None,
3765        )
3766    }
3767
3768    /// Like [`Tree::create_internal_range`], but with an optional prefix hash
3769    /// for prefix bloom filter skipping during prefix scans, and the
3770    /// `blob_source` a merge reads a base kept in the value log from.
3771    #[doc(hidden)]
3772    #[expect(
3773        clippy::too_many_arguments,
3774        reason = "each is a separate input of the iterator it builds"
3775    )]
3776    pub(crate) fn create_internal_range_with_prefix_hash<
3777        'a,
3778        K: AsRef<[u8]> + 'a,
3779        R: RangeBounds<K> + 'a,
3780    >(
3781        version: SuperVersion,
3782        range: &'a R,
3783        seqno: SeqNo,
3784        ephemeral: Option<(Arc<Memtable>, SeqNo)>,
3785        merge_operator: Option<Arc<dyn crate::merge_operator::MergeOperator>>,
3786        comparator: crate::comparator::SharedComparator,
3787        prefix_hash: Option<u64>,
3788        blob_source: Option<crate::blob_tree::BlobSource>,
3789    ) -> impl DoubleEndedIterator<Item = crate::Result<InternalValue>> + 'static {
3790        use crate::range::{IterState, TreeIter};
3791        use core::ops::Bound::{self, Excluded, Included, Unbounded};
3792
3793        let lo: Bound<UserKey> = match range.start_bound() {
3794            Included(x) => Included(x.as_ref().into()),
3795            Excluded(x) => Excluded(x.as_ref().into()),
3796            Unbounded => Unbounded,
3797        };
3798
3799        let hi: Bound<UserKey> = match range.end_bound() {
3800            Included(x) => Included(x.as_ref().into()),
3801            Excluded(x) => Excluded(x.as_ref().into()),
3802            Unbounded => Unbounded,
3803        };
3804
3805        let bounds: (Bound<UserKey>, Bound<UserKey>) = (lo, hi);
3806
3807        let iter_state = IterState {
3808            version,
3809            ephemeral,
3810            merge_operator,
3811            blob_source,
3812            comparator,
3813            prefix_hash,
3814            #[cfg(feature = "metrics")]
3815            metrics: None,
3816        };
3817
3818        TreeIter::create_range(iter_state, bounds, seqno)
3819    }
3820
3821    pub(crate) fn get_internal_entry_from_version(
3822        super_version: &SuperVersion,
3823        key: &[u8],
3824        seqno: SeqNo,
3825        comparator: &dyn crate::comparator::UserComparator,
3826    ) -> crate::Result<Option<InternalValue>> {
3827        // Search order: active → sealed → SST (newest first). A point
3828        // tombstone in a newer source is authoritative — no older source
3829        // can contain a newer value, so returning None is correct.
3830        if let Some(entry) = super_version.active_memtable.get(key, seqno) {
3831            let Some(entry) = ignore_tombstone_value(entry) else {
3832                return Ok(None);
3833            };
3834
3835            // Check if any range tombstone suppresses this entry
3836            if Self::is_suppressed_by_range_tombstones(
3837                super_version,
3838                key,
3839                entry.key.seqno,
3840                seqno,
3841                comparator,
3842            ) {
3843                return Ok(None);
3844            }
3845            return Ok(Some(entry));
3846        }
3847
3848        // Now look in sealed memtables
3849        if let Some(entry) =
3850            Self::get_internal_entry_from_sealed_memtables(super_version, key, seqno)
3851        {
3852            let Some(entry) = ignore_tombstone_value(entry) else {
3853                return Ok(None);
3854            };
3855
3856            if Self::is_suppressed_by_range_tombstones(
3857                super_version,
3858                key,
3859                entry.key.seqno,
3860                seqno,
3861                comparator,
3862            ) {
3863                return Ok(None);
3864            }
3865            return Ok(Some(entry));
3866        }
3867
3868        // Now look in tables... this may involve disk I/O
3869        let entry =
3870            Self::get_internal_entry_from_tables(&super_version.version, key, seqno, comparator)?;
3871
3872        if let Some(entry) = entry {
3873            if Self::is_suppressed_by_range_tombstones(
3874                super_version,
3875                key,
3876                entry.key.seqno,
3877                seqno,
3878                comparator,
3879            ) {
3880                return Ok(None);
3881            }
3882            return Ok(Some(entry));
3883        }
3884
3885        Ok(None)
3886    }
3887
3888    /// Value-only mirror of [`Self::get_internal_entry_from_version`].
3889    ///
3890    /// Returns `(value_type, seqno, value)` for the newest visible entry without
3891    /// reconstructing the entry key. Same search order (active -> sealed -> SST,
3892    /// newest first), tombstone filtering, and range-tombstone suppression; only
3893    /// the SST path differs, using the value-only [`TableValue`] lookup that
3894    /// skips the delta-key fusion of the full `InternalValue` path. Used by the
3895    /// value-returning `get` path, which never reads the matched key.
3896    pub(crate) fn get_value(
3897        super_version: &SuperVersion,
3898        key: &[u8],
3899        seqno: SeqNo,
3900        comparator: &dyn crate::comparator::UserComparator,
3901    ) -> crate::Result<Option<(ValueType, SeqNo, crate::Slice)>> {
3902        if let Some(entry) = super_version.active_memtable.get(key, seqno) {
3903            let Some(entry) = ignore_tombstone_value(entry) else {
3904                return Ok(None);
3905            };
3906            if Self::is_suppressed_by_range_tombstones(
3907                super_version,
3908                key,
3909                entry.key.seqno,
3910                seqno,
3911                comparator,
3912            ) {
3913                return Ok(None);
3914            }
3915            return Ok(Some((entry.key.value_type, entry.key.seqno, entry.value)));
3916        }
3917
3918        if let Some(entry) =
3919            Self::get_internal_entry_from_sealed_memtables(super_version, key, seqno)
3920        {
3921            let Some(entry) = ignore_tombstone_value(entry) else {
3922                return Ok(None);
3923            };
3924            if Self::is_suppressed_by_range_tombstones(
3925                super_version,
3926                key,
3927                entry.key.seqno,
3928                seqno,
3929                comparator,
3930            ) {
3931                return Ok(None);
3932            }
3933            return Ok(Some((entry.key.value_type, entry.key.seqno, entry.value)));
3934        }
3935
3936        let key_hash = crate::hash::hash64(key);
3937        let entry = Self::find_in_tables::<TableValue>(
3938            &super_version.version,
3939            key,
3940            seqno,
3941            key_hash,
3942            comparator,
3943        )?;
3944        if let Some((value_type, entry_seqno, value)) = entry {
3945            if Self::is_suppressed_by_range_tombstones(
3946                super_version,
3947                key,
3948                entry_seqno,
3949                seqno,
3950                comparator,
3951            ) {
3952                return Ok(None);
3953            }
3954            return Ok(Some((value_type, entry_seqno, value)));
3955        }
3956
3957        Ok(None)
3958    }
3959
3960    /// Checks if a key at `key_seqno` is suppressed by any range tombstone
3961    /// in the active memtable, sealed memtables, or SST tables, visible at `read_seqno`.
3962    pub(crate) fn is_suppressed_by_range_tombstones(
3963        super_version: &SuperVersion,
3964        key: &[u8],
3965        key_seqno: SeqNo,
3966        read_seqno: SeqNo,
3967        comparator: &dyn crate::comparator::UserComparator,
3968    ) -> bool {
3969        // Check active memtable range tombstones.
3970        // Future optimization: skip lock when memtable has no RTs (atomic count).
3971        if super_version
3972            .active_memtable
3973            .is_key_suppressed_by_range_tombstone(key, key_seqno, read_seqno)
3974        {
3975            return true;
3976        }
3977
3978        // Check sealed memtable range tombstones
3979        for mt in super_version.sealed_memtables.iter().rev() {
3980            if mt.is_key_suppressed_by_range_tombstone(key, key_seqno, read_seqno) {
3981                return true;
3982            }
3983        }
3984
3985        // Check SST table range tombstones.
3986        //
3987        // Per-table RT lists are sorted by start key (using comparator) on load,
3988        // so binary search narrows candidates to RTs with start <= key. A
3989        // table's key range does not bound its tombstones (a compaction cuts
3990        // them at the next output's first key without widening the range up
3991        // to it), so the stretch they cover rejects a table instead: from the
3992        // first one's start to the greatest end, two compares.
3993        for table in super_version
3994            .version
3995            .iter_levels()
3996            .flat_map(|lvl| lvl.iter())
3997            .filter(|run| run.holds_range_tombstones())
3998            .flat_map(|run| run.iter())
3999        {
4000            let rts = table.range_tombstones();
4001            let (Some(first), Some(end)) = (rts.first(), table.range_tombstones_end()) else {
4002                continue;
4003            };
4004            if comparator.compare(&first.start, key) == core::cmp::Ordering::Greater
4005                || comparator.compare(key, end) != core::cmp::Ordering::Less
4006            {
4007                continue;
4008            }
4009
4010            // Binary search: find the first RT whose start is > key (in comparator order).
4011            // All RTs before that index have start <= key and are candidates.
4012            let candidate_end = rts.partition_point(|rt| {
4013                comparator.compare(&rt.start, key) != core::cmp::Ordering::Greater
4014            });
4015
4016            for rt in rts.iter().take(candidate_end) {
4017                // start <= key holds by the search; the seqno checks are
4018                // cheaper than the key compare, so they go first.
4019                if key_seqno < rt.seqno
4020                    && rt.visible_at(read_seqno)
4021                    && comparator.compare(key, &rt.end) == core::cmp::Ordering::Less
4022                {
4023                    return true;
4024                }
4025            }
4026        }
4027
4028        false
4029    }
4030
4031    /// De-duplicates equal query keys in a comparator-sorted `remaining` index
4032    /// list, returning the `(key_index, bloom_hash)` pairs for the batched
4033    /// on-disk resolver (which requires strictly-sorted-unique input) and a
4034    /// `(duplicate_index, representative_index)` map. Pair with
4035    /// [`Self::fan_out_duplicates`] after the batch resolves.
4036    ///
4037    /// Shared by [`Self::multi_get`] and the `BlobTree` multi-get so the two
4038    /// cannot silently diverge: forwarding duplicate miss keys into the
4039    /// strictly-sorted-unique resolver was exactly the regression class this
4040    /// guards against. `remaining` must already be sorted by `comparator`.
4041    #[expect(
4042        clippy::indexing_slicing,
4043        reason = "remaining/miss_keys carry batch-local key indices < keys.len()"
4044    )]
4045    pub(crate) fn dedup_sorted_miss_keys<K: AsRef<[u8]>>(
4046        remaining: &[usize],
4047        keys: &[K],
4048        comparator: &dyn crate::comparator::UserComparator,
4049    ) -> DedupedMissKeys {
4050        let mut miss_keys: Vec<(usize, u64)> = Vec::with_capacity(remaining.len());
4051        let mut duplicates: Vec<(usize, usize)> = Vec::new();
4052        for &idx in remaining {
4053            let key = keys[idx].as_ref();
4054            match miss_keys.last() {
4055                Some(&(rep_idx, _))
4056                    if comparator.compare(keys[rep_idx].as_ref(), key)
4057                        == core::cmp::Ordering::Equal =>
4058                {
4059                    duplicates.push((idx, rep_idx));
4060                }
4061                _ => miss_keys.push((idx, crate::hash::hash64(key))),
4062            }
4063        }
4064        (miss_keys, duplicates)
4065    }
4066
4067    /// Fans each representative's answer out to its duplicate positions, so
4068    /// every input slot carries the same answer the per-key path would have
4069    /// produced. Counterpart to [`Self::dedup_sorted_miss_keys`]; call once the
4070    /// representatives' answers are in `answers`.
4071    #[expect(
4072        clippy::indexing_slicing,
4073        reason = "duplicate/representative indices are batch-local key indices < answers.len()"
4074    )]
4075    pub(crate) fn fan_out_duplicates<T: Clone>(
4076        duplicates: &[(usize, usize)],
4077        answers: &mut [Option<T>],
4078    ) {
4079        for &(dup_idx, rep_idx) in duplicates {
4080            let resolved = answers[rep_idx].clone();
4081            answers[dup_idx] = resolved;
4082        }
4083    }
4084
4085    /// Resolves the keys of `remaining` (sorted under `comparator`) against
4086    /// `level` key by key, run by run, and returns the versions found by key
4087    /// index and the keys left for the levels below, still sorted.
4088    ///
4089    /// On level 0, and on a deeper level of several runs, every run is
4090    /// checked and the highest seqno per key kept; a key found at the read's
4091    /// ceiling skips the later runs, which cannot beat it. On a level of one
4092    /// run, the table covering a key answers it for the whole level.
4093    ///
4094    /// # Errors
4095    ///
4096    /// The first read of a table that fails.
4097    #[expect(
4098        clippy::indexing_slicing,
4099        reason = "remaining's indices are valid key indices, and `found` and `at_ceiling` are as long as `keys`"
4100    )]
4101    fn resolve_level_serially<K: AsRef<[u8]>>(
4102        level_zero: bool,
4103        level: &crate::version::Level,
4104        mut remaining: Vec<(usize, u64)>,
4105        keys: &[K],
4106        seqno: SeqNo,
4107        comparator: &dyn crate::comparator::UserComparator,
4108    ) -> crate::Result<tables_read::SerialLevel> {
4109        // A deeper level a tiered layout leaves overlapping runs in is read as
4110        // L0 is: a key's covering table in one run may miss it while an older
4111        // run holds it.
4112        if level_zero || level.run_count() > 1 {
4113            // L0: must check ALL runs, keep highest seqno per key. Track keys
4114            // at the seqno ceiling (seqno + 1 == read_seqno): no other L0 run
4115            // can beat them, so skip them in subsequent runs. Both are dense
4116            // over 0..keys.len().
4117            let mut found: Vec<Option<InternalValue>> = vec![None; keys.len()];
4118            let mut at_ceiling = vec![false; keys.len()];
4119
4120            for run in level.iter() {
4121                // `at_ceiling` is read as this run's skip set (a key is visited
4122                // once per run, so the updates below only affect later runs)
4123                // and mutated from the returned outcomes: never both at once.
4124                let resolved =
4125                    Self::resolve_run_batched(run, &remaining, keys, seqno, comparator, |idx| {
4126                        at_ceiling[idx]
4127                    })?;
4128                for (idx, _hash, item) in resolved.covered {
4129                    let Some(item) = item else { continue };
4130                    match &found[idx] {
4131                        Some(current) if current.key.seqno >= item.key.seqno => {}
4132                        _ => {
4133                            if item.key.seqno.checked_add(1) == Some(seqno) {
4134                                at_ceiling[idx] = true;
4135                            }
4136                            found[idx] = Some(item);
4137                        }
4138                    }
4139                }
4140                // Uncovered keys stay in `remaining`; the retain below prunes
4141                // the ones any run resolved.
4142            }
4143
4144            // Remove found keys (both values and tombstones)
4145            remaining.retain(|&(idx, _)| found[idx].is_none());
4146            let found = found
4147                .into_iter()
4148                .enumerate()
4149                .filter_map(|(idx, item)| Some((idx, item?)))
4150                .collect();
4151            return Ok((found, remaining));
4152        }
4153
4154        // L1+ runs have non-overlapping key ranges within a level. A covering
4155        // run resolves a key definitively: a hit is found, a covering miss
4156        // drops it to lower levels (`covered_miss`), and an uncovered key
4157        // tries the next run in this level (`not_covered`).
4158        let mut found: Vec<(usize, InternalValue)> = Vec::new();
4159        let mut covered_miss: Vec<(usize, u64)> = Vec::new();
4160
4161        for run in level.iter() {
4162            let resolved =
4163                Self::resolve_run_batched(run, &remaining, keys, seqno, comparator, |_| false)?;
4164            for (idx, hash, item) in resolved.covered {
4165                if let Some(item) = item {
4166                    found.push((idx, item));
4167                } else {
4168                    // Covering run found, key absent: no other run in this
4169                    // level can have it. Keep for lower levels.
4170                    covered_miss.push((idx, hash));
4171                }
4172            }
4173            remaining = resolved.not_covered;
4174        }
4175
4176        // Merge back: keys without a covering run + keys with a covering miss
4177        // both proceed to lower levels. Re-sort to preserve comparator order
4178        // for the next level's sequential scan.
4179        let needs_sort = !covered_miss.is_empty();
4180        remaining.extend(covered_miss);
4181        if needs_sort {
4182            remaining
4183                .sort_by(|&(a, _), &(b, _)| comparator.compare(keys[a].as_ref(), keys[b].as_ref()));
4184        }
4185        Ok((found, remaining))
4186    }
4187
4188    /// Resolves `remaining` (sorted ascending under `comparator`) against a
4189    /// single run with per-table batched gets instead of a per-key `table.get`:
4190    /// consecutive keys covered by the same table within the run share one
4191    /// [`Table::batch_get`], so co-located keys decode their data block once.
4192    /// Byte-identical to per-key resolution (the same point reads, the same
4193    /// values). `skip(idx)` omits a key (e.g. one already pinned at the L0 seqno
4194    /// ceiling, where no later run can beat it).
4195    ///
4196    /// Returns, per covered non-skipped key, `(idx, hash, resolved item)` in
4197    /// input order, plus the keys this run does not cover (also in input order)
4198    /// for the caller to pass to the next run or level.
4199    #[expect(
4200        clippy::indexing_slicing,
4201        reason = "i < remaining.len() is loop-checked; idx values are valid key indices (caller's keys/results are aligned, same as batch_get_from_tables)"
4202    )]
4203    fn resolve_run_batched<K: AsRef<[u8]>>(
4204        run: &crate::version::Run<crate::Table>,
4205        remaining: &[(usize, u64)],
4206        keys: &[K],
4207        seqno: SeqNo,
4208        comparator: &dyn crate::comparator::UserComparator,
4209        skip: impl Fn(usize) -> bool,
4210    ) -> crate::Result<RunResolve> {
4211        let mut covered: Vec<CoveredKey> = Vec::new();
4212        let mut not_covered: Vec<(usize, u64)> = Vec::new();
4213
4214        // One pair of buffers reused across the run's tables, cleared per
4215        // table. Each is consumed before the next table fills it, so a fresh
4216        // Vec per table would only re-allocate the capacity the previous one
4217        // just released — once per table on a batched read path.
4218        let mut batch: Vec<(&[u8], u64)> = Vec::new();
4219        let mut batch_keys: Vec<(usize, u64)> = Vec::new();
4220
4221        let mut i = 0;
4222        while i < remaining.len() {
4223            let (idx, hash) = remaining[i];
4224            if skip(idx) {
4225                i += 1;
4226                continue;
4227            }
4228            let key = keys[idx].as_ref();
4229            let Some(table) = run.get_for_key_cmp(key, comparator) else {
4230                not_covered.push((idx, hash));
4231                i += 1;
4232                continue;
4233            };
4234
4235            // Gather the contiguous, non-skipped keys covered by THIS table. The
4236            // input is sorted and a run's tables partition the key space, so the
4237            // keys for one table form a contiguous slice; one `batch_get` drains
4238            // them with a single block decode for co-located keys.
4239            let table_id = table.id();
4240            batch.clear();
4241            batch_keys.clear();
4242            while i < remaining.len() {
4243                let (jdx, jhash) = remaining[i];
4244                if skip(jdx) {
4245                    i += 1;
4246                    continue;
4247                }
4248                let jkey = keys[jdx].as_ref();
4249                match run.get_for_key_cmp(jkey, comparator) {
4250                    Some(t) if t.id() == table_id => {
4251                        batch.push((jkey, jhash));
4252                        batch_keys.push((jdx, jhash));
4253                        i += 1;
4254                    }
4255                    _ => break,
4256                }
4257            }
4258
4259            // `drain`, not `into_iter`: the buffer is reused by the next table,
4260            // so its capacity has to survive the walk. The lint assumes the
4261            // vector is dead afterwards, which is exactly what this is not.
4262            #[expect(
4263                clippy::iter_with_drain,
4264                reason = "buffer is reused across tables; into_iter would consume it"
4265            )]
4266            for ((kidx, khash), item) in batch_keys.drain(..).zip(table.batch_get(&batch, seqno)?) {
4267                covered.push((kidx, khash, item));
4268            }
4269        }
4270
4271        Ok(RunResolve {
4272            covered,
4273            not_covered,
4274        })
4275    }
4276
4277    /// The most of these `tables` whose files a level read holds open at
4278    /// once: what the smallest descriptor cache among them keeps, unbounded
4279    /// when their descriptors are pinned and open anyway.
4280    fn open_cap<'t>(tables: impl Iterator<Item = &'t Table>) -> usize {
4281        tables
4282            .filter_map(Table::descriptor_capacity)
4283            .min()
4284            .map_or(usize::MAX, |cap| {
4285                usize::try_from(cap).unwrap_or(usize::MAX).max(1)
4286            })
4287    }
4288
4289    /// Reads the keys of `ctx` on the calling thread, through the memtables
4290    /// and the levels of its version, and turns each key's newest version
4291    /// into its value as its value source says: the multi-get of a standard
4292    /// tree and of a blob tree alike.
4293    ///
4294    /// # Errors
4295    ///
4296    /// The first failure of a level resolved key by key, or of the lowest
4297    /// key whose merge or blob read failed.
4298    pub(crate) fn read_many<K: AsRef<[u8]>>(
4299        ctx: &read_job::ReadCtx<K>,
4300    ) -> crate::Result<Vec<Option<UserValue>>> {
4301        // Each key of the batch is a point lookup of the read mix.
4302        ctx.super_version
4303            .version
4304            .read_mix()
4305            .note_points(u64::try_from(ctx.keys.len()).unwrap_or(u64::MAX));
4306        let mut read = multi_get_read::MultiGetRead::new(ctx);
4307        Self::drive(&mut read, ctx)
4308    }
4309
4310    /// Drives a read of `ctx` to its end on the calling thread, and returns
4311    /// its answer. A block read goes to the read queue of the backend its
4312    /// table was opened through as it is asked for, and the queues hold the
4313    /// reads of a pass until the driver waits on them, so they go out
4314    /// together; jobs are run after the pass that asked for them, and the read
4315    /// moves on again before any wait. A read is handed back the moment it is
4316    /// in, while others may still be in flight: no table waits on the slowest
4317    /// file of the level.
4318    fn drive<'a, K: AsRef<[u8]>, M: read_job::ReadMachine<'a>>(
4319        level: &mut M,
4320        ctx: &read_job::ReadCtx<K>,
4321    ) -> M::Output {
4322        let mut sink = DriveSink {
4323            queues: Vec::new(),
4324            #[cfg(feature = "std")]
4325            wake: Arc::new(LevelWake {
4326                generation: core::sync::atomic::AtomicU64::new(0),
4327                sleeping: core::sync::atomic::AtomicBool::new(false),
4328                thread: std::thread::current(),
4329            }),
4330            jobs: Vec::new(),
4331            cache: None,
4332        };
4333        // The jobs being run; those their results ask for collect in the
4334        // sink meanwhile.
4335        let mut running: Vec<read_job::Job> = Vec::new();
4336
4337        loop {
4338            if let Some(answer) = level.pump(&mut sink) {
4339                return answer;
4340            }
4341            if !sink.jobs.is_empty() {
4342                core::mem::swap(&mut running, &mut sink.jobs);
4343                #[expect(
4344                    clippy::iter_with_drain,
4345                    reason = "the two job lists trade places on every pass and keep their allocations; into_iter would consume one"
4346                )]
4347                for job in running.drain(..) {
4348                    level.job_done(job.run(ctx), &mut sink);
4349                }
4350                continue;
4351            }
4352
4353            // A block is handed to its read the moment it is back.
4354            let mut on_done = |done: crate::fs::ReadDone| level.read_done(done);
4355            let cache = sink.cache;
4356            let queues = &mut sink.queues;
4357            #[cfg(feature = "std")]
4358            let wake = &sink.wake;
4359            // What has finished on any backend, without waiting. The wake's
4360            // generation is taken first, so a read back after this look is
4361            // seen by the sleep below.
4362            #[cfg(feature = "std")]
4363            let seen = wake.generation.load(core::sync::atomic::Ordering::SeqCst);
4364            let mut handed = 0usize;
4365            for q in queues.iter_mut() {
4366                q.queue.wait(0, &mut |done| {
4367                    handed += 1;
4368                    on_done(done);
4369                });
4370            }
4371            if handed > 0 {
4372                continue;
4373            }
4374            // Nothing is back. One backend still reading is waited on itself.
4375            // A level whose tables sit on several (after a route change, until
4376            // compaction moves them) sleeps until any of them has a read back,
4377            // so a fast backend's tables move on while a slow one is still
4378            // reading. A queue that cannot wake the level, or holds reads only
4379            // a wait carries out, is waited on itself.
4380            let must_wait = |q: &LevelQueue<'_>| !q.wakes || q.queue.held() > 0;
4381            let mut reading = queues.iter_mut().filter(|q| q.queue.outstanding() > 0);
4382            let Some(first) = reading.next() else {
4383                // Nothing in flight: the level moves on from what is back.
4384                continue;
4385            };
4386            let second = reading.next();
4387            if second.is_none() || must_wait(first) {
4388                Self::timed_wait(first.queue.as_mut(), cache, &mut on_done);
4389                continue;
4390            }
4391            if let Some(q) = second.into_iter().chain(reading).find(|q| must_wait(q)) {
4392                Self::timed_wait(q.queue.as_mut(), cache, &mut on_done);
4393                continue;
4394            }
4395            // Every queue still reading wakes the level, which only a queue
4396            // given the wake under `std` does.
4397            #[cfg(feature = "std")]
4398            {
4399                wake.sleeping
4400                    .store(true, core::sync::atomic::Ordering::SeqCst);
4401                while wake.generation.load(core::sync::atomic::Ordering::SeqCst) == seen {
4402                    std::thread::park();
4403                }
4404                wake.sleeping
4405                    .store(false, core::sync::atomic::Ordering::SeqCst);
4406            }
4407        }
4408    }
4409
4410    /// Waits on `queue` for at least one read, handing each to `on_done`,
4411    /// and folds a sample of such waits into `cache`'s load-time estimate as
4412    /// the wait's length shared among the reads it handed back: what each
4413    /// read cost the storage, whether the queue reads them one after another
4414    /// or overlaps them in flight.
4415    fn timed_wait(
4416        queue: &mut dyn crate::fs::ReadQueue,
4417        cache: Option<&crate::cache::Cache>,
4418        on_done: &mut dyn FnMut(crate::fs::ReadDone),
4419    ) {
4420        let timer = crate::cache::Cache::time_load();
4421        let mut reads = 0usize;
4422        queue.wait(1, &mut |done| {
4423            reads += 1;
4424            on_done(done);
4425        });
4426        if let Some(cache) = cache {
4427            cache.record_batch_wait(timer, reads);
4428        }
4429    }
4430
4431    /// Resolves the current version's level `level_idx` from its staged plan
4432    /// for `keys` on the calling thread, with the default comparator and
4433    /// metadata budget: `true` when it answered the level (results updated,
4434    /// found keys dropped from `still_remaining`), `false` when it hands the
4435    /// level to the serial resolve.
4436    #[cfg(test)]
4437    fn resolve_level_staged<K: AsRef<[u8]>>(
4438        &self,
4439        level_idx: usize,
4440        still_remaining: &mut Vec<(usize, u64)>,
4441        keys: Vec<K>,
4442        results: &mut [Option<InternalValue>],
4443    ) -> crate::Result<bool> {
4444        /// One level's resolve, answering into the caller's `results`.
4445        struct OneLevel<'a, 'r, K> {
4446            level: level_resolve::LevelResolve<'a, K>,
4447            results: &'r mut [Option<InternalValue>],
4448        }
4449
4450        impl<'a, K: AsRef<[u8]>> read_job::ReadMachine<'a> for OneLevel<'a, '_, K> {
4451            type Output = bool;
4452
4453            fn pump<S: read_job::ReadSink<'a>>(&mut self, out: &mut S) -> Option<bool> {
4454                match self.level.pump(self.results, out) {
4455                    level_resolve::LevelStep::Pending => None,
4456                    level_resolve::LevelStep::Resolved => Some(true),
4457                    level_resolve::LevelStep::Serial => Some(false),
4458                }
4459            }
4460
4461            fn job_done<S: read_job::ReadSink<'a>>(
4462                &mut self,
4463                done: read_job::JobDone,
4464                out: &mut S,
4465            ) {
4466                self.level.job_done(done, out);
4467            }
4468
4469            fn read_done(&mut self, done: crate::fs::ReadDone) {
4470                self.level.read_done(done);
4471            }
4472        }
4473
4474        let ctx = read_job::ReadCtx {
4475            super_version: self.snapshot_for_read(SeqNo::MAX)?,
4476            keys,
4477            seqno: SeqNo::MAX,
4478            comparator: crate::comparator::default_comparator(),
4479            merge_operator: None,
4480            merge_base: None,
4481            values: read_job::Values::Inline,
4482            metadata_budget: crate::config::DEFAULT_MULTI_GET_METADATA_BUDGET,
4483        };
4484        let Some(level) = ctx.version().level(level_idx) else {
4485            return Ok(true);
4486        };
4487        let mut one = OneLevel {
4488            level: level_resolve::LevelResolve::new(
4489                level,
4490                level_idx,
4491                core::mem::take(still_remaining),
4492                &ctx.keys,
4493                ctx.comparator.as_ref(),
4494                ctx.seqno,
4495                ctx.metadata_budget,
4496            ),
4497            results,
4498        };
4499        let resolved = Self::drive(&mut one, &ctx);
4500        *still_remaining = one.level.into_remaining();
4501        Ok(resolved)
4502    }
4503
4504    /// Counts, once per (table, key), the keys a table's filter let through
4505    /// that no read of that table found: a filter query and, where the read
4506    /// saw every version, a false positive, as a point read counts them.
4507    #[expect(
4508        clippy::indexing_slicing,
4509        reason = "task indices come from enumerating `tasks`"
4510    )]
4511    fn count_chunked_false_positives(
4512        tasks: &[BlockTask<'_>],
4513        mut found: Vec<(crate::TableId, usize)>,
4514    ) {
4515        found.sort_unstable();
4516        found.dedup();
4517        let mut missed: Vec<(crate::TableId, usize, usize)> = tasks
4518            .iter()
4519            .enumerate()
4520            .filter(|(_, task)| task.table.counts_filter_misses())
4521            .flat_map(|(at, task)| {
4522                task.keys
4523                    .iter()
4524                    .map(move |&(key, _)| (task.table.id(), key, at))
4525            })
4526            .filter(|&(table, key, _)| found.binary_search(&(table, key)).is_err())
4527            .collect();
4528        missed.sort_unstable_by_key(|&(table, key, _)| (table, key));
4529        missed.dedup_by_key(|&mut (table, key, _)| (table, key));
4530        for (_, _, at) in missed {
4531            let task = &tasks[at];
4532            task.table.count_filter_miss(task.table_seqno);
4533        }
4534    }
4535
4536    /// Decodes the block of task `index` from `bytes`, keeping it in the cache
4537    /// when it fits in `keep_room` and handing it to the reads waiting on
4538    /// `lead`, and point-reads its keys, adding each hit to `hits` as
4539    /// `(task index, key index, entry)`.
4540    fn resolve_block_task<K: AsRef<[u8]>>(
4541        task: &BlockTask<'_>,
4542        index: usize,
4543        bytes: &[u8],
4544        keep_room: &mut u64,
4545        lead: Option<crate::cache::BlockLead>,
4546        keys: &[K],
4547        hits: &mut Vec<(usize, usize, InternalValue)>,
4548    ) -> crate::Result<()> {
4549        let block = task
4550            .table
4551            .decode_data_block_keeping(bytes, &task.handle, keep_room, lead)?;
4552        Self::read_task_keys(task, index, &block, keys, hits)
4553    }
4554
4555    /// Point-reads the keys of task `index` in its data `block`, adding each
4556    /// hit to `hits` as `(task index, key index, entry)`.
4557    #[expect(
4558        clippy::indexing_slicing,
4559        reason = "the task's key indices are valid (caller's keys aligned)"
4560    )]
4561    fn read_task_keys<K: AsRef<[u8]>>(
4562        task: &BlockTask<'_>,
4563        index: usize,
4564        block: &crate::table::DataBlock,
4565        keys: &[K],
4566        hits: &mut Vec<(usize, usize, InternalValue)>,
4567    ) -> crate::Result<()> {
4568        for &(kidx, key_hash) in &task.keys {
4569            if let Some(item) = task.table.point_read_translated(
4570                block,
4571                keys[kidx].as_ref(),
4572                key_hash,
4573                task.table_seqno,
4574            )? {
4575                hits.push((index, kidx, item));
4576            }
4577        }
4578        Ok(())
4579    }
4580
4581    /// Keeps the higher-seqno of an existing result and a new candidate (the L0
4582    /// newest-version-wins merge; correct for L1+ too, where each key has one
4583    /// candidate).
4584    #[expect(
4585        clippy::indexing_slicing,
4586        reason = "idx is a valid key index aligned with results"
4587    )]
4588    fn keep_highest(results: &mut [Option<InternalValue>], idx: usize, item: InternalValue) {
4589        match &results[idx] {
4590            Some(current) if current.key.seqno >= item.key.seqno => {}
4591            _ => results[idx] = Some(item),
4592        }
4593    }
4594
4595    fn get_internal_entry_from_tables(
4596        version: &Version,
4597        key: &[u8],
4598        seqno: SeqNo,
4599        comparator: &dyn crate::comparator::UserComparator,
4600    ) -> crate::Result<Option<InternalValue>> {
4601        let key_hash = crate::hash::hash64(key);
4602        Self::find_in_tables::<TableEntry>(version, key, seqno, key_hash, comparator)
4603    }
4604
4605    /// Generic level-walk for point reads, monomorphized over the lookup result type.
4606    ///
4607    /// A level of several runs (always L0, and a deeper level a tiered
4608    /// layout leaves overlapping runs in): check ALL runs, keep highest seqno.
4609    /// A level of one run: at most one table covers the key.
4610    /// Once a level yields a match, lower levels cannot have newer data.
4611    fn find_in_tables<T: TablePointLookup>(
4612        version: &Version,
4613        key: &[u8],
4614        seqno: SeqNo,
4615        key_hash: u64,
4616        comparator: &dyn crate::comparator::UserComparator,
4617    ) -> crate::Result<Option<T>> {
4618        for (level_idx, level) in version.iter_levels().enumerate() {
4619            if level_idx == 0 || level.run_count() > 1 {
4620                let mut best: Option<T> = None;
4621
4622                for run in level.iter() {
4623                    if let Some(table) = run.get_for_key_cmp(key, comparator)
4624                        && let Some(item) = T::lookup(table, key, seqno, key_hash)?
4625                    {
4626                        match &best {
4627                            Some(current) if current.entry_seqno() >= item.entry_seqno() => {}
4628                            _ => {
4629                                // Short-circuit: point reads use exclusive upper bound,
4630                                // so the highest visible seqno is read_seqno - 1.
4631                                // If matched, no other L0 run can have a higher one.
4632                                if item.entry_seqno().checked_add(1) == Some(seqno) {
4633                                    return Ok(item.filter_tombstone());
4634                                }
4635                                best = Some(item);
4636                            }
4637                        }
4638                    }
4639                }
4640
4641                if let Some(entry) = best {
4642                    return Ok(entry.filter_tombstone());
4643                }
4644            } else if let Some(table) = level
4645                .iter()
4646                .next()
4647                .and_then(|run| run.get_for_key_cmp(key, comparator))
4648                && let Some(item) = T::lookup(table, key, seqno, key_hash)?
4649            {
4650                // One run: the table covering the key answers for the level.
4651                return Ok(item.filter_tombstone());
4652            }
4653        }
4654
4655        Ok(None)
4656    }
4657
4658    pub(crate) fn get_internal_entry_from_sealed_memtables(
4659        super_version: &SuperVersion,
4660        key: &[u8],
4661        seqno: SeqNo,
4662    ) -> Option<InternalValue> {
4663        for mt in super_version.sealed_memtables.iter().rev() {
4664            if let Some(entry) = mt.get(key, seqno) {
4665                return Some(entry);
4666            }
4667        }
4668
4669        None
4670    }
4671
4672    /// Resolves the super-version serving snapshot `seqno`, as an owned clone
4673    /// a caller can keep for as long as it reads (an iterator, a scan).
4674    ///
4675    /// Every snapshot above the retention floor is served by the current
4676    /// version (see
4677    /// [`SuperVersions::get_version_for_snapshot`](crate::version::SuperVersions::get_version_for_snapshot)),
4678    /// so under `std` it is cloned from the lock-free mirror rather than out of
4679    /// the locked history. The clone is the reader's hold: it keeps the
4680    /// version's tables on disk until the reader drops it, however many
4681    /// installs happen in the meantime.
4682    ///
4683    /// # Errors
4684    ///
4685    /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention)
4686    /// when `0 < seqno <= floor`.
4687    #[cfg(feature = "std")]
4688    pub(crate) fn get_version_for_snapshot(&self, seqno: SeqNo) -> crate::Result<SuperVersion> {
4689        let current = self.latest_super_version.load();
4690        current.check_serves(seqno)?;
4691        Ok(SuperVersion::clone(&current))
4692    }
4693
4694    /// Resolves the super-version serving snapshot `seqno`; see the `std`
4695    /// build's twin. Without the mirror it clones out of the locked history.
4696    ///
4697    /// # Errors
4698    ///
4699    /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention)
4700    /// when `0 < seqno <= floor`.
4701    #[cfg(not(feature = "std"))]
4702    pub(crate) fn get_version_for_snapshot(&self, seqno: SeqNo) -> crate::Result<SuperVersion> {
4703        self.version_history.read().get_version_for_snapshot(seqno)
4704    }
4705
4706    /// Unflushed memtable bytes the memtable backpressure axis compares: the
4707    /// total of the shared write-buffer budget when the tree joined one, else
4708    /// the tree's own active and sealed memtables.
4709    #[cfg(feature = "std")]
4710    pub(crate) fn unflushed_memtable_bytes(&self) -> u64 {
4711        match &self.config.write_buffer_budget {
4712            Some(budget) => budget.unflushed_bytes(),
4713            None => self.latest_super_version.load().unflushed_bytes(),
4714        }
4715    }
4716
4717    /// The tree's own unflushed memtable bytes; see the `std` build's twin.
4718    #[cfg(not(feature = "std"))]
4719    pub(crate) fn unflushed_memtable_bytes(&self) -> u64 {
4720        self.version_history
4721            .read()
4722            .latest_version_ref()
4723            .unflushed_bytes()
4724    }
4725
4726    /// The snapshot for one point read, without a clone.
4727    ///
4728    /// Every snapshot above the retention floor is served by the current
4729    /// version, so under `std` a point read never takes the history `RwLock`:
4730    /// it loads the mirrored current [`SuperVersion`], checks the floor it
4731    /// carries, and reads under the load guard. Recent inserts stay visible
4732    /// because they mutate the shared `active_memtable` behind a stable Arc;
4733    /// the current version only changes on rotation, flush and compaction,
4734    /// which refresh the mirror under the write lock.
4735    ///
4736    /// What a caller gets, and may assume: the memtables and tables of ONE
4737    /// version, the current one. Nothing here hides a compaction from a read
4738    /// the floor admits: a snapshot below an install is answered from the
4739    /// install's output, which holds every key version that snapshot reads.
4740    ///
4741    /// Point reads only: a guard held across a long scan would delay the
4742    /// mirror's writers, so iterators keep their own clones (see
4743    /// [`get_version_for_snapshot`](Self::get_version_for_snapshot)). no-std has
4744    /// no mirror (`arc-swap` is std-only) and clones out of the locked history.
4745    ///
4746    /// # Errors
4747    ///
4748    /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention)
4749    /// when `0 < seqno <= floor`.
4750    #[inline]
4751    pub(crate) fn snapshot_for_read(
4752        &self,
4753        seqno: SeqNo,
4754    ) -> crate::Result<crate::version::SnapshotRef> {
4755        #[cfg(feature = "std")]
4756        {
4757            let current = self.latest_super_version.load();
4758            current.check_serves(seqno)?;
4759            Ok(crate::version::SnapshotRef::Latest(current))
4760        }
4761        #[cfg(not(feature = "std"))]
4762        {
4763            self.version_history
4764                .read()
4765                .get_version_for_snapshot(seqno)
4766                .map(crate::version::SnapshotRef::Owned)
4767        }
4768    }
4769
4770    /// Opens an LSM-tree in the given directory.
4771    ///
4772    /// Will recover previous state if the folder was previously
4773    /// occupied by an LSM-tree, including the previous configuration.
4774    /// If not, a new tree will be initialized with the given config.
4775    ///
4776    /// After recovering a previous state, use `Tree::set_active_memtable`
4777    /// to fill the memtable with data from a write-ahead log for full durability.
4778    ///
4779    /// # Errors
4780    ///
4781    /// Returns error, if an IO error occurred.
4782    ///
4783    /// `lock` says how the open comes by the directory lock: a caller that
4784    /// wrote the tree's files under it passes it in, so no other opener gets
4785    /// in between.
4786    ///
4787    /// `names` says who syncs the folders that gain an entry when the open
4788    /// makes the tree's folder.
4789    ///
4790    /// Returns the tree, and when this open made it rather than recovering
4791    /// one, what takes it back.
4792    pub(crate) fn open(
4793        config: Config,
4794        #[cfg(feature = "std")] lock: crate::config::DirectoryLock,
4795        names: NewFolderNames,
4796    ) -> crate::Result<(Self, Option<Creation>)> {
4797        log::debug!("Opening LSM-tree at {}", config.path.display());
4798
4799        // Resolve the per-tree compaction compression pool once, at open: if the
4800        // caller supplied no shared pool but asked for >1 thread, build the
4801        // default rayon-backed pool now so every compaction reuses it (building
4802        // a pool per compaction would spawn threads on each run). A caller-
4803        // supplied pool is left untouched. Shadowed under `parallel` only, so
4804        // non-parallel builds don't carry an unused `mut`.
4805        // `mut` under `zstd_any` too: installing the dictionaries ASSIGNS the
4806        // config a registry of its own rather than storing through the one it
4807        // arrived with, which is what keeps two trees opened from clones of one
4808        // config from sharing a set.
4809        #[cfg_attr(
4810            not(any(feature = "parallel", zstd_any)),
4811            expect(unused_mut, reason = "assigned only by the parallel / zstd builds")
4812        )]
4813        let mut config = config;
4814
4815        #[cfg(feature = "parallel")]
4816        if config.compaction_pool.is_none() && config.compaction_threads > 1 {
4817            config.compaction_pool = Some(Arc::new(
4818                crate::table::writer::RayonSpawner::with_threads(config.compaction_threads)?,
4819            ));
4820        }
4821
4822        // Gate on the `page_ecc` cargo feature: caller asked for ECC
4823        // but the build does not link the Reed-Solomon codec. We have
4824        // no way to verify or recover RS parity without the codec, so
4825        // refuse to open rather than silently downgrade integrity.
4826        // Two surfaces to check:
4827        //   - `Config::page_ecc(true)`  → SST data-block ECC
4828        //   - `Config::with_runtime_config(RuntimeConfig { page_ecc: true, .. })`
4829        //     → manifest-Block ECC (consumed by manifest_blocks::writer)
4830        // Both silently no-op without the feature; refusing here is
4831        // the only place callers see a typed error.
4832        if (config.page_ecc || config.initial_runtime_config.page_ecc)
4833            && !cfg!(feature = "page_ecc")
4834        {
4835            return Err(crate::Error::PageEccUnsupported);
4836        }
4837
4838        // Acquire the cross-process directory lock BEFORE any manifest access
4839        // (the installed-snapshot probe + `has_existing_version_state` check below, and
4840        // the recover / create paths). Acquiring it here makes `open()`
4841        // exclusive end-to-end: a concurrent opener fails fast with
4842        // `Error::Locked` instead of racing through the probe and observing a
4843        // peer's half-created directory (which would surface as the InvalidData
4844        // "half-written checkpoint" path rather than `Locked`). The `LOCK` file
4845        // needs its directory to exist, so create the root directory first
4846        // (idempotent; `create_new` re-creates the `tables/` subtree). The lock
4847        // is threaded into the constructor so it lives for the tree's lifetime.
4848        #[cfg(feature = "std")]
4849        let restoring = matches!(lock, crate::config::DirectoryLock::Held(_));
4850        // The folders that gain an entry when the open makes the tree folder:
4851        // a tree created here syncs each of them, though by then they exist.
4852        let made_by_open = new_entry_holders(&*config.fs, &config.path)?;
4853        let open_holders: Vec<(crate::path::PathBuf, Arc<dyn Fs>)> = made_by_open
4854            .iter()
4855            .map(|holder| (holder.clone(), Arc::clone(&config.fs)))
4856            .collect();
4857        // What the tree folder holds before this open, when it exists: a tree
4858        // the open makes there and then takes back takes back only what it
4859        // added.
4860        let found = if made_by_open.is_empty() {
4861            Some(folder_entries(&*config.fs, &config.path)?)
4862        } else {
4863            None
4864        };
4865        #[cfg(feature = "std")]
4866        let directory_lock = match lock {
4867            crate::config::DirectoryLock::Held(held) => held,
4868            crate::config::DirectoryLock::Acquire => {
4869                config.fs.create_dir_all(&config.path)?;
4870                match crate::config::acquire_directory_lock(
4871                    &*config.fs,
4872                    &config.path,
4873                    config.directory_lock,
4874                ) {
4875                    Ok(held) => held,
4876                    Err(e) => {
4877                        // Without the lock the folder may be another opener's
4878                        // as much as this one's: nothing is removed, and the
4879                        // names this open made are made durable instead, so
4880                        // a retry that finds them needs no sync for them.
4881                        sync_made_names(&open_holders);
4882                        return Err(e);
4883                    }
4884                }
4885            }
4886        };
4887
4888        // From here to the creation, a failure takes back the folders this
4889        // open made, as a failed creation does: a retry finds them existing
4890        // and would give the folders holding their names no sync.
4891        let prelude = (|| -> crate::Result<bool> {
4892            // A restore that published the manifest but never made the restored
4893            // memtable contents durable left a tree that lacks them: serving it
4894            // would answer from a partial snapshot. Only the restore that finishes
4895            // the job opens it.
4896            #[cfg(feature = "std")]
4897            if !restoring
4898                && config
4899                    .fs
4900                    .exists(&config.path.join(crate::file::RESTORE_MARKER_FILE))?
4901            {
4902                return Err(crate::Error::from(crate::io::Error::new(
4903                    crate::io::ErrorKind::InvalidData,
4904                    format!(
4905                        "{} holds an interrupted restore; run restore_pinned again to finish it",
4906                        config.path.display(),
4907                    ),
4908                )));
4909            }
4910
4911            // Check for old version
4912            if config.fs.exists(&config.path.join("version"))? {
4913                log::error!(
4914                    "refusing to open: this directory has a `version` marker file, which only the \
4915                 retired V1 layout wrote. V6 is the only on-disk format THIS engine decodes, and \
4916                 it ships no conversion tooling for V1. If the directory is a V1 database the data is \
4917                 not lost: open it with the engine that wrote it, or convert it there. If the \
4918                 file is unrelated to this store, move it aside and retry"
4919                );
4920                // The marker's contents are deliberately not read: no legacy
4921                // decode path exists to act on them, so parsing it could only
4922                // change the wording of this error. That makes the presence of
4923                // the file evidence, not proof, hence "has a marker" rather than
4924                // "is a V1 database", and the second escape hatch for a directory
4925                // that merely happens to carry the name.
4926                //
4927                // Scoped to this engine on purpose. The operator reads this line
4928                // while deciding what to do with the directory, and "no conversion
4929                // path" full stop would read as "unrecoverable" — which is wrong,
4930                // and the opposite of what the repair gate documents (an
4931                // unsupported version needs offline conversion or a matching
4932                // binary; see `is_repairable_open_error`).
4933                //
4934                // Literal discriminant: V1 is a retired format and FormatVersion
4935                // carries no legacy variants (single-format contract).
4936                return Err(crate::Error::InvalidVersion(1));
4937            }
4938
4939            // Recover when the tree holds an installed snapshot, create one
4940            // otherwise. The installed snapshot is verified here, before the
4941            // dictionaries below are read or their registrations swept: its
4942            // footer under the configured provider, its format, and that it is
4943            // the version its name says.
4944            let installed =
4945                crate::version::recovery::installed_snapshot(&config.path, &*config.fs)?;
4946            if let Some(id) = installed {
4947                crate::version::recovery::verify_snapshot(
4948                    &config.path,
4949                    id,
4950                    &*config.fs,
4951                    config.encryption.clone(),
4952                )?;
4953            }
4954            let recovering = installed.is_some();
4955
4956            // Load the tree's own dictionaries before anything opens a table: a
4957            // table resolves the dictionary id it recorded against this set, so an
4958            // empty one fails every dictionary-compressed table in the tree. Before
4959            // the recover / create split, because a freshly created tree writes
4960            // dictionary-compressed tables too.
4961            //
4962            // AFTER the probe above, which verified the manifest under the
4963            // configured provider. A dictionary file carries no header saying it is
4964            // sealed, so without the key an encrypted one reads as bytes that fail
4965            // their hash, which is how a damaged one reads, and a damaged one is
4966            // what `open_or_repair` repairs. The probe fails a wrong or missing key
4967            // first, as the configuration error it is.
4968            //
4969            // A dictionary supplied through the config joins them, which is what
4970            // keeps `Config::dict` working: on the first open it is the only one
4971            // there, and it is registered at the end of this function so later
4972            // opens need no config at all.
4973            //
4974            // Under the directory lock, since it reads the tree's own folder and
4975            // SWEEPS unpublished `.tmp` registrations: doing that before the lock
4976            // lets an opener that is about to fail with `Locked` delete a live
4977            // owner's in-flight registration between its write and its rename.
4978            #[cfg(zstd_any)]
4979            config.install_own_zstd_dictionaries()?;
4980
4981            // Only now can the compression policies be checked: they name
4982            // dictionaries by id, and the tree may hold one without the caller
4983            // supplying the bytes again. Validating before the load would refuse
4984            // exactly the reopen the tree owning its dictionaries exists to allow.
4985            #[cfg(zstd_any)]
4986            config.initial_runtime_config.check_dictionaries(
4987                config.kv_separation_opts.is_some(),
4988                &config.current_zstd_dictionaries(),
4989            )?;
4990
4991            // No installed snapshot MUST coincide with a directory whose
4992            // tables and blob folders hold nothing; otherwise we are looking
4993            // at a half-written checkpoint (or other interrupted sealing),
4994            // and calling `create_new` would overwrite the partial state with
4995            // an empty tree, turning a recoverable failure into data loss.
4996            // What a creation that never completed leaves (its empty folders,
4997            // a temporary snapshot never renamed into place, the empty log
4998            // made before that rename) holds no data and is created over. A
4999            // snapshot under its own name is installed, with or without its
5000            // log, and is recovered instead.
5001            if !recovering && has_existing_version_state(&config)? {
5002                // Return Error::Io(InvalidData, ...) rather than
5003                // Error::Unrecoverable so callers that don't read
5004                // logs still get a programmatic surface with the
5005                // path and remediation hint embedded. `log::error!`
5006                // stays for human ops who DO watch logs and want
5007                // the full context at the moment of failure (the
5008                // structured error is what propagates up the call
5009                // chain; the log line records the diagnosis next
5010                // to the timestamp).
5011                let msg = format!(
5012                    "Tree::open: refusing to recover {} — it holds no installed manifest \
5013                     snapshot (a v<N>) but its tables/ or blobs/ folder holds files, \
5014                     or an edits-N holds edits. This is the on-disk signature of a \
5015                     half-written \
5016                     checkpoint or interrupted sealing. Remove the partial directory and \
5017                     retry the checkpoint, or restore the snapshot from a backup before \
5018                     reopening.",
5019                    config.path.display(),
5020                );
5021                log::error!("{msg}");
5022                return Err(crate::Error::from(crate::io::Error::new(
5023                    crate::io::ErrorKind::InvalidData,
5024                    msg,
5025                )));
5026            }
5027            Ok(recovering)
5028        })();
5029        let recovering = match prelude {
5030            Ok(recovering) => recovering,
5031            Err(e) => {
5032                if !made_by_open.is_empty() {
5033                    // Another opener may have filled the folder between the
5034                    // probe that found it missing and this open's lock: only
5035                    // what is empty is taken back. The lock file goes while
5036                    // the lock is held, so no opener is left holding the lock
5037                    // of a file another one can make anew; where an open
5038                    // file cannot be removed it stays, and so does the folder.
5039                    #[cfg(feature = "std")]
5040                    {
5041                        let lock_file = config.path.join(crate::config::DIRECTORY_LOCK_FILE);
5042                        if let Err(e) = config.fs.remove_file(&lock_file)
5043                            && e.kind() != crate::io::ErrorKind::NotFound
5044                        {
5045                            log::warn!(
5046                                "Tree::open: could not remove {} after a failed open ({e})",
5047                                lock_file.display()
5048                            );
5049                        }
5050                        drop(directory_lock);
5051                    }
5052                    let made = made_folders(&config.path, &made_by_open)
5053                        .into_iter()
5054                        .map(|folder| (folder, Arc::clone(&config.fs)))
5055                        .collect();
5056                    undo_creation(None, made, &open_holders);
5057                }
5058                return Err(e);
5059            }
5060        };
5061
5062        let creation = (!recovering).then(|| Creation {
5063            path: config.path.clone(),
5064            fs: Arc::clone(&config.fs),
5065            holders: made_by_open.clone(),
5066            found,
5067            names,
5068            sync_mode: config.sync_mode,
5069        });
5070        let tree = if recovering {
5071            Self::recover(
5072                config,
5073                #[cfg(feature = "std")]
5074                directory_lock,
5075            )
5076        } else {
5077            Self::create_new(
5078                config,
5079                &made_by_open,
5080                names,
5081                #[cfg(feature = "std")]
5082                directory_lock,
5083            )
5084        }?;
5085
5086        // A dictionary supplied through the config becomes the tree's, so the
5087        // caller supplies it once rather than on every open forever. Idempotent
5088        // by id, so re-opening with the same one writes nothing.
5089        //
5090        // BOTH write slots, not just the table one: a KV-separated tree
5091        // compresses its blob files against a dictionary of their own, and a
5092        // blob file whose dictionary the tree never stored cannot be read back
5093        // from a checkpoint or after the caller stops supplying it — the same
5094        // failure the table side had, one file class over.
5095        //
5096        // After the recover / create above, because registering installs a
5097        // version edit and there is no history to install into before that.
5098        #[cfg(zstd_any)]
5099        let registered = [
5100            tree.config.zstd_dictionary.clone(),
5101            tree.config
5102                .kv_separation_opts
5103                .as_ref()
5104                .and_then(|o| o.zstd_dictionary.clone()),
5105        ]
5106        .into_iter()
5107        .flatten()
5108        .try_for_each(|dict| tree.register_zstd_dictionary(dict));
5109        // A tree this open made, whose open then fails, is taken back: the
5110        // caller is told it was not opened, and a retry makes it anew.
5111        #[cfg(zstd_any)]
5112        if let Err(e) = registered {
5113            if let Some(creation) = creation {
5114                creation.take_back(tree);
5115            }
5116            return Err(e);
5117        }
5118
5119        Ok((tree, creation))
5120    }
5121
5122    /// Returns `true` if there are some tables that are being compacted.
5123    #[doc(hidden)]
5124    #[must_use]
5125    pub fn is_compacting(&self) -> bool {
5126        !self.compaction_state.lock().hidden_set().is_empty()
5127    }
5128
5129    /// Computed storage admission predicate backing
5130    /// [`AbstractTree::write_admission`].
5131    ///
5132    /// Cheap: reads in-memory size accounting only (no syscall). Returns
5133    /// `Ok(())` unless admission control is enabled AND a budget is set AND the
5134    /// live footprint plus reserved headroom exceeds it.
5135    /// Best-effort minimum free space across every filesystem this tree writes
5136    /// to: the primary data path AND each per-level route (`Config::level_routes`
5137    /// can place cold-level SSTs on separate volumes). The admission gate must
5138    /// reflect the tightest volume, since a full routed disk fails compaction /
5139    /// flush targeting it even while the primary still has room.
5140    ///
5141    /// A backend that cannot report free space (or an I/O hiccup) yields
5142    /// `u64::MAX` = "no disk pressure", so a probe failure never falsely drives
5143    /// the tree read-only.
5144    fn probe_disk_free(&self) -> u64 {
5145        self.0.config.min_available_space()
5146    }
5147
5148    /// Disk-aware capacity figures for [`AbstractTree::storage_stats`], given the
5149    /// live footprint `used`: `(capacity, available, compaction_possible)`.
5150    ///
5151    /// `capacity` is the tighter of the configured quota and the physical disk
5152    /// headroom (`free + used`) — the same effective limit
5153    /// [`Self::compute_write_admission`] gates against — reported regardless of
5154    /// whether the admission gate is enabled (introspection is always available).
5155    /// `None` capacity/available means unbounded (no quota AND the backend
5156    /// cannot report free space). `compaction_possible` is `true` when unbounded
5157    /// or when at least [`MIN_RESERVED_HEADROOM`] of working room remains.
5158    pub(crate) fn admission_capacity(&self, used: u64) -> (Option<u64>, Option<u64>, bool) {
5159        let quota = self
5160            .0
5161            .runtime_config
5162            .load()
5163            .storage_limit_bytes
5164            .unwrap_or(u64::MAX);
5165        let free = self.probe_disk_free();
5166        // `free == u64::MAX` is the "backend can't report free space" sentinel:
5167        // adding `used` would overflow, so treat capacity as quota-only (the
5168        // explicit branch avoids the overflow without masking it with saturation).
5169        // Otherwise `free + used` ≤ ~2× disk capacity and cannot overflow u64.
5170        let capacity = if free == u64::MAX {
5171            quota
5172        } else {
5173            quota.min(free + used)
5174        };
5175        if capacity == u64::MAX {
5176            return (None, None, true);
5177        }
5178        // `available = max(0, capacity - used)`: an operator quota set below the
5179        // live footprint makes `capacity < used`, and available space cannot be
5180        // negative. The clamp-to-zero IS the intended semantics here.
5181        let available = capacity.saturating_sub(used);
5182        (
5183            Some(capacity),
5184            Some(available),
5185            available >= MIN_RESERVED_HEADROOM,
5186        )
5187    }
5188
5189    /// The logical partition-quota headroom for the two-layer space model:
5190    /// `max(0, storage_limit_bytes - used)`, or `u64::MAX` when no quota is set.
5191    ///
5192    /// The quota half of [`crate::compaction::worker::merge_would_fit`]; the
5193    /// free space of each volume is the other. An operator quota set below the
5194    /// live footprint leaves zero headroom — the clamp-to-zero is the intended
5195    /// min-semantics, not masking.
5196    pub(crate) fn quota_headroom(&self, used: u64) -> u64 {
5197        self.0
5198            .runtime_config
5199            .load()
5200            .storage_limit_bytes
5201            .map_or(u64::MAX, |limit| limit.saturating_sub(used))
5202    }
5203
5204    /// Whether the opt-in storage admission gate is active (a near-full disk or
5205    /// configured quota can drive the tree read-only and gate compaction space).
5206    /// Capacity introspection figures are reported regardless; this only governs
5207    /// whether the gate actually enforces.
5208    pub(crate) fn storage_admission_enabled(&self) -> bool {
5209        self.0.runtime_config.load().storage_admission_check
5210    }
5211
5212    #[expect(
5213        clippy::significant_drop_tightening,
5214        reason = "the admission cache lock intentionally spans the recompute \
5215                  (stat + disk-free probe) so concurrent admission checks \
5216                  coalesce on a single probe rather than each issuing a syscall"
5217    )]
5218    fn compute_write_admission(&self) -> crate::Result<()> {
5219        let rc = self.0.runtime_config.load();
5220        if !rc.storage_admission_check {
5221            return Ok(());
5222        }
5223
5224        // Take ONE coherent snapshot of the latest super-version and derive
5225        // BOTH the on-disk footprint and the pending-memtable bytes from it.
5226        // Reading them from two separate `latest_version()` loads would be a
5227        // TOCTOU bug: a flush installing a new version between the two reads
5228        // could pair an old (larger) disk usage with new (smaller) pending
5229        // bytes — or vice versa — and open the gate incorrectly.
5230        let super_version = self.version_history.read().latest_version();
5231        let vid = super_version.version.id();
5232
5233        // True physical footprint, including blob files — the SAME basis
5234        // `storage_stats()` reports, so the gate and the reported usage agree.
5235        // NOT `disk_space()` (metadata Level::size, which omits blob files and
5236        // undercounts the physical file by the meta block / footer).
5237        //
5238        // Cached so gated writes don't re-stat every live file or re-probe disk
5239        // on every call. `used_bytes` only changes when a new version is
5240        // installed (flush / compaction), so it is recomputed on a version
5241        // change. `disk_free` can change under us (another process writing the
5242        // same filesystem), so it is ALSO re-probed once its sample is older
5243        // than `ADMISSION_DISK_FREE_TTL` — bounding staleness without a syscall
5244        // per write. `update_runtime_config` resets the entry for an immediate
5245        // re-probe. The values live behind one mutex as a coherent unit (see
5246        // `TreeInner::admission_used_cache`).
5247        //
5248        // The TTL fast-path is std-only: under `no_std` there is no monotonic
5249        // clock (`crate::time::Instant::elapsed` is a zero stub), so an
5250        // elapsed-time window cannot bound staleness — a same-version sample
5251        // would otherwise look fresh forever and a filling disk would never be
5252        // re-probed. Under `no_std` the fast-path is skipped, so `disk_free` is
5253        // re-probed on every gated write (the `used` footprint stays cached by
5254        // version either way), keeping admission safe without a monotonic clock.
5255        let now = crate::time::Instant::now();
5256        let (used, disk_free) = {
5257            let mut cache = self.0.admission_used_cache.lock();
5258            match *cache {
5259                // Fresh: same version AND disk sample within the TTL. std-only —
5260                // `cfg!(feature = "std")` is `false` under `no_std`, so the guard
5261                // short-circuits there and the next arm re-probes every call.
5262                Some((cvid, used, free, at))
5263                    if cvid == vid
5264                        && cfg!(feature = "std")
5265                        && at.elapsed() < ADMISSION_DISK_FREE_TTL =>
5266                {
5267                    (used, free)
5268                }
5269                // Same version, stale disk sample: keep `used`, re-probe disk.
5270                Some((cvid, used, _, _)) if cvid == vid => {
5271                    let free = self.probe_disk_free();
5272                    *cache = Some((vid, used, free, now));
5273                    (used, free)
5274                }
5275                // New version (or unset): recompute footprint and re-probe disk.
5276                _ => {
5277                    let used = crate::storage_stats::compute_used_bytes(&super_version.version)?;
5278                    let free = self.probe_disk_free();
5279                    *cache = Some((vid, used, free, now));
5280                    (used, free)
5281                }
5282            }
5283        };
5284
5285        // Effective limit is the tighter of the configured quota and the
5286        // physical disk headroom (free + what we already occupy): the disk can
5287        // fill from other processes even below a generous quota, and a tree with
5288        // no quota at all must still stop before ENOSPC. `None` quota = unbounded
5289        // by configuration; disk-free then alone bounds it.
5290        //
5291        // `disk_free` is the MINIMUM free across every volume the tree writes to
5292        // (`probe_disk_free` mins the primary path and all `level_routes`). The
5293        // `+ used` here is NOT an accounting of one volume's usage against
5294        // another's free space — it cancels out of the disk branch of the gate:
5295        // passing requires `used + reserved <= disk_free + used`, i.e.
5296        // `reserved <= disk_free`. So a passing gate guarantees the TIGHTEST
5297        // volume alone has at least `reserved` free — a conservative per-volume
5298        // headroom, never the sum of an empty routed volume's slack plus an
5299        // unrelated full volume's occupancy. A route that drops below `reserved`
5300        // free drives the whole tree read-only, exactly so a later flush /
5301        // compaction targeting that route cannot hit ENOSPC.
5302        let quota = rc.storage_limit_bytes.unwrap_or(u64::MAX);
5303        // `disk_free == u64::MAX` is the "backend can't report" sentinel; adding
5304        // `used` would overflow, so treat the limit as quota-only (explicit
5305        // branch, no saturation masking). Otherwise `disk_free + used` ≤ ~2× disk
5306        // capacity and cannot overflow u64.
5307        let limit = if disk_free == u64::MAX {
5308            quota
5309        } else {
5310            quota.min(disk_free + used)
5311        };
5312        // Both sources unbounded → nothing to gate.
5313        if limit == u64::MAX {
5314            return Ok(());
5315        }
5316
5317        // Reserved headroom keeps the soft budget from becoming a hard wall:
5318        // enough to flush every pending memtable (plus a margin for the
5319        // index/filter/footer overhead a flush adds) so a queued flush always
5320        // fits at the limit, with a floor for compaction working space.
5321        // Internal flush / compaction are never gated, so this band is the
5322        // engine's room to reclaim.
5323        //
5324        // Count ALL pending memtable bytes in this snapshot — the active one AND
5325        // any sealed (rotated) memtables awaiting flush — not just the active
5326        // one: after a rotation the active memtable is empty but the sealed
5327        // memtable's queued flush will still consume disk, so it must be
5328        // reserved for. Memtable sizes are bounded by RAM, so the sum (and the
5329        // +1/8 overhead margin below) cannot overflow u64 → plain arithmetic.
5330        let pending_memtable_bytes: u64 = super_version.active_memtable.size()
5331            + super_version
5332                .sealed_memtables
5333                .iter()
5334                .map(|m| m.size())
5335                .sum::<u64>();
5336
5337        let reserved =
5338            (pending_memtable_bytes + pending_memtable_bytes / 8).max(MIN_RESERVED_HEADROOM);
5339        // `used` (disk) + `reserved` (RAM-bounded) cannot realistically overflow,
5340        // but keep the comparison fail-closed with checked arithmetic: any
5341        // overflow means "definitely over budget", so deny.
5342        match used.checked_add(reserved) {
5343            Some(total) if total <= limit => Ok(()),
5344            _ => Err(crate::Error::StorageFull { used, limit }),
5345        }
5346    }
5347
5348    fn inner_compact(
5349        &self,
5350        strategy: Arc<dyn CompactionStrategy>,
5351        gc_watermark: SeqNo,
5352    ) -> crate::Result<crate::compaction::CompactionResult> {
5353        use crate::compaction::worker::{Options, do_compaction};
5354
5355        let mut opts = Options::from_tree(self, strategy);
5356        opts.gc_watermark = gc_watermark;
5357
5358        let result = do_compaction(&opts)?;
5359
5360        log::debug!("Compaction run over");
5361
5362        Ok(result)
5363    }
5364
5365    /// Iterator over the whole tree at snapshot `seqno`.
5366    ///
5367    /// # Errors
5368    ///
5369    /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention)
5370    /// when `seqno` is at or below the retention floor; the error is
5371    /// raised here, before any I/O, rather than as an iterator item.
5372    #[doc(hidden)]
5373    pub fn create_iter(
5374        &self,
5375        seqno: SeqNo,
5376        ephemeral: Option<(Arc<Memtable>, SeqNo)>,
5377    ) -> crate::Result<impl DoubleEndedIterator<Item = crate::Result<KvPair>> + 'static> {
5378        self.create_range::<UserKey, _>(&.., seqno, ephemeral)
5379    }
5380
5381    /// Iterator over `range` at snapshot `seqno`.
5382    ///
5383    /// # Errors
5384    ///
5385    /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention)
5386    /// when `seqno` is at or below the retention floor; the error is
5387    /// raised here, before any I/O, rather than as an iterator item.
5388    ///
5389    /// The version is resolved ONCE here and moved into the iterator, so the
5390    /// whole traversal reads one file set: a compaction installing mid-scan
5391    /// neither adds its output nor removes the inputs this iterator holds, and
5392    /// those inputs stay on disk until the iterator drops.
5393    #[doc(hidden)]
5394    pub fn create_range<'a, K: AsRef<[u8]> + 'a, R: RangeBounds<K> + 'a>(
5395        &self,
5396        range: &'a R,
5397        seqno: SeqNo,
5398        ephemeral: Option<(Arc<Memtable>, SeqNo)>,
5399    ) -> crate::Result<impl DoubleEndedIterator<Item = crate::Result<KvPair>> + 'static> {
5400        let super_version = self.get_version_for_snapshot(seqno)?;
5401
5402        Ok(Self::create_internal_range_with_prefix_hash(
5403            super_version,
5404            range,
5405            seqno,
5406            ephemeral,
5407            self.config.merge_operator.clone(),
5408            self.config.comparator.clone(),
5409            None,
5410            self.blob_source(),
5411        )
5412        .map(|item| match item {
5413            Ok(kv) => Ok((kv.key.user_key, kv.value)),
5414            Err(e) => Err(e),
5415        }))
5416    }
5417
5418    /// Build a [`SeekableTreeIter`](crate::range::SeekableTreeIter) over
5419    /// `[lo, hi)`. Source collection (Phase 1) runs once; repositions reuse it.
5420    ///
5421    /// Because the sources are collected once and reused, every reposition
5422    /// resolves against the file set of the version taken here, however long
5423    /// the iterator lives. That is the same one-version rule the plain range
5424    /// iterator states, and it binds for longer.
5425    ///
5426    /// # Errors
5427    ///
5428    /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention)
5429    /// when `seqno` is at or below the retention floor.
5430    #[doc(hidden)]
5431    pub fn create_seekable_range_bounds(
5432        &self,
5433        lo: Bound<UserKey>,
5434        hi: Bound<UserKey>,
5435        seqno: SeqNo,
5436        ephemeral: Option<(Arc<Memtable>, SeqNo)>,
5437    ) -> crate::Result<crate::range::SeekableTreeIter> {
5438        self.create_seekable_range_bounds_with(lo, hi, seqno, ephemeral, self.blob_source())
5439    }
5440
5441    /// [`Self::create_seekable_range_bounds`], reading values kept in the
5442    /// value log through `blob_source`, the one its caller took as the read
5443    /// began.
5444    pub(crate) fn create_seekable_range_bounds_with(
5445        &self,
5446        lo: Bound<UserKey>,
5447        hi: Bound<UserKey>,
5448        seqno: SeqNo,
5449        ephemeral: Option<(Arc<Memtable>, SeqNo)>,
5450        blob_source: Option<crate::blob_tree::BlobSource>,
5451    ) -> crate::Result<crate::range::SeekableTreeIter> {
5452        use crate::range::{IterState, SeekableTreeIter};
5453
5454        let super_version = self.get_version_for_snapshot(seqno)?;
5455
5456        let iter_state = IterState {
5457            version: super_version,
5458            ephemeral,
5459            merge_operator: self.config.merge_operator.clone(),
5460            blob_source,
5461            comparator: self.config.comparator.clone(),
5462            prefix_hash: None,
5463            #[cfg(feature = "metrics")]
5464            metrics: Some(self.0.metrics.clone()),
5465        };
5466
5467        Ok(SeekableTreeIter::create(iter_state, lo, hi, seqno))
5468    }
5469
5470    /// Iterator over the keys starting with `prefix` at snapshot `seqno`.
5471    ///
5472    /// # Errors
5473    ///
5474    /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention)
5475    /// when `seqno` is at or below the retention floor; the error is
5476    /// raised here, before any I/O, rather than as an iterator item.
5477    ///
5478    /// One version for the whole traversal, as for the range iterator. The
5479    /// prefix filter narrows which of ITS tables are consulted; it never
5480    /// reaches a table outside the version resolved here.
5481    #[doc(hidden)]
5482    pub fn create_prefix<'a, K: AsRef<[u8]> + 'a>(
5483        &self,
5484        prefix: K,
5485        seqno: SeqNo,
5486        ephemeral: Option<(Arc<Memtable>, SeqNo)>,
5487    ) -> crate::Result<impl DoubleEndedIterator<Item = crate::Result<KvPair>> + 'static> {
5488        Ok(self
5489            .create_internal_prefix(
5490                prefix.as_ref(),
5491                seqno,
5492                ephemeral,
5493                self.config.merge_operator.clone(),
5494                self.blob_source(),
5495            )?
5496            .map(|item| match item {
5497                Ok(kv) => Ok((kv.key.user_key, kv.value)),
5498                Err(e) => Err(e),
5499            }))
5500    }
5501
5502    /// Versions visible at `seqno` under `prefix`, a merge chain resolved by
5503    /// `merge_operator` (reading a separated base from `blob_source`) or, with
5504    /// none, yielded as its newest operand.
5505    fn create_internal_prefix(
5506        &self,
5507        prefix: &[u8],
5508        seqno: SeqNo,
5509        ephemeral: Option<(Arc<Memtable>, SeqNo)>,
5510        merge_operator: Option<Arc<dyn crate::merge_operator::MergeOperator>>,
5511        blob_source: Option<crate::blob_tree::BlobSource>,
5512    ) -> crate::Result<
5513        impl DoubleEndedIterator<Item = crate::Result<InternalValue>> + 'static + use<>,
5514    > {
5515        use crate::prefix::compute_prefix_hash;
5516        use crate::range::{IterState, TreeIter, prefix_to_range};
5517
5518        let prefix_hash = compute_prefix_hash(self.config.prefix_extractor.as_ref(), prefix);
5519
5520        let range = prefix_to_range(prefix);
5521
5522        let super_version = self.get_version_for_snapshot(seqno)?;
5523
5524        let iter_state = IterState {
5525            version: super_version,
5526            ephemeral,
5527            merge_operator,
5528            blob_source,
5529            comparator: self.config.comparator.clone(),
5530            prefix_hash,
5531            #[cfg(feature = "metrics")]
5532            metrics: Some(self.0.metrics.clone()),
5533        };
5534
5535        Ok(TreeIter::create_range(iter_state, range, seqno))
5536    }
5537
5538    /// The keys visible at `seqno`, each as its newest version with a merge
5539    /// chain left unmerged: what the key-only reads walk.
5540    fn create_key_only_iter(
5541        &self,
5542        seqno: SeqNo,
5543        ephemeral: Option<(Arc<Memtable>, SeqNo)>,
5544    ) -> crate::Result<
5545        impl DoubleEndedIterator<Item = crate::Result<InternalValue>> + 'static + use<>,
5546    > {
5547        let super_version = self.get_version_for_snapshot(seqno)?;
5548        Ok(Self::create_internal_range_with_prefix_hash::<UserKey, _>(
5549            super_version,
5550            &..,
5551            seqno,
5552            ephemeral,
5553            None,
5554            self.config.comparator.clone(),
5555            None,
5556            None,
5557        ))
5558    }
5559
5560    /// Seals the active memtable under the held version lock, returning it,
5561    /// or `None` when it is empty.
5562    pub(crate) fn rotate_memtable_locked(
5563        &self,
5564        history: &mut crate::version::SuperVersions,
5565    ) -> Option<Arc<Memtable>> {
5566        let super_version = history.latest_version_ref();
5567
5568        if super_version.active_memtable.is_empty() {
5569            return None;
5570        }
5571
5572        let yanked_memtable = Arc::clone(&super_version.active_memtable);
5573
5574        let mut copy = super_version.clone();
5575        copy.active_memtable = Arc::new(Memtable::new(
5576            self.memtable_id_counter.next(),
5577            self.config.comparator.clone(),
5578        ));
5579        copy.sealed_memtables =
5580            Arc::new(super_version.sealed_memtables.add(yanked_memtable.clone()));
5581
5582        // Rotate does not modify the memtable so it cannot break snapshots
5583        copy.seqno = super_version.seqno;
5584
5585        history.replace_latest_version(copy);
5586
5587        log::trace!(
5588            "rotate: added memtable id={} to sealed memtables",
5589            yanked_memtable.id,
5590        );
5591
5592        Some(yanked_memtable)
5593    }
5594
5595    /// Adds an item to the active memtable.
5596    ///
5597    /// Returns the added item's size and new size of the memtable.
5598    #[doc(hidden)]
5599    #[must_use]
5600    pub fn append_entry(&self, value: InternalValue) -> (u64, u64) {
5601        #[cfg(feature = "metrics")]
5602        self.accept(&value);
5603        let kv_digest = self.insert_digest(&value);
5604
5605        // The insert holds its thread's shard of the insert gate, which a
5606        // version replacing the active memtable closes before it publishes:
5607        // `value` and its digest land in the active memtable the gate hands
5608        // out, and no rotation seals it mid-insert. Unlike the history read
5609        // lock, the shard is a line no other writer thread touches.
5610        #[cfg(feature = "std")]
5611        {
5612            self.insert_gate
5613                .enter()
5614                .memtable()
5615                .insert_with_kv_digest(value, kv_digest)
5616        }
5617        // no-std: the history read lock, with the same guarantee.
5618        #[cfg(not(feature = "std"))]
5619        {
5620            self.version_history
5621                .read()
5622                .latest_version_ref()
5623                .active_memtable
5624                .insert_with_kv_digest(value, kv_digest)
5625        }
5626    }
5627
5628    /// Adds a cell row whose references are `refs` to the active memtable,
5629    /// once each reference is shown to name a frame the current version still
5630    /// holds.
5631    ///
5632    /// The check and the insert run under the version-history read lock, the
5633    /// lock a version install takes for writing: an install that removes or
5634    /// punches a blob file sees this row's registration in the memtable, or
5635    /// this check sees the file gone.
5636    ///
5637    /// # Errors
5638    ///
5639    /// [`crate::Error::BlobRef`] when a reference names a blob file the current
5640    /// version no longer holds, or a frame before that file's live start: a
5641    /// relocation has moved it, and the key's latest version names the copy.
5642    /// Also when its object was released after the version it was read from
5643    /// (each reference comes with that version's id).
5644    pub(crate) fn append_cell_row(
5645        &self,
5646        value: InternalValue,
5647        refs: &[(crate::blob_tree::handle::BlobIndirection, u64)],
5648    ) -> crate::Result<(u64, u64)> {
5649        let kv_digest = self.insert_digest(&value);
5650        let history = self.version_history.read();
5651        let latest = history.latest_version_ref();
5652        for (reference, source) in refs {
5653            let held = latest
5654                .version
5655                .blob_files
5656                .get(reference.vhandle.blob_file_id)
5657                .is_some_and(|file| reference.vhandle.offset >= file.live_data_start());
5658            if !held {
5659                return Err(crate::Error::BlobRef(
5660                    "stale reference: the object has moved, read the key again",
5661                ));
5662            }
5663            // An object let go of since the read: writing it back would make
5664            // this row hold what the accounting already charged as garbage.
5665            if history
5666                .released()
5667                .released_since(*source, &reference.vhandle)
5668            {
5669                return Err(crate::Error::BlobRef(
5670                    "stale reference: the object was released, read the key again",
5671                ));
5672            }
5673        }
5674        let inserted = latest
5675            .active_memtable
5676            .insert_with_kv_digest(value, kv_digest);
5677        // Held through the insert: the check above is only as good as the
5678        // version it read staying the latest until the row is in.
5679        drop(history);
5680        Ok(inserted)
5681    }
5682
5683    /// Charges `value`, taken in from the caller, to the payload the tree's
5684    /// writes are measured against: a deletion its key, anything else its key
5685    /// and value.
5686    #[cfg(feature = "metrics")]
5687    fn accept(&self, value: &InternalValue) {
5688        let key = value.key.user_key.len();
5689        if value.key.value_type.is_tombstone() {
5690            self.0.metrics.accepted_deletion(key);
5691        } else {
5692            self.0.metrics.accepted_value(key + value.value.len());
5693        }
5694    }
5695
5696    /// The insert-time per-KV digest of `value`, when the runtime config asks
5697    /// for one.
5698    fn insert_digest(
5699        &self,
5700        value: &InternalValue,
5701    ) -> Option<(u32, crate::runtime_config::ChecksumAlgorithm)> {
5702        // Per-KV residence digest (KvChecksumComputePoint::AtInsert): compute
5703        // the entry's 4-byte logical-content digest now, so a RAM bit-flip
5704        // while it sits in the memtable is caught at flush. The digest covers
5705        // the OWNED `value` and is independent of which active memtable
5706        // receives it, so computing it before taking the version-history guard
5707        // is correct (a concurrent rotation just routes the same value+digest
5708        // into the new active memtable) AND keeps the hash out of the read-lock
5709        // critical section. The gate is one relaxed byte mirrored from the
5710        // runtime config (`TreeInner::kv_digest_at_insert`); under the default
5711        // `AtBlockCompile` (or `Off`) it is `0` and no digest is computed.
5712        let gate = self
5713            .0
5714            .kv_digest_at_insert
5715            .load(core::sync::atomic::Ordering::Relaxed);
5716        inner::kv_digest_algo_from_gate(gate).and_then(|algo| {
5717            crate::table::block::kv_checksum::kv_digest(value, algo).map(|d| {
5718                #[expect(
5719                    clippy::cast_possible_truncation,
5720                    reason = "AtInsert is config-validated to a 4-byte algorithm; the digest fits u32"
5721                )]
5722                let lo = d as u32;
5723                (lo, algo)
5724            })
5725        })
5726    }
5727
5728    /// Adds multiple items to the active memtable in bulk.
5729    ///
5730    /// Acquires the version-history lock once and delegates to
5731    /// [`Memtable::insert_batch`] for batch size accounting.
5732    ///
5733    /// Returns the total bytes added and new size of the memtable.
5734    #[doc(hidden)]
5735    #[must_use]
5736    pub(crate) fn append_batch(&self, items: Vec<InternalValue>) -> (u64, u64) {
5737        // Per-KV residence digest under AtInsert (see `append_entry`): pass the
5738        // algorithm so the bulk path fixes each entry's digest at insert. The
5739        // default path passes `None` and is unchanged.
5740        let kv_algo = inner::kv_digest_algo_from_gate(
5741            self.0
5742                .kv_digest_at_insert
5743                .load(core::sync::atomic::Ordering::Relaxed),
5744        );
5745        #[cfg(feature = "metrics")]
5746        for item in &items {
5747            self.accept(item);
5748        }
5749
5750        // Hold the read guard for the entire insert to prevent rotate_memtable()
5751        // from sealing this memtable mid-batch (which could cause data loss if
5752        // a concurrent flush persists only a prefix of the batch).
5753        self.version_history
5754            .read()
5755            .latest_version_ref()
5756            .active_memtable
5757            .insert_batch_with_kv_algo(items, kv_algo)
5758    }
5759
5760    /// Recovers previous state, by loading the level manifest, tables and blob files.
5761    ///
5762    /// # Errors
5763    ///
5764    /// Returns error, if an IO error occurred.
5765    #[expect(
5766        clippy::too_many_lines,
5767        reason = "Tree::recover threads the whole open sequence (snapshot validation, \
5768                  Manifest decode, encryption + runtime plumbing, version recovery, \
5769                  TreeInner assembly) — splitting it would create helper functions whose \
5770                  only caller is this one site"
5771    )]
5772    fn recover(
5773        mut config: Config,
5774        // The cross-process directory lock acquired by `Tree::open` before the
5775        // manifest probe; held for the tree's lifetime via
5776        // `TreeInner::directory_lock`.
5777        #[cfg(feature = "std")] directory_lock: Option<Box<dyn crate::fs::FsFile>>,
5778    ) -> crate::Result<Self> {
5779        use crate::stop_signal::StopSignal;
5780        use inner::get_next_tree_id;
5781
5782        log::info!("Recovering LSM-tree at {}", config.path.display());
5783
5784        // Validate manifest metadata (format version, comparator name)
5785        // BEFORE recover_levels, so a rejected open is side-effect free
5786        // — recover_levels loads tables and cleans up orphans.
5787        // Tree type is checked after recovery (needs the Version object).
5788        // NOTE: the version file is read twice (here for metadata, then inside
5789        // recover_levels for table/blob data). This is intentional — metadata
5790        // validation must complete before any disk-mutating recovery work.
5791        // Version id of the installed on-disk snapshot. This is the base the
5792        // edit log replays on top of; the live version id can be higher (it
5793        // has no `v{id}` file of its own). Threaded into the version history
5794        // so the next persist appends to / rotates the right snapshot's log.
5795        let snapshot_id = crate::version::recovery::installed_snapshot(&config.path, &*config.fs)?
5796            .ok_or(crate::Error::ManifestFooterInvalid(
5797                "the tree holds no installed manifest snapshot",
5798            ))?;
5799        {
5800            let version_id = snapshot_id;
5801            let manifest_path = config.path.join(format!("v{version_id}"));
5802            // Open the manifest with a default runtime snapshot: ECC
5803            // awareness is captured per-Block via the header
5804            // (`ECC_PARITY` flag), so the reader does not need to know
5805            // which ECC mode the writer used and nothing on this path
5806            // reads the runtime.
5807            let mut archive_reader = crate::manifest_blocks::reader::ManifestArchiveReader::open(
5808                &manifest_path,
5809                &*config.fs,
5810                alloc::sync::Arc::new(crate::runtime_config::RuntimeConfig::default()),
5811                config.encryption.clone(),
5812            )?;
5813            let manifest = Manifest::decode_from(&mut archive_reader)?;
5814
5815            // V6 is the only variant `FormatVersion` can decode to (the
5816            // engine reads exactly one on-disk format, no legacy paths), so
5817            // anything else already failed above: on its framing if the
5818            // manifest is not shaped like the current one, on the version
5819            // field if it is. This match stays as the explicit gate the
5820            // format contract documents — and as the compile-time hook that
5821            // forces a review of the open path when a new variant is added.
5822            match manifest.version {
5823                FormatVersion::V6 => {}
5824            }
5825
5826            let supplied_name = config.comparator.name();
5827            if manifest.comparator_name != supplied_name {
5828                log::warn!(
5829                    "Comparator mismatch: tree was created with {:?} but opened with {:?}",
5830                    manifest.comparator_name,
5831                    supplied_name,
5832                );
5833                return Err(crate::Error::ComparatorMismatch {
5834                    stored: manifest.comparator_name,
5835                    supplied: supplied_name,
5836                });
5837            }
5838
5839            // IMPORTANT: Restore persisted config
5840            config.level_count = manifest.level_count;
5841        }
5842
5843        let tree_id = get_next_tree_id();
5844
5845        #[cfg(feature = "metrics")]
5846        let metrics = Arc::new(Metrics::default());
5847
5848        let (version, pooled) = Self::recover_levels(
5849            &config.path,
5850            tree_id,
5851            &config,
5852            #[cfg(feature = "metrics")]
5853            &metrics,
5854        )?;
5855
5856        {
5857            let requested_tree_type = match config.kv_separation_opts {
5858                Some(_) => crate::TreeType::Blob,
5859                None => crate::TreeType::Standard,
5860            };
5861
5862            if version.tree_type() != requested_tree_type {
5863                log::error!(
5864                    "Tried to open a {requested_tree_type:?}Tree, but the existing tree is of type {:?}Tree. This indicates a misconfiguration or corruption.",
5865                    version.tree_type(),
5866                );
5867                // A dedicated error, NOT `Unrecoverable`: the auto-repair path
5868                // answers `Unrecoverable` with a manifest rebuild, and a
5869                // rebuild under the mismatched type commits the wrong tree
5870                // shape (a Standard rebuild of a blob tree strands its blob
5871                // files for the orphan sweep). A configuration error must
5872                // propagate to the caller instead.
5873                return Err(crate::Error::TreeTypeMismatch {
5874                    requested: requested_tree_type,
5875                    actual: version.tree_type(),
5876                });
5877            }
5878        }
5879
5880        // Table ids and L0 recency keys share one counter, and a flushed or
5881        // ingested table's recency is an id taken that no table carries: the
5882        // next id has to be above both, or a flush after the reopen would lay
5883        // out behind one it is newer than. The pooled files hold ids of their
5884        // own, which no later table may take.
5885        let highest_table_id = version
5886            .iter_tables()
5887            .map(|table| table.id().max(table.l0_recency()))
5888            .chain(pooled.iter().flat_map(|(_, _, ids)| ids.iter().copied()))
5889            .max()
5890            .unwrap_or_default();
5891        let table_id_counter = SequenceNumberCounter::new(highest_table_id + 1);
5892        let life = crate::close::Life::new();
5893        let table_files = Arc::new(crate::table::file_pool::TableFilePools::for_tree(
5894            table_id_counter.clone(),
5895            config.table_file_pool,
5896            config.sync_mode,
5897            config.table_file_refill,
5898            Arc::clone(&life),
5899        ));
5900        for (folder, folder_fs, ids) in pooled {
5901            table_files.adopt(&folder, &folder_fs, ids);
5902        }
5903
5904        let comparator = config.comparator.clone();
5905
5906        let deletion_pause = crate::deletion_pause::DeletionPause::for_tree(Arc::clone(&life));
5907        #[cfg(feature = "std")]
5908        let background_deleter =
5909            Arc::new(crate::BackgroundDeleter::for_tree(None, Arc::clone(&life)));
5910        let heal_hints =
5911            crate::heal_hints::HealHints::new_shared(config.initial_runtime_config.auto_heal);
5912
5913        // Clone the seed snapshot BEFORE moving config into the Arc
5914        // below — the runtime handle initializer needs it after the
5915        // move.
5916        let initial_runtime = config.initial_runtime_config.clone();
5917        let sync_mode = config.sync_mode;
5918        // Same reason: built before the move.
5919        let compaction_rate_limiter = config.tree_compaction_rate_limiter();
5920        // A strategy reads the compaction budget from the config: given the
5921        // live limiter, it sees a rate changed after open.
5922        config.live_compaction_rate_limiter = Some(Arc::clone(&compaction_rate_limiter));
5923        let compaction_capacity = config.tree_compaction_capacity();
5924        let write_rate_limiter = config.tree_write_rate_limiter();
5925        // A compaction throttled by the limiter sleeps until its deadline;
5926        // dropping the tree has to wake it.
5927        let stop_signal = StopSignal::default();
5928        #[cfg(feature = "std")]
5929        stop_signal.wake_on_stop(&compaction_rate_limiter);
5930        let filter_budget = Arc::<crate::filter_budget::FilterBudget>::default();
5931        // The open installed the recovered state as its own snapshot.
5932        let snapshot_id = version.id();
5933        let mut super_versions = SuperVersions::new(
5934            version,
5935            &comparator,
5936            sync_mode,
5937            snapshot_id,
5938            config.manifest_log_rotate_bytes,
5939        );
5940        if config.filter_advisor.is_some() {
5941            super_versions = super_versions.with_filter_budget(Arc::clone(&filter_budget));
5942        }
5943        #[cfg(feature = "std")]
5944        let latest_super_version = super_versions.latest_handle();
5945        #[cfg(feature = "std")]
5946        let insert_gate = super_versions.insert_gate_handle();
5947        #[cfg(feature = "std")]
5948        if let Some(budget) = &config.write_buffer_budget {
5949            budget.register(Arc::downgrade(&latest_super_version));
5950        }
5951        let inner = TreeInner {
5952            id: tree_id,
5953            life,
5954            closing: core::sync::atomic::AtomicBool::new(false),
5955            memtable_id_counter: SequenceNumberCounter::new(1),
5956            table_id_counter,
5957            table_files,
5958            blob_file_id_counter: SequenceNumberCounter::default(),
5959            version_history: Arc::new(RwLock::new(super_versions)),
5960            #[cfg(feature = "std")]
5961            latest_super_version,
5962            #[cfg(feature = "std")]
5963            insert_gate,
5964            stop_signal,
5965            config: Arc::new(config),
5966            major_compaction_lock: RwLock::default(),
5967            flush_lock: Mutex::default(),
5968            #[cfg(feature = "std")]
5969            directory_lock: directory_lock.map(Arc::from),
5970            compaction_state: Arc::new(Mutex::new(CompactionState::default())),
5971            deletion_pause: Arc::clone(&deletion_pause),
5972            #[cfg(feature = "std")]
5973            background_deleter: Arc::clone(&background_deleter),
5974            heal_hints: Arc::clone(&heal_hints),
5975            filter_budget,
5976            compaction_rate_limiter,
5977            compaction_capacity,
5978            write_rate_limiter,
5979            kv_digest_at_insert: portable_atomic::AtomicU8::new(inner::kv_digest_at_insert_gate(
5980                &initial_runtime,
5981            )),
5982            runtime_config: Arc::new(crate::runtime_config::handle::RuntimeConfigHandle::new(
5983                initial_runtime,
5984            )),
5985            admission_used_cache: Mutex::new(None),
5986            #[cfg(zstd_any)]
5987            retired_write_snapshots: Mutex::new(Vec::new()),
5988
5989            #[cfg(feature = "metrics")]
5990            metrics,
5991
5992            #[cfg(test)]
5993            test_hooks: inner::TestHooks::default(),
5994        };
5995
5996        // Install the pause on every recovered table / blob file so their
5997        // Drop impls consult it when a checkpoint is in flight. Snapshot
5998        // the Arc handles into owned collections so the read lock is
5999        // released before iterating (avoids `significant_drop_tightening`).
6000        // Snapshot the version under the read lock, then drop the lock before
6001        // collecting so the version_history lock isn't held across the clones.
6002        let version = inner.version_history.read().latest_version().version;
6003        let recovered_tables: Vec<Table> = version.iter_tables().cloned().collect();
6004        let recovered_blobs: Vec<BlobFile> = version.blob_files.iter().cloned().collect();
6005
6006        let sinks = crate::table::TableSinks {
6007            deletion_pause: &deletion_pause,
6008            heal_hints: &heal_hints,
6009            read_budget: inner.config.columnar_read_budget,
6010            #[cfg(feature = "std")]
6011            background_deleter: &background_deleter,
6012            track_filter_probes: inner.config.filter_advisor.is_some(),
6013        };
6014        for table in &recovered_tables {
6015            table.bind_to_tree(&sinks);
6016        }
6017        for blob_file in &recovered_blobs {
6018            blob_file.bind_to_tree(&sinks);
6019        }
6020
6021        // Re-arm the tight-space reclaims a previous session could not finish.
6022        // A reclaim deferred because a checkpoint still hard-linked the file
6023        // lived only in that session's queue, and the unrestricted view that
6024        // could re-arm it is gone once the process restarts, so the consumed
6025        // prefix would stay allocated for the table's lifetime. Nothing is
6026        // persisted for this: the intent is DERIVABLE, and the extent is
6027        // exactly the prefix the committed bound cuts away. A prefix already
6028        // punched re-punches as a no-op, so a completed reclaim costs one
6029        // call, and only on a restricted table.
6030        #[cfg(feature = "std")]
6031        {
6032            for table in &recovered_tables {
6033                let Some(bound) = table.restrict_lower_bound() else {
6034                    continue;
6035                };
6036                // Reclaiming frees space; it never decides what the tree
6037                // serves. A table that opened cleanly must not be denied to
6038                // readers because its punch offset cannot be re-derived, so a
6039                // failure here is reported and skipped. An ENVIRONMENTAL fault
6040                // still propagates: it says nothing about this table, and a
6041                // retry can re-read it.
6042                let offset = match table.punch_offset_for(bound) {
6043                    Ok(offset) => offset,
6044                    Err(e) if e.is_environmental() => return Err(e),
6045                    Err(e) => {
6046                        log::warn!(
6047                            "table {} carries a committed restriction whose punch offset \
6048                             could not be re-derived ({e}); its consumed prefix stays \
6049                             allocated until the next tight-space compaction",
6050                            table.id(),
6051                        );
6052                        continue;
6053                    }
6054                };
6055                if offset > 0 {
6056                    deletion_pause.retain_reclaim(
6057                        Arc::clone(&table.fs),
6058                        (*table.path).clone(),
6059                        alloc::vec![(0, offset)],
6060                    );
6061                }
6062            }
6063            // Blob files carry the same deferred intent, in their committed
6064            // frontier rather than a restriction bound.
6065            for blob in &recovered_blobs {
6066                let extent = match blob.committed_reclaimable_prefix() {
6067                    Ok(Some(extent)) => extent,
6068                    Ok(None) => continue,
6069                    Err(e) if e.is_environmental() => return Err(e),
6070                    Err(e) => {
6071                        log::warn!(
6072                            "blob file {:?} carries a committed frontier whose reclaimable \
6073                             extent could not be re-derived ({e}); its consumed prefix stays \
6074                             allocated until the next relocation",
6075                            blob.id(),
6076                        );
6077                        continue;
6078                    }
6079                };
6080                deletion_pause.retain_reclaim(
6081                    Arc::clone(&blob.0.fs),
6082                    blob.0.path.clone(),
6083                    alloc::vec![extent],
6084                );
6085            }
6086            deletion_pause.retry_pending_reclaims();
6087        }
6088
6089        Ok(Self(Arc::new(inner)))
6090    }
6091
6092    /// Creates a new LSM-tree in a directory.
6093    fn create_new(
6094        config: Config,
6095        // The folders that gained an entry when `Tree::open` made the tree
6096        // folder, to lock it.
6097        made_by_open: &[crate::path::PathBuf],
6098        // Whether this creation syncs those folders, or its caller does.
6099        names: NewFolderNames,
6100        // The cross-process directory lock acquired by `Tree::open`, held for
6101        // the tree's lifetime.
6102        #[cfg(feature = "std")] directory_lock: Option<Box<dyn crate::fs::FsFile>>,
6103    ) -> crate::Result<Self> {
6104        use crate::file::{BLOBS_FOLDER, fsync_directory};
6105        use crate::fs::SyncMode;
6106        use crate::path::PathBuf;
6107
6108        /// Adds `folder` on `fs` to `folders` unless it is there already.
6109        fn once(folders: &mut Vec<(PathBuf, Arc<dyn Fs>)>, folder: &Path, fs: &Arc<dyn Fs>) {
6110            if !folders
6111                .iter()
6112                .any(|(at, on)| at == folder && Arc::ptr_eq(on, fs))
6113            {
6114                folders.push((folder.to_path_buf(), Arc::clone(fs)));
6115            }
6116        }
6117
6118        let path = config.path.clone();
6119        log::trace!("Creating LSM-tree at {}", path.display());
6120
6121        let sync_mode = config.sync_mode;
6122
6123        // Every folder that gains a new entry is synced once, before the first
6124        // snapshot is written, so a sync that fails fails the creation before
6125        // there is a tree for a retry to open instead. The tree folder on the
6126        // tree's own backend is the one exception: the directory sync that
6127        // makes the snapshot's name durable makes every name made in it
6128        // durable too. A route on another backend holds its own names, even
6129        // one whose path lies inside the tree folder. The folders above the
6130        // tree folder are left to the caller under `NewFolderNames::Deferred`,
6131        // which syncs them once for a batch of trees after all of them.
6132        let mut holders: Vec<(PathBuf, Arc<dyn Fs>)> = Vec::new();
6133        // Every folder a chain made, on its backend: what a failed creation
6134        // removes, so a retry makes the chain anew and syncs it, rather than
6135        // finding folders whose names were never made durable.
6136        let mut made: Vec<(PathBuf, Arc<dyn Fs>)> = Vec::new();
6137        // The open made the tree folder already, to lock it, and every folder
6138        // above it that did not exist: their names are new all the same.
6139        for folder in made_folders(&path, made_by_open) {
6140            once(&mut made, &folder, &config.fs);
6141        }
6142        if names == NewFolderNames::Synced {
6143            for holder in made_by_open {
6144                once(&mut holders, holder, &config.fs);
6145            }
6146        }
6147
6148        let built = (|| -> crate::Result<()> {
6149            // A tree folder that a restore made and locked before this open:
6150            // probed under the cleanup, so a probe that fails takes back the
6151            // folders found missing before it rather than leave them unsynced.
6152            let tree_holders = new_entry_holders(&*config.fs, &path)?;
6153            for folder in made_folders(&path, &tree_holders) {
6154                once(&mut made, &folder, &config.fs);
6155            }
6156            for holder in &tree_holders {
6157                once(&mut holders, holder, &config.fs);
6158            }
6159            (*config.fs).create_dir_all(&path)?;
6160            for (tables_folder, folder_fs) in config.all_tables_folders() {
6161                let covered = Arc::ptr_eq(&folder_fs, &config.fs);
6162                let folder_holders = new_entry_holders(&*folder_fs, &tables_folder)?;
6163                for folder in made_folders(&tables_folder, &folder_holders) {
6164                    once(&mut made, &folder, &folder_fs);
6165                }
6166                for holder in folder_holders {
6167                    if !(covered && holder == path) {
6168                        once(&mut holders, &holder, &folder_fs);
6169                    }
6170                }
6171                folder_fs.create_dir_all(&tables_folder)?;
6172            }
6173            if config.kv_separation_opts.is_some() {
6174                let blobs = path.join(BLOBS_FOLDER);
6175                if !(*config.fs).exists(&blobs)? {
6176                    once(&mut made, &blobs, &config.fs);
6177                }
6178                (*config.fs).create_dir_all(&blobs)?;
6179            }
6180            // A barrier orders syncs on its own device only: the last sync of
6181            // the creation on each other device than the tree's is a full one,
6182            // and the ones before it there are ordered ahead of it.
6183            let tree_volume = config.fs.volume_id(&path);
6184            let volumes: Vec<Option<u64>> = holders
6185                .iter()
6186                .map(|(folder, folder_fs)| folder_fs.volume_id(folder))
6187                .collect();
6188            for (at, (folder, folder_fs)) in holders.iter().enumerate() {
6189                let volume = volumes.get(at).copied().flatten();
6190                let foreign = volume.is_none() || volume != tree_volume;
6191                let last_there =
6192                    volume.is_none() || !volumes.iter().skip(at + 1).any(|later| *later == volume);
6193                let mode = if sync_mode == SyncMode::Barrier && foreign && last_there {
6194                    SyncMode::Full
6195                } else {
6196                    sync_mode
6197                };
6198                fsync_directory(folder, &**folder_fs, mode)?;
6199            }
6200            Ok(())
6201        })();
6202
6203        let config_fs = Arc::clone(&config.fs);
6204        let created = built.and_then(|()| {
6205            TreeInner::create_new(
6206                config,
6207                #[cfg(feature = "std")]
6208                directory_lock,
6209            )
6210        });
6211        match created {
6212            Ok(inner) => Ok(Self(Arc::new(inner))),
6213            Err(e) => {
6214                undo_creation(Some((&path, &config_fs)), made, &holders);
6215                Err(e)
6216            }
6217        }
6218    }
6219
6220    /// Recovers the level manifest, loading all tables from disk.
6221    ///
6222    /// When [`level_routes`](Config::level_routes) is configured, all
6223    /// configured table folders are scanned so tables on different storage
6224    /// tiers are discovered correctly.
6225    #[expect(
6226        clippy::too_many_lines,
6227        reason = "recovery logic is inherently complex"
6228    )]
6229    fn recover_levels<P: AsRef<Path>>(
6230        tree_path: P,
6231        tree_id: TreeId,
6232        config: &Config,
6233        #[cfg(feature = "metrics")] metrics: &Arc<Metrics>,
6234    ) -> crate::Result<(Version, PooledFiles)> {
6235        use crate::{TableId, file::fsync_directory};
6236
6237        let tree_path = tree_path.as_ref();
6238
6239        let recovery = recover(
6240            tree_path,
6241            &*config.fs,
6242            config.manifest_recovery_mode,
6243            config.encryption.clone(),
6244        )?;
6245
6246        // The installed on-disk snapshot: the generation orphan cleanup
6247        // must preserve. Intermediate versions live only in the edit log, so the
6248        // latest version id (`version.id()`) has no `v{id}` file of its own.
6249        let snapshot_id = recovery.snapshot_id;
6250        let mut sweep = OpenSweep::default();
6251
6252        let mut table_map = {
6253            let mut result: crate::HashMap<
6254                TableId,
6255                (
6256                    u8, /* Level index */
6257                    Checksum,
6258                    SeqNo,
6259                    TableId, /* L0 recency */
6260                ),
6261            > = crate::HashMap::default();
6262
6263            for (level_idx, table_ids) in recovery.table_ids.iter().enumerate() {
6264                for run in table_ids {
6265                    for table in run {
6266                        #[expect(
6267                            clippy::expect_used,
6268                            reason = "there are always less than 256 levels"
6269                        )]
6270                        result.insert(
6271                            table.id,
6272                            (
6273                                level_idx
6274                                    .try_into()
6275                                    .expect("there are less than 256 levels"),
6276                                table.checksum,
6277                                table.global_seqno,
6278                                table.recency,
6279                            ),
6280                        );
6281                    }
6282                }
6283            }
6284
6285            result
6286        };
6287
6288        let cnt = table_map.len();
6289
6290        // Immutable snapshot of every table id the manifest knows. `table_map`
6291        // is drained as tables are recovered below, so it cannot answer "is this
6292        // id live?" order-independently; this set can. Used to sweep an orphaned
6293        // `.heal-attest` sidecar whose SST was retired.
6294        let manifest_ids: crate::HashSet<TableId> = table_map.keys().copied().collect();
6295
6296        log::debug!("Recovering {cnt} tables from {}", tree_path.display());
6297
6298        let progress_mod = match cnt {
6299            _ if cnt <= 20 => 1,
6300            _ if cnt <= 100 => 10,
6301            _ => 100,
6302        };
6303
6304        let mut tables = vec![];
6305        // Track recovered table IDs so duplicate sightings (via symlinks,
6306        // junctions, or case-insensitive aliases of the same directory) are
6307        // skipped rather than orphan-deleted.
6308        let mut recovered_table_ids: crate::HashSet<TableId> = crate::HashSet::default();
6309        let mut orphaned_tables: Vec<(crate::path::PathBuf, Arc<dyn crate::fs::Fs>)> = vec![];
6310        // Copies the digest PROVED stale. Left on disk they are worse than
6311        // clutter: once the winning route is removed or temporarily unmounted,
6312        // such a file is the only sighting of its id, arbitration is skipped
6313        // for want of a duplicate, and the stale generation is served instead
6314        // of the missing route being reported. They are swept only after a
6315        // winner for that id is established, so a scan that never finds one
6316        // keeps every copy it has.
6317        let mut rejected_copies: Vec<(TableId, crate::path::PathBuf, Arc<dyn crate::fs::Fs>)> =
6318            Vec::new();
6319        // Where an ambiguous id was accepted from, so a LATER sighting of it can
6320        // be judged against the winner rather than merely skipped.
6321        let mut accepted_copies: crate::HashMap<
6322            TableId,
6323            (crate::path::PathBuf, Arc<dyn crate::fs::Fs>),
6324        > = crate::HashMap::default();
6325        // First recovery failure per manifest id, kept until a later routed
6326        // folder yields a copy that opens. Whatever is left once every folder
6327        // is scanned is a table the manifest names and no copy delivers, so
6328        // its error is the open's.
6329        let mut unrecovered_sightings: crate::HashMap<TableId, crate::Error> =
6330            crate::HashMap::default();
6331        // Repair replacements whose authority could not be settled against the
6332        // damaged original beside them. Held until every folder is scanned: a
6333        // later routed copy that opens against the manifest settles it (the temp
6334        // is then provably NOT what the manifest names, so it is swept there and
6335        // then), and only an id no copy delivers surfaces its ambiguity.
6336        // A LIST, not a per-id map: routing can put a temp for the same id in
6337        // more than one folder, and each one is a file that has to be settled.
6338        let mut deferred_temps: Vec<(
6339            TableId,
6340            crate::path::PathBuf,
6341            Arc<dyn crate::fs::Fs>,
6342            crate::Error,
6343        )> = Vec::new();
6344
6345        // Scan all configured table folders (primary + level routes).
6346        let all_folders = config.all_tables_folders();
6347
6348        // One listing per folder, taken up front. The recovery loop has to know
6349        // whether an id is sighted in MORE THAN ONE routed folder BEFORE it
6350        // accepts the first sighting, and answering that later would mean
6351        // listing every folder a second time.
6352        let mut folder_scans: Vec<(
6353            &crate::path::PathBuf,
6354            &Arc<dyn crate::fs::Fs>,
6355            Vec<crate::fs::FsDirEntry>,
6356        )> = Vec::with_capacity(all_folders.len());
6357        // Ids present in more than one folder. Only these are digest-arbitrated
6358        // at open: a post-commit sweep that failed leaves an INTACT stale twin,
6359        // and `Table::recover` parses structure without re-deriving the
6360        // manifest's digest, so it opens the stale generation just as happily.
6361        // Ids sighted once cannot be ambiguous, and hashing them would turn
6362        // every open into a full read of the tree.
6363        let mut folder_sightings: crate::HashMap<TableId, usize> = crate::HashMap::default();
6364        for (table_base_folder, folder_fs) in &all_folders {
6365            if !folder_fs.exists(table_base_folder)? {
6366                folder_fs.create_dir_all(table_base_folder)?;
6367                fsync_directory(table_base_folder, &**folder_fs, config.sync_mode)?;
6368                if let Some(parent) = table_base_folder.parent() {
6369                    fsync_directory(parent, &**folder_fs, config.sync_mode)?;
6370                    if let Some(grandparent) = parent.parent() {
6371                        fsync_directory(grandparent, &**folder_fs, config.sync_mode)?;
6372                    }
6373                }
6374            }
6375
6376            // Pending repair swaps resolve FIRST: a committed swap's `{id}` file
6377            // is the superseded source until the rename lands, so adopting it
6378            // before the temp entry is processed would hand this session a
6379            // handle onto bytes the finished swap then replaces on disk.
6380            let mut dirents = folder_fs.read_dir(table_base_folder)?;
6381            dirents.sort_by_key(|e| {
6382                !matches!(
6383                    crate::file::TableDirEntry::classify(&e.file_name),
6384                    crate::file::TableDirEntry::RepairTmp(_)
6385                )
6386            });
6387            for dirent in &dirents {
6388                if let crate::file::TableDirEntry::Table(id) =
6389                    crate::file::TableDirEntry::classify(&dirent.file_name)
6390                {
6391                    *folder_sightings.entry(id).or_insert(0) += 1;
6392                }
6393            }
6394            folder_scans.push((table_base_folder, folder_fs, dirents));
6395        }
6396        let ambiguous_ids: crate::HashSet<TableId> = folder_sightings
6397            .into_iter()
6398            .filter(|(_, count)| *count > 1)
6399            .map(|(id, _)| id)
6400            .collect();
6401
6402        // The empty table files no version names, per folder: the pool each
6403        // folder kept, adopted by the tree.
6404        let mut pooled: PooledFiles = Vec::new();
6405        let mut pooled_ids: Vec<TableId> = Vec::new();
6406        for (table_base_folder, folder_fs, dirents) in folder_scans {
6407            for dirent in dirents {
6408                let crate::fs::FsDirEntry {
6409                    path: table_file_path,
6410                    file_name,
6411                    is_dir,
6412                } = dirent;
6413
6414                let table_file_name = &file_name;
6415                if is_dir {
6416                    log::warn!(
6417                        "Skipping unexpected directory in tables folder: {}",
6418                        table_file_path.display()
6419                    );
6420                    continue;
6421                }
6422
6423                // One grammar decides what each name IS (`TableDirEntry`, shared
6424                // with the repair scan so the two can never disagree on
6425                // ownership); this match is the open's POLICY for each kind.
6426                let table_id = match crate::file::TableDirEntry::classify(table_file_name) {
6427                    // An in-place heal's detach copy, renamed over the live path
6428                    // on success: a survivor is a crash leftover no manifest ever
6429                    // references. Sweep it.
6430                    crate::file::TableDirEntry::HealTmp(_) => {
6431                        log::warn!(
6432                            "Removing abandoned heal copy: {}",
6433                            table_file_path.display()
6434                        );
6435                        sweep.remove(folder_fs, &table_file_path);
6436                        continue;
6437                    }
6438                    // A crashed attestation publish: either the live sidecar it
6439                    // would have replaced still bridges the crash window, or the
6440                    // heal is re-run. Disposable.
6441                    crate::file::TableDirEntry::HealAttestTmp(_) => {
6442                        log::warn!(
6443                            "Removing abandoned heal-attest temp: {}",
6444                            table_file_path.display()
6445                        );
6446                        sweep.remove(folder_fs, &table_file_path);
6447                        continue;
6448                    }
6449                    // A LIVE table's pending attestation is preserved (the next
6450                    // scrub reconciles a crashed digest refresh through it). One
6451                    // whose SST left the manifest is unreconcilable forever:
6452                    // sweep the orphan rather than re-process it on every open.
6453                    crate::file::TableDirEntry::HealAttest(attest_id) => {
6454                        if !manifest_ids.contains(&attest_id) {
6455                            log::warn!(
6456                                "Removing orphaned heal attestation (its table is gone): {}",
6457                                table_file_path.display()
6458                            );
6459                            sweep.remove(folder_fs, &table_file_path);
6460                        }
6461                        continue;
6462                    }
6463                    // A crashed bound publish. Disposable.
6464                    crate::file::TableDirEntry::RestrictBoundTmp(_) => {
6465                        log::warn!(
6466                            "Removing abandoned restrict-bound temp: {}",
6467                            table_file_path.display()
6468                        );
6469                        sweep.remove(folder_fs, &table_file_path);
6470                        continue;
6471                    }
6472                    // A LIVE table's restriction bound is preserved (manifest
6473                    // repair reads it); an ORPHAN one is swept so a reused id
6474                    // cannot later pick up a stale restriction.
6475                    crate::file::TableDirEntry::RestrictBound(bound_id) => {
6476                        if !manifest_ids.contains(&bound_id) {
6477                            log::warn!(
6478                                "Removing orphaned restrict-bound sidecar (its table is gone): {}",
6479                                table_file_path.display()
6480                            );
6481                            sweep.remove(folder_fs, &table_file_path);
6482                        }
6483                        continue;
6484                    }
6485                    // A repair's replacement still at its temp name. The manifest
6486                    // is the authority on what it is — but it names the id in
6487                    // BOTH crash cases (before the commit its entry still
6488                    // describes the SOURCE beside the temp), so the entry's
6489                    // checksum decides: only a committed repair recorded the
6490                    // temp's digest, and it died before the swap — this file is
6491                    // what the manifest describes, so the swap is finished.
6492                    // Any other temp is an abandoned build (possibly truncated
6493                    // mid-write), and swapping it in would destroy the source
6494                    // the manifest names: it is garbage. An open resolves this
6495                    // exactly as a re-run of the repair would, from the durable
6496                    // manifest alone.
6497                    crate::file::TableDirEntry::RepairTmp(tmp_id) => {
6498                        #[cfg(feature = "std")]
6499                        {
6500                            let published = match table_map.get(&tmp_id) {
6501                                Some(&(_, manifest_checksum, _, _)) => {
6502                                    match crate::repair::repair_tmp_is_published(
6503                                        config,
6504                                        folder_fs,
6505                                        &table_file_path,
6506                                        tmp_id,
6507                                        manifest_checksum,
6508                                        recovery.restrictions.get(&tmp_id),
6509                                    ) {
6510                                        Ok(published) => published,
6511                                        // A refused mount / missing key says
6512                                        // nothing about which copy the manifest
6513                                        // names: surface it.
6514                                        Err(e) if e.is_environmental() => return Err(e),
6515                                        // Neither this temp nor the damaged
6516                                        // original beside it can prove it is the
6517                                        // manifest's copy. A LATER routed folder
6518                                        // may still hold that copy, and deciding
6519                                        // here would end the open on a leftover a
6520                                        // failed post-commit sweep left behind.
6521                                        // Defer: leave the temp untouched, keep
6522                                        // the ambiguity, and let the end of the
6523                                        // scan decide (see `deferred_temps`).
6524                                        Err(e) => {
6525                                            log::warn!(
6526                                                "repair replacement {} is ambiguous ({e}); \
6527                                                 deferring until every routed folder is scanned",
6528                                                table_file_path.display(),
6529                                            );
6530                                            deferred_temps.push((
6531                                                tmp_id,
6532                                                table_file_path,
6533                                                Arc::clone(folder_fs),
6534                                                e,
6535                                            ));
6536                                            continue;
6537                                        }
6538                                    }
6539                                }
6540                                None => false,
6541                            };
6542                            if published {
6543                                log::warn!(
6544                                    "Finishing a repair's pending swap of table {tmp_id}: {}",
6545                                    table_file_path.display()
6546                                );
6547                                crate::repair::commit_repair_tmp(
6548                                    folder_fs.as_ref(),
6549                                    &table_file_path,
6550                                    &table_base_folder.join(tmp_id.to_string()),
6551                                    config.sync_mode,
6552                                    recovery.restrictions.contains_key(&tmp_id),
6553                                )?;
6554                            } else {
6555                                log::warn!(
6556                                    "Removing abandoned repair replacement: {}",
6557                                    table_file_path.display()
6558                                );
6559                                // A RESTRICTED salvage also wrote
6560                                // `{temp}.restrict-bound`; that companion must
6561                                // go with the temp — its name classifies as
6562                                // Foreign and would fail this very open.
6563                                let companion =
6564                                    crate::restrict_bound::sidecar_path(&table_file_path);
6565                                if folder_fs.exists(&companion)? {
6566                                    sweep.remove(folder_fs, &companion);
6567                                }
6568                                sweep.remove(folder_fs, &table_file_path);
6569                            }
6570                        }
6571                        // Without the repair module nothing here can verify or
6572                        // finish a swap the manifest may describe, and sweeping
6573                        // the temp could destroy the only copy of what the
6574                        // manifest names.
6575                        #[cfg(not(feature = "std"))]
6576                        {
6577                            if manifest_ids.contains(&tmp_id) {
6578                                log::error!(
6579                                    "Table {tmp_id} exists only as an unpublished repair \
6580                                     replacement; run a repair to finish it: {}",
6581                                    table_file_path.display()
6582                                );
6583                                return Err(crate::Error::Unrecoverable);
6584                            }
6585                            log::warn!(
6586                                "Removing abandoned repair replacement: {}",
6587                                table_file_path.display()
6588                            );
6589                            // Same companion rule as the std arm above (the
6590                            // `restrict_bound` helper is std-gated, so the
6591                            // name is spelled out).
6592                            let companion = table_base_folder.join(alloc::format!(
6593                                "{tmp_id}{}.restrict-bound",
6594                                crate::file::REPAIR_TMP_SUFFIX
6595                            ));
6596                            if folder_fs.exists(&companion)? {
6597                                sweep.remove(folder_fs, &companion);
6598                            }
6599                            sweep.remove(folder_fs, &table_file_path);
6600                        }
6601                        continue;
6602                    }
6603                    // The companion's fate followed its temp when that entry
6604                    // resolved (first, by sort order): a finished swap renamed
6605                    // it into place, an abandoned build's sweep removed it. A
6606                    // survivor here is an orphan (its temp is gone) — remove
6607                    // it rather than reject the whole open over it.
6608                    crate::file::TableDirEntry::RepairTmpCompanion(_) => {
6609                        if folder_fs.exists(&table_file_path)? {
6610                            log::warn!(
6611                                "Removing orphaned repair-replacement sidecar: {}",
6612                                table_file_path.display()
6613                            );
6614                            sweep.remove(folder_fs, &table_file_path);
6615                        }
6616                        continue;
6617                    }
6618                    // Not a shape the engine names, so not engine state: passed
6619                    // over untouched. Refusing the store here would let any
6620                    // stray file (an operator's note, a backup, a desktop
6621                    // environment's directory metadata) make it unopenable,
6622                    // which is why scanners used to carry a list of foreign
6623                    // names to tolerate. The grammar answers it instead.
6624                    crate::file::TableDirEntry::Foreign => {
6625                        log::debug!(
6626                            "Ignoring {table_file_name:?} in the tables folder: not an engine file"
6627                        );
6628                        continue;
6629                    }
6630                    crate::file::TableDirEntry::Table(id) => id,
6631                };
6632
6633                // Remove from map to prevent duplicate recovery if the same
6634                // table file exists in multiple scanned folders.
6635                if let Some(entry) = table_map.remove(&table_id) {
6636                    let (level_idx, checksum, global_seqno, recency) = entry;
6637                    let pin_filter = config.filter_block_pinning_policy.get(level_idx.into());
6638                    let pin_index = config.index_block_pinning_policy.get(level_idx.into());
6639
6640                    let table = {
6641                        let mut params = crate::table::RecoverParams::new(
6642                            table_file_path.clone(),
6643                            checksum,
6644                            table_id,
6645                            folder_fs.clone(),
6646                            config.comparator.clone(),
6647                            config.cache.clone(),
6648                        );
6649                        params.global_seqno = global_seqno;
6650                        params.recency = Some(recency);
6651                        params.tree_id = tree_id;
6652                        params.descriptor_table.clone_from(&config.descriptor_table);
6653                        params.pin_filter = pin_filter;
6654                        params.pin_index = pin_index;
6655                        params.encryption.clone_from(&config.encryption);
6656                        params.prefix_extractor.clone_from(&config.prefix_extractor);
6657                        #[cfg(zstd_any)]
6658                        {
6659                            params.zstd_dictionaries = config.current_zstd_dictionaries();
6660                        }
6661                        #[cfg(feature = "metrics")]
6662                        {
6663                            params.metrics = metrics.clone();
6664                        }
6665                        match Table::recover(params) {
6666                            Ok(table) => table,
6667                            Err(e) => {
6668                                // A routed tree can hold this id in more than
6669                                // one folder, and repair's post-commit sweep of
6670                                // the copy it displaced can fail after the
6671                                // manifest is already durable. Ending the search
6672                                // on the first sighting would then shut the tree
6673                                // on a table that is present and intact one
6674                                // folder over. Put the id back, remember the
6675                                // failure, and keep scanning; if no folder
6676                                // yields the manifest's copy, this error is what
6677                                // the open reports.
6678                                log::warn!(
6679                                    "table {table_id} at {} did not recover ({e}); \
6680                                     looking for another routed copy",
6681                                    table_file_path.display(),
6682                                );
6683                                table_map.insert(table_id, entry);
6684                                unrecovered_sightings.entry(table_id).or_insert(e);
6685                                continue;
6686                            }
6687                        }
6688                    };
6689
6690                    // Opening proves the file is STRUCTURALLY sound, never that
6691                    // it is the generation the manifest committed: recover parses
6692                    // metadata and loads data blocks lazily. Where two folders
6693                    // hold this id, take the digest as the arbiter, exactly as
6694                    // repair does, so a stale twin an interrupted sweep left
6695                    // behind cannot win on scan order.
6696                    if ambiguous_ids.contains(&table_id) {
6697                        // The candidate is UNRESTRICTED here: the manifest's
6698                        // restrictions are attached when the version is built,
6699                        // which is after this scan. Digesting it whole would
6700                        // compare against a live-suffix digest and reject every
6701                        // copy of a restricted id, so the bound is supplied
6702                        // explicitly.
6703                        let live =
6704                            match table.suffix_checksum_for(recovery.restrictions.get(&table_id)) {
6705                                Ok(live) => live,
6706                                // A fault in the ENVIRONMENT says nothing about
6707                                // these bytes, so it propagates and a retry can
6708                                // re-read them.
6709                                Err(e) if e.is_environmental() => return Err(e),
6710                                // Anything else is damage to THIS copy: metadata
6711                                // can parse while a data extent is unreadable, and
6712                                // ending the scan on it would let a damaged
6713                                // displaced copy permanently block the intact one a
6714                                // folder over. Same treatment as a failed recover.
6715                                Err(e) => {
6716                                    log::warn!(
6717                                        "table {table_id} at {} could not be digested ({e}); \
6718                                     looking for another routed copy",
6719                                        table_file_path.display(),
6720                                    );
6721                                    table_map.insert(table_id, entry);
6722                                    unrecovered_sightings.entry(table_id).or_insert(e);
6723                                    continue;
6724                                }
6725                            };
6726                        if live != checksum {
6727                            log::warn!(
6728                                "table {table_id} at {} is not the generation the manifest \
6729                                 committed; looking for another routed copy",
6730                                table_file_path.display(),
6731                            );
6732                            table_map.insert(table_id, entry);
6733                            unrecovered_sightings.entry(table_id).or_insert(
6734                                crate::Error::ChecksumMismatch {
6735                                    got: live,
6736                                    expected: checksum,
6737                                },
6738                            );
6739                            rejected_copies.push((
6740                                table_id,
6741                                table_file_path,
6742                                Arc::clone(folder_fs),
6743                            ));
6744                            continue;
6745                        }
6746                        accepted_copies
6747                            .insert(table_id, (table_file_path.clone(), Arc::clone(folder_fs)));
6748                    }
6749
6750                    tables.push(table);
6751                    recovered_table_ids.insert(table_id);
6752                    unrecovered_sightings.remove(&table_id);
6753
6754                    if tables.len() % progress_mod == 0 {
6755                        log::debug!("Recovered {}/{cnt} tables", tables.len());
6756                    }
6757                } else if recovered_table_ids.contains(&table_id) {
6758                    // Duplicate sighting of an already-recovered manifest table
6759                    // (e.g., via symlink or case-insensitive alias). Never an
6760                    // orphan: that would delete the live SST. But an id the
6761                    // digest arbitrated has a KNOWN answer, so a later sighting
6762                    // is judged rather than merely skipped, or a stale twin
6763                    // scanned after the winner would outlive the open.
6764                    let stale = match accepted_copies.get(&table_id) {
6765                        Some((winner, winner_fs)) if *winner != table_file_path => {
6766                            differs_byte_for_byte(
6767                                &**winner_fs,
6768                                winner,
6769                                folder_fs.as_ref(),
6770                                &table_file_path,
6771                            )?
6772                        }
6773                        _ => false,
6774                    };
6775                    if stale {
6776                        rejected_copies.push((
6777                            table_id,
6778                            table_file_path.clone(),
6779                            Arc::clone(folder_fs),
6780                        ));
6781                    }
6782                    log::warn!(
6783                        "Skipping duplicate sighting of manifest table {table_id} in {}",
6784                        table_file_path.display(),
6785                    );
6786                } else if config.table_file_pool > 0
6787                    // The counter starts above every pooled id, so one at its
6788                    // top would leave it no id to hand out.
6789                    && table_id < crate::seqno::MAX_SEQNO
6790                    && folder_fs
6791                        .metadata(&table_file_path)
6792                        .is_ok_and(|meta| meta.is_file && meta.len == 0)
6793                    // A table is written into a pooled file where it lies: a
6794                    // symlink would take it to a file the tree never made.
6795                    && folder_fs
6796                        .read_link(&table_file_path)
6797                        .is_ok_and(|target| target.is_none())
6798                {
6799                    // No version names it and no table is empty: a pooled
6800                    // file, used again rather than made anew. Anything else
6801                    // unnamed, or whose kind cannot be read, is an orphan like
6802                    // any other.
6803                    pooled_ids.push(table_id);
6804                } else {
6805                    orphaned_tables.push((table_file_path, folder_fs.clone()));
6806                }
6807            }
6808            if !pooled_ids.is_empty() {
6809                pooled.push((
6810                    table_base_folder.clone(),
6811                    Arc::clone(folder_fs),
6812                    core::mem::take(&mut pooled_ids),
6813                ));
6814            }
6815        }
6816
6817        // Every folder is scanned, so an id that never recovered has no copy
6818        // left to try. Report the failure that stopped it — a missing-table
6819        // diagnostic would hide the reason the file on disk could not be read.
6820        if let Some((_, e)) = unrecovered_sightings
6821            .into_iter()
6822            .find(|(id, _)| table_map.contains_key(id))
6823        {
6824            return Err(e);
6825        }
6826
6827        // A deferred replacement whose id RECOVERED from some folder is settled:
6828        // that copy matched the manifest, so this temp is provably NOT what the
6829        // manifest names — an abandoned build, removed here exactly as the
6830        // in-scan branch removes one. One whose id never arrived is still
6831        // ambiguous: the temp may be the only copy of what the manifest
6832        // describes, so the ambiguity is what the open reports.
6833        #[cfg(feature = "std")]
6834        for (tmp_id, temp_path, temp_fs, ambiguity) in deferred_temps {
6835            if !recovered_table_ids.contains(&tmp_id) {
6836                return Err(ambiguity);
6837            }
6838            log::warn!(
6839                "Removing abandoned repair replacement for table {tmp_id} (the copy this open \
6840                 recovered is what the manifest names): {}",
6841                temp_path.display(),
6842            );
6843            // The restricted-salvage companion classifies as Foreign and would
6844            // fail the next open, so it goes with the temp.
6845            let companion = crate::restrict_bound::sidecar_path(&temp_path);
6846            if temp_fs.exists(&companion)? {
6847                sweep.remove(&temp_fs, &companion);
6848            }
6849            sweep.remove(&temp_fs, &temp_path);
6850        }
6851
6852        if tables.len() < cnt {
6853            // Route configuration is NOT persisted.  This is a best-effort
6854            // heuristic: it checks each missing table's level against the
6855            // current routes, but cannot detect same-level path changes
6856            // (e.g., L0 routed to /hot_old → /hot_new).  Persisting route
6857            // provenance per-table in the manifest would enable exact
6858            // detection but requires a format change.
6859            //
6860            // - Level IS covered by a current route → its directory was scanned
6861            //   and the file was not found → data corruption / deletion.
6862            // - Level is NOT covered → falls back to primary (always scanned).
6863            //   If the table isn't there, it was likely in a route that has
6864            //   since been removed from the config.
6865            //
6866            // Return RouteMismatch only when ALL missing tables are on levels
6867            // not covered by any current route.  If ANY missing table is on a
6868            // covered level, at least one SST was genuinely lost.
6869            if let Some(routes) = &config.level_routes {
6870                let all_missing_uncovered = table_map
6871                    .values()
6872                    .all(|(level, ..)| !routes.iter().any(|r| r.levels.contains(level)));
6873
6874                if all_missing_uncovered {
6875                    let found = tables.len();
6876                    let missing_ids: Vec<_> = table_map.keys().collect();
6877
6878                    log::error!(
6879                        "Route mismatch: expected {cnt} tables but found {found} — \
6880                         level_routes do not cover all previously used levels. \
6881                         Missing table IDs: {missing_ids:?}",
6882                    );
6883                    return Err(crate::Error::RouteMismatch {
6884                        expected: cnt,
6885                        found,
6886                    });
6887                }
6888            }
6889
6890            log::error!(
6891                "Recovered less tables than expected: {:?}",
6892                table_map.keys(),
6893            );
6894            return Err(crate::Error::Unrecoverable);
6895        }
6896
6897        log::debug!("Successfully recovered {} tables", tables.len());
6898
6899        // Pair each blob file with its live-data frontier: a file whose consumed
6900        // prefix was reclaimed in place records a checksum over the suffix only,
6901        // so the recovered view must carry the offset that digest starts at.
6902        let blob_ids_with_frontier: Vec<(crate::vlog::BlobFileId, crate::Checksum, u64)> = recovery
6903            .blob_file_ids
6904            .iter()
6905            .map(|&(id, checksum)| {
6906                (
6907                    id,
6908                    checksum,
6909                    recovery.blob_restrictions.get(&id).copied().unwrap_or(0),
6910                )
6911            })
6912            .collect();
6913        let (blob_files, orphaned_blob_files) = crate::vlog::recover_blob_files(
6914            &tree_path.join(crate::file::BLOBS_FOLDER),
6915            &blob_ids_with_frontier,
6916            tree_id,
6917            config.descriptor_table.as_ref(),
6918            &config.fs,
6919            #[cfg(zstd_any)]
6920            &config.current_zstd_dictionaries(),
6921        )?;
6922
6923        let version =
6924            Version::from_recovery(recovery, &tables, &blob_files, config.comparator.as_ref())?;
6925
6926        // Registered ids the tree no longer holds are dropped. One a recovered
6927        // file names cannot be among them, since that file would have refused
6928        // to open; the rest owe a reader nothing once the file is gone, while
6929        // every checkpoint copies what the version registers and would fail on
6930        // the missing file.
6931        #[cfg(zstd_any)]
6932        let version = {
6933            let held = config.current_zstd_dictionaries();
6934            if version.dicts().iter().all(|id| held.get(*id).is_some()) {
6935                version
6936            } else {
6937                let dicts = version
6938                    .dicts()
6939                    .iter()
6940                    .copied()
6941                    .filter(|id| held.get(*id).is_some())
6942                    .collect();
6943                version.with_dicts(dicts)
6944            }
6945        };
6946
6947        // The state this open recovered becomes durable before the tree is
6948        // served: a caller takes the persisted seqno of the tree it opened as
6949        // the point up to which its own log may be trimmed, and what was read
6950        // may be an edit or a snapshot name whose sync failed, which a power
6951        // loss can still take back. Syncing the files read is no proof (a
6952        // sync after a failed one may report success for pages the kernel
6953        // already dropped), so the recovered state is written as a fresh
6954        // snapshot under the next id, installed as a rotation installs one:
6955        // its bytes synced, renamed into place, the folder synced. A repair
6956        // report the tree still owes rides in the version, so the snapshot
6957        // carries it on.
6958        //
6959        // A tree whose id reached the top half of the range goes back to id
6960        // 0 instead. Recovery takes the snapshot with the highest id, so no
6961        // other may be left above the new one: every manifest file but the
6962        // snapshot read goes, durably, before the install, and that snapshot
6963        // goes, durably, right after it. A power loss in between leaves the
6964        // snapshot read as the highest, which a reopen installs again.
6965        let renumbered = version.is_renumbered_on_open();
6966        if renumbered {
6967            log::warn!(
6968                "version {} is in the top half of the id range; reinstalling the tree as \
6969                 version 0",
6970                version.id()
6971            );
6972            Self::remove_manifests_but(tree_path, snapshot_id, &config.fs, config.sync_mode)?;
6973        }
6974        let version = version.reinstalled();
6975        crate::version::persist_version(
6976            tree_path,
6977            &version,
6978            config.comparator.name(),
6979            &*config.fs,
6980            Arc::new(config.initial_runtime_config.clone()),
6981            config.encryption.clone(),
6982            config.sync_mode,
6983        )?;
6984        if renumbered {
6985            Self::remove_manifests_but(tree_path, version.id(), &config.fs, config.sync_mode)?;
6986        }
6987
6988        // Republish any restriction sidecar that never landed. The sidecar is
6989        // written AFTER its slice commits, so a failure (or a crash) in that
6990        // window leaves a committed restriction with no `.restrict-bound` file.
6991        // The manifest still describes it, so a normal open is unaffected — but
6992        // a later manifest-loss repair would find an unrestricted input beside
6993        // the slice output and publish BOTH histories, applying every merge
6994        // operand of the consumed prefix twice (operands are deliberately never
6995        // deduplicated across sources). The manifest is the authority for the
6996        // bound, so the missing sidecar is derived from it here, once the
6997        // install above made that manifest durable: derived from a manifest
6998        // still in an unsynced log, it would outlive that manifest when a
6999        // power loss takes it back, and drop an input prefix whose
7000        // replacement went with it.
7001        #[cfg(feature = "std")]
7002        Self::republish_missing_restriction_sidecars(&version, config)?;
7003
7004        // Everything of the generation read goes, now that it is superseded:
7005        // its snapshot and its log.
7006        Self::cleanup_orphaned_version(tree_path, version.id(), &config.fs, &mut sweep)?;
7007
7008        // A copy the digest rejected goes only once its id has a winner: with
7009        // one it is provably superseded, without one it may be all that is
7010        // left. Its `.restrict-bound` companion goes with it, or the next scan
7011        // classifies that name as foreign and fails the open.
7012        for (table_id, path, copy_fs) in rejected_copies {
7013            if !recovered_table_ids.contains(&table_id) {
7014                continue;
7015            }
7016            log::warn!(
7017                "Removing superseded copy of table {table_id}: {}",
7018                path.display()
7019            );
7020            #[cfg(feature = "std")]
7021            {
7022                let companion = crate::restrict_bound::sidecar_path(&path);
7023                if copy_fs.exists(&companion)? {
7024                    sweep.remove(&copy_fs, &companion);
7025                }
7026            }
7027            sweep.remove(&copy_fs, &path);
7028        }
7029
7030        for (table_path, orphan_fs) in orphaned_tables {
7031            log::debug!("Deleting orphaned table {}", table_path.display());
7032            sweep.remove(&orphan_fs, &table_path);
7033        }
7034
7035        for blob_file_path in orphaned_blob_files {
7036            log::debug!("Deleting orphaned blob file {}", blob_file_path.display());
7037            sweep.remove(&config.fs, &blob_file_path);
7038        }
7039
7040        sweep.finish()?;
7041        Ok((version, pooled))
7042    }
7043
7044    /// Writes the `.restrict-bound` sidecar of every restricted table in
7045    /// `version` that has none, so the bound the manifest holds is recoverable
7046    /// without it. An existing sidecar is REREAD rather than assumed good: its
7047    /// mere presence proves nothing, and a repair that later trusts a corrupt
7048    /// one derives a conservative bound (dropping up to one live block), while
7049    /// a valid-but-STALE one — the shape a second slice leaves when its own
7050    /// write fails — restricts LESS than reality and resurrects consumed rows.
7051    /// A sidecar that already records this table and this bound is left alone,
7052    /// so a healthy tree does not churn the file on every open. Failures
7053    /// propagate — an unrecoverable restriction is exactly what this closes, so
7054    /// silently skipping it would keep the window open.
7055    ///
7056    /// The sidecar is a filesystem artifact of the tight-space reclaim path, so
7057    /// this is a no-op without `std` — a build without it never writes one.
7058    #[cfg(feature = "std")]
7059    fn republish_missing_restriction_sidecars(
7060        version: &Version,
7061        config: &Config,
7062    ) -> crate::Result<()> {
7063        for table in version.iter_tables() {
7064            let Some(bound) = table.restrict_lower_bound() else {
7065                continue;
7066            };
7067            let recorded =
7068                crate::restrict_bound::read(&*table.fs, &table.path, config.encryption.as_deref())?;
7069            let state = match &recorded {
7070                crate::restrict_bound::SidecarRead::Present(id, recorded_bound)
7071                    if *id == table.metadata.id && recorded_bound.as_slice() == bound.as_ref() =>
7072                {
7073                    continue;
7074                }
7075                crate::restrict_bound::SidecarRead::Present(..) => {
7076                    "disagrees with the manifest (stale bound or another table)"
7077                }
7078                crate::restrict_bound::SidecarRead::Corrupt => "unreadable",
7079                crate::restrict_bound::SidecarRead::Missing => "absent",
7080            };
7081            log::warn!(
7082                "table {} carries a committed restriction whose sidecar is {state}; \
7083                 republishing it from the manifest",
7084                table.id(),
7085            );
7086            table.write_restrict_sidecar(bound, config.sync_mode)?;
7087        }
7088        Ok(())
7089    }
7090
7091    /// Removes stale manifest files left by older generations: every `v{id}`
7092    /// snapshot except the live one (`v{snapshot_id}`) and every `edits-{id}`
7093    /// log except the live snapshot's (`edits-{snapshot_id}`). A crashed
7094    /// rotation can leak an old snapshot or its log; this sweeps them on open.
7095    /// The live snapshot and log are the installed generation.
7096    ///
7097    /// # Behavior change vs pre-Fs-trait code
7098    ///
7099    /// The previous implementation used `std::fs::read_dir` + `to_string_lossy()`,
7100    /// which silently skipped non-UTF-8 filenames. `Fs::read_dir` returns
7101    /// `InvalidData` for such entries instead (see [`FsDirEntry`](crate::fs::FsDirEntry) docs), so this
7102    /// function now fails fast on non-UTF-8 names. This is intentional: version
7103    /// files are always `v{u64}` — any non-UTF-8 entry indicates filesystem
7104    /// corruption and should surface as an error rather than be silently ignored.
7105    fn cleanup_orphaned_version(
7106        path: &Path,
7107        snapshot_id: crate::version::VersionId,
7108        fs: &Arc<dyn crate::fs::Fs>,
7109        sweep: &mut OpenSweep,
7110    ) -> crate::Result<()> {
7111        use crate::version::recovery::{ManifestFile, manifest_file_of};
7112        for dirent in fs.read_dir(path)? {
7113            if dirent.is_dir {
7114                continue;
7115            }
7116            let orphan = match manifest_file_of(&dirent.file_name) {
7117                Some(ManifestFile::Snapshot(id) | ManifestFile::Log(id)) => id != snapshot_id,
7118                Some(ManifestFile::Temporary(_)) => true,
7119                None => false,
7120            };
7121            if orphan {
7122                log::trace!("Cleanup orphaned manifest file {}", dirent.file_name);
7123                sweep.remove(fs, &dirent.path);
7124            }
7125        }
7126
7127        Ok(())
7128    }
7129
7130    /// Removes every manifest file [`Self::cleanup_orphaned_version`] finds
7131    /// stale beside snapshot `keep`, at once rather than after the install,
7132    /// and makes the removals durable.
7133    ///
7134    /// The snapshots go first, durably, and only then their logs: a snapshot
7135    /// that outlives its log would be recovered without the edits in it.
7136    fn remove_manifests_but(
7137        path: &Path,
7138        keep: crate::version::VersionId,
7139        fs: &Arc<dyn crate::fs::Fs>,
7140        sync_mode: crate::fs::SyncMode,
7141    ) -> crate::Result<()> {
7142        use crate::version::recovery::{ManifestFile, manifest_file_of};
7143        let mut snapshots = OpenSweep::default();
7144        for dirent in fs.read_dir(path)? {
7145            let other = match manifest_file_of(&dirent.file_name) {
7146                Some(ManifestFile::Snapshot(id)) => id != keep,
7147                Some(ManifestFile::Temporary(_)) => true,
7148                Some(ManifestFile::Log(_)) | None => false,
7149            };
7150            if !dirent.is_dir && other {
7151                snapshots.remove(fs, &dirent.path);
7152            }
7153        }
7154        snapshots.finish()?;
7155        crate::file::fsync_directory(path, &**fs, sync_mode)?;
7156        let mut rest = OpenSweep::default();
7157        Self::cleanup_orphaned_version(path, keep, fs, &mut rest)?;
7158        rest.finish()?;
7159        crate::file::fsync_directory(path, &**fs, sync_mode)?;
7160        Ok(())
7161    }
7162}
7163
7164/// Who syncs the folders that gain an entry when an open makes a tree's
7165/// folder.
7166#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7167pub(crate) enum NewFolderNames {
7168    /// The open, before the tree's first snapshot.
7169    Synced,
7170    /// The open's caller, once for a batch of trees after every one of them
7171    /// is made.
7172    Deferred,
7173}
7174
7175/// A tree an open made, and what takes it back: its folder on its backend,
7176/// the folders that gained an entry making that folder, what the folder held
7177/// before the open when it existed, and who syncs those folders.
7178pub(crate) struct Creation {
7179    path: crate::path::PathBuf,
7180    fs: Arc<dyn Fs>,
7181    /// Nearest first; none when the tree folder existed already.
7182    holders: Vec<crate::path::PathBuf>,
7183    /// The names in the tree folder before the open; `None` when the open
7184    /// made the folder.
7185    found: Option<Vec<alloc::string::String>>,
7186    names: NewFolderNames,
7187    sync_mode: crate::fs::SyncMode,
7188}
7189
7190impl Creation {
7191    /// Takes back `tree`, which this open made: what it put in its folder goes
7192    /// while its lock is still held, then the folders the open made, once
7193    /// empty, and the folders that held their names are synced where the open
7194    /// synced them, so the tree does not come back after a power loss.
7195    pub(crate) fn take_back(self, tree: Tree) {
7196        #[cfg(feature = "std")]
7197        let lock = tree.0.directory_lock.clone();
7198        drop(tree);
7199        self.empty();
7200        #[cfg(feature = "std")]
7201        drop(lock);
7202        undo_creation(None, self.made_folders(), &[]);
7203        if self.names == NewFolderNames::Synced {
7204            sync_made_names(&self.holders_on_backend());
7205        }
7206    }
7207
7208    /// Removes what the tree put in its folder.
7209    fn empty(&self) {
7210        empty_tree_folder(&self.path, &self.fs, self.found.as_deref());
7211    }
7212
7213    /// The folders the open made, on the tree's backend.
7214    fn made_folders(&self) -> Vec<(crate::path::PathBuf, Arc<dyn Fs>)> {
7215        made_folders(&self.path, &self.holders)
7216            .into_iter()
7217            .map(|folder| (folder, Arc::clone(&self.fs)))
7218            .collect()
7219    }
7220
7221    /// The folders that gained an entry for the tree folder, on its backend.
7222    fn holders_on_backend(&self) -> Vec<(crate::path::PathBuf, Arc<dyn Fs>)> {
7223        self.holders
7224            .iter()
7225            .map(|holder| (holder.clone(), Arc::clone(&self.fs)))
7226            .collect()
7227    }
7228}
7229
7230/// The names in the folder `path`.
7231fn folder_entries(fs: &dyn Fs, path: &Path) -> crate::Result<Vec<alloc::string::String>> {
7232    Ok(fs
7233        .read_dir(path)?
7234        .into_iter()
7235        .map(|entry| entry.file_name)
7236        .collect())
7237}
7238
7239/// A tree a batch made, and while the batch runs, the tree's directory lock.
7240struct BatchMade {
7241    creation: Creation,
7242    #[cfg(feature = "std")]
7243    lock: Option<Arc<dyn crate::fs::FsFile>>,
7244}
7245
7246/// Opens a tree for each config, making the missing ones, and syncs each
7247/// folder that gained a new tree's name once, after all of them: see
7248/// [`Config::open_many`].
7249pub(crate) fn open_batch(
7250    configs: impl IntoIterator<Item = Config>,
7251) -> crate::Result<Vec<crate::AnyTree>> {
7252    let mut trees = Vec::new();
7253    let mut made: Vec<BatchMade> = Vec::new();
7254    for config in configs {
7255        let fs = Arc::clone(&config.fs);
7256        let path = config.path.clone();
7257        let sync_mode = config.sync_mode;
7258        let holders = match new_entry_holders(&*fs, &path) {
7259            Ok(holders) => holders,
7260            Err(e) => {
7261                undo_batch(trees, made, None);
7262                return Err(e);
7263            }
7264        };
7265        match config.open_with(
7266            #[cfg(feature = "std")]
7267            crate::config::DirectoryLock::Acquire,
7268            NewFolderNames::Deferred,
7269        ) {
7270            Ok((tree, creation)) => {
7271                // A tree made in a folder that existed already is the batch's
7272                // as much as one whose folder it made: it is taken back too,
7273                // though the folder stays.
7274                if let Some(creation) = creation {
7275                    made.push(BatchMade {
7276                        #[cfg(feature = "std")]
7277                        lock: batch_tree(&tree).0.directory_lock.clone(),
7278                        creation,
7279                    });
7280                }
7281                trees.push(tree);
7282            }
7283            Err(e) => {
7284                // The failed open took back what it made; the folders that
7285                // gained an entry for it are synced with the batch's.
7286                let failed = Creation {
7287                    path,
7288                    fs,
7289                    holders,
7290                    found: None,
7291                    names: NewFolderNames::Deferred,
7292                    sync_mode,
7293                };
7294                undo_batch(trees, made, Some(&failed));
7295                return Err(e);
7296            }
7297        }
7298    }
7299    let holders = batch_holders(made.iter().map(|tree| &tree.creation));
7300    for (folder, fs, mode) in holders {
7301        if let Err(e) = crate::file::fsync_directory(&folder, &*fs, mode) {
7302            undo_batch(trees, made, None);
7303            return Err(e.into());
7304        }
7305    }
7306    Ok(trees)
7307}
7308
7309/// The tree behind `tree`, whose folder holds the manifest and the lock.
7310#[cfg(feature = "std")]
7311fn batch_tree(tree: &crate::AnyTree) -> &Tree {
7312    match tree {
7313        crate::AnyTree::Standard(tree) => tree,
7314        crate::AnyTree::Blob(tree) => &tree.index,
7315    }
7316}
7317
7318/// Whether `a` and `b` name one namespace of paths: one handle, or two
7319/// handles of one backend, as configs made one by one each hold their own
7320/// handle of the real file system.
7321fn same_backend(a: &Arc<dyn Fs>, b: &Arc<dyn Fs>) -> bool {
7322    Arc::ptr_eq(a, b) || a.backend_id().is_some_and(|id| b.backend_id() == Some(id))
7323}
7324
7325/// The folders that hold the names of the trees `made`, each once, on its
7326/// backend, with the sync that makes them durable: each tree's own mode where
7327/// they agree and a full sync where they do not, and a full one under barrier
7328/// syncs for a folder on another device than a tree it holds, whose own syncs
7329/// a barrier there does not order.
7330fn batch_holders<'a>(
7331    made: impl Iterator<Item = &'a Creation>,
7332) -> Vec<(crate::path::PathBuf, Arc<dyn Fs>, crate::fs::SyncMode)> {
7333    use crate::fs::SyncMode;
7334    let mut holders: Vec<(crate::path::PathBuf, Arc<dyn Fs>, SyncMode)> = Vec::new();
7335    for tree in made {
7336        let tree_volume = tree.fs.volume_id(&tree.path);
7337        for folder in &tree.holders {
7338            let volume = tree.fs.volume_id(folder);
7339            let mode = if tree.sync_mode == SyncMode::Barrier
7340                && (volume.is_none() || volume != tree_volume)
7341            {
7342                SyncMode::Full
7343            } else {
7344                tree.sync_mode
7345            };
7346            match holders
7347                .iter_mut()
7348                .find(|(at, on, _)| at == folder && same_backend(on, &tree.fs))
7349            {
7350                Some((_, _, held)) if *held != mode => *held = SyncMode::Full,
7351                Some(_) => {}
7352                None => holders.push((folder.clone(), Arc::clone(&tree.fs), mode)),
7353            }
7354        }
7355    }
7356    holders
7357}
7358
7359/// Takes back a failed batch: closes the trees it opened, removes what each
7360/// tree it made put in its folder while still holding that tree's lock, then
7361/// the folders it made once they are empty, and makes the removals durable by
7362/// syncing every folder that held a name the batch made, `failed`'s included,
7363/// so no tree of the batch comes back after a power loss.
7364fn undo_batch(trees: Vec<crate::AnyTree>, made: Vec<BatchMade>, failed: Option<&Creation>) {
7365    let holders: Vec<(crate::path::PathBuf, Arc<dyn Fs>)> =
7366        batch_holders(made.iter().map(|tree| &tree.creation).chain(failed))
7367            .into_iter()
7368            .map(|(folder, fs, _)| (folder, fs))
7369            .collect();
7370    // The trees close their files; each made one's lock stays held by `made`.
7371    drop(trees);
7372    // Another opener taking a lock now would have its tree taken back from
7373    // under it: what each tree holds goes while its lock is held.
7374    let mut folders = Vec::new();
7375    for tree in &made {
7376        tree.creation.empty();
7377        folders.extend(tree.creation.made_folders());
7378    }
7379    // Then the locks go, and only folders left empty with them, deepest
7380    // first: one the batch made for several of its trees goes after all of
7381    // them, and one a lock no longer keeps another opener out of may hold
7382    // that opener's tree by now.
7383    #[cfg(feature = "std")]
7384    for tree in made {
7385        drop(tree.lock);
7386    }
7387    undo_creation(None, folders, &[]);
7388    sync_made_names(&holders);
7389}
7390
7391/// Removes what a tree made in the tree folder `path`, the lock file last:
7392/// everything in a folder the open made (`found` is `None`), and in one that
7393/// existed, holding `found`, only what was not there before. A tables or
7394/// blob folder that was there was empty, as a creation requires, so what it
7395/// holds now is the tree's. The lock file goes while the lock is still held,
7396/// so no opener is left holding the lock of a file another one can make
7397/// anew. What cannot be removed stays, and keeps the folder. A folder that
7398/// existed is synced, so its emptying is durable and no snapshot of the tree
7399/// comes back after a power loss.
7400fn empty_tree_folder(path: &Path, fs: &Arc<dyn Fs>, found: Option<&[alloc::string::String]>) {
7401    let entries = match fs.read_dir(path) {
7402        Ok(entries) => entries,
7403        Err(e) => {
7404            log::warn!(
7405                "could not list {} to take back the tree made there ({e})",
7406                path.display()
7407            );
7408            return;
7409        }
7410    };
7411    let (lock, rest): (Vec<_>, Vec<_>) = entries
7412        .into_iter()
7413        .partition(|entry| entry.file_name == crate::config::DIRECTORY_LOCK_FILE);
7414    let remove = |at: &Path, is_dir: bool| {
7415        let removed = if is_dir {
7416            fs.remove_dir_all(at)
7417        } else {
7418            fs.remove_file(at)
7419        };
7420        match removed {
7421            Ok(()) => {}
7422            Err(gone) if gone.kind() == crate::io::ErrorKind::NotFound => {}
7423            Err(kept) => log::warn!(
7424                "could not remove {} taking back the tree made there ({kept})",
7425                at.display()
7426            ),
7427        }
7428    };
7429    for entry in rest.into_iter().chain(lock) {
7430        let was_there = found.is_some_and(|found| found.contains(&entry.file_name));
7431        if !was_there {
7432            remove(&entry.path, entry.is_dir);
7433        } else if entry.is_dir
7434            && (entry.file_name == crate::file::TABLES_FOLDER
7435                || entry.file_name == crate::file::BLOBS_FOLDER)
7436        {
7437            match fs.read_dir(&entry.path) {
7438                Ok(inner) => {
7439                    for inner in inner {
7440                        remove(&inner.path, inner.is_dir);
7441                    }
7442                    // Files a power loss brought back here would read as a
7443                    // half-written tree to the next open.
7444                    if let Err(e) =
7445                        crate::file::fsync_directory(&entry.path, &**fs, crate::fs::SyncMode::Full)
7446                    {
7447                        log::warn!(
7448                            "could not sync {} after taking back the tree made there ({e})",
7449                            entry.path.display()
7450                        );
7451                    }
7452                }
7453                Err(e) => log::warn!(
7454                    "could not list {} to take back the tree made there ({e})",
7455                    entry.path.display()
7456                ),
7457            }
7458        }
7459    }
7460    if found.is_some()
7461        && let Err(e) = crate::file::fsync_directory(path, &**fs, crate::fs::SyncMode::Full)
7462    {
7463        log::warn!(
7464            "could not sync {} after taking back the tree made there ({e}); a power loss \
7465             may bring the tree back",
7466            path.display()
7467        );
7468    }
7469}
7470
7471/// Takes back what a failed creation made: the folders in `made`, so a retry
7472/// makes them anew and syncs the folders `holders` that gained their names;
7473/// or, where one cannot be removed, those syncs now, since a retry finds it
7474/// existing and syncs nothing for it. `tree` names the tree folder (on its
7475/// backend) when everything in it is the creation's own.
7476fn undo_creation(
7477    tree: Option<(&Path, &Arc<dyn Fs>)>,
7478    mut made: Vec<(crate::path::PathBuf, Arc<dyn Fs>)>,
7479    holders: &[(crate::path::PathBuf, Arc<dyn Fs>)],
7480) {
7481    // Deepest first, so a folder is empty when its turn comes. The tree folder
7482    // goes with what the creation put in it; any other folder is shared with
7483    // whatever else may have been put there since it was found missing, which
7484    // no lock kept out: it is removed only while empty.
7485    made.sort_by_key(|(folder, _)| core::cmp::Reverse(folder.components().count()));
7486    let mut left = false;
7487    for (folder, folder_fs) in &made {
7488        let own =
7489            tree.is_some_and(|(tree, tree_fs)| folder == tree && Arc::ptr_eq(folder_fs, tree_fs));
7490        let removed = if own {
7491            folder_fs.remove_dir_all(folder)
7492        } else {
7493            folder_fs.remove_dir(folder)
7494        };
7495        match removed {
7496            Ok(()) => {}
7497            Err(gone) if gone.kind() == crate::io::ErrorKind::NotFound => {}
7498            Err(kept) => {
7499                log::warn!(
7500                    "Tree::open: could not remove {} after a failed creation ({kept}); \
7501                     syncing the folders it made instead",
7502                    folder.display()
7503                );
7504                left = true;
7505            }
7506        }
7507    }
7508    if left {
7509        sync_made_names(holders);
7510    }
7511}
7512
7513/// Makes durable the names a failed creation leaves behind, by syncing the
7514/// folders `holders` that gained them, each with a device flush: a retry
7515/// finds those folders existing and syncs nothing for them.
7516fn sync_made_names(holders: &[(crate::path::PathBuf, Arc<dyn Fs>)]) {
7517    use crate::file::fsync_directory;
7518    use crate::fs::SyncMode;
7519    for (folder, folder_fs) in holders {
7520        if let Err(sync) = fsync_directory(folder, &**folder_fs, SyncMode::Full) {
7521            log::warn!(
7522                "Tree::open: could not sync {} after a failed creation ({sync}); a power \
7523                 loss may take back the folder it made",
7524                folder.display()
7525            );
7526        }
7527    }
7528}
7529
7530/// Every folder made on the way to `folder`, deepest first, given the folders
7531/// that gained an entry making it ([`new_entry_holders`], nearest first):
7532/// `folder` and each one above it up to, not including, the farthest of them.
7533/// Empty when nothing was made.
7534fn made_folders(folder: &Path, holders: &[crate::path::PathBuf]) -> Vec<crate::path::PathBuf> {
7535    let Some(farthest) = holders.last() else {
7536        return Vec::new();
7537    };
7538    let mut made = Vec::new();
7539    let mut at = folder;
7540    while at != farthest.as_path() {
7541        made.push(at.to_path_buf());
7542        match at.parent() {
7543            Some(parent) => at = parent,
7544            None => break,
7545        }
7546    }
7547    made
7548}
7549
7550/// The empty table files an open found that no version names, per tables
7551/// folder and its backend.
7552type PooledFiles = Vec<(
7553    crate::path::PathBuf,
7554    Arc<dyn crate::fs::Fs>,
7555    Vec<crate::TableId>,
7556)>;
7557
7558/// What an open finds no version names, removed only once the state it judged
7559/// that by is installed as a fresh snapshot.
7560///
7561/// What an open reads may be what a failed sync left: an edit in the log that
7562/// was never synced, or a snapshot name whose install or withdrawal was never
7563/// made durable. Syncing those files again proves nothing (a sync after a
7564/// failed one may report success for pages the kernel already dropped), so the
7565/// open writes the state it recovered as a new snapshot instead, and a removal
7566/// judged by that state runs after it is durable.
7567#[derive(Default)]
7568struct OpenSweep {
7569    pending: Vec<(Arc<dyn Fs>, crate::path::PathBuf)>,
7570}
7571
7572impl OpenSweep {
7573    /// Queues `path` on `fs` for removal.
7574    fn remove(&mut self, fs: &Arc<dyn Fs>, path: &Path) {
7575        self.pending.push((Arc::clone(fs), path.to_path_buf()));
7576    }
7577
7578    /// Removes every queued file, a file already gone included, and fails
7579    /// with the first removal that failed. Each removal is tried: one file
7580    /// that cannot go must not keep the others, the generation an open
7581    /// superseded among them, from going.
7582    fn finish(self) -> crate::Result<()> {
7583        let mut first = None;
7584        for (fs, path) in self.pending {
7585            match fs.remove_file(&path) {
7586                Ok(()) => {}
7587                Err(e) if e.kind() == crate::io::ErrorKind::NotFound => {}
7588                Err(e) => {
7589                    first.get_or_insert(e);
7590                }
7591            }
7592        }
7593        first.map_or(Ok(()), |e| Err(e.into()))
7594    }
7595}
7596
7597/// The folders that gain a new entry when `folder` is made on `fs`: each one
7598/// above it up to the first that exists already, which holds the first new
7599/// one. A folder that exists makes nothing new.
7600fn new_entry_holders(fs: &dyn Fs, folder: &Path) -> crate::Result<Vec<crate::path::PathBuf>> {
7601    let mut holders = Vec::new();
7602    if fs.exists(folder)? {
7603        return Ok(holders);
7604    }
7605    let mut made = folder;
7606    while let Some(holder) = made.parent() {
7607        holders.push(holder.to_path_buf());
7608        if fs.exists(holder)? {
7609            break;
7610        }
7611        made = holder;
7612    }
7613    Ok(holders)
7614}
7615
7616/// Returns `true` if any tables folder of the tree (the primary one and every
7617/// [`level_routes`](crate::Config::level_routes) one) or its `blobs/` folder
7618/// holds a file, or the tree folder holds an edit log holding an edit.
7619///
7620/// Used by [`Tree::open`], for a directory with no installed snapshot, to
7621/// distinguish a fresh directory or a creation that never completed (empty
7622/// folders, a snapshot never renamed into place: nothing of value, safe to
7623/// `create_new` over) from a lost manifest, a half-written checkpoint or
7624/// another interrupted sealing (files already placed: must error rather than
7625/// silently overwrite, and leave the files to orphan cleanup).
7626///
7627/// A missing folder is treated as "no state": `create_new` is what creates
7628/// the directories in the first place, so callers may invoke `Tree::open`
7629/// against a path that does not exist yet.
7630fn has_existing_version_state(config: &Config) -> crate::Result<bool> {
7631    let blobs = (
7632        config.path.join(crate::file::BLOBS_FOLDER),
7633        Arc::clone(&config.fs),
7634    );
7635    // Files only: a folder there holds no table itself. A level route inside
7636    // the primary tables folder is one, empty after a creation cut short or
7637    // when made in advance, and is listed here as a tables folder of its own.
7638    for (folder, fs) in config.all_tables_folders().into_iter().chain([blobs]) {
7639        match fs.read_dir(&folder) {
7640            Ok(entries) if entries.iter().any(|entry| !entry.is_dir) => return Ok(true),
7641            Ok(_) => {}
7642            Err(e) if e.kind() == crate::io::ErrorKind::NotFound => {}
7643            Err(e) => return Err(e.into()),
7644        }
7645    }
7646    // A tree's history (a clear, the retention floor it raised) outlives its
7647    // tables, and a lost snapshot leaves its log behind: an edit in any log,
7648    // or the log of any generation past the first, which only a rotation
7649    // makes, is a tree that lived. A creation cut short leaves only the
7650    // first generation's empty log.
7651    let entries = match config.fs.read_dir(&config.path) {
7652        Ok(entries) => entries,
7653        Err(e) if e.kind() == crate::io::ErrorKind::NotFound => return Ok(false),
7654        Err(e) => return Err(e.into()),
7655    };
7656    // A name is judged by its shape, digits after the prefix, not by whether
7657    // the digits parse: a snapshot whose id is past the counter's range is
7658    // no installed one, yet no creation wrote it either, and it is refused
7659    // rather than created over.
7660    let digits = |rest: Option<&str>| {
7661        rest.is_some_and(|id| !id.is_empty() && id.bytes().all(|b| b.is_ascii_digit()))
7662    };
7663    for entry in &entries {
7664        let name = entry.file_name.as_str();
7665        if digits(name.strip_prefix('v')) {
7666            return Ok(true);
7667        }
7668        if digits(name.strip_prefix("edits-"))
7669            && (name != "edits-0" || config.fs.metadata(&entry.path)?.len > 0)
7670        {
7671            return Ok(true);
7672        }
7673    }
7674    Ok(false)
7675}
7676
7677/// Whether two sightings of one table id PROVABLY hold different bytes.
7678///
7679/// Used to judge a duplicate scanned after its id was already accepted: the
7680/// winner matched the manifest, so a copy whose bytes differ from it cannot
7681/// also be the committed generation and is safe to sweep. Identical bytes mean
7682/// an alias or an exact copy, which is kept.
7683///
7684/// Only a completed comparison is proof. A read that fails on the BYTES proves
7685/// nothing about which generation this is, so it answers `false` and the file
7686/// stays; an ENVIRONMENTAL fault propagates, since a retry can re-read it.
7687///
7688/// # Errors
7689///
7690/// Propagates an environmental read failure of either file.
7691fn differs_byte_for_byte(
7692    winner_fs: &dyn crate::fs::Fs,
7693    winner: &Path,
7694    candidate_fs: &dyn crate::fs::Fs,
7695    candidate: &Path,
7696) -> crate::Result<bool> {
7697    let digest = |fs: &dyn crate::fs::Fs, path: &Path| -> crate::Result<Option<u128>> {
7698        match crate::file::checksum_from_with_overrides(fs, path, 0, &[]) {
7699            Ok(d) => Ok(Some(d)),
7700            Err(e) if e.is_environmental() => Err(e),
7701            Err(_) => Ok(None),
7702        }
7703    };
7704    let (Some(a), Some(b)) = (digest(winner_fs, winner)?, digest(candidate_fs, candidate)?) else {
7705        return Ok(false);
7706    };
7707    Ok(a != b)
7708}
7709
7710/// Raises a query's lower bound to a table's tight-space restriction, if any.
7711///
7712/// Keys below `restriction` are the punched-out prefix served by the
7713/// replacement table, so a range estimate must not charge them to the
7714/// restricted view. Returns `lo` unchanged when the table is unrestricted or
7715/// the restriction is at or below `lo`.
7716fn effective_lower_bound<'a>(
7717    lo: core::ops::Bound<&'a [u8]>,
7718    restriction: Option<&'a [u8]>,
7719    cmp: &dyn crate::comparator::UserComparator,
7720) -> core::ops::Bound<&'a [u8]> {
7721    use core::cmp::Ordering;
7722    use core::ops::Bound;
7723    match (lo, restriction) {
7724        (Bound::Unbounded, Some(rb)) => Bound::Included(rb),
7725        (Bound::Included(k) | Bound::Excluded(k), Some(rb))
7726            if cmp.compare(rb, k) == Ordering::Greater =>
7727        {
7728            Bound::Included(rb)
7729        }
7730        _ => lo,
7731    }
7732}
7733
7734#[cfg(test)]
7735mod cardinality_tests;
7736
7737#[cfg(test)]
7738#[expect(clippy::expect_used, reason = "test code")]
7739mod scan_since_freeze_tests;
7740
7741#[cfg(test)]
7742#[expect(clippy::expect_used, clippy::panic, reason = "test code")]
7743mod scan_since_scope_tests;
7744
7745#[cfg(all(test, feature = "metrics"))]
7746mod cache_stats_tests;
7747
7748#[cfg(all(test, feature = "std"))]
7749#[expect(clippy::expect_used, reason = "test code")]
7750mod restricted_reclaim_tests;
7751
7752#[cfg(all(test, feature = "std", zstd_any))]
7753mod dict_collect_tests;
7754
7755#[cfg(all(test, feature = "std", zstd_any))]
7756#[expect(clippy::expect_used, reason = "test code")]
7757mod live_compression_tests;
7758
7759#[cfg(test)]
7760#[expect(clippy::expect_used, reason = "test code")]
7761mod partition_size_tests;
7762
7763#[cfg(all(test, feature = "std"))]
7764mod chunk_order_tests;
7765
7766#[cfg(all(test, feature = "std"))]
7767mod chunk_file_tests;
7768
7769#[cfg(all(test, feature = "std"))]
7770#[expect(clippy::expect_used, reason = "test code")]
7771mod merge_read_tests;
7772
7773#[cfg(all(test, feature = "std"))]
7774mod probe_stats_tests;
7775
7776#[cfg(all(test, feature = "std"))]
7777mod filter_advisor_tests;
7778
7779#[cfg(all(test, feature = "std"))]
7780mod open_batch_tests;
7781
7782#[cfg(all(test, feature = "std"))]
7783#[expect(clippy::expect_used, reason = "test code")]
7784mod clear_race_tests;
7785
7786#[cfg(all(test, feature = "std"))]
7787#[expect(clippy::expect_used, reason = "test code")]
7788mod insert_rotation_tests;
7789
7790#[cfg(all(test, feature = "std"))]
7791#[expect(clippy::expect_used, reason = "test code")]
7792mod overlapping_runs_tests;