Skip to main content

lsm_tree/
abstract_tree.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2024-present, fjall-rs
3// Copyright (c) 2026-present, Dmitry Prudnikov
4
5use crate::tree::inner::{FlushGuard, VersionsWriteGuard};
6use crate::{
7    AnyTree, BlobTree, Config, Guard, InternalValue, KvPair, Memtable, SeqNo, TableId, Tree,
8    UserKey, UserValue,
9    iter_guard::{IterGuardImpl, SeekableGuardIter},
10    table::Table,
11    version::Version,
12    vlog::BlobFile,
13};
14use alloc::sync::Arc;
15#[cfg(not(feature = "std"))]
16use alloc::{boxed::Box, vec::Vec};
17use core::ops::RangeBounds;
18
19pub type RangeItem = crate::Result<KvPair>;
20
21type FlushToTablesResult = (Vec<Table>, Option<Vec<BlobFile>>);
22
23/// Summary of a checkpoint produced by
24/// [`AbstractTree::create_checkpoint`].
25///
26/// All byte counts are *logical* file sizes — hard links share the
27/// underlying inode storage, so a checkpoint's marginal disk usage is
28/// typically zero until the original files are compacted away.
29#[derive(Debug, Clone, Copy)]
30pub struct CheckpointInfo {
31    /// Number of SST files captured.
32    pub sst_files: usize,
33    /// Number of blob (value-log) files captured. Always `0` for a
34    /// standard [`Tree`].
35    pub blob_files: usize,
36    /// Sum of the logical file sizes of every file the checkpoint captured:
37    /// the SSTs, the blob files, and the `.restrict-bound` sidecar that belongs
38    /// to a tight-space-restricted SST (per-table state, written beside its
39    /// table, that a restore needs to recover the exact bound). Tree-level
40    /// metadata — the manifest, the version pointer, the config — is NOT
41    /// counted: this figure describes the captured data, not the whole
42    /// directory.
43    ///
44    /// It measures the same SET of files as
45    /// [`StorageStats::used_bytes`](crate::StorageStats::used_bytes), which
46    /// reports them PHYSICALLY, so the two differ by exactly the bytes a
47    /// tight-space reclaim punched out and agree everywhere else.
48    pub total_bytes: u64,
49    /// The version ID embedded in the checkpoint's `current` pointer.
50    pub version_id: u64,
51    /// Lower-bound visible-seqno watermark for the snapshot.
52    ///
53    /// Captured from the tree's `visible_seqno` generator BEFORE
54    /// [`AbstractTree::current_version`]. Following the standard
55    /// "lowest-excluded" watermark convention, `info.seqno = N` means
56    /// every record with `seqno < N` was committed at sample time and
57    /// is therefore guaranteed to be present in the snapshot. Records
58    /// with `seqno == N` may or may not be included (writers can hold
59    /// a record in the memtable for an instant before publishing the
60    /// next watermark); records with `seqno > N` may also be present
61    /// (writers can advance the counter between sample and version
62    /// snapshot, and those keys still land in the captured memtable).
63    ///
64    /// PITR consumers MUST use `seqno < info.seqno` as the inclusion
65    /// gate. Using `<=` (treating this as a max-included ceiling)
66    /// could move a recovery cutoff past data still needed from WAL
67    /// or replication; the field is a strict lower-exclusive watermark,
68    /// not a max-included ceiling.
69    pub seqno: SeqNo,
70}
71
72// Sealed on purpose: this trait is still public as a consumer-side bound
73// (`&impl AbstractTree`), but external implementations are no longer part of
74// the supported extension surface. Internal flush/version hooks keep evolving
75// with crate-owned tree types and must not create downstream semver traps.
76//
77// `sealed` stays `pub` only so sibling modules in this crate can write
78// `crate::abstract_tree::sealed::Sealed` in their impls. The parent module
79// `abstract_tree` is not publicly exported from the crate root, so downstream
80// crates still cannot name or implement this trait.
81pub mod sealed {
82    pub trait Sealed {}
83}
84
85/// Outcome of [`AbstractTree::refresh_table_checksum`].
86#[cfg(feature = "std")]
87#[doc(hidden)]
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89pub enum ChecksumRefreshOutcome {
90    /// The digest was installed into the manifest.
91    Refreshed,
92    /// Legitimate no-op: the table was compacted away, or its restriction no
93    /// longer matches the one the digest was computed for; the current view
94    /// carries its own (compaction-installed) digest for the next patrol to
95    /// reconcile.
96    Stale,
97    /// The install lock was held by a concurrent compaction (blocking on it
98    /// would invert the heal-lock / compaction-state order and deadlock). The
99    /// manifest digest stays stale against durable healed bytes: the caller
100    /// must surface this as a finding, keep the attestation, and let a later
101    /// patrol retry.
102    Contended,
103}
104
105/// Generic Tree API
106#[enum_dispatch::enum_dispatch]
107pub trait AbstractTree: sealed::Sealed {
108    /// Debug method for tracing the MVCC history of a key.
109    #[doc(hidden)]
110    fn print_trace(&self, key: &[u8]) -> crate::Result<()>;
111
112    /// Returns the number of cached table file descriptors.
113    fn table_file_cache_size(&self) -> usize;
114
115    // TODO: remove
116    #[doc(hidden)]
117    fn version_memtable_size_sum(&self) -> u64 {
118        self.get_version_history_lock().memtable_size_sum()
119    }
120
121    #[doc(hidden)]
122    fn next_table_id(&self) -> TableId;
123
124    #[doc(hidden)]
125    fn id(&self) -> crate::TreeId;
126
127    /// Like [`AbstractTree::get`], but returns the actual internal entry, not just the user value.
128    ///
129    /// Used in tests.
130    #[doc(hidden)]
131    fn get_internal_entry(&self, key: &[u8], seqno: SeqNo) -> crate::Result<Option<InternalValue>>;
132
133    #[doc(hidden)]
134    fn current_version(&self) -> Version;
135
136    /// Records that the table with `table_id` now has the digest `checksum`,
137    /// persisting it to the manifest through a version upgrade.
138    ///
139    /// Called after an in-place heal rewrites a table's bytes: the healed
140    /// file's digest no longer matches the one captured at recovery, and
141    /// without the refresh a later [`verify`](crate::verify) pass (or a
142    /// repair) would flag the healed file as corrupted against the stale
143    /// manifest digest. A no-op when the table is no longer part of the
144    /// current version (compacted away while the heal ran — the old file is
145    /// on its way out).
146    ///
147    /// `expected_restriction` is the tight-space restriction bound the CALLER
148    /// computed `checksum` for (its captured view's [`restrict_lower_bound`]).
149    /// This method holds the install lock, so it re-checks it against the CURRENT
150    /// view under that lock and refuses the install (a no-op) if a compaction
151    /// swapped the view to a different restriction meanwhile: a whole-file digest
152    /// installed into a suffix-only restricted manifest (or vice versa) could
153    /// never match the punched file.
154    ///
155    /// Returns [`ChecksumRefreshOutcome::Refreshed`] when the digest was
156    /// installed, [`ChecksumRefreshOutcome::Stale`] on a legitimate no-op (the
157    /// table was compacted away, or its restriction no longer matches
158    /// `expected_restriction` — the current view carries its own digest), and
159    /// [`ChecksumRefreshOutcome::Contended`] when the install lock was held by
160    /// a concurrent compaction. The caller uses this to decide whether to clear
161    /// the heal attestation (only `Refreshed` may) and whether the pass stayed
162    /// clean: a contended skip leaves the manifest digest stale against durable
163    /// healed bytes, which must surface as a finding rather than a clean
164    /// report.
165    ///
166    /// [`restrict_lower_bound`]: crate::table::Table::restrict_lower_bound
167    #[cfg(feature = "std")]
168    #[doc(hidden)]
169    fn refresh_table_checksum(
170        &self,
171        table_id: TableId,
172        checksum: crate::checksum::Checksum,
173        expected_restriction: Option<&crate::UserKey>,
174    ) -> crate::Result<ChecksumRefreshOutcome>;
175
176    /// The tree's configured durability mode
177    /// ([`Config::sync_mode`](crate::config::Config::sync_mode)). Maintenance
178    /// paths that write outside the flush pipeline (the in-place heal) read
179    /// it here so their syncs honor the same durability the tree's own
180    /// writes use.
181    #[doc(hidden)]
182    fn sync_mode(&self) -> crate::fs::SyncMode;
183
184    /// The tree's configured prefix extractor, or `None` when it indexes no
185    /// prefixes. The patrol scrub's filter cross-check reads it here so it
186    /// can verify a rebuilt full filter carries the source's prefix hashes,
187    /// not just its complete-key hashes.
188    #[doc(hidden)]
189    fn prefix_extractor(&self) -> Option<alloc::sync::Arc<dyn crate::prefix::PrefixExtractor>>;
190
191    /// Returns a read-only snapshot of the tree's on-disk storage footprint:
192    /// total used bytes, entry count, the average shape of a stored entry
193    /// (average key / value bytes), and an estimate of how many more
194    /// average-shaped entries fit in a byte budget (see
195    /// [`StorageStats::estimated_remaining_entries`](crate::StorageStats::estimated_remaining_entries)).
196    ///
197    /// Computed from the live version's table + blob metadata plus one
198    /// size-stat per live file; it never reads a data block. The default
199    /// implementation reports [`StorageStatus::Healthy`](crate::StorageStatus::Healthy);
200    /// the standard tree overrides it to report
201    /// [`StorageStatus::CompactionInProgress`](crate::StorageStatus::CompactionInProgress) while a
202    /// compaction runs.
203    ///
204    /// # Examples
205    ///
206    /// ```
207    /// # use lsm_tree::Error as TreeError;
208    /// use lsm_tree::{AbstractTree, Config};
209    ///
210    /// let folder = tempfile::tempdir()?;
211    /// let tree = Config::new(&folder, Default::default(), Default::default()).open()?;
212    ///
213    /// for i in 0..100u64 {
214    ///     tree.insert(format!("key{i:04}"), "value", i);
215    /// }
216    /// tree.flush_active_memtable(0)?;
217    ///
218    /// let stats = tree.storage_stats()?;
219    /// assert_eq!(stats.item_count, 100);
220    /// // Roughly how many more average-shaped entries fit in another 1 MiB.
221    /// let _headroom = stats.estimated_remaining_entries(1024 * 1024);
222    /// #
223    /// # Ok::<(), TreeError>(())
224    /// ```
225    ///
226    /// # Errors
227    ///
228    /// Returns an error if a live file's size cannot be stat-ed.
229    fn storage_stats(&self) -> crate::Result<crate::StorageStats> {
230        crate::storage_stats::compute_storage_stats(&self.current_version(), false, true)
231    }
232
233    /// Per-LSM-level and per-segment size + entry-count stats, for tiering and
234    /// erasure-coding placement decisions (which level / segment is large enough
235    /// to demote, EC-encode, or migrate).
236    ///
237    /// Cheap: derived from the live version's metadata plus one file-size stat
238    /// per segment (no data-block scan). The per-level totals reconcile with
239    /// [`storage_stats`](Self::storage_stats): summed across levels they equal
240    /// the SST portion of [`StorageStats::used_bytes`](crate::StorageStats::used_bytes)
241    /// and [`StorageStats::item_count`](crate::StorageStats::item_count).
242    ///
243    /// # Examples
244    ///
245    /// ```
246    /// # use lsm_tree::Error as TreeError;
247    /// use lsm_tree::{AbstractTree, Config};
248    ///
249    /// let folder = tempfile::tempdir()?;
250    /// let tree = Config::new(&folder, Default::default(), Default::default()).open()?;
251    /// for i in 0..100u32 {
252    ///     tree.insert(format!("k{i:04}"), "v", 0);
253    /// }
254    /// tree.flush_active_memtable(0)?;
255    ///
256    /// let levels = tree.level_segment_stats()?;
257    /// let total: u64 = levels.iter().map(|l| l.item_count).sum();
258    /// assert_eq!(total, tree.storage_stats()?.item_count);
259    /// #
260    /// # Ok::<(), TreeError>(())
261    /// ```
262    ///
263    /// # Errors
264    ///
265    /// Returns an error if a segment's file size cannot be stat-ed.
266    fn level_segment_stats(&self) -> crate::Result<Vec<crate::LevelStats>> {
267        crate::storage_stats::compute_level_segment_stats(&self.current_version())
268    }
269
270    /// Estimated bytes pending compaction under `strategy`: on-disk data above
271    /// its level's target that must eventually be rewritten downward (a `RocksDB`
272    /// `estimate-pending-compaction-bytes` analog), a compaction-debt signal for a
273    /// scheduler / tiering consumer.
274    ///
275    /// The strategy is supplied by the caller because the engine does not own a
276    /// configured compaction strategy (it is injected per compaction run); a
277    /// `&dyn` keeps this object-safe. Returns `0` for strategies without a
278    /// size-target notion of debt (FIFO, drop-range), or when the tree is at or
279    /// below its target shape. See
280    /// [`CompactionStrategy::pending_compaction_bytes`](crate::compaction::CompactionStrategy::pending_compaction_bytes).
281    fn compaction_debt(&self, strategy: &dyn crate::compaction::CompactionStrategy) -> u64 {
282        strategy.pending_compaction_bytes(&self.current_version())
283    }
284
285    /// Computed write-backpressure verdict from the live L0 table count and the
286    /// strategy's pending-compaction bytes, against the configured
287    /// [`RuntimeConfig::backpressure`](crate::runtime_config::RuntimeConfig)
288    /// thresholds.
289    ///
290    /// Advisory, mirroring [`write_admission`](Self::write_admission): the caller
291    /// consults it and throttles in its own write loop (sleep `suggested_delay`
292    /// at [`Slowdown`](crate::Backpressure::Slowdown), pause at
293    /// [`Stop`](crate::Backpressure::Stop)). The engine never blocks on it,
294    /// because it does not own the compaction that drains the debt — an internal
295    /// stall could deadlock the very thread that would compact.
296    ///
297    /// The strategy is supplied by the caller for the same reason as
298    /// [`compaction_debt`](Self::compaction_debt). Returns
299    /// [`Backpressure::None`](crate::Backpressure::None) by default (no
300    /// thresholds configured).
301    fn write_backpressure(
302        &self,
303        _strategy: &dyn crate::compaction::CompactionStrategy,
304    ) -> crate::Backpressure {
305        crate::Backpressure::None
306    }
307
308    /// Storage admission gate: `Ok(())` if a write may proceed, or
309    /// [`Error::StorageFull`](crate::Error::StorageFull) if the tree is
310    /// over budget and should be treated as read-only.
311    ///
312    /// Opt-in: returns `Ok(())` unless
313    /// [`storage_admission_check`](crate::runtime_config::RuntimeConfig::storage_admission_check)
314    /// is enabled. The predicate is computed (not latched), so once space is
315    /// freed — the budget raised, a compaction reclaiming space, or disk freed —
316    /// the next call admits again with no restart.
317    ///
318    /// Intended as a cheap pre-check the caller consults before applying a
319    /// write batch. The footprint is cached per version, so the check is a
320    /// constant-time read on the common path and only re-measures when a new
321    /// version is installed (flush / compaction). Internal flush / compaction
322    /// are never gated, so the engine can always reclaim.
323    ///
324    /// # Errors
325    ///
326    /// [`Error::StorageFull`](crate::Error::StorageFull) when the live footprint
327    /// plus reserved headroom exceeds the effective budget.
328    fn write_admission(&self) -> crate::Result<()> {
329        Ok(())
330    }
331
332    /// `true` when the admission gate is currently closed (see
333    /// [`write_admission`](Self::write_admission)). Convenience for callers that
334    /// want a boolean rather than a `Result`. Always `false` unless admission
335    /// control is enabled and the tree is over budget.
336    ///
337    /// Only [`Error::StorageFull`](crate::Error::StorageFull) counts as
338    /// read-only: an unrelated admission error (e.g. an I/O failure while
339    /// measuring the footprint) is NOT an out-of-space condition and must not be
340    /// reported as one.
341    fn is_read_only(&self) -> bool {
342        matches!(
343            self.write_admission(),
344            Err(crate::Error::StorageFull { .. })
345        )
346    }
347
348    /// Proactively verifies every block's XXH3 checksum across every SST in
349    /// the tree's current version — a scrubber for catching bit rot before it
350    /// surfaces as a user-visible read failure (cron / scrub jobs).
351    ///
352    /// Reports at block granularity and never aborts early. The returned
353    /// [`BlockVerifyReport`](crate::verify::BlockVerifyReport) records
354    /// block-corruption findings with `(file, offset)`, while file-level errors
355    /// (e.g. [`BlockVerifyError::SstFileUnreadable`](crate::verify::BlockVerifyError::SstFileUnreadable))
356    /// carry the file only (no offset). It does not surface per-entry indices or
357    /// ECC-correction counts (when ECC-at-rest is enabled a within-budget corrupt
358    /// block may still be healed on read as a side effect of the scan, but the
359    /// report does not tally corrections).
360    ///
361    /// Filesystems with native per-block integrity (ZFS, Btrfs, `ReFS`, S3 —
362    /// see [`Fs::capabilities`](crate::fs::Fs::capabilities)) already detect
363    /// corruption on read; this scrub is the portable check for the rest.
364    ///
365    /// Use [`Self::verify_checksum_with`] for parallelism / throttle control.
366    #[cfg(feature = "std")]
367    fn verify_checksum(&self) -> crate::verify::BlockVerifyReport
368    where
369        Self: Sized,
370    {
371        crate::verify::verify_block_checksums(self)
372    }
373
374    /// Like [`Self::verify_checksum`] but with configurable parallelism and
375    /// I/O throttle (see [`VerifyOptions`](crate::verify::VerifyOptions)).
376    #[cfg(feature = "std")]
377    fn verify_checksum_with(
378        &self,
379        options: &crate::verify::VerifyOptions,
380    ) -> crate::verify::BlockVerifyReport
381    where
382        Self: Sized,
383    {
384        crate::verify::verify_block_checksums_with(self, options)
385    }
386
387    #[doc(hidden)]
388    fn get_version_history_lock(&self) -> VersionsWriteGuard<'_>;
389
390    /// Creates a hard-linked checkpoint of the tree's on-disk state in
391    /// `target_path` for point-in-time recovery (PITR) backup.
392    ///
393    /// The checkpoint is a fully functional tree that can be opened
394    /// independently via [`Config::open`](crate::Config::open). For the
395    /// common single-filesystem case all SST files (and blob files, for
396    /// [`BlobTree`]) are hard-linked rather than copied, so the operation
397    /// is O(1) per file and consumes zero additional disk space until the
398    /// original files are compacted away — at which point the inode is
399    /// kept alive by the checkpoint link.
400    ///
401    /// # Cross-filesystem / cross-backend fall-back
402    ///
403    /// When a source file lives on a different filesystem than the
404    /// checkpoint target — e.g. an SST routed to a hot tier via
405    /// [`level_routes`](crate::Config::level_routes) on a separate volume,
406    /// or a backup directory on a foreign mount — the hard link cannot
407    /// be created (Unix `EXDEV`). In that case the checkpoint silently
408    /// falls back to a streamed byte copy, which:
409    ///
410    /// - takes time linear in the file size instead of O(1), and
411    /// - consumes disk space equal to the copied bytes on the target
412    ///   volume (no inode sharing across filesystems).
413    ///
414    /// Each fall-back call emits one [`log::debug`] line (deliberately not
415    /// `warn`: a misconfigured tier could trigger this path once per SST
416    /// and per blob — thousands of times per snapshot — and per-file
417    /// warnings would drown real signal). Operators wanting hard-visibility
418    /// of unexpected full copies should enable debug logging on the `fs`
419    /// module or watch the `CheckpointInfo.total_bytes` figure (≫ inode
420    /// link cost means the fallback fired). The same `debug` policy applies
421    /// when source and target use entirely different [`Fs`](crate::fs::Fs)
422    /// backends (e.g. [`MemFs`](crate::fs::MemFs) → [`StdFs`](crate::fs::StdFs)
423    /// in tests).
424    ///
425    /// # Concurrency
426    ///
427    /// While the checkpoint is being built, compaction continues normally
428    /// but the physical removal of obsolete files is deferred until the
429    /// checkpoint hard-links are in place. This is implemented by an
430    /// internal reference-counted deletion gate; callers do not have to
431    /// pause compaction themselves.
432    ///
433    /// # Errors
434    ///
435    /// Returns an error if:
436    /// - the active memtable could not be flushed,
437    /// - `target_path` already exists (to prevent accidental overwrites),
438    /// - a hard link / copy fall-back could not be created, or
439    /// - the manifest / version pointer files could not be replicated.
440    ///
441    /// On error any partial checkpoint files are removed automatically
442    /// (best-effort) so callers can safely retry against the same path.
443    // std-only: checkpoint creation hard-links / copies files via std::fs.
444    #[cfg(feature = "std")]
445    fn create_checkpoint(&self, target_path: &crate::path::Path) -> crate::Result<CheckpointInfo>;
446
447    /// Seals the active memtable and flushes to table(s).
448    ///
449    /// If there are already other sealed memtables lined up, those will be flushed as well.
450    ///
451    /// Only used in tests.
452    #[doc(hidden)]
453    fn flush_active_memtable(&self, eviction_seqno: SeqNo) -> crate::Result<()> {
454        let lock = self.get_flush_lock();
455        self.rotate_memtable();
456        self.flush(&lock, eviction_seqno)?;
457        Ok(())
458    }
459
460    /// Synchronously flushes pending sealed memtables to tables.
461    ///
462    /// Returns the sum of flushed memtable sizes that were flushed.
463    ///
464    /// The function may not return a result, if nothing was flushed.
465    ///
466    /// # Errors
467    ///
468    /// Returns `Err` on an I/O error, or on a memtable residence-verification
469    /// failure under [`KvChecksumComputePoint::AtInsert`](crate::runtime_config::KvChecksumComputePoint::AtInsert):
470    /// a sealed memtable whose insert-time per-KV digests do not verify before
471    /// flush surfaces [`crate::Error::MemtableKvChecksumMismatch`],
472    /// [`crate::Error::MemtableKvChecksumCorruptAlgorithm`], or
473    /// [`crate::Error::InvalidTag`] (corrupt `value_type`).
474    fn flush(&self, _lock: &FlushGuard<'_>, seqno_threshold: SeqNo) -> crate::Result<Option<u64>> {
475        use crate::{
476            compaction::stream::CompactionStream, merge::Merger, range_tombstone::RangeTombstone,
477        };
478
479        let version_history = self.get_version_history_lock();
480        let latest = version_history.latest_version();
481
482        if latest.sealed_memtables.len() == 0 {
483            return Ok(None);
484        }
485
486        let sealed_ids = latest
487            .sealed_memtables
488            .iter()
489            .map(|mt| mt.id)
490            .collect::<Vec<_>>();
491
492        let flushed_size = latest.sealed_memtables.iter().map(|mt| mt.size()).sum();
493
494        // AtInsert residence check: verify each sealed memtable's insert-time
495        // per-KV digests against a recompute over the entries' current bytes
496        // before writing them out. A divergence means an entry was corrupted
497        // (a RAM bit-flip) while it sat in the memtable. Memtables with no
498        // insert digests (the default) return immediately without walking.
499        //
500        // This is a SEPARATE pass over the as-inserted memtable entries, not
501        // fused into the writer's per-KV footer encode, and deliberately so:
502        // the footer digest is computed over POST-merge / post-seqno-filter
503        // bytes (the CompactionStream below applies the merge operator), so a
504        // merge operator's combined value differs from any single inserted
505        // value. Comparing the carried insert digest against the footer digest
506        // would false-positive on every legitimate merge. Residence corruption
507        // is a property of what was inserted (pre-merge); it must be checked
508        // here, against the raw memtable entries.
509        for mt in latest.sealed_memtables.iter() {
510            mt.verify_kv_residence()?;
511        }
512
513        // Collect range tombstones from sealed memtables
514        let mut range_tombstones: Vec<RangeTombstone> = Vec::new();
515        for mt in latest.sealed_memtables.iter() {
516            range_tombstones.extend(mt.range_tombstones_sorted());
517        }
518        range_tombstones
519            .sort_by(|a, b| a.cmp_with_comparator(b, self.tree_config().comparator.as_ref()));
520        range_tombstones.dedup();
521
522        let merger = Merger::new(
523            latest
524                .sealed_memtables
525                .iter()
526                .map(|mt| mt.iter().map(Ok))
527                .collect::<Vec<_>>(),
528            self.tree_config().comparator.clone(),
529        );
530        // RT suppression is not needed here: flush writes both entries and RTs
531        // to the output tables. Suppression happens at read time, not write time.
532        let stream = CompactionStream::new(merger, seqno_threshold)
533            .with_merge_operator(self.tree_config().merge_operator.clone());
534
535        drop(version_history);
536
537        // Clone needed: flush_to_tables_with_rt consumes the Vec, but on the
538        // RT-only path (no KV data, tables.is_empty()) we re-insert RTs into the
539        // active memtable. Flush is infrequent and RT count is small.
540        if let Some((tables, blob_files)) =
541            self.flush_to_tables_with_rt(stream, range_tombstones.clone())?
542        {
543            // If no tables were produced (RT-only memtable), re-insert RTs
544            // into active memtable so they aren't lost
545            if tables.is_empty() && !range_tombstones.is_empty() {
546                let active = self.active_memtable();
547                for rt in &range_tombstones {
548                    let _ =
549                        active.insert_range_tombstone(rt.start.clone(), rt.end.clone(), rt.seqno);
550                }
551            }
552
553            self.register_tables(
554                &tables,
555                blob_files.as_deref(),
556                None,
557                &sealed_ids,
558                seqno_threshold,
559            )?;
560        }
561
562        Ok(Some(flushed_size))
563    }
564
565    /// Returns an iterator that scans through the entire tree.
566    ///
567    /// Avoid using this function, or limit it as otherwise it may scan a lot of items.
568    ///
569    /// A snapshot the history no longer retains (see
570    /// [`oldest_retained_seqno`](Self::oldest_retained_seqno)) yields
571    /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention)
572    /// as the iterator's first and only item.
573    fn iter(
574        &self,
575        seqno: SeqNo,
576        index: Option<(Arc<Memtable>, SeqNo)>,
577    ) -> Box<dyn DoubleEndedIterator<Item = IterGuardImpl> + Send + 'static> {
578        self.range::<&[u8], _>(.., seqno, index)
579    }
580
581    /// Returns an iterator over a prefixed set of items.
582    ///
583    /// Avoid using an empty prefix as it may scan a lot of items (unless limited).
584    ///
585    /// A snapshot the history no longer retains (see
586    /// [`oldest_retained_seqno`](Self::oldest_retained_seqno)) yields
587    /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention)
588    /// as the iterator's first and only item.
589    fn prefix<K: AsRef<[u8]>>(
590        &self,
591        prefix: K,
592        seqno: SeqNo,
593        index: Option<(Arc<Memtable>, SeqNo)>,
594    ) -> Box<dyn DoubleEndedIterator<Item = IterGuardImpl> + Send + 'static>;
595
596    /// Returns an iterator over a range of items.
597    ///
598    /// Avoid using full or unbounded ranges as they may scan a lot of items (unless limited).
599    ///
600    /// A snapshot the history no longer retains (see
601    /// [`oldest_retained_seqno`](Self::oldest_retained_seqno)) yields
602    /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention)
603    /// as the iterator's first and only item.
604    fn range<K: AsRef<[u8]>, R: RangeBounds<K>>(
605        &self,
606        range: R,
607        seqno: SeqNo,
608        index: Option<(Arc<Memtable>, SeqNo)>,
609    ) -> Box<dyn DoubleEndedIterator<Item = IterGuardImpl> + Send + 'static>;
610
611    /// Returns a range iterator that can reposition (seek) in place without
612    /// reopening its per-SST readers.
613    ///
614    /// Unlike [`range`](Self::range)'s boxed `DoubleEndedIterator`, the returned
615    /// [`SeekableGuardIter`] additionally exposes `seek_to` / `seek_to_for_prev`,
616    /// so a consumer can jump a live iterator to any key (`RocksDB` `Seek` /
617    /// `SeekForPrev`) — enabling data-dependent scans (joins, skip-scan) without
618    /// reopening per-SST readers per jump.
619    ///
620    /// A snapshot the history no longer retains (see
621    /// [`oldest_retained_seqno`](Self::oldest_retained_seqno)) yields
622    /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention)
623    /// as the iterator's first and only item (also through `peek_key`); seeks
624    /// on such an iterator are no-ops.
625    fn range_seekable<K: AsRef<[u8]>, R: RangeBounds<K>>(
626        &self,
627        range: R,
628        seqno: SeqNo,
629        index: Option<(Arc<Memtable>, SeqNo)>,
630    ) -> Box<dyn SeekableGuardIter + 'static>;
631
632    /// Scans a sequence of disjoint, ascending key sub-intervals, reusing one set
633    /// of per-SST readers across all of them.
634    ///
635    /// The per-SST setup is paid once (when the underlying seekable iterator is
636    /// opened); each interval is served by repositioning that iterator. This
637    /// amortizes the setup across `N` intervals instead of paying it per
638    /// interval, so multi-interval scan throughput scales with the total rows
639    /// returned rather than the interval count.
640    ///
641    /// The interval source is pulled lazily, so intervals may be produced on
642    /// demand (e.g. computed from rows already returned).
643    ///
644    /// A snapshot the history no longer retains (see
645    /// [`oldest_retained_seqno`](Self::oldest_retained_seqno)) yields
646    /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention)
647    /// as the iterator's first and only item.
648    fn batch_range_scan<K: AsRef<[u8]>, R: RangeBounds<K> + 'static, I: IntoIterator<Item = R>>(
649        &self,
650        intervals: I,
651        seqno: SeqNo,
652        index: Option<(Arc<Memtable>, SeqNo)>,
653    ) -> Box<dyn Iterator<Item = IterGuardImpl> + Send + 'static>
654    where
655        I::IntoIter: Send + 'static;
656
657    /// Returns the approximate number of tombstones in the tree.
658    fn tombstone_count(&self) -> u64;
659
660    /// Returns the approximate number of weak tombstones (single deletes) in the tree.
661    fn weak_tombstone_count(&self) -> u64;
662
663    /// Returns the approximate number of values reclaimable once weak tombstones can be GC'd.
664    fn weak_tombstone_reclaimable_count(&self) -> u64;
665
666    /// Drops tables that are fully contained in a given range.
667    ///
668    /// Accepts any `RangeBounds`, including unbounded or exclusive endpoints.
669    /// If the normalized lower bound is greater than the upper bound, the
670    /// method returns without performing any work.
671    ///
672    /// # Errors
673    ///
674    /// Will return `Err` only if an IO error occurs.
675    fn drop_range<K: AsRef<[u8]>, R: RangeBounds<K>>(&self, range: R) -> crate::Result<()>;
676
677    /// Drops all tables and clears all memtables atomically.
678    ///
679    /// # Errors
680    ///
681    /// Will return `Err` only if an IO error occurs.
682    fn clear(&self) -> crate::Result<()>;
683
684    /// Performs major compaction, blocking the caller until it's done.
685    ///
686    /// Returns a [`crate::compaction::CompactionResult`] describing what action was taken.
687    ///
688    /// # Garbage-collection / merge-fold watermark (`seqno_threshold`)
689    ///
690    /// `seqno_threshold` is the MVCC garbage-collection watermark: the engine may
691    /// collapse history that no snapshot reading at a seqno `< seqno_threshold`
692    /// can still observe. Concretely, only entries whose seqno is `< seqno_threshold`
693    /// are eligible for:
694    ///
695    /// - dropping shadowed versions / GC-ing tombstones, and
696    /// - **folding merge operands** via the [`crate::MergeOperator`]: a key written
697    ///   only through [`Self::merge`] (no base value) accumulates one operand per
698    ///   call, and reads re-apply the whole chain (`O(operands)` per read) until
699    ///   compaction folds it. Folding a chain into a single value is only
700    ///   MVCC-safe when no live snapshot reads *between* the operands, which is
701    ///   exactly what `seqno_threshold` certifies.
702    ///
703    /// The engine does **not** track snapshots (unlike a `RocksDB`-style
704    /// snapshot list); the caller owns snapshot lifecycle and must supply this
705    /// watermark:
706    ///
707    /// - To fold/GC everything (no active snapshots), pass a value **above every
708    ///   live seqno** (e.g. the next value from the [`crate::SequenceNumberCounter`]).
709    /// - With outstanding snapshots, pass the **oldest** snapshot's seqno so their
710    ///   reads stay correct.
711    /// - `seqno_threshold == 0` certifies nothing as collapsible, so **no folding
712    ///   or GC happens** — `major_compact(target, 0)` only restructures tables and
713    ///   leaves a merge-only key's full operand chain intact.
714    ///
715    /// The same watermark prunes the version history: every version older
716    /// than the newest one installed below `seqno_threshold` is released
717    /// (and the tables only those versions referenced become deletable).
718    /// Afterwards a snapshot at or below the oldest retained version's seqno
719    /// (see [`oldest_retained_seqno`](Self::oldest_retained_seqno)) can no
720    /// longer be read and fails with
721    /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention),
722    /// which is why the watermark must not exceed the oldest snapshot still in
723    /// use.
724    ///
725    /// # Errors
726    ///
727    /// Will return `Err` if an IO error occurs.
728    fn major_compact(
729        &self,
730        target_size: u64,
731        seqno_threshold: SeqNo,
732    ) -> crate::Result<crate::compaction::CompactionResult>;
733
734    /// Returns the disk space used by stale blobs.
735    fn stale_blob_bytes(&self) -> u64 {
736        0
737    }
738
739    /// Gets the space usage of all filters in the tree.
740    ///
741    /// May not correspond to the actual memory size because filter blocks may be paged out.
742    fn filter_size(&self) -> u64;
743
744    /// Gets the memory usage of all pinned filters in the tree.
745    fn pinned_filter_size(&self) -> usize;
746
747    /// Gets the memory usage of all pinned index blocks in the tree.
748    fn pinned_block_index_size(&self) -> usize;
749
750    /// Gets the length of the version free list.
751    fn version_free_list_len(&self) -> usize;
752
753    /// Returns the metrics structure.
754    #[cfg(feature = "metrics")]
755    fn metrics(&self) -> &Arc<crate::Metrics>;
756
757    /// A point-in-time [`CacheStats`](crate::CacheStats) snapshot of block-cache
758    /// effectiveness (cumulative hit / miss counts and rate) and occupancy
759    /// (current size against capacity).
760    ///
761    /// The stable, owned observability view over the block cache, so a consumer
762    /// can read cache health without holding the mutable
763    /// [`metrics`](Self::metrics) handle. Counts are cumulative since process
764    /// start; derive a rate over an interval from the delta between two polls.
765    #[cfg(feature = "metrics")]
766    fn cache_stats(&self) -> crate::CacheStats;
767
768    /// Acquires the flush lock which is required to call [`Tree::flush`].
769    fn get_flush_lock(&self) -> FlushGuard<'_>;
770
771    /// Synchronously flushes a memtable to a table.
772    ///
773    /// This method will not make the table immediately available,
774    /// use [`AbstractTree::register_tables`] for that.
775    ///
776    /// # Errors
777    ///
778    /// Will return `Err` if an IO error occurs.
779    fn flush_to_tables(
780        &self,
781        stream: impl Iterator<Item = crate::Result<InternalValue>>,
782    ) -> crate::Result<Option<FlushToTablesResult>> {
783        self.flush_to_tables_with_rt(stream, Vec::new())
784    }
785
786    /// Like [`AbstractTree::flush_to_tables`], but also writes range tombstones.
787    ///
788    /// This is an internal extension hook on the crate's sealed tree types and
789    /// is hidden from generated documentation.
790    ///
791    /// # Errors
792    ///
793    /// Will return `Err` if an IO error occurs.
794    #[doc(hidden)]
795    fn flush_to_tables_with_rt(
796        &self,
797        stream: impl Iterator<Item = crate::Result<InternalValue>>,
798        range_tombstones: Vec<crate::range_tombstone::RangeTombstone>,
799    ) -> crate::Result<Option<FlushToTablesResult>>;
800
801    /// Atomically registers flushed tables into the tree, removing their associated sealed memtables.
802    ///
803    /// # Errors
804    ///
805    /// Will return `Err` if an IO error occurs.
806    fn register_tables(
807        &self,
808        tables: &[Table],
809        blob_files: Option<&[BlobFile]>,
810        frag_map: Option<crate::blob_tree::FragmentationMap>,
811        sealed_memtables_to_delete: &[crate::tree::inner::MemtableId],
812        gc_watermark: SeqNo,
813    ) -> crate::Result<()>;
814
815    /// Clears the active memtable atomically.
816    fn clear_active_memtable(&self);
817
818    /// Returns the number of sealed memtables.
819    fn sealed_memtable_count(&self) -> usize;
820
821    /// Performs compaction on the tree's levels, blocking the caller until it's done.
822    ///
823    /// Returns a [`crate::compaction::CompactionResult`] describing what action was taken.
824    ///
825    /// # Errors
826    ///
827    /// Will return `Err` if an IO error occurs.
828    fn compact(
829        &self,
830        strategy: Arc<dyn crate::compaction::CompactionStrategy>,
831        seqno_threshold: SeqNo,
832    ) -> crate::Result<crate::compaction::CompactionResult>;
833
834    /// Returns the next table's ID.
835    fn get_next_table_id(&self) -> TableId;
836
837    /// Returns the tree config.
838    fn tree_config(&self) -> &Config;
839
840    /// Returns the highest sequence number.
841    fn get_highest_seqno(&self) -> Option<SeqNo> {
842        let memtable_seqno = self.get_highest_memtable_seqno();
843        let table_seqno = self.get_highest_persisted_seqno();
844        memtable_seqno.max(table_seqno)
845    }
846
847    /// Returns the active memtable.
848    fn active_memtable(&self) -> Arc<Memtable>;
849
850    /// Returns the tree type.
851    fn tree_type(&self) -> crate::TreeType {
852        if self.tree_config().kv_separation_opts.is_some() {
853            crate::TreeType::Blob
854        } else {
855            crate::TreeType::Standard
856        }
857    }
858
859    /// Seals the active memtable.
860    fn rotate_memtable(&self) -> Option<Arc<Memtable>>;
861
862    /// Returns the number of tables currently in the tree.
863    fn table_count(&self) -> usize;
864
865    /// Returns the number of tables in `levels[idx]`.
866    ///
867    /// Returns `None` if the level does not exist (if idx >= 7).
868    fn level_table_count(&self, idx: usize) -> Option<usize>;
869
870    /// Returns the number of disjoint runs in L0.
871    ///
872    /// Can be used to determine whether to write stall.
873    fn l0_run_count(&self) -> usize;
874
875    /// Returns the number of blob files currently in the tree.
876    fn blob_file_count(&self) -> usize;
877
878    /// Approximates the number of items in the tree.
879    fn approximate_len(&self) -> usize;
880
881    /// Returns the disk space usage.
882    fn disk_space(&self) -> u64;
883
884    /// Estimates the on-disk bytes and entry count contained in `range` at
885    /// `seqno`, WITHOUT reading any data block.
886    ///
887    /// The estimate interpolates each overlapping SST's data-block offsets at
888    /// the range boundaries (block granularity) and adds the active + sealed
889    /// memtables' in-range share. For a KV-separated tree the per-SST blob
890    /// bytes are apportioned by the same in-range fraction (blob files are not
891    /// key-indexed, so a finer estimate is impossible without reading data).
892    /// Intended for query planning (split-point selection, cost-based join
893    /// ordering), not exact accounting; accuracy is typically within ~10-15%
894    /// on roughly-uniform data.
895    ///
896    /// # Examples
897    ///
898    /// ```
899    /// # use lsm_tree::Error as TreeError;
900    /// use lsm_tree::{AbstractTree, Config};
901    ///
902    /// let folder = tempfile::tempdir()?;
903    /// let tree = Config::new(&folder, Default::default(), Default::default()).open()?;
904    /// for i in 0..100u32 {
905    ///     tree.insert(format!("k{i:04}"), "value", 0);
906    /// }
907    /// tree.flush_active_memtable(0)?;
908    ///
909    /// // The full range covers every entry exactly once it is all flushed —
910    /// // estimated without reading a single data block.
911    /// let all = tree.approximate_range_stats::<&str, _>(.., 1)?;
912    /// assert_eq!(all.key_count, tree.approximate_len() as u64);
913    /// assert!(all.bytes > 0);
914    ///
915    /// // A range past every key is empty.
916    /// let none = tree.approximate_range_stats("zzzz".."zzzzz", 1)?;
917    /// assert_eq!(none.key_count, 0);
918    /// assert_eq!(none.bytes, 0);
919    /// #
920    /// # Ok::<(), TreeError>(())
921    /// ```
922    ///
923    /// # Errors
924    ///
925    /// Returns an error if a block index or table metadata read fails.
926    fn approximate_range_stats<K: AsRef<[u8]>, R: RangeBounds<K>>(
927        &self,
928        range: R,
929        seqno: SeqNo,
930    ) -> crate::Result<crate::ApproximateRangeStats>;
931
932    /// Estimates the row cardinality and selectivity of `range` at `seqno`,
933    /// WITHOUT reading any data block.
934    ///
935    /// Uses the per-data-block zone map (per-block row counts + key ranges) for a
936    /// block-granularity row count: every data block whose key range overlaps the
937    /// query contributes its recorded row count, plus the active + sealed
938    /// memtables' in-range counts. When a table has no zone map the row count
939    /// falls back to the byte-fraction estimate of [`approximate_range_stats`].
940    /// Selectivity is `rows / total_rows`, monotonic in predicate tightness, for
941    /// cost-based planning (join ordering, scan-vs-seek). Not exact accounting.
942    ///
943    /// [`approximate_range_stats`]: AbstractTree::approximate_range_stats
944    ///
945    /// # Examples
946    ///
947    /// ```
948    /// # use lsm_tree::Error as TreeError;
949    /// use lsm_tree::{AbstractTree, Config};
950    ///
951    /// let folder = tempfile::tempdir()?;
952    /// let tree = Config::new(&folder, Default::default(), Default::default()).open()?;
953    /// for i in 0..100u32 {
954    ///     tree.insert(format!("k{i:04}"), "v", 0);
955    /// }
956    /// tree.flush_active_memtable(0)?;
957    ///
958    /// // The full range covers every row; selectivity is 1.0.
959    /// let all = tree.approximate_range_cardinality::<&str, _>(.., 1)?;
960    /// assert_eq!(all.rows, tree.approximate_len() as u64);
961    /// assert!((all.selectivity - 1.0).abs() < 1e-9);
962    ///
963    /// // A tighter range selects fewer rows.
964    /// let part = tree.approximate_range_cardinality("k0000".."k0050", 1)?;
965    /// assert!(part.selectivity <= all.selectivity);
966    /// #
967    /// # Ok::<(), TreeError>(())
968    /// ```
969    ///
970    /// # Errors
971    ///
972    /// Returns an error if a block index, zone map, or table metadata read fails.
973    fn approximate_range_cardinality<K: AsRef<[u8]>, R: RangeBounds<K>>(
974        &self,
975        range: R,
976        seqno: SeqNo,
977    ) -> crate::Result<crate::RangeCardinality>;
978
979    /// Returns the highest sequence number of the active memtable.
980    fn get_highest_memtable_seqno(&self) -> Option<SeqNo>;
981
982    /// Returns the highest sequence number that is flushed to disk.
983    fn get_highest_persisted_seqno(&self) -> Option<SeqNo>;
984
985    /// Returns the seqno of the oldest version the history still retains:
986    /// the lower bound of the readable snapshot window.
987    ///
988    /// A read at snapshot `seqno` is served from the newest retained version
989    /// installed below it, so a snapshot is servable iff it is `0` (sees
990    /// nothing from any version) or strictly above this seqno; a read at
991    /// `0 < seqno <= oldest_retained_seqno()` fails with
992    /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention).
993    /// The boundary advances when [`major_compact`](Self::major_compact)
994    /// prunes the history up to its `seqno_threshold` and when
995    /// [`clear`](Self::clear) drains it, so a caller holding a long-lived
996    /// snapshot (a lagging consumer, a point-in-time query) can validate it
997    /// here before reading and report "history collected" instead of an
998    /// unexpected error mid-scan. A fresh tree reports `0`.
999    ///
1000    /// The boundary survives a reopen. The install that discards what older
1001    /// snapshots saw records it in the same version edit: after a GC
1002    /// compaction with watermark `w` the reopened boundary is `w - 1`, capped
1003    /// at the compaction's own install seqno (the retained pre-compaction
1004    /// version that served reads between the live front and `w` does not
1005    /// survive a restart), after a `clear`, a table drop or a compaction
1006    /// whose filter transformed rows it is that install's seqno. A manifest rebuilt by
1007    /// [`Config::repair`](crate::Config::repair) cannot know what the lost
1008    /// manifest recorded and seeds the boundary from
1009    /// [`Config::repair_retention_floor`](crate::Config::repair_retention_floor)
1010    /// (default `0`: every snapshot served).
1011    ///
1012    /// # Examples
1013    ///
1014    /// ```
1015    /// # let folder = tempfile::tempdir()?;
1016    /// use lsm_tree::{AbstractTree, Config, Error, SequenceNumberCounter};
1017    ///
1018    /// let seqno = SequenceNumberCounter::default();
1019    /// let tree = Config::new(folder, seqno.clone(), Default::default()).open()?;
1020    /// assert_eq!(tree.oldest_retained_seqno(), 0);
1021    ///
1022    /// tree.insert("a", "v1", seqno.next());
1023    /// tree.flush_active_memtable(0)?;
1024    /// let stale = seqno.get();
1025    /// tree.insert("a", "v2", seqno.next());
1026    /// tree.flush_active_memtable(0)?;
1027    ///
1028    /// // A watermark above every live snapshot lets compaction prune the
1029    /// // history; the boundary moves past the stale snapshot.
1030    /// tree.major_compact(u64::MAX, seqno.get())?;
1031    /// let oldest = tree.oldest_retained_seqno();
1032    /// assert!(stale <= oldest);
1033    /// assert!(matches!(
1034    ///     tree.get("a", stale),
1035    ///     Err(Error::SnapshotBelowRetention { .. })
1036    /// ));
1037    /// assert_eq!(tree.get("a", oldest + 1)?.as_deref(), Some(b"v2".as_slice()));
1038    /// #
1039    /// # Ok::<(), lsm_tree::Error>(())
1040    /// ```
1041    fn oldest_retained_seqno(&self) -> SeqNo;
1042
1043    /// Returns the PERSISTED retention boundary: the highest snapshot seqno
1044    /// the tree will refuse after a reopen.
1045    ///
1046    /// [`oldest_retained_seqno`](Self::oldest_retained_seqno) is the LIVE
1047    /// boundary, which the in-memory version history may hold below this
1048    /// one: a table drop with no GC watermark keeps its pre-drop version
1049    /// retained (and serving) until the restart, while the manifest already
1050    /// records the drop's install seqno here. The two agree after a reopen.
1051    ///
1052    /// This is the value a deployment records for a later manifest repair
1053    /// ([`Config::repair_retention_floor`](crate::Config::repair_retention_floor)):
1054    /// it already folds in every operation that advanced the boundary (GC
1055    /// compactions, `clear`, table drops, filtering compactions), so no
1056    /// caller-side bookkeeping of watermarks is needed. `0` until the first
1057    /// such install.
1058    fn retention_floor(&self) -> SeqNo;
1059
1060    /// Scans the entire tree, returning the number of items.
1061    ///
1062    /// ###### Caution
1063    ///
1064    /// This operation scans the entire tree: O(n) complexity!
1065    ///
1066    /// Never, under any circumstances, use .`len()` == 0 to check
1067    /// if the tree is empty, use [`Tree::is_empty`] instead.
1068    ///
1069    /// # Examples
1070    ///
1071    /// ```
1072    /// # use lsm_tree::Error as TreeError;
1073    /// use lsm_tree::{AbstractTree, Config, Tree};
1074    ///
1075    /// let folder = tempfile::tempdir()?;
1076    /// let tree = Config::new(&folder, Default::default(), Default::default()).open()?;
1077    ///
1078    /// assert_eq!(tree.len(0, None)?, 0);
1079    /// tree.insert("1", "abc", 0);
1080    /// tree.insert("3", "abc", 1);
1081    /// tree.insert("5", "abc", 2);
1082    /// assert_eq!(tree.len(3, None)?, 3);
1083    /// #
1084    /// # Ok::<(), TreeError>(())
1085    /// ```
1086    ///
1087    /// # Errors
1088    ///
1089    /// Will return `Err` if an IO error occurs.
1090    fn len(&self, seqno: SeqNo, index: Option<(Arc<Memtable>, SeqNo)>) -> crate::Result<usize> {
1091        let mut count = 0;
1092
1093        for item in self.iter(seqno, index) {
1094            let _ = item.key()?;
1095            count += 1;
1096        }
1097
1098        Ok(count)
1099    }
1100
1101    /// Returns `true` if the tree is empty.
1102    ///
1103    /// This operation has O(log N) complexity.
1104    ///
1105    /// # Examples
1106    ///
1107    /// ```
1108    /// # let folder = tempfile::tempdir()?;
1109    /// use lsm_tree::{AbstractTree, Config, Tree};
1110    ///
1111    /// let tree = Config::new(folder, Default::default(), Default::default()).open()?;
1112    /// assert!(tree.is_empty(0, None)?);
1113    ///
1114    /// tree.insert("a", "abc", 0);
1115    /// assert!(!tree.is_empty(1, None)?);
1116    /// #
1117    /// # Ok::<(), lsm_tree::Error>(())
1118    /// ```
1119    ///
1120    /// # Errors
1121    ///
1122    /// Will return `Err` if an IO error occurs.
1123    fn is_empty(&self, seqno: SeqNo, index: Option<(Arc<Memtable>, SeqNo)>) -> crate::Result<bool> {
1124        Ok(self
1125            .first_key_value(seqno, index)
1126            .map(crate::Guard::key)
1127            .transpose()?
1128            .is_none())
1129    }
1130
1131    /// Returns the first key-value pair in the tree.
1132    /// The key in this pair is the minimum key in the tree.
1133    ///
1134    /// # Examples
1135    ///
1136    /// ```
1137    /// # use lsm_tree::Error as TreeError;
1138    /// # use lsm_tree::{AbstractTree, Config, Tree, Guard};
1139    /// #
1140    /// # let folder = tempfile::tempdir()?;
1141    /// let tree = Config::new(folder, Default::default(), Default::default()).open()?;
1142    ///
1143    /// tree.insert("1", "abc", 0);
1144    /// tree.insert("3", "abc", 1);
1145    /// tree.insert("5", "abc", 2);
1146    ///
1147    /// let key = tree.first_key_value(3, None).expect("item should exist").key()?;
1148    /// assert_eq!(&*key, "1".as_bytes());
1149    /// #
1150    /// # Ok::<(), TreeError>(())
1151    /// ```
1152    ///
1153    /// # Errors
1154    ///
1155    /// Will return `Err` if an IO error occurs.
1156    fn first_key_value(
1157        &self,
1158        seqno: SeqNo,
1159        index: Option<(Arc<Memtable>, SeqNo)>,
1160    ) -> Option<IterGuardImpl> {
1161        self.iter(seqno, index).next()
1162    }
1163
1164    /// Returns the last key-value pair in the tree.
1165    /// The key in this pair is the maximum key in the tree.
1166    ///
1167    /// # Examples
1168    ///
1169    /// ```
1170    /// # use lsm_tree::Error as TreeError;
1171    /// # use lsm_tree::{AbstractTree, Config, Tree, Guard};
1172    /// #
1173    /// # let folder = tempfile::tempdir()?;
1174    /// # let tree = Config::new(folder, Default::default(), Default::default()).open()?;
1175    /// #
1176    /// tree.insert("1", "abc", 0);
1177    /// tree.insert("3", "abc", 1);
1178    /// tree.insert("5", "abc", 2);
1179    ///
1180    /// let key = tree.last_key_value(3, None).expect("item should exist").key()?;
1181    /// assert_eq!(&*key, "5".as_bytes());
1182    /// #
1183    /// # Ok::<(), TreeError>(())
1184    /// ```
1185    ///
1186    /// # Errors
1187    ///
1188    /// Will return `Err` if an IO error occurs.
1189    fn last_key_value(
1190        &self,
1191        seqno: SeqNo,
1192        index: Option<(Arc<Memtable>, SeqNo)>,
1193    ) -> Option<IterGuardImpl> {
1194        self.iter(seqno, index).next_back()
1195    }
1196
1197    /// Returns the size of a value if it exists.
1198    ///
1199    /// # Examples
1200    ///
1201    /// ```
1202    /// # let folder = tempfile::tempdir()?;
1203    /// use lsm_tree::{AbstractTree, Config, Tree};
1204    ///
1205    /// let tree = Config::new(folder, Default::default(), Default::default()).open()?;
1206    /// tree.insert("a", "my_value", 0);
1207    ///
1208    /// let size = tree.size_of("a", 1)?.unwrap_or_default();
1209    /// assert_eq!("my_value".len() as u32, size);
1210    ///
1211    /// let size = tree.size_of("b", 1)?.unwrap_or_default();
1212    /// assert_eq!(0, size);
1213    /// #
1214    /// # Ok::<(), lsm_tree::Error>(())
1215    /// ```
1216    ///
1217    /// # Errors
1218    ///
1219    /// Will return `Err` if an IO error occurs.
1220    fn size_of<K: AsRef<[u8]>>(&self, key: K, seqno: SeqNo) -> crate::Result<Option<u32>>;
1221
1222    /// Retrieves an item from the tree.
1223    ///
1224    /// # Examples
1225    ///
1226    /// ```
1227    /// # let folder = tempfile::tempdir()?;
1228    /// use lsm_tree::{AbstractTree, Config, Tree};
1229    ///
1230    /// let tree = Config::new(folder, Default::default(), Default::default()).open()?;
1231    /// tree.insert("a", "my_value", 0);
1232    ///
1233    /// let item = tree.get("a", 1)?;
1234    /// assert_eq!(Some("my_value".as_bytes().into()), item);
1235    /// #
1236    /// # Ok::<(), lsm_tree::Error>(())
1237    /// ```
1238    ///
1239    /// # Errors
1240    ///
1241    /// Will return `Err` if an IO error occurs, or
1242    /// [`Error::SnapshotBelowRetention`](crate::Error::SnapshotBelowRetention)
1243    /// when the history no longer retains a version for `seqno` (see
1244    /// [`oldest_retained_seqno`](Self::oldest_retained_seqno)). The same applies
1245    /// to every read that resolves a snapshot: [`get_pinned`](Self::get_pinned),
1246    /// [`contains_key`](Self::contains_key), [`size_of`](Self::size_of),
1247    /// [`multi_get`](Self::multi_get), [`len`](Self::len),
1248    /// [`is_empty`](Self::is_empty), [`first_key_value`](Self::first_key_value),
1249    /// [`last_key_value`](Self::last_key_value),
1250    /// [`approximate_range_stats`](Self::approximate_range_stats) and
1251    /// [`approximate_range_cardinality`](Self::approximate_range_cardinality).
1252    fn get<K: AsRef<[u8]>>(&self, key: K, seqno: SeqNo) -> crate::Result<Option<UserValue>>;
1253
1254    /// Retrieves an item from the tree as a [`PinnableSlice`](crate::PinnableSlice).
1255    ///
1256    /// When the value is backed by an on-disk data block, implementations
1257    /// may return [`PinnableSlice::Pinned`](crate::PinnableSlice::Pinned) holding a reference to that block's
1258    /// decompressed buffer (avoiding a data copy). Memtable and blob-resolved
1259    /// values use [`PinnableSlice::Owned`](crate::PinnableSlice::Owned). The default implementation always
1260    /// returns `Owned`; only [`Tree`] overrides with the pinned path.
1261    ///
1262    /// The existing [`AbstractTree::get`] method is unaffected.
1263    ///
1264    /// # Examples
1265    ///
1266    /// ```
1267    /// # let folder = tempfile::tempdir()?;
1268    /// use lsm_tree::{AbstractTree, Config, Tree};
1269    ///
1270    /// let tree = Config::new(&folder, Default::default(), Default::default()).open()?;
1271    /// tree.insert("a", "my_value", 0);
1272    ///
1273    /// let item = tree.get_pinned("a", 1)?;
1274    /// assert_eq!(item.as_ref().map(|v| v.as_ref()), Some("my_value".as_bytes()));
1275    /// #
1276    /// # Ok::<(), lsm_tree::Error>(())
1277    /// ```
1278    ///
1279    /// # Errors
1280    ///
1281    /// Will return `Err` if an IO error occurs.
1282    fn get_pinned<K: AsRef<[u8]>>(
1283        &self,
1284        key: K,
1285        seqno: SeqNo,
1286    ) -> crate::Result<Option<crate::PinnableSlice>> {
1287        // Default: delegate to get() and wrap as Owned
1288        self.get(key, seqno)
1289            .map(|opt| opt.map(crate::PinnableSlice::owned))
1290    }
1291
1292    /// Returns `true` if the tree contains the specified key.
1293    ///
1294    /// # Examples
1295    ///
1296    /// ```
1297    /// # let folder = tempfile::tempdir()?;
1298    /// # use lsm_tree::{AbstractTree, Config, Tree};
1299    /// #
1300    /// let tree = Config::new(folder, Default::default(), Default::default()).open()?;
1301    /// assert!(!tree.contains_key("a", 0)?);
1302    ///
1303    /// tree.insert("a", "abc", 0);
1304    /// assert!(tree.contains_key("a", 1)?);
1305    /// #
1306    /// # Ok::<(), lsm_tree::Error>(())
1307    /// ```
1308    ///
1309    /// # Errors
1310    ///
1311    /// Will return `Err` if an IO error occurs.
1312    fn contains_key<K: AsRef<[u8]>>(&self, key: K, seqno: SeqNo) -> crate::Result<bool> {
1313        self.get(key, seqno).map(|x| x.is_some())
1314    }
1315
1316    /// Returns `true` if the tree contains any key with the given prefix.
1317    ///
1318    /// This is a convenience method that checks whether the corresponding
1319    /// prefix iterator yields at least one item, while surfacing any IO
1320    /// errors via the `Result` return type. Implementations may override
1321    /// this method to provide a more efficient prefix-existence check.
1322    ///
1323    /// # Examples
1324    ///
1325    /// ```
1326    /// # let folder = tempfile::tempdir()?;
1327    /// use lsm_tree::{AbstractTree, Config, Tree};
1328    ///
1329    /// let tree = Config::new(folder, Default::default(), Default::default()).open()?;
1330    /// assert!(!tree.contains_prefix("abc", 0, None)?);
1331    ///
1332    /// tree.insert("abc:1", "value", 0);
1333    /// assert!(tree.contains_prefix("abc", 1, None)?);
1334    /// assert!(!tree.contains_prefix("xyz", 1, None)?);
1335    /// #
1336    /// # Ok::<(), lsm_tree::Error>(())
1337    /// ```
1338    ///
1339    /// # Errors
1340    ///
1341    /// Will return `Err` if an IO error occurs.
1342    fn contains_prefix<K: AsRef<[u8]>>(
1343        &self,
1344        prefix: K,
1345        seqno: SeqNo,
1346        index: Option<(Arc<Memtable>, SeqNo)>,
1347    ) -> crate::Result<bool> {
1348        match self.prefix(prefix, seqno, index).next() {
1349            Some(guard) => guard.key().map(|_| true),
1350            None => Ok(false),
1351        }
1352    }
1353
1354    /// Reads multiple keys from the tree.
1355    ///
1356    /// Implementations may choose to perform all lookups against a single
1357    /// version snapshot and acquire the version lock only once, which can be
1358    /// more efficient than calling [`AbstractTree::get`] in a loop. The
1359    /// default trait implementation, however, is a convenience wrapper that
1360    /// simply calls [`AbstractTree::get`] for each key and therefore does not
1361    /// guarantee a single-snapshot or single-lock acquisition. Optimized
1362    /// implementations (such as [`Tree`] and [`BlobTree`]) provide the
1363    /// single-snapshot/one-lock behavior.
1364    ///
1365    /// # Examples
1366    ///
1367    /// ```
1368    /// # let folder = tempfile::tempdir()?;
1369    /// use lsm_tree::{AbstractTree, Config, Tree};
1370    ///
1371    /// let tree = Config::new(folder, Default::default(), Default::default()).open()?;
1372    /// tree.insert("a", "value_a", 0);
1373    /// tree.insert("b", "value_b", 1);
1374    ///
1375    /// let results = tree.multi_get(["a", "b", "c"], 2)?;
1376    /// assert_eq!(results[0], Some("value_a".as_bytes().into()));
1377    /// assert_eq!(results[1], Some("value_b".as_bytes().into()));
1378    /// assert_eq!(results[2], None);
1379    /// #
1380    /// # Ok::<(), lsm_tree::Error>(())
1381    /// ```
1382    ///
1383    /// # Errors
1384    ///
1385    /// Will return `Err` if an IO error occurs.
1386    fn multi_get<K: AsRef<[u8]>>(
1387        &self,
1388        keys: impl IntoIterator<Item = K>,
1389        seqno: SeqNo,
1390    ) -> crate::Result<Vec<Option<UserValue>>> {
1391        keys.into_iter().map(|key| self.get(key, seqno)).collect()
1392    }
1393
1394    /// Applies a [`WriteBatch`](crate::WriteBatch) with the given sequence number.
1395    ///
1396    /// All entries share a single seqno. This is more efficient than individual
1397    /// writes because the version-history lock and memtable size accounting
1398    /// are performed only once for the entire batch.
1399    ///
1400    /// **Visibility:** entries become individually visible to concurrent readers
1401    /// as they are inserted. For atomic batch visibility, the caller must
1402    /// publish `seqno` (via `visible_seqno.fetch_max(seqno + 1)`) only
1403    /// **after** this method returns.
1404    ///
1405    /// Returns the total bytes added and new size of the memtable.
1406    ///
1407    /// # Examples
1408    ///
1409    /// ```
1410    /// # let folder = tempfile::tempdir()?;
1411    /// use lsm_tree::{AbstractTree, Config, Tree, WriteBatch};
1412    ///
1413    /// let tree = Config::new(&folder, Default::default(), Default::default()).open()?;
1414    ///
1415    /// let mut batch = WriteBatch::new();
1416    /// batch.insert("key1", "value1");
1417    /// batch.insert("key2", "value2");
1418    /// batch.remove("key3");
1419    ///
1420    /// let (bytes_added, memtable_size) = tree.apply_batch(batch, 0)?;
1421    /// assert!(bytes_added > 0);
1422    /// #
1423    /// # Ok::<(), lsm_tree::Error>(())
1424    /// ```
1425    ///
1426    /// # Errors
1427    ///
1428    /// Returns [`Error::MixedOperationBatch`](crate::Error::MixedOperationBatch)
1429    /// if the batch contains mixed operation types for the same user key.
1430    fn apply_batch(&self, batch: crate::WriteBatch, seqno: SeqNo) -> crate::Result<(u64, u64)>;
1431
1432    /// Inserts a key-value pair into the tree.
1433    ///
1434    /// If the key already exists, the item will be overwritten.
1435    ///
1436    /// Returns the added item's size and new size of the memtable.
1437    ///
1438    /// # Examples
1439    ///
1440    /// ```
1441    /// # let folder = tempfile::tempdir()?;
1442    /// use lsm_tree::{AbstractTree, Config, Tree};
1443    ///
1444    /// let tree = Config::new(folder, Default::default(), Default::default()).open()?;
1445    /// tree.insert("a", "abc", 0);
1446    /// #
1447    /// # Ok::<(), lsm_tree::Error>(())
1448    /// ```
1449    ///
1450    /// # Errors
1451    ///
1452    /// Will return `Err` if an IO error occurs.
1453    fn insert<K: Into<UserKey>, V: Into<UserValue>>(
1454        &self,
1455        key: K,
1456        value: V,
1457        seqno: SeqNo,
1458    ) -> (u64, u64);
1459
1460    /// Admission-gated [`insert`](Self::insert): consults
1461    /// [`write_admission`](Self::write_admission) first and declines with
1462    /// [`Error::StorageFull`](crate::Error::StorageFull) when the tree is over
1463    /// budget, otherwise inserts and returns the same `(added_bytes,
1464    /// memtable_size)` tuple. This is the write entry a space-aware caller uses
1465    /// so over-budget writes are refused up front rather than failing a flush
1466    /// later; bare [`insert`](Self::insert) stays infallible for callers that
1467    /// do not opt into admission control.
1468    ///
1469    /// # Errors
1470    ///
1471    /// [`Error::StorageFull`](crate::Error::StorageFull) when the admission gate
1472    /// is closed.
1473    fn try_insert<K: Into<UserKey>, V: Into<UserValue>>(
1474        &self,
1475        key: K,
1476        value: V,
1477        seqno: SeqNo,
1478    ) -> crate::Result<(u64, u64)> {
1479        self.write_admission()?;
1480        Ok(self.insert(key, value, seqno))
1481    }
1482
1483    /// Admission-gated [`merge`](Self::merge). See
1484    /// [`try_insert`](Self::try_insert).
1485    ///
1486    /// # Errors
1487    ///
1488    /// [`Error::StorageFull`](crate::Error::StorageFull) when the admission gate
1489    /// is closed.
1490    fn try_merge<K: Into<UserKey>, V: Into<UserValue>>(
1491        &self,
1492        key: K,
1493        operand: V,
1494        seqno: SeqNo,
1495    ) -> crate::Result<(u64, u64)> {
1496        self.write_admission()?;
1497        Ok(self.merge(key, operand, seqno))
1498    }
1499
1500    /// Admission-gated [`remove`](Self::remove). See
1501    /// [`try_insert`](Self::try_insert).
1502    ///
1503    /// Note a tombstone is itself a write that consumes space; an over-budget
1504    /// tree refuses it too. Reclaim space via compaction (never gated) rather
1505    /// than relying on deletes when already read-only.
1506    ///
1507    /// # Errors
1508    ///
1509    /// [`Error::StorageFull`](crate::Error::StorageFull) when the admission gate
1510    /// is closed.
1511    fn try_remove<K: Into<UserKey>>(&self, key: K, seqno: SeqNo) -> crate::Result<(u64, u64)> {
1512        self.write_admission()?;
1513        Ok(self.remove(key, seqno))
1514    }
1515
1516    /// Admission-gated [`remove_weak`](Self::remove_weak). See
1517    /// [`try_insert`](Self::try_insert).
1518    ///
1519    /// # Errors
1520    ///
1521    /// [`Error::StorageFull`](crate::Error::StorageFull) when the admission gate
1522    /// is closed.
1523    fn try_remove_weak<K: Into<UserKey>>(&self, key: K, seqno: SeqNo) -> crate::Result<(u64, u64)> {
1524        self.write_admission()?;
1525        Ok(self.remove_weak(key, seqno))
1526    }
1527
1528    /// Admission-gated [`remove_range`](Self::remove_range). See
1529    /// [`try_insert`](Self::try_insert).
1530    ///
1531    /// # Errors
1532    ///
1533    /// [`Error::StorageFull`](crate::Error::StorageFull) when the admission gate
1534    /// is closed.
1535    fn try_remove_range<K: Into<UserKey>>(
1536        &self,
1537        start: K,
1538        end: K,
1539        seqno: SeqNo,
1540    ) -> crate::Result<u64> {
1541        self.write_admission()?;
1542        Ok(self.remove_range(start, end, seqno))
1543    }
1544
1545    /// Removes an item from the tree.
1546    ///
1547    /// Returns the added item's size and new size of the memtable.
1548    ///
1549    /// # Examples
1550    ///
1551    /// ```
1552    /// # let folder = tempfile::tempdir()?;
1553    /// # use lsm_tree::{AbstractTree, Config, Tree};
1554    /// #
1555    /// # let tree = Config::new(folder, Default::default(), Default::default()).open()?;
1556    /// tree.insert("a", "abc", 0);
1557    ///
1558    /// let item = tree.get("a", 1)?.expect("should have item");
1559    /// assert_eq!("abc".as_bytes(), &*item);
1560    ///
1561    /// tree.remove("a", 1);
1562    ///
1563    /// let item = tree.get("a", 2)?;
1564    /// assert_eq!(None, item);
1565    /// #
1566    /// # Ok::<(), lsm_tree::Error>(())
1567    /// ```
1568    ///
1569    /// # Errors
1570    ///
1571    /// Will return `Err` if an IO error occurs.
1572    fn remove<K: Into<UserKey>>(&self, key: K, seqno: SeqNo) -> (u64, u64);
1573
1574    /// Writes a merge operand for a key.
1575    ///
1576    /// The operand is stored as a partial update that will be combined with
1577    /// other operands and/or a base value via the configured [`crate::MergeOperator`]
1578    /// during reads and compaction.
1579    ///
1580    /// Returns the added item's size and new size of the memtable.
1581    ///
1582    /// # Examples
1583    ///
1584    /// ```
1585    /// # let folder = tempfile::tempdir()?;
1586    /// # use lsm_tree::{AbstractTree, Config, MergeOperator, UserValue};
1587    /// # use std::sync::Arc;
1588    /// # struct SumMerge;
1589    /// # impl MergeOperator for SumMerge {
1590    /// #     fn merge(&self, _key: &[u8], base: Option<&[u8]>, operands: &[&[u8]]) -> lsm_tree::Result<UserValue> {
1591    /// #         let mut sum: i64 = base.map_or(0, |b| i64::from_le_bytes(b.try_into().unwrap()));
1592    /// #         for op in operands { sum += i64::from_le_bytes((*op).try_into().unwrap()); }
1593    /// #         Ok(sum.to_le_bytes().to_vec().into())
1594    /// #     }
1595    /// # }
1596    /// # let tree = Config::new(folder, Default::default(), Default::default())
1597    /// #     .with_merge_operator(Some(Arc::new(SumMerge)))
1598    /// #     .open()?;
1599    /// tree.merge("counter", 1_i64.to_le_bytes(), 0);
1600    /// # Ok::<(), lsm_tree::Error>(())
1601    /// ```
1602    fn merge<K: Into<UserKey>, V: Into<UserValue>>(
1603        &self,
1604        key: K,
1605        operand: V,
1606        seqno: SeqNo,
1607    ) -> (u64, u64);
1608
1609    /// Removes an item from the tree.
1610    ///
1611    /// The tombstone marker of this delete operation will vanish when it
1612    /// collides with its corresponding insertion.
1613    /// This may cause older versions of the value to be resurrected, so it should
1614    /// only be used and preferred in scenarios where a key is only ever written once.
1615    ///
1616    /// Returns the added item's size and new size of the memtable.
1617    ///
1618    /// # Examples
1619    ///
1620    /// ```
1621    /// # let folder = tempfile::tempdir()?;
1622    /// # use lsm_tree::{AbstractTree, Config, Tree};
1623    /// #
1624    /// # let tree = Config::new(folder, Default::default(), Default::default()).open()?;
1625    /// tree.insert("a", "abc", 0);
1626    ///
1627    /// let item = tree.get("a", 1)?.expect("should have item");
1628    /// assert_eq!("abc".as_bytes(), &*item);
1629    ///
1630    /// tree.remove_weak("a", 1);
1631    ///
1632    /// let item = tree.get("a", 2)?;
1633    /// assert_eq!(None, item);
1634    /// #
1635    /// # Ok::<(), lsm_tree::Error>(())
1636    /// ```
1637    ///
1638    /// # Errors
1639    ///
1640    /// Will return `Err` if an IO error occurs.
1641    #[doc(hidden)]
1642    fn remove_weak<K: Into<UserKey>>(&self, key: K, seqno: SeqNo) -> (u64, u64);
1643
1644    /// Deletes all keys in the range `[start, end)` by inserting a range tombstone.
1645    ///
1646    /// This is much more efficient than deleting keys individually when
1647    /// removing a contiguous range of keys.
1648    ///
1649    /// Returns the approximate size added to the memtable.
1650    /// Returns 0 if `start >= end` (invalid interval is silently ignored).
1651    ///
1652    /// This is a required method on the crate's sealed tree types.
1653    fn remove_range<K: Into<UserKey>>(&self, start: K, end: K, seqno: SeqNo) -> u64;
1654
1655    /// Deletes all keys with the given prefix by inserting a range tombstone.
1656    ///
1657    /// This is sugar over [`AbstractTree::remove_range`] using prefix bounds.
1658    ///
1659    /// Returns the approximate size added to the memtable.
1660    /// Returns 0 for empty prefixes or all-`0xFF` prefixes (cannot form valid half-open range).
1661    fn remove_prefix<K: AsRef<[u8]>>(&self, prefix: K, seqno: SeqNo) -> u64 {
1662        use crate::range::prefix_to_range;
1663        use core::ops::Bound;
1664
1665        let (lo, hi) = prefix_to_range(prefix.as_ref());
1666
1667        let Bound::Included(start) = lo else { return 0 };
1668
1669        // Bound::Unbounded means the prefix is all 0xFF — no representable
1670        // exclusive upper bound exists, so we cannot form a valid range tombstone.
1671        let Bound::Excluded(end) = hi else { return 0 };
1672
1673        self.remove_range(start, end, seqno)
1674    }
1675}