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