Skip to main content

slatedb/
db.rs

1//! This module provides the core database functionality for SlateDB.
2//! It provides methods for reading and writing to the database, as well as for flushing the database to disk.
3//!
4//! The `Db` struct represents a database.
5//!
6//! # Examples
7//!
8//! Basic usage of the `Db` struct:
9//!
10//! ```
11//! use slatedb::{Db, Error};
12//! use slatedb::object_store::memory::InMemory;
13//! use std::sync::Arc;
14//!
15//! #[tokio::main]
16//! async fn main() -> Result<(), Error> {
17//!     let object_store = Arc::new(InMemory::new());
18//!     let db = Db::open("test_db", object_store).await?;
19//!     Ok(())
20//! }
21//! ```
22
23pub use crate::db_status::{DbStatus, SegmentPrefix};
24
25use crate::db_cache::CacheTarget;
26use crate::db_cache_manager;
27use std::ops::Range;
28use std::sync::Arc;
29
30use bytes::Bytes;
31use fail_parallel::{fail_point, FailPointRegistry};
32use object_store::path::Path;
33use object_store::{parse_url_opts, ObjectStore};
34
35use crate::compactor::COMPACTOR_TASK_NAME;
36use crate::db_transaction::DbTransaction;
37use crate::dispatcher::MessageHandlerExecutor;
38use crate::garbage_collector::GC_TASK_NAME;
39use crate::transaction_manager::IsolationLevel;
40use crate::CloseReason;
41use log::{debug, info, trace, warn};
42use parking_lot::RwLock;
43use std::time::Duration;
44
45use crate::batch::WriteBatch;
46use crate::batch_write::{BatchWriterMessage, WriteBatchRequest, WRITE_BATCH_TASK_NAME};
47use crate::bytes_range::{ByteRangeBounds, BytesRange};
48use crate::cached_object_store::CachedObjectStore;
49use crate::clock::MonotonicClock;
50use crate::config::{
51    FlushOptions, FlushType, MergeOptions, PutOptions, ReadOptions, ScanOptions, Settings,
52    WriteOptions,
53};
54use crate::db_common::extract_segment_prefix;
55use crate::db_iter::{DbIterator, DbRecencyIterator};
56use crate::db_snapshot::DbSnapshot;
57use crate::db_state::{collect_touched_segments, DbState, SsTableId};
58use crate::db_stats::DbStats;
59use crate::error::SlateDBError;
60use crate::iter::IterationOrder;
61use crate::manifest::{Manifest, VersionedManifest};
62use crate::mem_table::KVTableMetadata;
63use crate::memtable_flusher::{FlushResult, FlushTarget, MemtableFlusher};
64use crate::merge_operator::{instrument_merge_operator, MergeOperatorType};
65use crate::oracle::{DbOracle, Oracle};
66use crate::paths::PathResolver;
67use crate::prefix_extractor::PrefixExtractor;
68use crate::reader::{Reader, ScanContext};
69use crate::snapshot_manager::SnapshotManager;
70use crate::sst_iter::SstIteratorOptions;
71use crate::tablestore::TableStore;
72use crate::transaction_manager::TransactionManager;
73use crate::types::KeyValue;
74use crate::utils::{format_bytes_si, SafeSender, WatchableOnceCellReader};
75use crate::wal_replay::{WalReplayIterator, WalReplayOptions};
76use crate::{DbCacheManagerOps, DbMetadataOps, DbReadOps, DbWriteOps};
77use slatedb_common::clock::SystemClock;
78use slatedb_common::metrics::MetricsRecorderHelper;
79use slatedb_common::DbRand;
80use slatedb_txn_obj::DirtyObject;
81
82use crate::db_status::{ClosedResultWriter, DbStatusManager};
83use crate::wal::{WalEvent, WalObserver, WalStatus};
84pub use builder::DbBuilder;
85pub use builder::DbReaderBuilder;
86
87pub(crate) mod builder;
88
89pub(crate) struct DbInner {
90    pub(crate) state: Arc<RwLock<DbState>>,
91    pub(crate) settings: Settings,
92    pub(crate) table_store: Arc<TableStore>,
93    pub(crate) memtable_flusher: Arc<MemtableFlusher>,
94    pub(crate) write_notifier: SafeSender<BatchWriterMessage>,
95    pub(crate) db_stats: DbStats,
96    /// Kept alive so the underlying `MetricsRecorder` is not dropped while
97    /// metric handles in `DbStats` (and other stats structs) are still in use.
98    /// See: https://github.com/slatedb/slatedb/issues/1469
99    #[allow(dead_code)]
100    pub(crate) recorder: MetricsRecorderHelper,
101    #[allow(dead_code)]
102    pub(crate) fp_registry: Arc<FailPointRegistry>,
103    /// A clock which is guaranteed to be monotonic. it's previous value is
104    /// stored in the manifest and WAL, will be updated after WAL replay.
105    pub(crate) mono_clock: Arc<MonotonicClock>,
106    pub(crate) system_clock: Arc<dyn SystemClock>,
107    pub(crate) rand: Arc<DbRand>,
108    pub(crate) oracle: Arc<DbOracle>,
109    pub(crate) flush_merge_operator: Option<MergeOperatorType>,
110    pub(crate) reader: Reader,
111    /// [`wal_observer`] inspects the status of WAL buffer. The WAL buffer itself is owned by
112    /// the batch write task.
113    pub(crate) wal_observer: DbWalObserver,
114    pub(crate) wal_enabled: bool,
115    /// [`txn_manager`] tracks all the live transactions and related metadata.
116    pub(crate) txn_manager: Arc<TransactionManager>,
117    pub(crate) snapshot_manager: Arc<SnapshotManager>,
118    pub(crate) status_manager: Arc<DbStatusManager>,
119    /// Segment extractor (RFC-0024). When `Some`, the writer routes every
120    /// key through this extractor and groups flush output into per-segment
121    /// L0 SSTs. When `None`, the database is the singleton `prefix=""`
122    /// segment encoded in the manifest's top-level tree.
123    pub(crate) segment_extractor: Option<Arc<dyn PrefixExtractor>>,
124}
125
126impl DbInner {
127    pub(crate) async fn new(
128        settings: Settings,
129        system_clock: Arc<dyn SystemClock>,
130        rand: Arc<DbRand>,
131        table_store: Arc<TableStore>,
132        manifest: DirtyObject<Manifest>,
133        memtable_flusher: Arc<MemtableFlusher>,
134        write_notifier: SafeSender<BatchWriterMessage>,
135        wal_observer: Box<dyn WalObserver>,
136        recorder: MetricsRecorderHelper,
137        fp_registry: Arc<FailPointRegistry>,
138        merge_operator: Option<crate::merge_operator::MergeOperatorType>,
139        status_manager: Arc<DbStatusManager>,
140        segment_extractor: Option<Arc<dyn PrefixExtractor>>,
141    ) -> Result<Self, SlateDBError> {
142        // both last_seq and last_committed_seq will be updated after WAL replay.
143        let last_l0_seq = manifest.value.core.last_l0_seq;
144        let oracle = Arc::new(DbOracle::new(
145            last_l0_seq,
146            last_l0_seq,
147            last_l0_seq,
148            status_manager.clone(),
149        ));
150
151        let mono_clock = Arc::new(MonotonicClock::new(
152            system_clock.clone(),
153            manifest.value.core.last_l0_clock_tick,
154        ));
155
156        // state are mostly manifest, including IMM, L0, etc.
157        let db_state = DbState::new(manifest);
158        let state = Arc::new(RwLock::new(db_state));
159
160        let db_stats = DbStats::new(&recorder);
161        let wal_enabled = DbInner::wal_enabled_in_options(&settings);
162        let flush_merge_operator = merge_operator.clone().map(|merge_operator| {
163            instrument_merge_operator(
164                merge_operator,
165                db_stats.merge_operator_flush_operands.clone(),
166            )
167        });
168
169        let reader = Reader::new(
170            table_store.clone(),
171            db_stats.clone(),
172            mono_clock.clone(),
173            oracle.clone(),
174            merge_operator.clone(),
175        );
176
177        let txn_manager = Arc::new(TransactionManager::new(oracle.clone(), rand.clone()));
178        let snapshot_manager = Arc::new(SnapshotManager::new(oracle.clone(), rand.clone()));
179        let wal_observer = DbWalObserver::new(
180            wal_observer,
181            oracle.clone(),
182            state.clone(),
183            status_manager.clone(),
184        );
185
186        let db_inner = Self {
187            state,
188            settings,
189            memtable_flusher,
190            oracle,
191            wal_enabled,
192            table_store,
193            wal_observer,
194            write_notifier,
195            db_stats,
196            mono_clock,
197            system_clock,
198            rand,
199            flush_merge_operator,
200            recorder,
201            fp_registry,
202            reader,
203            txn_manager,
204            snapshot_manager,
205            status_manager,
206            segment_extractor,
207        };
208        Ok(db_inner)
209    }
210
211    /// Get the value for a given key.
212    pub(crate) async fn get_with_options<K: AsRef<[u8]>>(
213        &self,
214        key: K,
215        options: &ReadOptions,
216    ) -> Result<Option<Bytes>, SlateDBError> {
217        self.get_key_value_with_options(key, options)
218            .await
219            .map(|kv_opt| kv_opt.map(|kv| kv.value))
220    }
221
222    /// Get the full row entry for a given key.
223    pub(crate) async fn get_key_value_with_options<K: AsRef<[u8]>>(
224        &self,
225        key: K,
226        options: &ReadOptions,
227    ) -> Result<Option<KeyValue>, SlateDBError> {
228        self.check_closed()?;
229        let db_state = self.state.read().view();
230        self.reader
231            .get_key_value_with_options(key, options, &db_state, None, None)
232            .await
233    }
234
235    /// Shared scan path for plain range scans and prefix scans. When
236    /// `prefix` is set, every key in `range` starts with it and prefix
237    /// bloom filters are consulted to skip non-matching SSTs.
238    pub(crate) async fn scan_with_options(
239        &self,
240        range: BytesRange,
241        options: &ScanOptions,
242        prefix: Option<Bytes>,
243    ) -> Result<DbIterator, SlateDBError> {
244        self.check_closed()?;
245        let db_state = self.state.read().view();
246        self.reader
247            .scan_with_options(
248                range,
249                options,
250                ScanContext {
251                    db_state: &db_state,
252                    write_batch_iter: None,
253                    max_seq: None,
254                    prefix,
255                },
256            )
257            .await
258    }
259
260    pub(crate) async fn scan_prefix_by_recency_with_options(
261        &self,
262        prefix: Bytes,
263        options: &ScanOptions,
264    ) -> Result<DbRecencyIterator, SlateDBError> {
265        self.check_closed()?;
266        let db_state = self.state.read().view();
267        self.reader
268            .scan_prefix_by_recency_with_options(prefix, options, &db_state)
269            .await
270    }
271
272    #[allow(unused_variables)]
273    pub(crate) fn wal_enabled_in_options(settings: &Settings) -> bool {
274        #[cfg(feature = "wal_disable")]
275        return settings.wal_enabled;
276        #[cfg(not(feature = "wal_disable"))]
277        return true;
278    }
279
280    pub(crate) async fn write_with_options(
281        &self,
282        batch: WriteBatch,
283        options: &WriteOptions,
284        txn: Option<DbTransaction>,
285    ) -> Result<WriteHandle, SlateDBError> {
286        self.db_stats.write_batch_count.increment(1);
287        self.db_stats.write_ops.increment(batch.op_count() as u64);
288        self.check_closed()?;
289        if batch.ops.is_empty() {
290            return Err(SlateDBError::EmptyBatch);
291        }
292
293        let (tx, rx) = tokio::sync::oneshot::channel();
294        let batch_msg = BatchWriterMessage::WriteBatch(WriteBatchRequest {
295            batch,
296            options: options.clone(),
297            done: tx,
298            txn,
299        });
300
301        self.maybe_apply_backpressure().await?;
302        self.write_notifier.send(batch_msg)?;
303
304        // TODO: this can be modified as awaiting the last_durable_seq watermark & fatal error.
305
306        let write_handle = rx.await??;
307
308        if options.await_durable {
309            let seq = write_handle.seq;
310            let mut status_subscription = self.status_manager.subscribe();
311            let status = status_subscription
312                .wait_for(|s| s.durable_seq >= seq || s.close_reason.is_some())
313                .await
314                .map_err(|_| SlateDBError::Closed)?;
315            if status.durable_seq < seq {
316                self.check_closed()?;
317                warn!(
318                    "durable seq {} not advanced past write seq {} and db not closed",
319                    status.durable_seq, seq
320                );
321                return Err(SlateDBError::InvalidDBState);
322            }
323        }
324
325        Ok(write_handle)
326    }
327
328    #[inline]
329    pub(crate) async fn maybe_apply_backpressure(&self) -> Result<(), SlateDBError> {
330        loop {
331            self.check_closed()?;
332            let wal_status = self.wal_observer.status()?;
333            let (active_memtable_size_bytes, imm_memtable_size_bytes) = {
334                let guard = self.state.read();
335                let estimate = |metadata: KVTableMetadata| {
336                    self.table_store.estimate_encoded_size_compacted(
337                        metadata.entry_num,
338                        metadata.entries_size_in_bytes,
339                    )
340                };
341                let active_memtable_size_bytes = estimate(guard.memtable().table().metadata());
342                let imm_memtable_size_bytes = guard
343                    .state()
344                    .imm_memtable
345                    .iter()
346                    .map(|imm| estimate(imm.table().metadata()))
347                    .fold(0usize, |total, size| total.saturating_add(size));
348                (active_memtable_size_bytes, imm_memtable_size_bytes)
349            };
350            let total_mem_size_bytes = active_memtable_size_bytes
351                .saturating_add(imm_memtable_size_bytes)
352                .saturating_add(wal_status.estimated_bytes);
353            self.db_stats
354                .total_mem_size_bytes
355                .set(total_mem_size_bytes as i64);
356
357            trace!(
358                "checking backpressure [total_mem_size_bytes={}, active_memtable_size_bytes={}, imm_memtable_size_bytes={}, wal_size_bytes={}, max_unflushed_bytes={}]",
359                format_bytes_si(total_mem_size_bytes as u64),
360                format_bytes_si(active_memtable_size_bytes as u64),
361                format_bytes_si(imm_memtable_size_bytes as u64),
362                format_bytes_si(wal_status.estimated_bytes as u64),
363                format_bytes_si(self.settings.max_unflushed_bytes as u64),
364            );
365
366            if total_mem_size_bytes >= self.settings.max_unflushed_bytes {
367                self.db_stats.backpressure_count.increment(1);
368                warn!(
369                    "unflushed WAL and memtable size exceeds max_unflushed_bytes. applying backpressure. [total_mem_size_bytes={}, active_memtable_size_bytes={}, imm_memtable_size_bytes={}, wal_size_bytes={}, max_unflushed_bytes={}]",
370                    format_bytes_si(total_mem_size_bytes as u64),
371                    format_bytes_si(active_memtable_size_bytes as u64),
372                    format_bytes_si(imm_memtable_size_bytes as u64),
373                    format_bytes_si(wal_status.estimated_bytes as u64),
374                    format_bytes_si(self.settings.max_unflushed_bytes as u64),
375                );
376
377                let maybe_oldest_unflushed_memtable = {
378                    let guard = self.state.read();
379                    guard.state().imm_memtable.back().cloned()
380                };
381
382                // There is a window of time after total_mem_size_bytes is larger than
383                // max_unflushed_bytes but before we get the memtable. During that time, if
384                // the memtable and WAL are fully flushed out, we should short circuit to
385                // avoid blocking indefinitely.
386                if maybe_oldest_unflushed_memtable.is_none() && wal_status.estimated_bytes == 0 {
387                    continue;
388                }
389
390                let await_memtable_uploaded = async {
391                    if let Some(oldest_unflushed_memtable) = maybe_oldest_unflushed_memtable {
392                        oldest_unflushed_memtable.await_uploaded().await
393                    } else {
394                        std::future::pending().await
395                    }
396                };
397
398                let await_flush_wal = self
399                    .wal_observer
400                    .wait_until_wal_flushed(wal_status.last_flushed_wal_id);
401
402                let timeout_fut = self.system_clock.sleep(Duration::from_secs(30));
403                let await_closed = async {
404                    let mut watcher = self.status_manager.result_reader();
405                    match watcher.await_value().await {
406                        Ok(()) => Err(SlateDBError::Closed),
407                        Err(e) => Err(e),
408                    }
409                };
410
411                tokio::select! {
412                    biased;
413
414                    result = await_closed => result?,
415                    result = await_memtable_uploaded => result?,
416                    result = await_flush_wal => result?,
417                    _ = timeout_fut => {
418                        warn!("backpressure timeout: waited 30s, no memtable/WAL flushed yet");
419                    }
420                };
421            } else {
422                break;
423            }
424        }
425        Ok(())
426    }
427
428    async fn flush_imm_memtables(&self, target: FlushTarget) -> Result<FlushResult, SlateDBError> {
429        self.memtable_flusher().flush(target).await
430    }
431
432    pub(crate) async fn flush_memtables(
433        &self,
434        target: FlushTarget,
435    ) -> Result<FlushResult, SlateDBError> {
436        // flush the batch writer to freeze the active memtable and flush all WALs to unblock
437        // memtable flush
438        self.request_batch_writer_flush(true).await?;
439        self.flush_imm_memtables(target).await
440    }
441
442    pub(crate) fn memtable_flusher(&self) -> &MemtableFlusher {
443        &self.memtable_flusher
444    }
445
446    /// Flush in-memory writes to disk. See [`Db::flush_with_options`] for details.
447    ///
448    /// `check_status` exists so we can call flush in [`Db::close`] after marking the
449    /// database as closed.
450    ///
451    /// ## Arguments
452    /// - `options`: the flush options to use.
453    /// - `check_status`: if true, checks the database status before flushing.
454    ///
455    /// ## Returns
456    /// - `Ok(())` if the flush was successful.
457    /// - `Err(SlateDBError)` if there was an error flushing the database. If
458    ///   `check_status` is true, this may return `SlateDBError::Closed` if the database
459    ///   has already been closed.
460    pub(crate) async fn flush(
461        &self,
462        options: FlushOptions,
463        check_status: bool,
464    ) -> Result<(), SlateDBError> {
465        self.db_stats.flush_requests.increment(1);
466        if check_status {
467            self.check_closed()?;
468        }
469        match options.flush_type {
470            FlushType::Wal => {
471                if !self.wal_enabled {
472                    return Err(SlateDBError::WalDisabled);
473                }
474                self.request_batch_writer_flush(false).await
475            }
476            FlushType::MemTable => self.flush_memtables(FlushTarget::All).await.map(|_| ()),
477        }
478    }
479
480    async fn replay_wal(&self, wal_id_range: Range<u64>) -> Result<(), SlateDBError> {
481        let mut current_memtable_wal_id = self
482            .state
483            .read()
484            .state()
485            .manifest
486            .value
487            .core
488            .replay_after_wal_id;
489        let writer_epoch = self.state.read().state().manifest.value.writer_epoch;
490        fail_point!(
491            Arc::clone(&self.fp_registry),
492            "replay-wal-pause",
493            writer_epoch == 1,
494            |_| -> Result<(), SlateDBError> { Ok(()) }
495        );
496
497        let sst_iter_options = SstIteratorOptions {
498            max_fetch_tasks: 1,
499            blocks_to_fetch: 256,
500            cache_blocks: false,
501            cache_metadata: false,
502            eager_spawn: true,
503            order: IterationOrder::Ascending,
504            prefix: None,
505            filter_context: None,
506        };
507
508        let replay_options = WalReplayOptions {
509            sst_batch_size: 4,
510            max_memtable_bytes: self.settings.l0_sst_size_bytes,
511            sst_iter_options,
512            min_seq: None,
513        };
514
515        let db_state = self.state.read().state().core().clone();
516        let mut replay_iter = WalReplayIterator::range(
517            wal_id_range,
518            &db_state,
519            replay_options,
520            Arc::clone(&self.table_store),
521        )
522        .await?;
523
524        loop {
525            let replayed_table = match replay_iter.next().await {
526                Ok(Some(replayed_table)) => replayed_table,
527                Ok(None) => break,
528                // If the manifest or an SST referenced by the WAL is missing, it may
529                // indicate that a newer writer has advanced `replay_after_wal_id` and
530                // the GC has removed this WAL entry. Check the latest manifest's
531                // writer_epoch to see if this client is fenced.
532                Err(err) if err.has_object_store_not_found() => {
533                    self.memtable_flusher.refresh_manifest().await?;
534                    if self.state.read().state().manifest.value.writer_epoch > writer_epoch {
535                        return Err(SlateDBError::Fenced);
536                    }
537                    return Err(err);
538                }
539                Err(err) => return Err(err),
540            };
541
542            // RFC-0024: re-extract each replayed entry's prefix to
543            // populate the memtable's touched-segment set. Per the
544            // validation model, durable WAL entries were validated when
545            // the writer accepted them, so we do not re-run the
546            // antichain check here. An empty/absent prefix under
547            // the current extractor remains a hard error — the
548            // entry can't be routed to any segment.
549            if let Some(extractor) = self.segment_extractor.as_ref() {
550                let mut touched_segments: std::collections::BTreeSet<Bytes> =
551                    std::collections::BTreeSet::new();
552                let mut iter = replayed_table.table.table().iter();
553                while let Some(entry) = iter.next_sync() {
554                    touched_segments
555                        .insert(extract_segment_prefix(extractor.as_ref(), &entry.key)?);
556                }
557                replayed_table
558                    .table
559                    .record_touched_segments(touched_segments);
560            }
561            // Replayed rows come from WAL SSTs in remote storage, so they are already
562            // durable. Update `last_remote_persisted_seq` before replaying to avoid a race with
563            // the memtable flusher. The flusher calls flush_wals() to guarantee all data in the
564            // memtable is already durable in the WAL. Since we're replaying, the WAL is empty and
565            // `last_remote_persisted_seq` does not get updated; it remains at l0_last_seq. This
566            // would cause the flusher's assertion that the remote persisted seq is always >= the
567            // last seq in the memtable to fail. By updating `last_remote_persisted_seq` here, we
568            // ensure the assertion holds true.
569            assert!(self.oracle.last_remote_persisted_seq() <= replayed_table.last_seq);
570            self.oracle.advance_durable_seq(replayed_table.last_seq);
571            self.maybe_freeze_memtable(current_memtable_wal_id);
572            self.maybe_apply_backpressure().await?;
573            let replayed_table_last_wal_id = replayed_table.last_wal_id;
574            self.replay_memtable(current_memtable_wal_id, replayed_table)?;
575            current_memtable_wal_id = replayed_table_last_wal_id;
576        }
577
578        let guard = self.state.read();
579        self.status_manager
580            .report_memtable_segments(collect_touched_segments(&guard.view()));
581
582        Ok(())
583    }
584
585    async fn preload_cache(
586        &self,
587        cached_obj_store: &CachedObjectStore,
588        path_resolver: &PathResolver,
589    ) -> Result<(), SlateDBError> {
590        let state = self.state.read().state();
591        let cache_opts = &self.settings.object_store_cache_options;
592        crate::utils::preload_cache_from_manifest(
593            &state.manifest.value.core,
594            cached_obj_store,
595            path_resolver,
596            cache_opts.preload_disk_cache_on_startup,
597            cache_opts.max_cache_size_bytes.unwrap_or(usize::MAX),
598        )
599        .await
600    }
601
602    /// Returns the latest database status snapshot.
603    pub(crate) fn status(&self) -> DbStatus {
604        self.status_manager.status()
605    }
606
607    /// Returns an error if the database has been closed.
608    ///
609    /// ## Returns
610    /// - `Ok(())` if the DB is still open.
611    /// - `Err(SlateDBError::Closed)` if the DB was closed successfully
612    ///   (state.result_reader() returns Ok(())).
613    /// - `Err(e)` if the DB was closed with an error, where `e` is the error
614    ///   (state.result_reader() returns Err(e)).
615    pub(crate) fn check_closed(&self) -> Result<(), SlateDBError> {
616        if let Some(result) = self.status_manager.result_reader().read() {
617            return match result {
618                Ok(()) => Err(SlateDBError::Closed),
619                Err(e) => Err(e),
620            };
621        }
622        Ok(())
623    }
624
625    pub(crate) fn manifest(&self) -> VersionedManifest {
626        self.state.read().state().manifest.clone().into()
627    }
628}
629
630#[derive(Clone)]
631pub struct Db {
632    pub(crate) inner: Arc<DbInner>,
633    task_executor: Arc<MessageHandlerExecutor>,
634}
635
636impl Db {
637    /// Open a new database with default options.
638    ///
639    /// ## Arguments
640    /// - `path`: the path to the database
641    /// - `object_store`: the object store to use for the database
642    ///
643    /// ## Returns
644    /// - `Db`: the database
645    ///
646    /// ## Errors
647    /// - `Error`: if there was an error opening the database
648    ///
649    /// ## Examples
650    ///
651    /// ```
652    /// use slatedb::{Db, Error};
653    /// use slatedb::object_store::memory::InMemory;
654    /// use std::sync::Arc;
655    ///
656    /// #[tokio::main]
657    /// async fn main() -> Result<(), Error> {
658    ///     let object_store = Arc::new(InMemory::new());
659    ///     let db = Db::open("test_db", object_store).await?;
660    ///     Ok(())
661    /// }
662    /// ```
663    pub async fn open<P: Into<Path>>(
664        path: P,
665        object_store: Arc<dyn ObjectStore>,
666    ) -> Result<Self, crate::Error> {
667        // Use the builder API internally
668        Self::builder(path, object_store).build().await
669    }
670
671    /// Creates a new builder for a database at the given path.
672    ///
673    /// ## Arguments
674    /// - `path`: the path to the database
675    /// - `object_store`: the object store to use for the database
676    ///
677    /// ## Returns
678    /// - `DbBuilder`: the builder to initialize the database
679    ///
680    /// ## Examples
681    ///
682    /// ```
683    /// use slatedb::{Db, Error};
684    /// use slatedb::object_store::memory::InMemory;
685    /// use std::sync::Arc;
686    ///
687    /// #[tokio::main]
688    /// async fn main() -> Result<(), Error> {
689    ///     let object_store = Arc::new(InMemory::new());
690    ///     let db = Db::builder("/tmp/test_db", object_store)
691    ///         .build()
692    ///         .await?;
693    ///     Ok(())
694    /// }
695    /// ```
696    pub fn builder<P: Into<Path>>(path: P, object_store: Arc<dyn ObjectStore>) -> DbBuilder<P> {
697        DbBuilder::new(path, object_store)
698    }
699
700    /// Close the database.
701    ///
702    /// ## Returns
703    /// - `Result<(), Error>`: if there was an error closing the database
704    ///
705    /// ## Examples
706    ///
707    /// ```
708    /// use slatedb::{Db, Error};
709    /// use slatedb::object_store::{ObjectStore, memory::InMemory};
710    /// use std::sync::Arc;
711    ///
712    /// #[tokio::main]
713    /// async fn main() -> Result<(), Error> {
714    ///     let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
715    ///     let db = Db::open("test_db", object_store).await?;
716    ///     db.close().await?;
717    ///     Ok(())
718    /// }
719    /// ```
720    pub async fn close(&self) -> Result<(), crate::Error> {
721        let should_flush = match self.status().close_reason {
722            // If already closed, don't close again.
723            Some(CloseReason::Clean) => return Err(SlateDBError::Closed.into()),
724            // If in failed state, allow close, but don't flush since the database
725            // might be in a bad state. Note that multiple close() calls will always
726            // run when in a failed state (vs. a clean closure, which will return
727            // Error::Closed(CloseReason::Clean) on subsequent calls).
728            Some(_) => false,
729            // Flush outstanding writes if the database is still open.
730            None => true,
731        };
732
733        // Mark the database as closed before flushing.
734        self.inner.status_manager.write_result(Ok(()));
735
736        let result = if should_flush {
737            // Flush memtables to L0 so that the WAL does not need to be
738            // replayed on the next startup.
739            self.inner
740                .flush(
741                    FlushOptions {
742                        flush_type: FlushType::MemTable,
743                    },
744                    false,
745                )
746                .await
747                .map_err(Into::into)
748                .inspect_err(|e| warn!("failed to flush db during close [error={:?}]", e))
749        } else {
750            Ok(())
751        };
752
753        MemtableFlusher::shutdown(&self.task_executor).await;
754
755        if let Err(e) = self.task_executor.shutdown_task(COMPACTOR_TASK_NAME).await {
756            warn!("failed to shutdown compactor task [error={:?}]", e);
757        }
758
759        if let Err(e) = self
760            .task_executor
761            .shutdown_task(crate::compaction_worker::COMPACTION_WORKER_TASK_NAME)
762            .await
763        {
764            warn!("failed to shutdown compaction worker task [error={:?}]", e);
765        }
766
767        if let Err(e) = self.task_executor.shutdown_task(GC_TASK_NAME).await {
768            warn!("failed to shutdown garbage collector task [error={:?}]", e);
769        }
770
771        if let Err(e) = self
772            .task_executor
773            .shutdown_task(WRITE_BATCH_TASK_NAME)
774            .await
775        {
776            warn!("failed to shutdown writer task [error={:?}]", e);
777        }
778
779        if let Err(e) = self.inner.table_store.close_cache().await {
780            warn!("failed to close block cache [error={:?}]", e);
781        }
782
783        info!("db closed");
784        result
785    }
786
787    /// Create a snapshot of the database.
788    ///
789    /// ## Returns
790    /// - `Result<Arc<DbSnapshot>, Error>`: the snapshot of the database, it represents
791    ///   a consistent view of the database at the time of the snapshot.
792    ///
793    /// ## Examples
794    ///
795    /// ```
796    /// use slatedb::{Db, Error};
797    /// use slatedb::object_store::{ObjectStore, memory::InMemory};
798    /// use std::sync::Arc;
799    /// use bytes::Bytes;
800    ///
801    /// #[tokio::main]
802    /// async fn main() -> Result<(), Error> {
803    ///     let object_store = Arc::new(InMemory::new());
804    ///     let db = Db::open("test_db", object_store).await?;
805    ///
806    ///     // Write some data and create a snapshot
807    ///     db.put(b"key1", b"value1").await?;
808    ///     let snapshot = db.snapshot().await?;
809    ///
810    ///     // Snapshot provides read-only access to database state
811    ///     let value = snapshot.get(b"key1").await?;
812    ///     assert_eq!(value, Some(Bytes::from(b"value1".as_ref())));
813    ///
814    ///     // Write more data to original database
815    ///     db.put(b"key2", b"value2").await?;
816    ///
817    ///     // Snapshot still sees old state, original db sees new data
818    ///     assert_eq!(snapshot.get(b"key2").await?, None);
819    ///     assert_eq!(db.get(b"key2").await?, Some(Bytes::from(b"value2".as_ref())));
820    ///
821    ///     Ok(())
822    /// }
823    /// ```
824    pub async fn snapshot(&self) -> Result<Arc<DbSnapshot>, crate::Error> {
825        self.inner.check_closed()?;
826        let snapshot = DbSnapshot::new(self.inner.clone(), None);
827        Ok(snapshot)
828    }
829
830    /// Get a value from the database with default read options.
831    ///
832    /// The `Bytes` object returned contains a slice of an entire
833    /// 4 KiB block. The block will be held in memory as long as the
834    /// caller holds a reference to the `Bytes` object. Consider
835    /// copying the data if you need to hold it for a long time.
836    ///
837    /// ## Arguments
838    /// - `key`: the key to get
839    ///
840    /// ## Returns
841    /// - `Result<Option<Bytes>, Error>`:
842    ///     - `Some(Bytes)`: the value if it exists
843    ///     - `None`: if the value does not exist
844    ///
845    /// ## Errors
846    /// - `Error`: if there was an error getting the value
847    ///
848    /// ## Examples
849    ///
850    /// ```
851    /// use slatedb::{Db, Error};
852    /// use slatedb::object_store::{ObjectStore, memory::InMemory};
853    /// use std::sync::Arc;
854    ///
855    /// #[tokio::main]
856    /// async fn main() -> Result<(), Error> {
857    ///     let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
858    ///     let db = Db::open("test_db", object_store).await?;
859    ///     db.put(b"key", b"value").await?;
860    ///     assert_eq!(db.get(b"key").await?, Some("value".into()));
861    ///     Ok(())
862    /// }
863    /// ```
864    pub async fn get<K: AsRef<[u8]> + Send>(&self, key: K) -> Result<Option<Bytes>, crate::Error> {
865        self.get_with_options(key, &ReadOptions::default()).await
866    }
867
868    /// Get a value from the database with custom read options.
869    ///
870    /// The `Bytes` object returned contains a slice of an entire
871    /// 4 KiB block. The block will be held in memory as long as the
872    /// caller holds a reference to the `Bytes` object. Consider
873    /// copying the data if you need to hold it for a long time.
874    ///
875    /// ## Arguments
876    /// - `key`: the key to get
877    /// - `options`: the read options to use (Note that [`ReadOptions::read_level`] has no effect for readers, which
878    ///   can only observe committed state).
879    ///
880    /// ## Returns
881    /// - `Result<Option<Bytes>, Error>`:
882    ///   - `Some(Bytes)`: the value if it exists
883    ///   - `None`: if the value does not exist
884    ///
885    /// ## Errors
886    /// - `Error`: if there was an error getting the value
887    ///
888    /// ## Examples
889    ///
890    /// ```
891    /// use slatedb::{Db, config::ReadOptions, Error};
892    /// use slatedb::object_store::{ObjectStore, memory::InMemory};
893    /// use std::sync::Arc;
894    ///
895    /// #[tokio::main]
896    /// async fn main() -> Result<(), Error> {
897    ///     let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
898    ///     let db = Db::open("test_db", object_store).await?;
899    ///     db.put(b"key", b"value").await?;
900    ///     assert_eq!(db.get_with_options(b"key", &ReadOptions::default()).await?, Some("value".into()));
901    ///     Ok(())
902    /// }
903    /// ```
904    pub async fn get_with_options<K: AsRef<[u8]> + Send>(
905        &self,
906        key: K,
907        options: &ReadOptions,
908    ) -> Result<Option<Bytes>, crate::Error> {
909        self.inner
910            .get_with_options(key, options)
911            .await
912            .map_err(Into::into)
913    }
914
915    /// Get a key-value pair from the database with default read options.
916    ///
917    /// Returns the key along with its value and metadata (sequence number,
918    /// creation timestamp, expiration timestamp). Unlike [`get`](Self::get),
919    /// which returns only the value bytes, this method returns a [`KeyValue`]
920    /// that includes row metadata.
921    ///
922    /// ## Arguments
923    /// - `key`: the key to look up
924    ///
925    /// ## Returns
926    /// - `Ok(Some(KeyValue))`: if the key exists and is not deleted/expired
927    /// - `Ok(None)`: if the key does not exist or is deleted/expired
928    ///
929    /// ## Errors
930    /// - `Error`: if there was an error reading from the database
931    pub async fn get_key_value<K: AsRef<[u8]> + Send>(
932        &self,
933        key: K,
934    ) -> Result<Option<KeyValue>, crate::Error> {
935        self.get_key_value_with_options(key, &ReadOptions::default())
936            .await
937    }
938
939    /// Get a key-value pair from the database with custom read options.
940    ///
941    /// Returns the key along with its value and metadata (sequence number,
942    /// creation timestamp, expiration timestamp). Unlike
943    /// [`get_with_options`](Self::get_with_options), which returns only the
944    /// value bytes, this method returns a [`KeyValue`] that includes row
945    /// metadata.
946    ///
947    /// ## Arguments
948    /// - `key`: the key to look up
949    /// - `options`: the read options to use
950    ///
951    /// ## Returns
952    /// - `Ok(Some(KeyValue))`: if the key exists and is not deleted/expired
953    /// - `Ok(None)`: if the key does not exist or is deleted/expired
954    ///
955    /// ## Errors
956    /// - `Error`: if there was an error reading from the database
957    pub async fn get_key_value_with_options<K: AsRef<[u8]> + Send>(
958        &self,
959        key: K,
960        options: &ReadOptions,
961    ) -> Result<Option<KeyValue>, crate::Error> {
962        let kv = self
963            .inner
964            .get_key_value_with_options(key, options)
965            .await
966            .map_err(crate::Error::from)?;
967        Ok(kv)
968    }
969
970    /// Scan a range of keys using the default scan options.
971    ///
972    /// returns a `DbIterator`
973    ///
974    /// ## Errors
975    /// - `Error`: if there was an error scanning the range of keys
976    ///
977    /// ## Returns
978    /// - `Result<DbIterator, Error>`: An iterator with the results of the scan
979    ///
980    /// ## Examples
981    ///
982    /// ```
983    /// use slatedb::{Db, Error};
984    /// use slatedb::object_store::{ObjectStore, memory::InMemory};
985    /// use std::sync::Arc;
986    ///
987    /// #[tokio::main]
988    /// async fn main() -> Result<(), Error> {
989    ///     let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
990    ///     let db = Db::open("test_db", object_store).await?;
991    ///     db.put(b"a", b"a_value").await?;
992    ///     db.put(b"b", b"b_value").await?;
993    ///
994    ///     let mut iter = db.scan("a".."b").await?;
995    ///     let kv = iter.next().await?.unwrap();
996    ///     assert_eq!(kv.key.as_ref(), b"a");
997    ///     assert_eq!(kv.value.as_ref(), b"a_value");
998    ///     assert_eq!(None, iter.next().await?);
999    ///     Ok(())
1000    /// }
1001    /// ```
1002    pub async fn scan<T>(&self, range: T) -> Result<DbIterator, crate::Error>
1003    where
1004        T: ByteRangeBounds + Send,
1005    {
1006        self.scan_with_options(range, &ScanOptions::default()).await
1007    }
1008
1009    /// Scan a range of keys with the provided options.
1010    ///
1011    /// returns a `DbIterator`
1012    ///
1013    /// ## Errors
1014    /// - `Error`: if there was an error scanning the range of keys
1015    ///
1016    /// ## Examples
1017    ///
1018    /// ```
1019    /// use slatedb::{Db, config::ScanOptions, config::DurabilityLevel, Error};
1020    /// use slatedb::object_store::{ObjectStore, memory::InMemory};
1021    /// use std::sync::Arc;
1022    ///
1023    /// #[tokio::main]
1024    /// async fn main() -> Result<(), Error> {
1025    ///     let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
1026    ///     let db = Db::open("test_db", object_store).await?;
1027    ///     db.put(b"a", b"a_value").await?;
1028    ///     db.put(b"b", b"b_value").await?;
1029    ///
1030    ///     let mut iter = db.scan_with_options("a".."b", &ScanOptions {
1031    ///         durability_filter: DurabilityLevel::Memory,
1032    ///         ..ScanOptions::default()
1033    ///     }).await?;
1034    ///     let kv = iter.next().await?.unwrap();
1035    ///     assert_eq!(kv.key.as_ref(), b"a");
1036    ///     assert_eq!(kv.value.as_ref(), b"a_value");
1037    ///     assert_eq!(None, iter.next().await?);
1038    ///     Ok(())
1039    /// }
1040    /// ```
1041    pub async fn scan_with_options<T>(
1042        &self,
1043        range: T,
1044        options: &ScanOptions,
1045    ) -> Result<DbIterator, crate::Error>
1046    where
1047        T: ByteRangeBounds + Send,
1048    {
1049        let start = range.start_bound().map(Bytes::copy_from_slice);
1050        let end = range.end_bound().map(Bytes::copy_from_slice);
1051        let range = (start, end);
1052        self.inner
1053            .scan_with_options(BytesRange::from(range), options, None)
1054            .await
1055            .map_err(Into::into)
1056    }
1057
1058    /// Scan keys that share the provided prefix, restricted to `subrange`,
1059    /// using the default scan options.
1060    ///
1061    /// The subrange bounds are key *suffixes* interpreted relative to the
1062    /// prefix: a bound `s` selects the full key `prefix ++ s`. Pass `..` to
1063    /// scan the prefix's entire keyspace. When a prefix extractor is
1064    /// configured, prefix bloom filters are consulted to skip SSTs that
1065    /// contain no matching keys.
1066    ///
1067    /// ## Arguments
1068    /// - `prefix`: the key prefix to scan
1069    /// - `subrange`: the range of key suffixes (relative to `prefix`) to
1070    ///   scan; `..` scans all keys with the prefix
1071    ///
1072    /// ## Returns
1073    /// - `Result<DbIterator, Error>`: An iterator with the results of the scan
1074    ///
1075    /// ## Examples
1076    ///
1077    /// ```
1078    /// use slatedb::{Db, Error};
1079    /// use slatedb::object_store::{ObjectStore, memory::InMemory};
1080    /// use std::sync::Arc;
1081    ///
1082    /// #[tokio::main]
1083    /// async fn main() -> Result<(), Error> {
1084    ///     let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
1085    ///     let db = Db::open("test_db", object_store).await?;
1086    ///     db.put(b"ab", b"v0").await?;
1087    ///     db.put(b"aba", b"v1").await?;
1088    ///     db.put(b"b", b"v2").await?;
1089    ///
1090    ///     let mut iter = db.scan_prefix(b"ab", ..).await?;
1091    ///     let kv = iter.next().await?.unwrap();
1092    ///     assert_eq!(kv.key.as_ref(), b"ab");
1093    ///     assert_eq!(kv.value.as_ref(), b"v0");
1094    ///     let kv = iter.next().await?.unwrap();
1095    ///     assert_eq!(kv.key.as_ref(), b"aba");
1096    ///     assert_eq!(kv.value.as_ref(), b"v1");
1097    ///     assert_eq!(None, iter.next().await?);
1098    ///
1099    ///     // Restrict the scan to suffixes from b"a" onward.
1100    ///     // Ordinary Rust range syntax works here; `as_slice()` is optional.
1101    ///     let mut iter = db.scan_prefix(b"ab", b"a".as_slice()..).await?;
1102    ///     let kv = iter.next().await?.unwrap();
1103    ///     assert_eq!(kv.key.as_ref(), b"aba");
1104    ///     assert_eq!(None, iter.next().await?);
1105    ///     Ok(())
1106    /// }
1107    /// ```
1108    pub async fn scan_prefix<P, T>(
1109        &self,
1110        prefix: P,
1111        subrange: T,
1112    ) -> Result<DbIterator, crate::Error>
1113    where
1114        P: AsRef<[u8]> + Send,
1115        T: ByteRangeBounds + Send,
1116    {
1117        self.scan_prefix_with_options(prefix, subrange, &ScanOptions::default())
1118            .await
1119    }
1120
1121    /// Scan keys that share the provided prefix, restricted to `subrange`,
1122    /// with custom options. See [`Self::scan_prefix`] for the subrange
1123    /// semantics.
1124    ///
1125    /// ## Arguments
1126    /// - `prefix`: the key prefix to scan
1127    /// - `subrange`: the range of key suffixes (relative to `prefix`) to
1128    ///   scan; `..` scans all keys with the prefix
1129    /// - `options`: the scan options to use
1130    ///
1131    /// ## Returns
1132    /// - `Result<DbIterator, Error>`: An iterator with the results of the scan
1133    ///
1134    /// ## Examples
1135    ///
1136    /// ```
1137    /// use slatedb::{Db, Error};
1138    /// use slatedb::config::ScanOptions;
1139    /// use slatedb::object_store::{ObjectStore, memory::InMemory};
1140    /// use std::sync::Arc;
1141    ///
1142    /// #[tokio::main]
1143    /// async fn main() -> Result<(), Error> {
1144    ///     let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
1145    ///     let db = Db::open("test_db", object_store).await?;
1146    ///     db.put(b"x1", b"v1").await?;
1147    ///     db.put(b"x2", b"v2").await?;
1148    ///     db.put(b"y", b"v3").await?;
1149    ///
1150    ///     let options = ScanOptions {
1151    ///         cache_blocks: false,
1152    ///         ..ScanOptions::default()
1153    ///     };
1154    ///     let mut iter = db.scan_prefix_with_options(b"x", .., &options).await?;
1155    ///     let kv = iter.next().await?.unwrap();
1156    ///     assert_eq!(kv.key.as_ref(), b"x1");
1157    ///     assert_eq!(kv.value.as_ref(), b"v1");
1158    ///     let kv = iter.next().await?.unwrap();
1159    ///     assert_eq!(kv.key.as_ref(), b"x2");
1160    ///     assert_eq!(kv.value.as_ref(), b"v2");
1161    ///     assert_eq!(None, iter.next().await?);
1162    ///     Ok(())
1163    /// }
1164    /// ```
1165    pub async fn scan_prefix_with_options<P, T>(
1166        &self,
1167        prefix: P,
1168        subrange: T,
1169        options: &ScanOptions,
1170    ) -> Result<DbIterator, crate::Error>
1171    where
1172        P: AsRef<[u8]> + Send,
1173        T: ByteRangeBounds + Send,
1174    {
1175        let prefix = Bytes::copy_from_slice(prefix.as_ref());
1176        let range = BytesRange::from_prefix_and_subrange(prefix.as_ref(), subrange);
1177        self.inner
1178            .scan_with_options(range, options, Some(prefix))
1179            .await
1180            .map_err(Into::into)
1181    }
1182
1183    /// Scan keys that share `prefix`, walking sources newest-first.
1184    ///
1185    /// **Warning:** this is a low-level, unopinionated iterator. It does
1186    /// **no** merging, **no** deduping, and **no** interpretation of
1187    /// entries across sources, unlike [`Self::scan_prefix`]. The API
1188    /// makes no assumptions about what duplicates, tombstones, or merge
1189    /// operands should mean; every such decision is left to the caller.
1190    ///
1191    /// Within each source, entries are emitted in the order requested by
1192    /// `options.order`: ascending (the default) or descending. Across
1193    /// sources, the walk is always newest-first, independent of
1194    /// `options.order`. Each source restarts its own scan at its own
1195    /// first matching key for that order, so the global emit sequence is
1196    /// not a single sorted key stream and the within-source key order
1197    /// resets at every source boundary. The same user key can appear
1198    /// multiple times, both across sources (once per source that holds
1199    /// it, newest source first) and within a single source (one entry
1200    /// per stored sequence number, newest seq first within the key
1201    /// group): nothing collapses versions. Tombstones and merge operands
1202    /// are surfaced as raw [`crate::types::RowEntry`] values. The caller
1203    /// is responsible for any dedup, delete handling, or merge
1204    /// resolution. Callers that need a totally ordered, fully merged
1205    /// view should use [`Self::scan_prefix`] instead; use this only when
1206    /// you want freshest-first results with the option to early-stop and
1207    /// are willing to interpret raw entries.
1208    ///
1209    /// Sources are walked in this order: active memtable, immutable
1210    /// memtables, then within the single matching segment that segment's
1211    /// L0 SSTs newest-first followed by its sorted runs newest-first. Each
1212    /// source is fully drained before moving to the next. Sources are
1213    /// lazily initialized: the filter check, index load, and first data
1214    /// block fetch only happen when the recency walk reaches that source.
1215    /// A prefix read whose data lives in the active memtable therefore
1216    /// performs zero I/O. When the walk does have to descend to SST
1217    /// sources, configuring prefix bloom filters lets the scan skip
1218    /// non-matching SSTs without a data-block fetch, which keeps I/O
1219    /// proportional to how recent the data is rather than to the size of
1220    /// the LSM.
1221    ///
1222    /// **Multi-segment prefixes are rejected.** If the prefix overlaps
1223    /// more than one segment, this returns an error with
1224    /// [`crate::ErrorKind::Invalid`]. The recency guarantee is only
1225    /// well-defined within a single segment: walking one segment's oldest
1226    /// data before touching another segment's newest data would violate
1227    /// freshest-first ordering. Callers that need cross-segment scans
1228    /// should use [`Self::scan_prefix`] instead.
1229    ///
1230    /// ## Arguments
1231    /// - `prefix`: the key prefix to scan
1232    ///
1233    /// ## Returns
1234    /// - `Result<RecencyIterator, Error>`: an iterator that yields raw
1235    ///   `RowEntry` values newest-first. Use
1236    ///   [`RecencyIterator::next_entry`] to pull the next entry, and
1237    ///   inspect `entry.value` for the [`crate::types::ValueDeletable`]
1238    ///   variant (`Value`, `Merge`, or `Tombstone`).
1239    ///
1240    /// ## Examples
1241    ///
1242    /// Pull entries from the freshest source under the prefix and stop.
1243    /// Because sources are walked newest-first and lazily initialized, an
1244    /// early-return like this only touches the source that holds the
1245    /// freshest data. Note that *within* a source the order is set by
1246    /// `options.order` (ascending by default), so the first yielded entry
1247    /// is the smallest key in the freshest source, not necessarily the
1248    /// most recently written key. Across sources the same key may appear
1249    /// more than once, so callers that want only the freshest value per
1250    /// key should dedupe.
1251    ///
1252    /// ```
1253    /// use slatedb::{Db, Error};
1254    /// use slatedb::object_store::{ObjectStore, memory::InMemory};
1255    /// use slatedb::ValueDeletable;
1256    /// use std::sync::Arc;
1257    ///
1258    /// #[tokio::main]
1259    /// async fn main() -> Result<(), Error> {
1260    ///     let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
1261    ///     let db = Db::open("test_db", object_store).await?;
1262    ///     db.put(b"user:42", b"alice").await?;
1263    ///     db.put(b"user:42", b"alice2").await?;
1264    ///     db.put(b"user:99", b"bob").await?;
1265    ///
1266    ///     let mut iter = db.scan_prefix_by_recency(b"user:").await?;
1267    ///     let entry = iter.next_entry().await?.unwrap();
1268    ///     // All three writes live in the active memtable (the freshest
1269    ///     // source). With ascending within-source order, "user:42" comes
1270    ///     // out first because it sorts before "user:99". Both writes to
1271    ///     // "user:42" are stored as separate sequence-numbered entries;
1272    ///     // within the "user:42" key group the newest seq is yielded
1273    ///     // first, so the first entry carries the latest write
1274    ///     // ("alice2"). A second pull would yield the older "user:42"
1275    ///     // entry ("alice") before advancing to "user:99".
1276    ///     assert_eq!(entry.key.as_ref(), b"user:42");
1277    ///     match entry.value {
1278    ///         ValueDeletable::Value(v) => assert_eq!(v.as_ref(), b"alice2"),
1279    ///         _ => panic!("expected a regular value"),
1280    ///     }
1281    ///     Ok(())
1282    /// }
1283    /// ```
1284    pub async fn scan_prefix_by_recency<P>(
1285        &self,
1286        prefix: P,
1287    ) -> Result<DbRecencyIterator, crate::Error>
1288    where
1289        P: AsRef<[u8]> + Send,
1290    {
1291        self.scan_prefix_by_recency_with_options(prefix, &ScanOptions::default())
1292            .await
1293    }
1294
1295    /// Recency-ordered prefix scan with custom options.
1296    ///
1297    /// Same contract as [`Self::scan_prefix_by_recency`] (raw entries
1298    /// emitted newest-source-first; caller handles dedupe, tombstones, and
1299    /// merge operands).
1300    ///
1301    /// ## Arguments
1302    /// - `prefix`: the key prefix to scan
1303    /// - `options`: the scan options to use
1304    ///
1305    /// ## Returns
1306    /// - `Result<RecencyIterator, Error>`: an iterator that yields raw
1307    ///   `RowEntry` values newest-first.
1308    ///
1309    /// ## Examples
1310    ///
1311    /// Use `cache_blocks: false` to scan recent data without polluting
1312    /// the block cache, and stop after pulling enough entries from the
1313    /// freshest source. Combined with the recency walk's early-stop, this
1314    /// is a cheap way to ask "is there a recent entry under this prefix?"
1315    /// without warming the cache for cold blocks the answer doesn't depend
1316    /// on.
1317    ///
1318    /// ```
1319    /// use slatedb::{Db, Error};
1320    /// use slatedb::config::ScanOptions;
1321    /// use slatedb::object_store::{ObjectStore, memory::InMemory};
1322    /// use std::sync::Arc;
1323    ///
1324    /// #[tokio::main]
1325    /// async fn main() -> Result<(), Error> {
1326    ///     let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
1327    ///     let db = Db::open("test_db", object_store).await?;
1328    ///     db.put(b"event:001", b"a").await?;
1329    ///     db.put(b"event:002", b"b").await?;
1330    ///
1331    ///     let options = ScanOptions {
1332    ///         cache_blocks: false,
1333    ///         ..ScanOptions::default()
1334    ///     };
1335    ///     let mut iter = db
1336    ///         .scan_prefix_by_recency_with_options(b"event:", &options)
1337    ///         .await?;
1338    ///     let first = iter.next_entry().await?.unwrap();
1339    ///     // Within-source order is ascending by default, so "event:001"
1340    ///     // is yielded before "event:002" even though both live in the
1341    ///     // same (freshest) source. Stop here: we only needed to see
1342    ///     // that something fresh exists under the prefix.
1343    ///     assert_eq!(first.key.as_ref(), b"event:001");
1344    ///     Ok(())
1345    /// }
1346    /// ```
1347    pub async fn scan_prefix_by_recency_with_options<P>(
1348        &self,
1349        prefix: P,
1350        options: &ScanOptions,
1351    ) -> Result<DbRecencyIterator, crate::Error>
1352    where
1353        P: AsRef<[u8]> + Send,
1354    {
1355        let prefix = Bytes::copy_from_slice(prefix.as_ref());
1356        self.inner
1357            .scan_prefix_by_recency_with_options(prefix, options)
1358            .await
1359            .map_err(Into::into)
1360    }
1361
1362    /// Write a value into the database with default `WriteOptions`.
1363    ///
1364    /// ## Arguments
1365    /// - `key`: the key to write
1366    /// - `value`: the value to write
1367    ///
1368    /// ## Errors
1369    /// - `Error`: if there was an error writing the value.
1370    ///
1371    /// ## Examples
1372    ///
1373    /// ```
1374    /// use slatedb::{Db, Error};
1375    /// use slatedb::object_store::{ObjectStore, memory::InMemory};
1376    /// use std::sync::Arc;
1377    ///
1378    /// #[tokio::main]
1379    /// async fn main() -> Result<(), Error> {
1380    ///     let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
1381    ///     let db = Db::open("test_db", object_store).await?;
1382    ///     let handle = db.put(b"key", b"value").await?;
1383    ///     Ok(())
1384    /// }
1385    /// ```
1386    pub async fn put<K, V>(&self, key: K, value: V) -> Result<WriteHandle, crate::Error>
1387    where
1388        K: AsRef<[u8]>,
1389        V: AsRef<[u8]>,
1390    {
1391        let mut batch = WriteBatch::new();
1392        batch.put(key, value);
1393        self.write(batch).await
1394    }
1395
1396    /// Write a value into the database with custom `PutOptions` and `WriteOptions`.
1397    ///
1398    /// ## Arguments
1399    /// - `key`: the key to write
1400    /// - `value`: the value to write
1401    /// - `put_opts`: the put options to use
1402    /// - `write_opts`: the write options to use
1403    ///
1404    /// ## Errors
1405    /// - `Error`: if there was an error writing the value.
1406    ///
1407    /// ## Examples
1408    ///
1409    /// ```
1410    /// use slatedb::{Db, config::{PutOptions, WriteOptions}, Error};
1411    /// use slatedb::object_store::{ObjectStore, memory::InMemory};
1412    /// use std::sync::Arc;
1413    ///
1414    /// #[tokio::main]
1415    /// async fn main() -> Result<(), Error> {
1416    ///     let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
1417    ///     let db = Db::open("test_db", object_store).await?;
1418    ///     let handle = db.put_with_options(b"key", b"value", &PutOptions::default(), &WriteOptions::default()).await?;
1419    ///     Ok(())
1420    /// }
1421    /// ```
1422    pub async fn put_with_options<K, V>(
1423        &self,
1424        key: K,
1425        value: V,
1426        put_opts: &PutOptions,
1427        write_opts: &WriteOptions,
1428    ) -> Result<WriteHandle, crate::Error>
1429    where
1430        K: AsRef<[u8]>,
1431        V: AsRef<[u8]>,
1432    {
1433        let mut batch = WriteBatch::new();
1434        batch.put_with_options(key, value, put_opts);
1435        self.write_with_options(batch, write_opts).await
1436    }
1437
1438    /// Write a value into the database using owned [`Bytes`], avoiding the
1439    /// copies that [`Db::put`] performs via `Bytes::copy_from_slice`. Prefer
1440    /// this form when the caller already holds the data as [`Bytes`] (e.g.
1441    /// from a prior read, a zero-copy buffer pool, or a client that produces
1442    /// [`Bytes`] directly).
1443    pub async fn put_bytes(&self, key: Bytes, value: Bytes) -> Result<WriteHandle, crate::Error> {
1444        self.put_bytes_with_options(key, value, &PutOptions::default(), &WriteOptions::default())
1445            .await
1446    }
1447
1448    /// Write a value into the database using owned [`Bytes`] with custom
1449    /// `PutOptions` and `WriteOptions`. See [`Db::put_bytes`] for why this
1450    /// form exists.
1451    pub async fn put_bytes_with_options(
1452        &self,
1453        key: Bytes,
1454        value: Bytes,
1455        put_opts: &PutOptions,
1456        write_opts: &WriteOptions,
1457    ) -> Result<WriteHandle, crate::Error> {
1458        let mut batch = WriteBatch::new();
1459        batch.put_bytes_with_options(key, value, put_opts);
1460        self.write_with_options(batch, write_opts).await
1461    }
1462
1463    /// Delete a key from the database with default `WriteOptions`.
1464    ///
1465    /// ## Arguments
1466    /// - `key`: the key to delete
1467    ///
1468    /// ## Errors
1469    /// - `Error`: if there was an error deleting the key.
1470    ///
1471    /// ## Examples
1472    ///
1473    /// ```
1474    /// use slatedb::{Db, Error};
1475    /// use slatedb::object_store::{ObjectStore, memory::InMemory};
1476    /// use std::sync::Arc;
1477    ///
1478    /// #[tokio::main]
1479    /// async fn main() -> Result<(), Error> {
1480    ///     let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
1481    ///     let db = Db::open("test_db", object_store).await?;
1482    ///     let handle = db.delete(b"key").await?;
1483    ///     Ok(())
1484    /// }
1485    /// ```
1486    pub async fn delete<K: AsRef<[u8]>>(&self, key: K) -> Result<WriteHandle, crate::Error> {
1487        let mut batch = WriteBatch::new();
1488        batch.delete(key.as_ref());
1489        self.write(batch).await
1490    }
1491
1492    /// Delete a key from the database with custom `WriteOptions`.
1493    ///
1494    /// ## Arguments
1495    /// - `key`: the key to delete
1496    /// - `options`: the write options to use
1497    ///
1498    /// ## Errors
1499    /// - `Error`: if there was an error deleting the key.
1500    ///
1501    /// ## Examples
1502    ///
1503    /// ```
1504    /// use slatedb::{Db, config::WriteOptions, Error};
1505    /// use slatedb::object_store::{ObjectStore, memory::InMemory};
1506    /// use std::sync::Arc;
1507    ///
1508    /// #[tokio::main]
1509    /// async fn main() -> Result<(), Error> {
1510    ///     let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
1511    ///     let db = Db::open("test_db", object_store).await?;
1512    ///     let handle = db.delete_with_options(b"key", &WriteOptions::default()).await?;
1513    ///     Ok(())
1514    /// }
1515    /// ```
1516    pub async fn delete_with_options<K: AsRef<[u8]>>(
1517        &self,
1518        key: K,
1519        options: &WriteOptions,
1520    ) -> Result<WriteHandle, crate::Error> {
1521        let mut batch = WriteBatch::new();
1522        batch.delete(key);
1523        self.write_with_options(batch, options).await
1524    }
1525
1526    /// Merge a value into the database with default `MergeOptions` and `WriteOptions`.
1527    ///
1528    /// Merge operations allow applications to bypass the traditional read/modify/write cycle
1529    /// by expressing partial updates using an associative operator. The merge operator must
1530    /// be configured when opening the database.
1531    ///
1532    /// ## Arguments
1533    /// - `key`: the key to merge into
1534    /// - `value`: the merge operand to apply
1535    ///
1536    /// ## Errors
1537    /// - `Error`: if there was an error merging the value, or if no merge operator is configured.
1538    ///
1539    /// ## Examples
1540    ///
1541    /// ```
1542    /// use slatedb::{Db, Error, MergeOperator, MergeOperatorError};
1543    /// use slatedb::object_store::{ObjectStore, memory::InMemory};
1544    /// use std::sync::Arc;
1545    /// use bytes::Bytes;
1546    ///
1547    /// struct StringConcatMergeOperator;
1548    ///
1549    /// impl MergeOperator for StringConcatMergeOperator {
1550    ///     fn merge(&self, _key: &Bytes, existing_value: Option<Bytes>, value: Bytes) -> Result<Bytes, MergeOperatorError> {
1551    ///         let mut result = existing_value.unwrap_or_default().as_ref().to_vec();
1552    ///         result.extend_from_slice(&value);
1553    ///         Ok(Bytes::from(result))
1554    ///     }
1555    /// }
1556    ///
1557    /// #[tokio::main]
1558    /// async fn main() -> Result<(), Error> {
1559    ///     let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
1560    ///     let db = Db::builder("test_db", object_store)
1561    ///         .with_merge_operator(Arc::new(StringConcatMergeOperator))
1562    ///         .build()
1563    ///         .await?;
1564    ///     let handle = db.merge(b"key", b"value").await?;
1565    ///     Ok(())
1566    /// }
1567    /// ```
1568    pub async fn merge<K, V>(&self, key: K, value: V) -> Result<WriteHandle, crate::Error>
1569    where
1570        K: AsRef<[u8]>,
1571        V: AsRef<[u8]>,
1572    {
1573        self.merge_with_options(
1574            key,
1575            value,
1576            &MergeOptions::default(),
1577            &WriteOptions::default(),
1578        )
1579        .await
1580    }
1581
1582    /// Merge a value into the database with custom `MergeOptions` and `WriteOptions`.
1583    ///
1584    /// Merge operations allow applications to bypass the traditional read/modify/write cycle
1585    /// by expressing partial updates using an associative operator. The merge operator must
1586    /// be configured when opening the database.
1587    ///
1588    /// ## Arguments
1589    /// - `key`: the key to merge into
1590    /// - `value`: the merge operand to apply
1591    /// - `merge_opts`: the merge options to use
1592    /// - `write_opts`: the write options to use
1593    ///
1594    /// ## Errors
1595    /// - `Error`: if there was an error merging the value, or if no merge operator is configured.
1596    ///
1597    /// ## Examples
1598    ///
1599    /// ```
1600    /// use slatedb::{Db, Error, MergeOperator, MergeOperatorError, config::{MergeOptions, WriteOptions}};
1601    /// use slatedb::object_store::{ObjectStore, memory::InMemory};
1602    /// use std::sync::Arc;
1603    /// use bytes::Bytes;
1604    ///
1605    /// struct StringConcatMergeOperator;
1606    ///
1607    /// impl MergeOperator for StringConcatMergeOperator {
1608    ///     fn merge(&self, _key: &Bytes, existing_value: Option<Bytes>, value: Bytes) -> Result<Bytes, MergeOperatorError> {
1609    ///         let mut result = existing_value.unwrap_or_default().as_ref().to_vec();
1610    ///         result.extend_from_slice(&value);
1611    ///         Ok(Bytes::from(result))
1612    ///     }
1613    /// }
1614    ///
1615    /// #[tokio::main]
1616    /// async fn main() -> Result<(), Error> {
1617    ///     let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
1618    ///     let db = Db::builder("test_db", object_store)
1619    ///         .with_merge_operator(Arc::new(StringConcatMergeOperator))
1620    ///         .build()
1621    ///         .await?;
1622    ///     let handle = db.merge_with_options(
1623    ///         b"key",
1624    ///         b"value",
1625    ///         &MergeOptions::default(),
1626    ///         &WriteOptions::default()
1627    ///     ).await?;
1628    ///     Ok(())
1629    /// }
1630    /// ```
1631    pub async fn merge_with_options<K, V>(
1632        &self,
1633        key: K,
1634        value: V,
1635        merge_opts: &MergeOptions,
1636        write_opts: &WriteOptions,
1637    ) -> Result<WriteHandle, crate::Error>
1638    where
1639        K: AsRef<[u8]>,
1640        V: AsRef<[u8]>,
1641    {
1642        if self.inner.flush_merge_operator.is_none() {
1643            return Err(SlateDBError::MergeOperatorMissing.into());
1644        }
1645
1646        let mut batch = WriteBatch::new();
1647        batch.merge_with_options(key, value, merge_opts);
1648        self.write_with_options(batch, write_opts).await
1649    }
1650
1651    /// Write a batch of put/delete operations atomically to the database. Batch writes
1652    /// block other gets and writes until the batch is written to the WAL (or memtable if
1653    /// WAL is disabled).
1654    ///
1655    /// ## Arguments
1656    /// - `batch`: the batch of put/delete operations to write
1657    ///
1658    /// ## Errors
1659    /// - `Error`: if there was an error writing the batch.
1660    ///
1661    /// ## Examples
1662    ///
1663    /// ```
1664    /// use slatedb::{WriteBatch, Db, Error};
1665    /// use slatedb::object_store::{ObjectStore, memory::InMemory};
1666    /// use std::sync::Arc;
1667    ///
1668    /// #[tokio::main]
1669    /// async fn main() -> Result<(), Error> {
1670    ///     let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
1671    ///     let db = Db::open("test_db", object_store).await?;
1672    ///
1673    ///     let mut batch = WriteBatch::new();
1674    ///     batch.put(b"key1", b"value1");
1675    ///     batch.put(b"key2", b"value2");
1676    ///     batch.delete(b"key1");
1677    ///     let handle = db.write(batch).await?;
1678    ///
1679    ///     Ok(())
1680    /// }
1681    /// ```
1682    pub async fn write(&self, batch: WriteBatch) -> Result<WriteHandle, crate::Error> {
1683        self.write_with_options(batch, &WriteOptions::default())
1684            .await
1685    }
1686
1687    /// Write a batch of put/delete operations atomically to the database. Batch writes
1688    /// block other gets and writes until the batch is written to the WAL (or memtable if
1689    /// WAL is disabled).
1690    ///
1691    /// ## Arguments
1692    /// - `batch`: the batch of put/delete operations to write
1693    /// - `options`: the write options to use
1694    ///
1695    /// ## Errors
1696    /// - `Error`: if there was an error writing the batch.
1697    ///
1698    /// ## Examples
1699    ///
1700    /// ```
1701    /// use slatedb::{WriteBatch, Db, config::WriteOptions, Error};
1702    /// use slatedb::object_store::{ObjectStore, memory::InMemory};
1703    /// use std::sync::Arc;
1704    ///
1705    /// #[tokio::main]
1706    /// async fn main() -> Result<(), Error> {
1707    ///     let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
1708    ///     let db = Db::open("test_db", object_store).await?;
1709    ///
1710    ///     let mut batch = WriteBatch::new();
1711    ///     batch.put(b"key1", b"value1");
1712    ///     batch.put(b"key2", b"value2");
1713    ///     batch.delete(b"key1");
1714    ///     let handle = db.write_with_options(batch, &WriteOptions::default()).await?;
1715    ///
1716    ///     Ok(())
1717    /// }
1718    /// ```
1719    pub async fn write_with_options(
1720        &self,
1721        batch: WriteBatch,
1722        options: &WriteOptions,
1723    ) -> Result<WriteHandle, crate::Error> {
1724        self.inner
1725            .write_with_options(batch, options, None)
1726            .await
1727            .map_err(Into::into)
1728    }
1729
1730    /// Flush in-memory writes to disk. This function blocks until the in-memory
1731    /// data has been durably written to object storage.
1732    ///
1733    /// If WAL is enabled, this method is equivalent to:
1734    /// `flush_with_options(FlushOptions { flush_type: FlushType::Wal })`
1735    ///
1736    /// If WAL is disabled, this method is equivalent to:
1737    /// `flush_with_options(FlushOptions { flush_type: FlushType::Memtable })`.
1738    ///
1739    /// ## Errors
1740    /// - `Error`: if there was an error flushing the database
1741    ///
1742    /// ## Examples
1743    ///
1744    /// ```
1745    /// use slatedb::{Db, Error};
1746    /// use slatedb::object_store::{ObjectStore, memory::InMemory};
1747    /// use std::sync::Arc;
1748    ///
1749    /// #[tokio::main]
1750    /// async fn main() -> Result<(), Error> {
1751    ///     let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
1752    ///     let db = Db::open("test_db", object_store).await?;
1753    ///     db.flush().await?;
1754    ///     Ok(())
1755    /// }
1756    /// ```
1757    pub async fn flush(&self) -> Result<(), crate::Error> {
1758        let flush_type = if self.inner.wal_enabled {
1759            FlushType::Wal
1760        } else {
1761            FlushType::MemTable
1762        };
1763        self.inner
1764            .flush(FlushOptions { flush_type }, true)
1765            .await
1766            .map_err(Into::into)
1767    }
1768
1769    /// Flush in-memory writes to disk with custom options.
1770    ///
1771    /// An error will be returned if `options.flush_type` is `FlushType::Wal` and the WAL
1772    /// is disabled.
1773    ///
1774    /// `FlushType::Memtable` is allowed even if WAL is enabled.
1775    ///
1776    /// ## Arguments
1777    /// - `options`: the flush options
1778    ///
1779    /// ## Returns
1780    /// - `Result<(), crate::Error>`: the result of the flush operation.
1781    ///
1782    /// ## Errors
1783    /// - `Error`: if there was an error flushing the database
1784    ///
1785    /// ## Examples
1786    ///
1787    /// ```
1788    /// use slatedb::{Db, Error};
1789    /// use slatedb::config::{FlushOptions, FlushType};
1790    /// use slatedb::object_store::{ObjectStore, memory::InMemory};
1791    /// use std::sync::Arc;
1792    ///
1793    /// #[tokio::main]
1794    /// async fn main() -> Result<(), Error> {
1795    ///     let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
1796    ///     let db = Db::open("test_db", object_store).await?;
1797    ///     db.flush_with_options(FlushOptions {
1798    ///         flush_type: FlushType::Wal,
1799    ///     })
1800    ///     .await?;
1801    ///     Ok(())
1802    /// }
1803    /// ```
1804    pub async fn flush_with_options(&self, options: FlushOptions) -> Result<(), crate::Error> {
1805        self.inner.flush(options, true).await.map_err(Into::into)
1806    }
1807
1808    /// Refresh the manifest immediately and wait for it to complete.
1809    ///
1810    /// The database normally refreshes its manifest on a background timer
1811    /// controlled by [`Settings::manifest_poll_interval`]. This method
1812    /// bypasses that timer, triggering an immediate refresh and waiting
1813    /// for it to finish.
1814    ///
1815    /// Use this when you know the manifest has changed externally and
1816    /// want to ensure the database has observed the update before
1817    /// proceeding — for example, after a compaction completes and you
1818    /// need to confirm that a compaction filter has been applied.
1819    ///
1820    /// ## Errors
1821    /// - Returns [`Error`] if the database is closed before the refresh
1822    ///   completes.
1823    pub async fn refresh_manifest(&self) -> Result<(), crate::Error> {
1824        self.inner.check_closed()?;
1825        self.inner
1826            .memtable_flusher
1827            .refresh_manifest()
1828            .await
1829            .map_err(Into::into)
1830    }
1831
1832    /// Begin a new transaction with the specified isolation level.
1833    ///
1834    /// ## Arguments
1835    /// - `isolation_level`: the isolation level for the transaction
1836    ///
1837    /// ## Returns
1838    /// - `Result<DbTransaction, crate::Error>`: the transaction handle
1839    ///
1840    /// ## Examples
1841    ///
1842    /// ```rust
1843    /// use slatedb::{Db, IsolationLevel};
1844    /// use slatedb::object_store::memory::InMemory;
1845    /// use std::sync::Arc;
1846    ///
1847    /// #[tokio::main]
1848    /// async fn main() -> Result<(), slatedb::Error> {
1849    ///     let object_store = Arc::new(InMemory::new());
1850    ///     let db = Db::open("test_db", object_store).await?;
1851    ///     let txn = db.begin(IsolationLevel::SerializableSnapshot).await?;
1852    ///     Ok(())
1853    /// }
1854    /// ```
1855    pub async fn begin(
1856        &self,
1857        isolation_level: IsolationLevel,
1858    ) -> Result<DbTransaction, crate::Error> {
1859        self.inner.check_closed()?;
1860        let txn = DbTransaction::new(
1861            self.inner.clone(),
1862            self.inner.txn_manager.clone(),
1863            isolation_level,
1864        );
1865        Ok(txn)
1866    }
1867
1868    /// Resolve an object store from a URL.
1869    ///
1870    /// URL must not have a path component. This is an artifact of the way `object_store`
1871    /// handles URL parsing. Paths should be provided in the various `*Builder::new`
1872    /// methods that take `path` arguments, not in the URL passed to this method.
1873    ///
1874    /// ## Arguments
1875    /// - `url`: the URL to resolve with no trailing path, for example `s3://my-bucket`.
1876    ///
1877    /// ## Returns
1878    /// - `Result<Arc<dyn ObjectStore>, crate::Error>`: the resolved object store
1879    ///
1880    /// ## Errors
1881    /// - `Error`: if the URL is unparseable, if the URL contains a path component, or if
1882    ///   there was an error initializing the object store.
1883    pub fn resolve_object_store(url: &str) -> Result<Arc<dyn ObjectStore>, crate::Error> {
1884        let url = url
1885            .try_into()
1886            .map_err(|e| SlateDBError::InvalidObjectStoreURL(url.to_string(), e))?;
1887        // Lowercase env keys because parse_url_opts only recognizes lower case option keys.
1888        let env_vars = std::env::vars().map(|(key, value)| (key.to_ascii_lowercase(), value));
1889        let (object_store, path) = parse_url_opts(&url, env_vars).map_err(SlateDBError::from)?;
1890        if !path.as_ref().is_empty() {
1891            return Err(SlateDBError::InvalidObjectStorePath(path.to_string()))?;
1892        }
1893        Ok(Arc::from(object_store))
1894    }
1895}
1896
1897#[async_trait::async_trait]
1898impl DbReadOps for Db {
1899    async fn get_with_options<K: AsRef<[u8]> + Send>(
1900        &self,
1901        key: K,
1902        options: &ReadOptions,
1903    ) -> Result<Option<Bytes>, crate::Error> {
1904        Db::get_with_options(self, key, options).await
1905    }
1906
1907    async fn get_key_value_with_options<K: AsRef<[u8]> + Send>(
1908        &self,
1909        key: K,
1910        options: &ReadOptions,
1911    ) -> Result<Option<KeyValue>, crate::Error> {
1912        Db::get_key_value_with_options(self, key, options).await
1913    }
1914
1915    async fn scan_with_options<T>(
1916        &self,
1917        range: T,
1918        options: &ScanOptions,
1919    ) -> Result<DbIterator, crate::Error>
1920    where
1921        T: ByteRangeBounds + Send,
1922    {
1923        Db::scan_with_options(self, range, options).await
1924    }
1925
1926    async fn scan_prefix_with_options<P, T>(
1927        &self,
1928        prefix: P,
1929        subrange: T,
1930        options: &ScanOptions,
1931    ) -> Result<DbIterator, crate::Error>
1932    where
1933        P: AsRef<[u8]> + Send,
1934        T: ByteRangeBounds + Send,
1935    {
1936        Db::scan_prefix_with_options(self, prefix, subrange, options).await
1937    }
1938}
1939
1940impl DbMetadataOps for Db {
1941    fn manifest(&self) -> VersionedManifest {
1942        self.inner.manifest()
1943    }
1944
1945    fn subscribe(&self) -> tokio::sync::watch::Receiver<DbStatus> {
1946        self.inner.status_manager.subscribe()
1947    }
1948
1949    fn status(&self) -> DbStatus {
1950        self.inner.status()
1951    }
1952}
1953
1954#[async_trait::async_trait]
1955impl DbWriteOps for Db {
1956    type Transaction = DbTransaction;
1957
1958    async fn put_with_options<K, V>(
1959        &self,
1960        key: K,
1961        value: V,
1962        put_opts: &PutOptions,
1963        write_opts: &WriteOptions,
1964    ) -> Result<WriteHandle, crate::Error>
1965    where
1966        K: AsRef<[u8]> + Send,
1967        V: AsRef<[u8]> + Send,
1968    {
1969        Db::put_with_options(self, key, value, put_opts, write_opts).await
1970    }
1971
1972    async fn delete_with_options<K: AsRef<[u8]> + Send>(
1973        &self,
1974        key: K,
1975        options: &WriteOptions,
1976    ) -> Result<WriteHandle, crate::Error> {
1977        Db::delete_with_options(self, key, options).await
1978    }
1979
1980    async fn merge_with_options<K, V>(
1981        &self,
1982        key: K,
1983        value: V,
1984        merge_opts: &MergeOptions,
1985        write_opts: &WriteOptions,
1986    ) -> Result<WriteHandle, crate::Error>
1987    where
1988        K: AsRef<[u8]> + Send,
1989        V: AsRef<[u8]> + Send,
1990    {
1991        Db::merge_with_options(self, key, value, merge_opts, write_opts).await
1992    }
1993
1994    async fn write_with_options(
1995        &self,
1996        batch: WriteBatch,
1997        options: &WriteOptions,
1998    ) -> Result<WriteHandle, crate::Error> {
1999        Db::write_with_options(self, batch, options).await
2000    }
2001
2002    async fn flush(&self) -> Result<(), crate::Error> {
2003        Db::flush(self).await
2004    }
2005
2006    async fn flush_with_options(&self, options: FlushOptions) -> Result<(), crate::Error> {
2007        Db::flush_with_options(self, options).await
2008    }
2009
2010    async fn begin(&self, isolation_level: IsolationLevel) -> Result<DbTransaction, crate::Error> {
2011        Db::begin(self, isolation_level).await
2012    }
2013}
2014
2015impl Db {
2016    /// See [`DbMetadataOps::manifest`].
2017    pub fn manifest(&self) -> VersionedManifest {
2018        <Self as DbMetadataOps>::manifest(self)
2019    }
2020
2021    /// See [`DbMetadataOps::subscribe`].
2022    pub fn subscribe(&self) -> tokio::sync::watch::Receiver<DbStatus> {
2023        <Self as DbMetadataOps>::subscribe(self)
2024    }
2025
2026    /// See [`DbMetadataOps::status`].
2027    pub fn status(&self) -> DbStatus {
2028        <Self as DbMetadataOps>::status(self)
2029    }
2030}
2031
2032#[async_trait::async_trait]
2033impl DbCacheManagerOps for Db {
2034    async fn warm_sst(
2035        &self,
2036        sst_id: SsTableId,
2037        targets: &[CacheTarget],
2038    ) -> Result<(), crate::Error> {
2039        self.inner.check_closed()?;
2040        let manifest = self.manifest();
2041        db_cache_manager::warm_sst_impl(&self.inner.table_store, &manifest, sst_id, targets).await
2042    }
2043
2044    async fn evict_cached_sst(&self, sst_id: SsTableId) -> Result<(), crate::Error> {
2045        self.inner.check_closed()?;
2046        db_cache_manager::evict_cached_sst_impl(&self.inner.table_store, sst_id).await
2047    }
2048}
2049
2050/// Handle returned from write operations, containing metadata about the write.
2051/// This structure is designed to be extensible for future enhancements.
2052#[derive(Debug, Clone)]
2053pub struct WriteHandle {
2054    pub(crate) seq: u64,
2055    pub(crate) create_ts: i64,
2056}
2057
2058impl WriteHandle {
2059    pub fn new(seq: u64, create_ts: i64) -> Self {
2060        Self { seq, create_ts }
2061    }
2062
2063    /// Returns the sequence number assigned to this write operation.
2064    pub fn seqnum(&self) -> u64 {
2065        self.seq
2066    }
2067
2068    /// Returns the creation timestamp assigned to this write operation.
2069    pub fn create_ts(&self) -> i64 {
2070        self.create_ts
2071    }
2072}
2073
2074/// Wraps [`WalObserver`] and injects a [`crate::wal_buffer::WalStatusListener`]
2075/// that updates the oracle and manifest, and drives cross-task notifications about wal events
2076/// via a [`tokio::sync::watch`] channel.
2077#[derive(Clone)]
2078pub(crate) struct DbWalObserver {
2079    status_rx: tokio::sync::watch::Receiver<Result<WalStatus, WalStatus>>,
2080    closed_reader: WatchableOnceCellReader<Result<(), SlateDBError>>,
2081    wrapped: Arc<dyn WalObserver>,
2082}
2083
2084impl DbWalObserver {
2085    fn new(
2086        wrapped: Box<dyn WalObserver>,
2087        oracle: Arc<DbOracle>,
2088        db_state: Arc<RwLock<DbState>>,
2089        closed_writer: Arc<dyn ClosedResultWriter>,
2090    ) -> Self {
2091        let (status_tx, status_rx) = tokio::sync::watch::channel(wrapped.status());
2092        let closed_reader = closed_writer.result_reader();
2093        wrapped
2094            .subscribe(Arc::new(move |event| {
2095                let status: Result<WalStatus, WalStatus> = match event {
2096                    WalEvent::WalFlushed(status) => {
2097                        if let Some(seq) = status.last_flushed_seq {
2098                            oracle.advance_durable_seq(seq);
2099                        }
2100                        let mut guard = db_state.write();
2101                        guard.set_next_wal_id(status.last_flushed_wal_id + 1);
2102                        drop(guard);
2103                        Ok(status)
2104                    }
2105                    WalEvent::WalClosed(status) => {
2106                        closed_writer.write_result(Err(status.clone().into()));
2107                        Err(status)
2108                    }
2109                };
2110                let _ = status_tx.send(status);
2111            }))
2112            .expect("failed to subscribe to wal");
2113        Self {
2114            status_rx,
2115            closed_reader,
2116            wrapped: wrapped.into(),
2117        }
2118    }
2119
2120    pub(crate) fn status(&self) -> Result<WalStatus, WalStatus> {
2121        self.wrapped.status()
2122    }
2123
2124    async fn wait_on_condition(
2125        &self,
2126        mut predicate: impl FnMut(&WalStatus) -> bool,
2127    ) -> Result<(), SlateDBError> {
2128        let mut status_rx = self.status_rx.clone();
2129        let result = status_rx
2130            .wait_for(|s| match s {
2131                Err(_) => true,
2132                Ok(s) => predicate(s),
2133            })
2134            .await;
2135        let Ok(result) = result else {
2136            drop(result);
2137            debug!("wal listener tx dropped - wait on db close");
2138            return self.closed_reader.clone().await_value().await;
2139        };
2140        let result = result.clone();
2141        result?;
2142        Ok(())
2143    }
2144
2145    /// Waits until the wal a given wal id is released by the wal writer
2146    async fn wait_until_wal_flushed(&self, last_flushed_wal_id: u64) -> Result<(), SlateDBError> {
2147        self.wait_on_condition(|status| status.last_flushed_wal_id > last_flushed_wal_id)
2148            .await
2149    }
2150}
2151
2152#[cfg(test)]
2153mod tests {
2154    use super::*;
2155    use crate::block_cache_policy::BlockCachePolicy;
2156    use crate::config::DurabilityLevel::{Memory, Remote};
2157    use crate::config::MetricLevel;
2158    use crate::config::{
2159        CheckpointOptions, CompactionWorkerOptions, CompactorOptions,
2160        GarbageCollectorDirectoryOptions, GarbageCollectorOptions, ObjectStoreCacheOptions,
2161        PutOptions, ScanOptions, Settings, SstBlockSize, Ttl, WriteOptions,
2162    };
2163    use crate::db::builder::GarbageCollectorBuilder;
2164    use crate::db_stats::IMMUTABLE_MEMTABLE_FLUSHES;
2165    use crate::format::sst::SsTableFormat;
2166    use crate::instrumented_object_store::stats::{
2167        REQUEST_COUNT as OBJECT_STORE_REQUEST_COUNT,
2168        REQUEST_DURATION_SECONDS as OBJECT_STORE_REQUEST_DURATION_SECONDS,
2169    };
2170    use crate::iter::RowEntryIterator;
2171    use crate::manifest::store::{ManifestStore, StoredManifest};
2172    use crate::manifest::{ManifestCore, VersionedManifest};
2173    use crate::merge_operator::{
2174        MERGE_OPERATOR_COMPACT_PATH, MERGE_OPERATOR_FLUSH_PATH, MERGE_OPERATOR_READ_PATH,
2175    };
2176    use crate::object_stores::ObjectStores;
2177    use crate::proptest_util::arbitrary;
2178    use crate::proptest_util::sample;
2179    use crate::seq_tracker::FindOption;
2180    use crate::sst_iter::{SstIterator, SstIteratorOptions};
2181    use crate::tablestore::TableStoreKind;
2182    use crate::test_utils::{
2183        assert_iterator, lookup_merge_operator_operands, GatedObjectStore,
2184        OnDemandCompactionSchedulerSupplier, StringConcatMergeOperator,
2185    };
2186    use crate::types::RowEntry;
2187    use crate::wal::WalError;
2188    use crate::wal_reader::WalReader;
2189    use crate::{proptest_util, test_utils, CloseReason, CompactorBuilder, KeyValue};
2190    use async_trait::async_trait;
2191    use chrono::{TimeZone, Utc};
2192    use fail_parallel::FailPointRegistry;
2193    use futures::{future, future::join_all, FutureExt, StreamExt};
2194    use object_store::memory::InMemory;
2195    use object_store::ObjectStore;
2196    use proptest::test_runner::{TestRng, TestRunner};
2197    use slatedb_common::clock::DefaultSystemClock;
2198    use slatedb_common::clock::MockSystemClock;
2199    use slatedb_common::metrics::{
2200        lookup_metric, lookup_metric_with_labels, DefaultMetricsRecorder, MetricValue,
2201    };
2202    use std::collections::BTreeMap;
2203    use std::collections::Bound::Included;
2204    use std::sync::atomic::{AtomicBool, Ordering};
2205    use std::time::Duration;
2206    use tokio::runtime::Runtime;
2207    use tracing::info;
2208
2209    fn object_store_labels(
2210        component: &'static str,
2211        store_type: &'static str,
2212        op: &'static str,
2213        api: &'static str,
2214    ) -> [(&'static str, &'static str); 4] {
2215        [
2216            ("component", component),
2217            ("store_type", store_type),
2218            ("op", op),
2219            ("api", api),
2220        ]
2221    }
2222
2223    fn lookup_object_store_histogram_count(
2224        recorder: &DefaultMetricsRecorder,
2225        labels: &[(&str, &str)],
2226    ) -> Option<u64> {
2227        recorder
2228            .snapshot()
2229            .by_name_and_labels(OBJECT_STORE_REQUEST_DURATION_SECONDS, labels)
2230            .map(|metric| match &metric.value {
2231                MetricValue::Histogram { count, .. } => *count,
2232                other => panic!("expected histogram metric, got {other:?}"),
2233            })
2234    }
2235
2236    fn lookup_object_store_op_request_count(
2237        recorder: &DefaultMetricsRecorder,
2238        component: &'static str,
2239        store_type: &'static str,
2240        op: &'static str,
2241    ) -> i64 {
2242        let apis = match op {
2243            "get" => &["get", "get_range", "get_ranges", "head"][..],
2244            "put" => &[
2245                "put",
2246                "multipart_init",
2247                "multipart_part",
2248                "multipart_complete",
2249            ][..],
2250            "delete" => &["delete"][..],
2251            _ => panic!("unexpected op {op}"),
2252        };
2253
2254        apis.iter()
2255            .map(|api| {
2256                lookup_metric_with_labels(
2257                    recorder,
2258                    OBJECT_STORE_REQUEST_COUNT,
2259                    &object_store_labels(component, store_type, op, api),
2260                )
2261                .unwrap_or(0)
2262            })
2263            .sum()
2264    }
2265
2266    fn lookup_object_store_op_histogram_count(
2267        recorder: &DefaultMetricsRecorder,
2268        component: &'static str,
2269        store_type: &'static str,
2270        op: &'static str,
2271    ) -> u64 {
2272        let apis = match op {
2273            "get" => &["get", "get_range", "get_ranges", "head"][..],
2274            "put" => &[
2275                "put",
2276                "multipart_init",
2277                "multipart_part",
2278                "multipart_complete",
2279            ][..],
2280            "delete" => &["delete"][..],
2281            _ => panic!("unexpected op {op}"),
2282        };
2283
2284        apis.iter()
2285            .map(|api| {
2286                lookup_object_store_histogram_count(
2287                    recorder,
2288                    &object_store_labels(component, store_type, op, api),
2289                )
2290                .unwrap_or(0)
2291            })
2292            .sum()
2293    }
2294
2295    #[tokio::test]
2296    async fn test_put_get_delete() {
2297        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2298        let kv_store = Db::builder("/tmp/test_kv_store", object_store)
2299            .with_settings(test_db_options(0, 1024, None))
2300            .build()
2301            .await
2302            .unwrap();
2303
2304        let key = b"test_key";
2305        let value = b"test_value";
2306        kv_store.put(key, value).await.unwrap();
2307        kv_store.flush().await.unwrap();
2308
2309        assert_eq!(
2310            kv_store.get(key).await.unwrap(),
2311            Some(Bytes::from_static(value))
2312        );
2313        kv_store.delete(key).await.unwrap();
2314        assert_eq!(None, kv_store.get(key).await.unwrap());
2315        kv_store.close().await.unwrap();
2316    }
2317
2318    #[tokio::test]
2319    async fn test_manifest_returns_current_versioned_manifest() {
2320        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2321        let db = Db::builder("/tmp/test_manifest_accessor", object_store)
2322            .with_settings(test_db_options(0, 1024, None))
2323            .build()
2324            .await
2325            .unwrap();
2326
2327        db.put(b"test_key", b"test_value").await.unwrap();
2328
2329        let manifest = db.manifest();
2330        let expected: VersionedManifest = db.inner.state.read().state().manifest.clone().into();
2331        assert_eq!(manifest, expected);
2332
2333        db.close().await.unwrap();
2334    }
2335
2336    #[tokio::test]
2337    async fn test_coarse_size_estimation_via_manifest() {
2338        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2339        let path = "/tmp/test_coarse_size_estimation";
2340        let should_compact = Arc::new(AtomicBool::new(false));
2341        let should_compact_clone = should_compact.clone();
2342        let compaction_scheduler = Arc::new(OnDemandCompactionSchedulerSupplier::new(Arc::new(
2343            move |_state| should_compact_clone.swap(false, Ordering::SeqCst),
2344        )));
2345        let db = Db::builder(path, object_store.clone())
2346            .with_settings(test_db_options(0, 1024, None))
2347            .with_compactor_builder(
2348                CompactorBuilder::new(path, object_store.clone())
2349                    .with_scheduler_supplier(compaction_scheduler)
2350                    .with_options(fast_compactor_options()),
2351            )
2352            .build()
2353            .await
2354            .unwrap();
2355        let db = Arc::new(db);
2356
2357        // Write keys in the range k0000..k0099 and flush to L0
2358        for i in 0..100u32 {
2359            let key = format!("k{:04}", i);
2360            db.put(key.as_bytes(), &[0u8; 64]).await.unwrap();
2361        }
2362        db.flush().await.unwrap();
2363
2364        // estimate_size on L0 views should return non-zero
2365        let manifest = db.manifest();
2366        assert!(!manifest.manifest.core.tree.l0.is_empty());
2367        for view in &manifest.manifest.core.tree.l0 {
2368            assert!(view.estimate_size() > 0);
2369        }
2370
2371        // Trigger compaction and wait for sorted runs
2372        should_compact.store(true, Ordering::SeqCst);
2373        let db_poll = db.clone();
2374        tokio::time::timeout(Duration::from_secs(10), async move {
2375            loop {
2376                {
2377                    let state = db_poll.inner.state.read();
2378                    if !state.state().core().tree.compacted.is_empty() {
2379                        return;
2380                    }
2381                }
2382                tokio::time::sleep(Duration::from_millis(10)).await;
2383            }
2384        })
2385        .await
2386        .unwrap();
2387
2388        let manifest = db.manifest();
2389        assert!(!manifest.manifest.core.tree.compacted.is_empty());
2390
2391        for sr in &manifest.manifest.core.tree.compacted {
2392            // A range covering all keys returns results
2393            let covering = sr
2394                .tables_covering_range(Bytes::from_static(b"k0000")..Bytes::from_static(b"k0100"));
2395            assert!(!covering.is_empty());
2396            for view in &covering {
2397                assert!(view.estimate_size() > 0);
2398            }
2399
2400            // A range before all keys returns nothing
2401            let outside = sr
2402                .tables_covering_range(Bytes::from_static(b"a0000")..Bytes::from_static(b"a9999"));
2403            assert!(outside.is_empty());
2404        }
2405
2406        db.close().await.unwrap();
2407    }
2408
2409    #[tokio::test]
2410    async fn test_scan_prefix_returns_matching_keys() {
2411        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2412        let kv_store = Db::builder("/tmp/test_scan_prefix", object_store)
2413            .with_settings(test_db_options(0, 1024, None))
2414            .build()
2415            .await
2416            .unwrap();
2417
2418        kv_store.put(b"ab", b"v0").await.unwrap();
2419        kv_store.put(b"aba", b"v1").await.unwrap();
2420        kv_store.put(b"abb", b"v2").await.unwrap();
2421        kv_store.put(b"ac", b"v3").await.unwrap();
2422
2423        let mut iter = kv_store.scan_prefix(b"ab", ..).await.unwrap();
2424        assert_eq!(iter.next().await.unwrap().unwrap().key.as_ref(), b"ab");
2425        assert_eq!(iter.next().await.unwrap().unwrap().key.as_ref(), b"aba");
2426        assert_eq!(iter.next().await.unwrap().unwrap().key.as_ref(), b"abb");
2427        assert_eq!(iter.next().await.unwrap(), None);
2428
2429        kv_store.close().await.unwrap();
2430    }
2431
2432    #[tokio::test]
2433    async fn test_scan_and_prefix_range_forms_are_accepted() {
2434        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2435        let kv_store = Db::builder("/tmp/test_scan_range_forms", object_store)
2436            .with_settings(test_db_options(0, 1024, None))
2437            .build()
2438            .await
2439            .unwrap();
2440
2441        kv_store.put(b"a", b"v0").await.unwrap();
2442        kv_store.put(b"aa", b"v1").await.unwrap();
2443        kv_store.put(b"ab", b"v2").await.unwrap();
2444        kv_store.put(b"b", b"v3").await.unwrap();
2445
2446        let mut all = kv_store.scan(..).await.unwrap();
2447        assert_eq!(all.next().await.unwrap().unwrap().key.as_ref(), b"a");
2448        assert_eq!(all.next().await.unwrap().unwrap().key.as_ref(), b"aa");
2449        assert_eq!(all.next().await.unwrap().unwrap().key.as_ref(), b"ab");
2450        assert_eq!(all.next().await.unwrap().unwrap().key.as_ref(), b"b");
2451        assert_eq!(all.next().await.unwrap(), None);
2452
2453        let mut range = kv_store.scan(b"a".to_vec()..=b"ab".to_vec()).await.unwrap();
2454        assert_eq!(range.next().await.unwrap().unwrap().key.as_ref(), b"a");
2455        assert_eq!(range.next().await.unwrap().unwrap().key.as_ref(), b"aa");
2456        assert_eq!(range.next().await.unwrap().unwrap().key.as_ref(), b"ab");
2457        assert_eq!(range.next().await.unwrap(), None);
2458
2459        let mut prefix = kv_store.scan_prefix(b"a", b"".to_vec()..).await.unwrap();
2460        assert_eq!(prefix.next().await.unwrap().unwrap().key.as_ref(), b"a");
2461        assert_eq!(prefix.next().await.unwrap().unwrap().key.as_ref(), b"aa");
2462        assert_eq!(prefix.next().await.unwrap().unwrap().key.as_ref(), b"ab");
2463        assert_eq!(prefix.next().await.unwrap(), None);
2464
2465        let mut bounded_prefix = kv_store
2466            .scan_prefix(b"a", b"a".to_vec()..=b"b".to_vec())
2467            .await
2468            .unwrap();
2469        assert_eq!(
2470            bounded_prefix.next().await.unwrap().unwrap().key.as_ref(),
2471            b"aa"
2472        );
2473        assert_eq!(
2474            bounded_prefix.next().await.unwrap().unwrap().key.as_ref(),
2475            b"ab"
2476        );
2477        assert_eq!(bounded_prefix.next().await.unwrap(), None);
2478
2479        kv_store.close().await.unwrap();
2480    }
2481
2482    #[tokio::test]
2483    async fn test_scan_descending_returns_records_in_reverse_order() {
2484        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2485        let kv_store = Db::builder("/tmp/test_scan_descending", object_store)
2486            .with_settings(test_db_options(0, 1024, None))
2487            .build()
2488            .await
2489            .unwrap();
2490
2491        kv_store.put(b"a", b"v0").await.unwrap();
2492        kv_store.put(b"b", b"v1").await.unwrap();
2493        kv_store.flush().await.unwrap();
2494        kv_store.put(b"c", b"v2").await.unwrap();
2495        kv_store.put(b"d", b"v3").await.unwrap();
2496
2497        let scan_options = ScanOptions::default().with_order(IterationOrder::Descending);
2498        let mut iter = kv_store.scan_with_options(.., &scan_options).await.unwrap();
2499        assert_eq!(iter.next().await.unwrap().unwrap().key.as_ref(), b"d");
2500        assert_eq!(iter.next().await.unwrap().unwrap().key.as_ref(), b"c");
2501        assert_eq!(iter.next().await.unwrap().unwrap().key.as_ref(), b"b");
2502        assert_eq!(iter.next().await.unwrap().unwrap().key.as_ref(), b"a");
2503        assert_eq!(iter.next().await.unwrap(), None);
2504
2505        kv_store.close().await.unwrap();
2506    }
2507
2508    #[tokio::test]
2509    async fn test_scan_descending_bounded_range() {
2510        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2511        let kv_store = Db::builder("/tmp/test_scan_descending_bounded", object_store)
2512            .with_settings(test_db_options(0, 1024, None))
2513            .build()
2514            .await
2515            .unwrap();
2516
2517        kv_store.put(b"a", b"v0").await.unwrap();
2518        kv_store.put(b"b", b"v1").await.unwrap();
2519        kv_store.put(b"c", b"v2").await.unwrap();
2520        kv_store.put(b"d", b"v3").await.unwrap();
2521        kv_store.put(b"e", b"v4").await.unwrap();
2522
2523        let scan_options = ScanOptions::default().with_order(IterationOrder::Descending);
2524        let mut iter = kv_store
2525            .scan_with_options(b"b".to_vec()..b"d".to_vec(), &scan_options)
2526            .await
2527            .unwrap();
2528        assert_eq!(iter.next().await.unwrap().unwrap().key.as_ref(), b"c");
2529        assert_eq!(iter.next().await.unwrap().unwrap().key.as_ref(), b"b");
2530        assert_eq!(iter.next().await.unwrap(), None);
2531
2532        kv_store.close().await.unwrap();
2533    }
2534
2535    #[tokio::test]
2536    async fn test_scan_descending_skips_deleted_keys() {
2537        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2538        let kv_store = Db::builder("/tmp/test_scan_descending_deletes", object_store)
2539            .with_settings(test_db_options(0, 1024, None))
2540            .build()
2541            .await
2542            .unwrap();
2543
2544        kv_store.put(b"a", b"v0").await.unwrap();
2545        kv_store.put(b"b", b"v1").await.unwrap();
2546        kv_store.put(b"c", b"v2").await.unwrap();
2547        kv_store.put(b"d", b"v3").await.unwrap();
2548        kv_store.delete(b"b").await.unwrap();
2549        kv_store.delete(b"d").await.unwrap();
2550
2551        let scan_options = ScanOptions::default().with_order(IterationOrder::Descending);
2552        let mut iter = kv_store.scan_with_options(.., &scan_options).await.unwrap();
2553        assert_eq!(iter.next().await.unwrap().unwrap().key.as_ref(), b"c");
2554        assert_eq!(iter.next().await.unwrap().unwrap().key.as_ref(), b"a");
2555        assert_eq!(iter.next().await.unwrap(), None);
2556
2557        kv_store.close().await.unwrap();
2558    }
2559
2560    #[tokio::test]
2561    async fn test_scan_prefix_descending() {
2562        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2563        let kv_store = Db::builder("/tmp/test_scan_prefix_descending", object_store)
2564            .with_settings(test_db_options(0, 1024, None))
2565            .build()
2566            .await
2567            .unwrap();
2568
2569        kv_store.put(b"prefix/a", b"v0").await.unwrap();
2570        kv_store.put(b"prefix/b", b"v1").await.unwrap();
2571        kv_store.put(b"prefix/c", b"v2").await.unwrap();
2572        kv_store.put(b"other/a", b"v3").await.unwrap();
2573
2574        let scan_options = ScanOptions::default().with_order(IterationOrder::Descending);
2575        let mut iter = kv_store
2576            .scan_prefix_with_options(b"prefix/", .., &scan_options)
2577            .await
2578            .unwrap();
2579        assert_eq!(
2580            iter.next().await.unwrap().unwrap().key.as_ref(),
2581            b"prefix/c"
2582        );
2583        assert_eq!(
2584            iter.next().await.unwrap().unwrap().key.as_ref(),
2585            b"prefix/b"
2586        );
2587        assert_eq!(
2588            iter.next().await.unwrap().unwrap().key.as_ref(),
2589            b"prefix/a"
2590        );
2591        assert_eq!(iter.next().await.unwrap(), None);
2592
2593        kv_store.close().await.unwrap();
2594    }
2595
2596    #[tokio::test]
2597    async fn test_scan_prefix_with_options_handles_unbounded_end() {
2598        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2599        let kv_store = Db::builder("/tmp/test_scan_prefix_unbounded", object_store)
2600            .with_settings(test_db_options(0, 1024, None))
2601            .build()
2602            .await
2603            .unwrap();
2604
2605        kv_store.put(&[0xff, 0xff], b"v0").await.unwrap();
2606        kv_store.put(&[0xff, 0xff, 0x00], b"v1").await.unwrap();
2607        kv_store.put(&[0xff, 0xff, 0x10], b"v2").await.unwrap();
2608        kv_store.put(&[0xff, 0xff, 0xff], b"v4").await.unwrap();
2609        kv_store.put(&[0xff, 0xfe], b"v3").await.unwrap();
2610
2611        let scan_options = ScanOptions {
2612            cache_blocks: false,
2613            ..ScanOptions::default()
2614        };
2615        let mut iter = kv_store
2616            .scan_prefix_with_options(&[0xff, 0xff], .., &scan_options)
2617            .await
2618            .unwrap();
2619        assert_eq!(
2620            iter.next().await.unwrap().unwrap().key.as_ref(),
2621            &[0xff, 0xff]
2622        );
2623        assert_eq!(
2624            iter.next().await.unwrap().unwrap().key.as_ref(),
2625            &[0xff, 0xff, 0x00]
2626        );
2627        assert_eq!(
2628            iter.next().await.unwrap().unwrap().key.as_ref(),
2629            &[0xff, 0xff, 0x10]
2630        );
2631        assert_eq!(
2632            iter.next().await.unwrap().unwrap().key.as_ref(),
2633            &[0xff, 0xff, 0xff]
2634        );
2635        assert_eq!(iter.next().await.unwrap(), None);
2636
2637        kv_store.close().await.unwrap();
2638    }
2639
2640    fn assert_value(entry: &crate::types::RowEntry, expected: &[u8]) {
2641        match &entry.value {
2642            crate::types::ValueDeletable::Value(v) => assert_eq!(v.as_ref(), expected),
2643            other => panic!("expected Value({expected:?}), got {other:?}"),
2644        }
2645    }
2646
2647    #[tokio::test]
2648    async fn test_scan_prefix_by_recency_returns_matching_keys_from_memtable() {
2649        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2650        let db = Db::builder("/tmp/test_recency_memtable", object_store)
2651            .with_settings(test_db_options(0, 64 * 1024, None))
2652            .build()
2653            .await
2654            .unwrap();
2655
2656        db.put(b"px:a", b"v0").await.unwrap();
2657        db.put(b"px:b", b"v1").await.unwrap();
2658        db.put(b"px:c", b"v2").await.unwrap();
2659        db.put(b"qq:x", b"vx").await.unwrap();
2660
2661        let mut iter = db.scan_prefix_by_recency(b"px:").await.unwrap();
2662        let e1 = iter.next_entry().await.unwrap().unwrap();
2663        let e2 = iter.next_entry().await.unwrap().unwrap();
2664        let e3 = iter.next_entry().await.unwrap().unwrap();
2665        assert_eq!(e1.key.as_ref(), b"px:a");
2666        assert_eq!(e2.key.as_ref(), b"px:b");
2667        assert_eq!(e3.key.as_ref(), b"px:c");
2668        assert!(iter.next_entry().await.unwrap().is_none());
2669
2670        db.close().await.unwrap();
2671    }
2672
2673    #[tokio::test]
2674    async fn test_scan_prefix_by_recency_no_match_returns_none() {
2675        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2676        let db = Db::builder("/tmp/test_recency_no_match", object_store)
2677            .with_settings(test_db_options(0, 64 * 1024, None))
2678            .build()
2679            .await
2680            .unwrap();
2681
2682        db.put(b"aa", b"v0").await.unwrap();
2683        db.put(b"ab", b"v1").await.unwrap();
2684
2685        let mut iter = db.scan_prefix_by_recency(b"zz").await.unwrap();
2686        assert!(iter.next_entry().await.unwrap().is_none());
2687
2688        db.close().await.unwrap();
2689    }
2690
2691    #[tokio::test]
2692    async fn test_scan_prefix_by_recency_emits_both_versions_across_sources() {
2693        // No dedup: a key present in both memtable (newer) and L0 (older)
2694        // appears twice, newer first.
2695        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2696        let db = Db::builder("/tmp/test_recency_no_dedup", object_store)
2697            .with_settings(test_db_options(0, 64 * 1024, None))
2698            .build()
2699            .await
2700            .unwrap();
2701
2702        db.put(b"px:a", b"old").await.unwrap();
2703        db.flush_with_options(FlushOptions {
2704            flush_type: FlushType::MemTable,
2705        })
2706        .await
2707        .unwrap();
2708
2709        db.put(b"px:a", b"new").await.unwrap();
2710
2711        let mut iter = db.scan_prefix_by_recency(b"px:").await.unwrap();
2712        let e1 = iter.next_entry().await.unwrap().unwrap();
2713        assert_eq!(e1.key.as_ref(), b"px:a");
2714        assert_value(&e1, b"new");
2715        let e2 = iter.next_entry().await.unwrap().unwrap();
2716        assert_eq!(e2.key.as_ref(), b"px:a");
2717        assert_value(&e2, b"old");
2718        assert!(e1.seq > e2.seq);
2719        assert!(iter.next_entry().await.unwrap().is_none());
2720
2721        db.close().await.unwrap();
2722    }
2723
2724    #[tokio::test]
2725    async fn test_scan_prefix_by_recency_emits_tombstones() {
2726        // Tombstones are surfaced as raw entries; the caller decides what
2727        // they mean.
2728        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2729        let db = Db::builder("/tmp/test_recency_tombstones", object_store)
2730            .with_settings(test_db_options(0, 64 * 1024, None))
2731            .build()
2732            .await
2733            .unwrap();
2734
2735        db.put(b"px:a", b"va").await.unwrap();
2736        db.put(b"px:b", b"vb").await.unwrap();
2737        db.flush_with_options(FlushOptions {
2738            flush_type: FlushType::MemTable,
2739        })
2740        .await
2741        .unwrap();
2742
2743        db.delete(b"px:a").await.unwrap();
2744
2745        let mut iter = db.scan_prefix_by_recency(b"px:").await.unwrap();
2746        // Memtable first: tombstone for px:a.
2747        let e1 = iter.next_entry().await.unwrap().unwrap();
2748        assert_eq!(e1.key.as_ref(), b"px:a");
2749        assert!(e1.value.is_tombstone());
2750        // L0 second: original values, ascending.
2751        let e2 = iter.next_entry().await.unwrap().unwrap();
2752        assert_eq!(e2.key.as_ref(), b"px:a");
2753        assert_value(&e2, b"va");
2754        let e3 = iter.next_entry().await.unwrap().unwrap();
2755        assert_eq!(e3.key.as_ref(), b"px:b");
2756        assert_value(&e3, b"vb");
2757        // Tombstone is from the freshest source so its seq dominates the
2758        // earlier put of px:a.
2759        assert!(e1.seq > e2.seq);
2760        assert!(iter.next_entry().await.unwrap().is_none());
2761
2762        db.close().await.unwrap();
2763    }
2764
2765    #[tokio::test]
2766    async fn test_scan_prefix_by_recency_walks_memtable_then_l0() {
2767        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2768        let db = Db::builder("/tmp/test_recency_multi_source", object_store)
2769            .with_settings(test_db_options(0, 64 * 1024, None))
2770            .build()
2771            .await
2772            .unwrap();
2773
2774        // Older L0 SST: px:b (older value), px:c.
2775        db.put(b"px:b", b"old_b").await.unwrap();
2776        db.put(b"px:c", b"vc").await.unwrap();
2777        db.flush_with_options(FlushOptions {
2778            flush_type: FlushType::MemTable,
2779        })
2780        .await
2781        .unwrap();
2782
2783        // Active memtable: px:a, px:b (newer value).
2784        db.put(b"px:a", b"va").await.unwrap();
2785        db.put(b"px:b", b"new_b").await.unwrap();
2786
2787        let mut iter = db.scan_prefix_by_recency(b"px:").await.unwrap();
2788        // Memtable drained first, ascending within source.
2789        let e1 = iter.next_entry().await.unwrap().unwrap();
2790        assert_eq!(e1.key.as_ref(), b"px:a");
2791        assert_value(&e1, b"va");
2792        let e2 = iter.next_entry().await.unwrap().unwrap();
2793        assert_eq!(e2.key.as_ref(), b"px:b");
2794        assert_value(&e2, b"new_b");
2795        // L0 next: px:b (older) then px:c. No dedup.
2796        let e3 = iter.next_entry().await.unwrap().unwrap();
2797        assert_eq!(e3.key.as_ref(), b"px:b");
2798        assert_value(&e3, b"old_b");
2799        let e4 = iter.next_entry().await.unwrap().unwrap();
2800        assert_eq!(e4.key.as_ref(), b"px:c");
2801        assert_value(&e4, b"vc");
2802        assert!(iter.next_entry().await.unwrap().is_none());
2803
2804        db.close().await.unwrap();
2805    }
2806
2807    #[tokio::test]
2808    async fn test_scan_prefix_by_recency_active_memtable_avoids_main_object_store_gets() {
2809        // When the prefix is satisfied entirely by the active memtable, the
2810        // recency scan should not issue any GETs against the main object
2811        // store (it shouldn't even open an L0/SR iterator).
2812        let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
2813        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2814        let db = Db::builder("/tmp/test_recency_no_main_gets", object_store)
2815            .with_settings(test_db_options(0, 64 * 1024, None))
2816            .with_metrics_recorder(metrics_recorder.clone())
2817            .build()
2818            .await
2819            .unwrap();
2820
2821        // Older data in L0. Different prefix; we want them to be present
2822        // but never visited.
2823        db.put(b"qq:a", b"vqa").await.unwrap();
2824        db.put(b"qq:b", b"vqb").await.unwrap();
2825        db.flush_with_options(FlushOptions {
2826            flush_type: FlushType::MemTable,
2827        })
2828        .await
2829        .unwrap();
2830
2831        // Active-memtable-only prefix.
2832        db.put(b"px:a", b"va").await.unwrap();
2833        db.put(b"px:b", b"vb").await.unwrap();
2834
2835        let gets_before =
2836            lookup_object_store_op_request_count(&metrics_recorder, "db", "main", "get");
2837
2838        let mut iter = db.scan_prefix_by_recency(b"px:").await.unwrap();
2839        let e1 = iter.next_entry().await.unwrap().unwrap();
2840        let e2 = iter.next_entry().await.unwrap().unwrap();
2841        assert_eq!(e1.key.as_ref(), b"px:a");
2842        assert_eq!(e2.key.as_ref(), b"px:b");
2843        // Caller stops here without driving the iterator into older sources.
2844
2845        let gets_after =
2846            lookup_object_store_op_request_count(&metrics_recorder, "db", "main", "get");
2847        assert_eq!(
2848            gets_before, gets_after,
2849            "scan_prefix_by_recency should not touch the main object store \
2850             when the prefix is fully covered by the active memtable"
2851        );
2852
2853        db.close().await.unwrap();
2854    }
2855
2856    #[tokio::test]
2857    async fn test_scan_prefix_by_recency_durability_remote_filters_unflushed() {
2858        // With DurabilityLevel::Remote and the put issued without awaiting
2859        // durability, only L0/SR-resident entries should be visible. The
2860        // not-yet-flushed memtable write is filtered out by max_seq.
2861        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2862        let db = Db::builder("/tmp/test_recency_remote_durability", object_store)
2863            .with_settings(test_db_options(0, 64 * 1024, None))
2864            .build()
2865            .await
2866            .unwrap();
2867
2868        db.put(b"px:a", b"va_durable").await.unwrap();
2869        db.flush_with_options(FlushOptions {
2870            flush_type: FlushType::MemTable,
2871        })
2872        .await
2873        .unwrap();
2874
2875        // Skip the WAL await. Without a follow-up flush, px:b lives only
2876        // in the in-memory memtable.
2877        db.put_with_options(
2878            b"px:b",
2879            b"vb_dirty",
2880            &PutOptions::default(),
2881            &WriteOptions {
2882                await_durable: false,
2883                seqnum: 0,
2884            },
2885        )
2886        .await
2887        .unwrap();
2888
2889        let opts = ScanOptions {
2890            durability_filter: Remote,
2891            ..ScanOptions::default()
2892        };
2893        let mut iter = db
2894            .scan_prefix_by_recency_with_options(b"px:", &opts)
2895            .await
2896            .unwrap();
2897        let e = iter.next_entry().await.unwrap().unwrap();
2898        assert_eq!(e.key.as_ref(), b"px:a");
2899        assert_value(&e, b"va_durable");
2900        assert!(iter.next_entry().await.unwrap().is_none());
2901
2902        db.close().await.unwrap();
2903    }
2904
2905    #[tokio::test]
2906    async fn test_scan_prefix_by_recency_walks_memtable_then_compacted_run() {
2907        // Walks memtable -> L0 -> compacted sorted run, verifying that the
2908        // sorted-run path is constructed and drained correctly when the
2909        // recency walk reaches it. Same-key versions surface from each
2910        // source in newest-first order with no dedup.
2911        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2912        let path = "/tmp/test_recency_compacted";
2913        let should_compact = Arc::new(AtomicBool::new(false));
2914        let should_compact_clone = should_compact.clone();
2915        let scheduler = Arc::new(OnDemandCompactionSchedulerSupplier::new(Arc::new(
2916            move |_state| should_compact_clone.swap(false, Ordering::SeqCst),
2917        )));
2918        let db = Db::builder(path, object_store.clone())
2919            .with_settings(test_db_options(0, 64 * 1024, None))
2920            .with_compactor_builder(
2921                CompactorBuilder::new(path, object_store.clone())
2922                    .with_scheduler_supplier(scheduler)
2923                    .with_options(fast_compactor_options()),
2924            )
2925            .build()
2926            .await
2927            .unwrap();
2928        let db = Arc::new(db);
2929
2930        // Oldest write goes to a sorted run after compaction.
2931        db.put(b"px:a", b"v_oldest").await.unwrap();
2932        db.flush_with_options(FlushOptions {
2933            flush_type: FlushType::MemTable,
2934        })
2935        .await
2936        .unwrap();
2937
2938        // Trigger compaction and wait for it to land.
2939        should_compact.store(true, Ordering::SeqCst);
2940        let db_poll = db.clone();
2941        tokio::time::timeout(Duration::from_secs(10), async move {
2942            loop {
2943                {
2944                    let state = db_poll.inner.state.read();
2945                    if !state.state().core().tree.compacted.is_empty() {
2946                        return;
2947                    }
2948                }
2949                tokio::time::sleep(Duration::from_millis(10)).await;
2950            }
2951        })
2952        .await
2953        .unwrap();
2954
2955        // L0 layer (newer than the sorted run, older than the memtable).
2956        db.put(b"px:a", b"v_l0").await.unwrap();
2957        db.flush_with_options(FlushOptions {
2958            flush_type: FlushType::MemTable,
2959        })
2960        .await
2961        .unwrap();
2962
2963        // Active memtable (freshest).
2964        db.put(b"px:a", b"v_memtable").await.unwrap();
2965
2966        let mut iter = db.scan_prefix_by_recency(b"px:").await.unwrap();
2967        let e1 = iter.next_entry().await.unwrap().unwrap();
2968        let e2 = iter.next_entry().await.unwrap().unwrap();
2969        let e3 = iter.next_entry().await.unwrap().unwrap();
2970        assert_eq!(e1.key.as_ref(), b"px:a");
2971        assert_value(&e1, b"v_memtable");
2972        assert_eq!(e2.key.as_ref(), b"px:a");
2973        assert_value(&e2, b"v_l0");
2974        assert_eq!(e3.key.as_ref(), b"px:a");
2975        assert_value(&e3, b"v_oldest");
2976        assert!(e1.seq > e2.seq);
2977        assert!(e2.seq > e3.seq);
2978        assert!(iter.next_entry().await.unwrap().is_none());
2979
2980        Arc::into_inner(db)
2981            .expect("db Arc should be uniquely held")
2982            .close()
2983            .await
2984            .unwrap();
2985    }
2986
2987    #[tokio::test]
2988    async fn test_scan_prefix_by_recency_errors_on_multi_segment_prefix() {
2989        use crate::manifest::{LsmTreeState, Segment};
2990        use std::collections::VecDeque;
2991        use std::sync::Arc;
2992
2993        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2994        let db = Db::builder("/tmp/test_recency_multi_segment", object_store)
2995            .with_settings(test_db_options(0, 64 * 1024, None))
2996            .build()
2997            .await
2998            .unwrap();
2999
3000        // Inject two non-nesting segments. Both prefixes share "hour=1"
3001        // but neither is a prefix of the other, so the antichain holds.
3002        // A scan with prefix b"hour=" overlaps both segments' intervals,
3003        // which is the case we want to reject.
3004        db.inner.state.write().modify(|m| {
3005            let core = &mut m.state.manifest.value.core;
3006            core.segment_extractor_name = Some("hour".into());
3007            core.segments = vec![
3008                Segment {
3009                    prefix: Bytes::from_static(b"hour=12/"),
3010                    tree: Arc::new(LsmTreeState {
3011                        last_compacted_l0_sst_view_id: None,
3012                        last_compacted_l0_sst_id: None,
3013                        l0: VecDeque::new(),
3014                        compacted: vec![],
3015                    }),
3016                },
3017                Segment {
3018                    prefix: Bytes::from_static(b"hour=13/"),
3019                    tree: Arc::new(LsmTreeState {
3020                        last_compacted_l0_sst_view_id: None,
3021                        last_compacted_l0_sst_id: None,
3022                        l0: VecDeque::new(),
3023                        compacted: vec![],
3024                    }),
3025                },
3026            ];
3027        });
3028
3029        match db.scan_prefix_by_recency(b"hour=").await {
3030            Err(e) => assert_eq!(e.kind(), crate::ErrorKind::Invalid),
3031            Ok(_) => panic!("expected multi-segment error"),
3032        }
3033
3034        // Single-segment prefixes still work.
3035        let mut iter = db.scan_prefix_by_recency(b"hour=12/").await.unwrap();
3036        assert!(iter.next_entry().await.unwrap().is_none());
3037
3038        db.close().await.unwrap();
3039    }
3040
3041    #[test]
3042    fn test_get_after_put() {
3043        let mut runner = new_proptest_runner(None);
3044        let runtime = Runtime::new().unwrap();
3045
3046        let table = sample::table(runner.rng(), 1000, 10);
3047        let db_options = test_db_options(0, 1024, None);
3048        let db = runtime.block_on(build_database_from_table(&table, db_options, true));
3049
3050        runner
3051            .run(
3052                &(arbitrary::bytes(100), arbitrary::bytes(100)),
3053                |(key, value)| {
3054                    runtime.block_on(async {
3055                        if !key.is_empty() {
3056                            db.put_with_options(
3057                                &key,
3058                                &value,
3059                                &PutOptions::default(),
3060                                &WriteOptions {
3061                                    await_durable: false,
3062                                    ..Default::default()
3063                                },
3064                            )
3065                            .await
3066                            .unwrap();
3067                            assert_eq!(
3068                                Some(value),
3069                                db.get_with_options(
3070                                    &key,
3071                                    &ReadOptions {
3072                                        durability_filter: Memory,
3073                                        dirty: false,
3074                                        cache_blocks: true,
3075                                        filter_context: None,
3076                                    }
3077                                )
3078                                .await
3079                                .unwrap()
3080                            );
3081                        }
3082                    });
3083                    Ok(())
3084                },
3085            )
3086            .unwrap();
3087    }
3088
3089    #[tokio::test]
3090    async fn test_no_flush_interval() {
3091        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3092        let db_options_no_flush_interval = {
3093            let mut db_options = test_db_options(0, 1024, None);
3094            db_options.flush_interval = None;
3095            db_options
3096        };
3097        let kv_store = Db::builder("/tmp/test_kv_store", object_store)
3098            .with_settings(db_options_no_flush_interval)
3099            .build()
3100            .await
3101            .unwrap();
3102        let key = b"test_key";
3103        let value = b"test_value";
3104
3105        kv_store
3106            .put_with_options(
3107                key,
3108                value,
3109                &PutOptions::default(),
3110                &WriteOptions {
3111                    await_durable: false,
3112                    ..Default::default()
3113                },
3114            )
3115            .await
3116            .unwrap();
3117
3118        // a sanity check: the wal contains the most recent write
3119        assert_ne!(
3120            kv_store
3121                .inner
3122                .wal_observer
3123                .status()
3124                .unwrap()
3125                .estimated_bytes,
3126            0
3127        );
3128
3129        // and a flush() should clear it
3130        kv_store.flush().await.unwrap();
3131        assert_eq!(
3132            kv_store
3133                .inner
3134                .wal_observer
3135                .status()
3136                .unwrap()
3137                .estimated_bytes,
3138            0
3139        );
3140    }
3141
3142    #[tokio::test]
3143    async fn test_close_triggers_flush_when_open() {
3144        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3145        let db_options_no_flush_interval = {
3146            let mut db_options = test_db_options(0, 1024, None);
3147            db_options.flush_interval = None;
3148            db_options
3149        };
3150        let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
3151        let kv_store = Db::builder("/tmp/test_close_triggers_flush", object_store)
3152            .with_settings(db_options_no_flush_interval)
3153            .with_metrics_recorder(metrics_recorder.clone())
3154            .build()
3155            .await
3156            .unwrap();
3157
3158        kv_store
3159            .put_with_options(
3160                b"test_key",
3161                b"test_value",
3162                &PutOptions::default(),
3163                &WriteOptions {
3164                    await_durable: false,
3165                    ..Default::default()
3166                },
3167            )
3168            .await
3169            .unwrap();
3170
3171        // Sanity check: WAL has buffered entries before close.
3172        assert_eq!(
3173            kv_store
3174                .inner
3175                .wal_observer
3176                .status()
3177                .unwrap()
3178                .buffered_wal_entries_count,
3179            1
3180        );
3181        assert_eq!(
3182            lookup_metric(
3183                &metrics_recorder,
3184                crate::wal_buffer::stats::WAL_BUFFER_FLUSHES
3185            )
3186            .unwrap(),
3187            0
3188        );
3189
3190        kv_store.close().await.unwrap();
3191
3192        // close() should trigger a flush when the db is open.
3193        assert_eq!(
3194            kv_store
3195                .inner
3196                .wal_observer
3197                .status()
3198                .unwrap_err()
3199                .buffered_wal_entries_count,
3200            0
3201        );
3202        assert_eq!(
3203            lookup_metric(
3204                &metrics_recorder,
3205                crate::wal_buffer::stats::WAL_BUFFER_FLUSHES
3206            )
3207            .unwrap(),
3208            1
3209        );
3210    }
3211
3212    #[tokio::test]
3213    async fn test_close_twice_returns_closed_clean() {
3214        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3215        let db = Db::builder("/tmp/test_close_twice_returns_closed_clean", object_store)
3216            .with_settings(test_db_options(0, 1024, None))
3217            .build()
3218            .await
3219            .unwrap();
3220
3221        db.close().await.unwrap();
3222        let err = db.close().await.unwrap_err();
3223        assert_eq!(err.kind(), crate::ErrorKind::Closed(CloseReason::Clean));
3224    }
3225
3226    #[tokio::test]
3227    async fn test_close_failed_state_does_not_flush() {
3228        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3229        let db_options_no_flush_interval = {
3230            let mut db_options = test_db_options(0, 1024, None);
3231            db_options.flush_interval = None;
3232            db_options
3233        };
3234        let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
3235        let db = Db::builder("/tmp/test_close_failed_state_no_flush", object_store)
3236            .with_settings(db_options_no_flush_interval)
3237            .with_metrics_recorder(metrics_recorder.clone())
3238            .build()
3239            .await
3240            .unwrap();
3241
3242        let put_seq = db
3243            .put_with_options(
3244                b"test_key",
3245                b"test_value",
3246                &PutOptions::default(),
3247                &WriteOptions {
3248                    await_durable: false,
3249                    ..Default::default()
3250                },
3251            )
3252            .await
3253            .unwrap()
3254            .seq;
3255
3256        // Sanity check: WAL has buffered entries before close.
3257        let wal_status = db.inner.wal_observer.status().unwrap();
3258        assert_eq!(wal_status.buffered_wal_entries_count, 1);
3259        assert!(wal_status.last_flushed_seq.unwrap_or(0) < put_seq);
3260        assert_eq!(
3261            lookup_metric(
3262                &metrics_recorder,
3263                crate::wal_buffer::stats::WAL_BUFFER_FLUSHES
3264            )
3265            .unwrap(),
3266            0
3267        );
3268
3269        // Simulate a failed state (e.g. fenced).
3270        db.inner
3271            .status_manager
3272            .write_result(Err(crate::error::SlateDBError::Fenced));
3273
3274        // close() should succeed but not flush when failed.
3275        db.close().await.unwrap();
3276
3277        let wal_status = db.inner.wal_observer.status().unwrap_err();
3278        assert!(matches!(wal_status.closed_reason, Some(WalError::Fenced)));
3279        assert!(wal_status.last_flushed_seq.unwrap_or(0) < put_seq);
3280        assert_eq!(
3281            lookup_metric(
3282                &metrics_recorder,
3283                crate::wal_buffer::stats::WAL_BUFFER_FLUSHES
3284            )
3285            .unwrap(),
3286            0
3287        );
3288        let status = db.status();
3289        assert_eq!(status.close_reason, Some(CloseReason::Fenced));
3290    }
3291
3292    #[tokio::test]
3293    async fn test_clean_close_flushes_pending_wal() {
3294        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3295        let db_options_no_flush_interval = {
3296            let mut db_options = test_db_options(0, 1024, None);
3297            db_options.flush_interval = None;
3298            db_options
3299        };
3300        let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
3301        let db = Db::builder("/tmp/test_clean_close_flushes_pending_wal", object_store)
3302            .with_settings(db_options_no_flush_interval)
3303            .with_metrics_recorder(metrics_recorder.clone())
3304            .build()
3305            .await
3306            .unwrap();
3307
3308        let put_seq = db
3309            .put_with_options(
3310                b"test_key",
3311                b"test_value",
3312                &PutOptions::default(),
3313                &WriteOptions {
3314                    await_durable: false,
3315                    ..Default::default()
3316                },
3317            )
3318            .await
3319            .unwrap()
3320            .seq;
3321
3322        let wal_status = db.inner.wal_observer.status().unwrap();
3323        assert_eq!(wal_status.buffered_wal_entries_count, 1);
3324        assert!(wal_status.last_flushed_seq.unwrap_or(0) < put_seq);
3325        assert_eq!(
3326            lookup_metric(
3327                &metrics_recorder,
3328                crate::wal_buffer::stats::WAL_BUFFER_FLUSHES
3329            )
3330            .unwrap(),
3331            0
3332        );
3333
3334        db.close().await.unwrap();
3335
3336        let wal_status = db.inner.wal_observer.status().unwrap_err();
3337        assert!(matches!(wal_status.closed_reason, Some(WalError::Closed)));
3338        assert_eq!(wal_status.last_flushed_seq, Some(put_seq));
3339        assert_eq!(
3340            lookup_metric(
3341                &metrics_recorder,
3342                crate::wal_buffer::stats::WAL_BUFFER_FLUSHES
3343            )
3344            .unwrap(),
3345            1
3346        );
3347        let status = db.status();
3348        assert_eq!(status.close_reason, Some(CloseReason::Clean));
3349    }
3350
3351    #[tokio::test]
3352    async fn test_close_flushes_memtables_to_l0() {
3353        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3354        let db_options = {
3355            let mut db_options = test_db_options(0, 1024, None);
3356            db_options.flush_interval = None;
3357            db_options
3358        };
3359        let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
3360        let db = Db::builder("/tmp/test_close_flushes_memtables_to_l0", object_store)
3361            .with_settings(db_options)
3362            .with_metrics_recorder(metrics_recorder.clone())
3363            .build()
3364            .await
3365            .unwrap();
3366
3367        db.put_with_options(
3368            b"test_key",
3369            b"test_value",
3370            &PutOptions::default(),
3371            &WriteOptions {
3372                await_durable: false,
3373                ..Default::default()
3374            },
3375        )
3376        .await
3377        .unwrap();
3378
3379        // No L0 flushes should have happened yet.
3380        assert_eq!(
3381            lookup_metric(&metrics_recorder, crate::db_stats::L0_FLUSH_BYTES).unwrap_or(0),
3382            0
3383        );
3384
3385        db.close().await.unwrap();
3386
3387        // close() should have flushed memtables to L0.
3388        assert!(
3389            lookup_metric(&metrics_recorder, crate::db_stats::L0_FLUSH_BYTES).unwrap() > 0,
3390            "expected L0 flush during close"
3391        );
3392    }
3393
3394    #[tokio::test]
3395    async fn test_memtable_write_bytes_matches_batch_payload() {
3396        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3397        let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
3398        let db = Db::builder(
3399            "/tmp/test_memtable_write_bytes_matches_batch_payload",
3400            object_store,
3401        )
3402        .with_settings(test_db_options(0, 1024 * 1024, None))
3403        .with_metrics_recorder(metrics_recorder.clone())
3404        .build()
3405        .await
3406        .unwrap();
3407
3408        db.put(b"hello", b"world!").await.unwrap();
3409        assert_eq!(
3410            lookup_metric(&metrics_recorder, crate::db_stats::MEMTABLE_WRITE_BYTES).unwrap(),
3411            (b"hello".len() + b"world!".len()) as i64,
3412        );
3413
3414        db.put(b"k2", b"v2").await.unwrap();
3415        assert_eq!(
3416            lookup_metric(&metrics_recorder, crate::db_stats::MEMTABLE_WRITE_BYTES).unwrap(),
3417            (b"hello".len() + b"world!".len() + b"k2".len() + b"v2".len()) as i64,
3418        );
3419
3420        // Deletes count only the key length.
3421        db.delete(b"k3").await.unwrap();
3422        assert_eq!(
3423            lookup_metric(&metrics_recorder, crate::db_stats::MEMTABLE_WRITE_BYTES).unwrap(),
3424            (b"hello".len() + b"world!".len() + b"k2".len() + b"v2".len() + b"k3".len()) as i64,
3425        );
3426
3427        db.close().await.unwrap();
3428    }
3429
3430    #[tokio::test]
3431    async fn test_memtable_write_bytes_matches_batch_payload_with_merges() {
3432        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3433        let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
3434        let db = Db::builder(
3435            "/tmp/test_memtable_write_bytes_matches_batch_payload_with_merges",
3436            object_store,
3437        )
3438        .with_settings(test_db_options(0, 1024 * 1024, None))
3439        .with_merge_operator(Arc::new(StringConcatMergeOperator {}))
3440        .with_metrics_recorder(metrics_recorder.clone())
3441        .build()
3442        .await
3443        .unwrap();
3444
3445        // A single merge per key is not folded by the merge iterator, so the
3446        // tracked size matches key + value.
3447        db.merge(b"k1", b"a").await.unwrap();
3448        let mut expected = (b"k1".len() + b"a".len()) as i64;
3449        assert_eq!(
3450            lookup_metric(&metrics_recorder, crate::db_stats::MEMTABLE_WRITE_BYTES).unwrap(),
3451            expected,
3452        );
3453
3454        // Multiple merges for the same key in a single batch are folded by the
3455        // merge iterator before counting. memtable_write_bytes reflects the merged
3456        // output (key + "abc"), not the sum of raw inputs (3 * key + "a" + "b" + "c").
3457        let mut batch = WriteBatch::new();
3458        batch.merge(b"k2", b"a");
3459        batch.merge(b"k2", b"b");
3460        batch.merge(b"k2", b"c");
3461        db.write(batch).await.unwrap();
3462        expected += (b"k2".len() + b"abc".len()) as i64;
3463        assert_eq!(
3464            lookup_metric(&metrics_recorder, crate::db_stats::MEMTABLE_WRITE_BYTES).unwrap(),
3465            expected,
3466        );
3467
3468        // Merges to distinct keys in one batch are not folded, so each entry
3469        // contributes its raw key + value.
3470        let mut batch = WriteBatch::new();
3471        batch.merge(b"k3", b"x");
3472        batch.merge(b"k4", b"yy");
3473        db.write(batch).await.unwrap();
3474        expected += (b"k3".len() + b"x".len() + b"k4".len() + b"yy".len()) as i64;
3475        assert_eq!(
3476            lookup_metric(&metrics_recorder, crate::db_stats::MEMTABLE_WRITE_BYTES).unwrap(),
3477            expected,
3478        );
3479
3480        db.close().await.unwrap();
3481    }
3482
3483    #[tokio::test]
3484    async fn test_wal_flush_bytes_after_explicit_flush() {
3485        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3486        let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
3487        let db_options = {
3488            let mut db_options = test_db_options(0, 1024 * 1024, None);
3489            db_options.flush_interval = None;
3490            db_options
3491        };
3492        let db = Db::builder(
3493            "/tmp/test_wal_flush_bytes_after_explicit_flush",
3494            object_store,
3495        )
3496        .with_settings(db_options)
3497        .with_metrics_recorder(metrics_recorder.clone())
3498        .build()
3499        .await
3500        .unwrap();
3501
3502        db.put_with_options(
3503            b"hello",
3504            b"world",
3505            &PutOptions::default(),
3506            &WriteOptions {
3507                await_durable: false,
3508                ..Default::default()
3509            },
3510        )
3511        .await
3512        .unwrap();
3513        assert_eq!(
3514            lookup_metric(&metrics_recorder, crate::wal_buffer::stats::WAL_FLUSH_BYTES)
3515                .unwrap_or(0),
3516            0,
3517        );
3518
3519        db.flush().await.unwrap();
3520
3521        let wal_bytes =
3522            lookup_metric(&metrics_recorder, crate::wal_buffer::stats::WAL_FLUSH_BYTES).unwrap();
3523        let memtable_bytes =
3524            lookup_metric(&metrics_recorder, crate::db_stats::MEMTABLE_WRITE_BYTES).unwrap();
3525        // WAL SST framing/footer makes the encoded payload at least as large as
3526        // the memtable payload if no compression is used.
3527        assert!(
3528            wal_bytes >= memtable_bytes,
3529            "wal_bytes={wal_bytes} memtable_bytes={memtable_bytes}",
3530        );
3531        db.close().await.unwrap();
3532    }
3533
3534    #[tokio::test]
3535    #[cfg(feature = "wal_disable")]
3536    async fn test_get_with_durability_level_when_wal_disabled() {
3537        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3538        let mut options = test_db_options(0, 1024 * 1024, None);
3539        options.wal_enabled = false;
3540        let db = Db::builder("/tmp/test_kv_store", object_store)
3541            .with_settings(options)
3542            .build()
3543            .await
3544            .unwrap();
3545        tokio::time::timeout(
3546            Duration::from_secs(1),
3547            db.task_executor
3548                .join_task(crate::wal_buffer::WAL_BUFFER_TASK_NAME),
3549        )
3550        .await
3551        .expect("native WAL task should not run when the WAL is disabled")
3552        .unwrap();
3553        let put_options = PutOptions::default();
3554        let write_options = WriteOptions {
3555            await_durable: false,
3556            ..Default::default()
3557        };
3558        let get_memory_options = ReadOptions::new().with_durability_filter(Memory);
3559        let get_remote_options = ReadOptions::new().with_durability_filter(Remote);
3560
3561        db.put_with_options(b"foo", b"bar", &put_options, &write_options)
3562            .await
3563            .unwrap();
3564        let val_bytes = Bytes::copy_from_slice(b"bar");
3565        assert_eq!(
3566            None,
3567            db.get_with_options(b"foo", &get_remote_options)
3568                .await
3569                .unwrap()
3570        );
3571        assert_eq!(
3572            Some(val_bytes.clone()),
3573            db.get_with_options(b"foo", &get_memory_options)
3574                .await
3575                .unwrap()
3576        );
3577        db.flush().await.unwrap();
3578        assert_eq!(
3579            Some(val_bytes.clone()),
3580            db.get_with_options(b"foo", &get_remote_options)
3581                .await
3582                .unwrap()
3583        );
3584        assert_eq!(
3585            Some(val_bytes.clone()),
3586            db.get_with_options(b"foo", &get_memory_options)
3587                .await
3588                .unwrap()
3589        );
3590    }
3591
3592    #[tokio::test]
3593    #[cfg(feature = "wal_disable")]
3594    async fn test_find_with_multiple_repeated_keys() {
3595        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3596        let mut options = test_db_options(0, 1024 * 1024, None);
3597        options.wal_enabled = false;
3598        let db = Db::builder("/tmp/test_kv_store", object_store)
3599            .with_settings(options)
3600            .build()
3601            .await
3602            .unwrap();
3603
3604        // write enough rows with the same key that we yield an L0 SST with multiple blocks
3605        let mut last_val: String = "foo".to_string();
3606        for x in 0..4096 {
3607            let val = format!("val{}", x);
3608            db.put_with_options(
3609                b"key",
3610                val.as_bytes(),
3611                &PutOptions {
3612                    ttl: Default::default(),
3613                },
3614                &WriteOptions {
3615                    await_durable: false,
3616                    ..Default::default()
3617                },
3618            )
3619            .await
3620            .unwrap();
3621            last_val = val;
3622            if db
3623                .inner
3624                .state
3625                .write()
3626                .memtable()
3627                .metadata()
3628                .entries_size_in_bytes
3629                > (SsTableFormat::default().block_size * 3)
3630            {
3631                break;
3632            }
3633        }
3634        assert_eq!(
3635            Some(Bytes::copy_from_slice(last_val.as_bytes())),
3636            db.get_with_options(b"key", &ReadOptions::new().with_durability_filter(Memory))
3637                .await
3638                .unwrap()
3639        );
3640        db.flush().await.unwrap();
3641
3642        let state = db.inner.state.read().view();
3643        assert_eq!(1, state.state.manifest.value.core.tree.l0.len());
3644        let view = state.state.manifest.value.core.tree.l0.front().unwrap();
3645        let index = db
3646            .inner
3647            .table_store
3648            .read_index(&view.sst, true)
3649            .await
3650            .unwrap();
3651        assert!(!index.borrow().block_meta().is_empty());
3652        assert_eq!(
3653            Some(Bytes::copy_from_slice(last_val.as_bytes())),
3654            db.get(b"key").await.unwrap()
3655        );
3656        db.close().await.unwrap();
3657    }
3658
3659    #[tokio::test]
3660    async fn test_db_records_main_and_wal_object_store_requests_separately() {
3661        // given:
3662        let main_object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3663        let wal_object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3664        let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
3665        let path = "/tmp/test_db_records_main_and_wal_object_store_requests_separately";
3666        let db = Db::builder(path, main_object_store)
3667            .with_settings(test_db_options(0, 1024, None))
3668            .with_wal_object_store(wal_object_store)
3669            .with_metrics_recorder(metrics_recorder.clone())
3670            .build()
3671            .await
3672            .unwrap();
3673
3674        let wal_before =
3675            lookup_object_store_op_request_count(&metrics_recorder, "db", "wal", "put");
3676        let main_before =
3677            lookup_object_store_op_request_count(&metrics_recorder, "db", "main", "put");
3678        let wal_hist_before =
3679            lookup_object_store_op_histogram_count(&metrics_recorder, "db", "wal", "put");
3680        let main_hist_before =
3681            lookup_object_store_op_histogram_count(&metrics_recorder, "db", "main", "put");
3682
3683        // when:
3684        db.put(b"key", b"value").await.unwrap();
3685        db.flush().await.unwrap();
3686        db.flush_with_options(FlushOptions {
3687            flush_type: FlushType::MemTable,
3688        })
3689        .await
3690        .unwrap();
3691
3692        let wal_after = lookup_object_store_op_request_count(&metrics_recorder, "db", "wal", "put");
3693        let main_after =
3694            lookup_object_store_op_request_count(&metrics_recorder, "db", "main", "put");
3695        let wal_hist_after =
3696            lookup_object_store_op_histogram_count(&metrics_recorder, "db", "wal", "put");
3697        let main_hist_after =
3698            lookup_object_store_op_histogram_count(&metrics_recorder, "db", "main", "put");
3699
3700        // then:
3701        assert!(wal_after > wal_before);
3702        assert!(main_after > main_before);
3703        assert!(wal_hist_after > wal_hist_before);
3704        assert!(main_hist_after > main_hist_before);
3705        db.close().await.unwrap();
3706    }
3707
3708    async fn build_database_from_table(
3709        table: &BTreeMap<Bytes, Bytes>,
3710        db_options: Settings,
3711        await_durable: bool,
3712    ) -> Db {
3713        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3714        let db = Db::builder("/tmp/test_kv_store", object_store)
3715            .with_settings(db_options)
3716            .build()
3717            .await
3718            .unwrap();
3719
3720        test_utils::seed_database(&db, table, false).await.unwrap();
3721
3722        if await_durable {
3723            db.flush().await.unwrap();
3724        }
3725
3726        db
3727    }
3728
3729    #[tokio::test]
3730    async fn test_should_allow_iterating_behind_box_dyn() {
3731        #[async_trait]
3732        trait IteratorSupplier {
3733            async fn iterator(&self) -> Box<dyn IteratorTrait>;
3734        }
3735
3736        struct DbHolder {
3737            db: Db,
3738        }
3739
3740        #[async_trait]
3741        impl IteratorSupplier for DbHolder {
3742            async fn iterator(&self) -> Box<dyn IteratorTrait> {
3743                let range = BytesRange::new_empty();
3744                let iter = self
3745                    .db
3746                    .inner
3747                    .scan_with_options(range, &ScanOptions::default(), None)
3748                    .await
3749                    .unwrap();
3750                Box::new(iter)
3751            }
3752        }
3753
3754        #[async_trait]
3755        trait IteratorTrait {
3756            async fn next(&mut self) -> Result<Option<KeyValue>, crate::Error>;
3757        }
3758
3759        #[async_trait]
3760        impl IteratorTrait for DbIterator {
3761            async fn next(&mut self) -> Result<Option<KeyValue>, crate::Error> {
3762                DbIterator::next(self).await
3763            }
3764        }
3765
3766        let db_options = test_db_options(0, 1024, None);
3767        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3768        let db = Db::builder("/tmp/test_kv_store", object_store)
3769            .with_settings(db_options)
3770            .build()
3771            .await
3772            .unwrap();
3773        let db_holder = DbHolder { db };
3774        let mut boxed = db_holder.iterator().await;
3775        let next = boxed.next().await;
3776        assert_eq!(next.unwrap(), None);
3777    }
3778
3779    async fn assert_records_in_range(
3780        table: &BTreeMap<Bytes, Bytes>,
3781        db: &Db,
3782        scan_options: &ScanOptions,
3783        range: BytesRange,
3784    ) {
3785        let mut iter = db
3786            .inner
3787            .scan_with_options(range.clone(), scan_options, None)
3788            .await
3789            .unwrap();
3790        test_utils::assert_ranged_db_scan(table, range, IterationOrder::Ascending, &mut iter).await;
3791    }
3792
3793    #[test]
3794    fn test_scan_returns_records_in_range() {
3795        let mut runner = new_proptest_runner(None);
3796        let table = sample::table(runner.rng(), 1000, 5);
3797
3798        let runtime = Runtime::new().unwrap();
3799        let db_options = test_db_options(0, 1024, None);
3800        let db = runtime.block_on(build_database_from_table(&table, db_options, true));
3801
3802        runner
3803            .run(&arbitrary::nonempty_range(10), |range| {
3804                runtime.block_on(assert_records_in_range(
3805                    &table,
3806                    &db,
3807                    &ScanOptions::default(),
3808                    range,
3809                ));
3810                Ok(())
3811            })
3812            .unwrap();
3813    }
3814
3815    fn new_proptest_runner(rng_seed: Option<[u8; 32]>) -> TestRunner {
3816        proptest_util::runner::new(file!(), rng_seed)
3817    }
3818
3819    #[test]
3820    fn test_scan_returns_uncommitted_records_if_read_level_uncommitted() {
3821        let mut runner = new_proptest_runner(None);
3822        let table = sample::table(runner.rng(), 1000, 5);
3823
3824        let runtime = Runtime::new().unwrap();
3825        let mut db_options = test_db_options(0, 1024, None);
3826        db_options.flush_interval = Some(Duration::from_secs(5));
3827        let db = runtime.block_on(build_database_from_table(&table, db_options, false));
3828
3829        runner
3830            .run(&arbitrary::nonempty_range(10), |range| {
3831                let scan_options = ScanOptions {
3832                    durability_filter: Memory,
3833                    ..ScanOptions::default()
3834                };
3835                runtime.block_on(assert_records_in_range(&table, &db, &scan_options, range));
3836                Ok(())
3837            })
3838            .unwrap();
3839    }
3840
3841    #[test]
3842    fn test_seek_outside_of_range_returns_invalid_argument() {
3843        let mut runner = new_proptest_runner(None);
3844        let table = sample::table(runner.rng(), 1000, 10);
3845
3846        let runtime = Runtime::new().unwrap();
3847        let db_options = test_db_options(0, 1024, None);
3848        let db = runtime.block_on(build_database_from_table(&table, db_options, true));
3849
3850        runner
3851            .run(
3852                &(arbitrary::nonempty_bytes(10), arbitrary::rng()),
3853                |(arbitrary_key, mut rng)| {
3854                    runtime.block_on(assert_out_of_bound_seek_returns_invalid_argument(
3855                        &db,
3856                        &mut rng,
3857                        arbitrary_key,
3858                    ));
3859                    Ok(())
3860                },
3861            )
3862            .unwrap();
3863
3864        async fn assert_out_of_bound_seek_returns_invalid_argument(
3865            db: &Db,
3866            rng: &mut TestRng,
3867            arbitrary_key: Bytes,
3868        ) {
3869            let mut iter = db
3870                .scan_with_options(..arbitrary_key.clone(), &ScanOptions::default())
3871                .await
3872                .unwrap();
3873
3874            let lower_bounded_range = BytesRange::from(arbitrary_key.clone()..);
3875            let value = sample::bytes_in_range(rng, &lower_bounded_range);
3876            let err = iter.seek(value.clone()).await.unwrap_err();
3877            assert!(
3878                err.to_string()
3879                    .contains("cannot seek to a key outside the iterator range"),
3880                "{}",
3881                err
3882            );
3883
3884            let mut iter = db
3885                .scan_with_options(arbitrary_key.clone().., &ScanOptions::default())
3886                .await
3887                .unwrap();
3888
3889            let upper_bounded_range = BytesRange::from(..arbitrary_key.clone());
3890            let value = sample::bytes_in_range(rng, &upper_bounded_range);
3891            let err = iter.seek(value.clone()).await.unwrap_err();
3892            assert!(
3893                err.to_string()
3894                    .contains("cannot seek to a key outside the iterator range"),
3895                "{}",
3896                err
3897            );
3898        }
3899    }
3900
3901    #[test]
3902    fn test_seek_fast_forwards_iterator() {
3903        let mut runner = new_proptest_runner(None);
3904        let table = sample::table(runner.rng(), 1000, 10);
3905
3906        let runtime = Runtime::new().unwrap();
3907        let db_options = test_db_options(0, 1024, None);
3908        let db = runtime.block_on(build_database_from_table(&table, db_options, true));
3909
3910        runner
3911            .run(
3912                &(arbitrary::nonempty_range(5), arbitrary::rng()),
3913                |(range, mut rng)| {
3914                    runtime.block_on(assert_seek_fast_forwards_iterator(
3915                        &table, &db, &range, &mut rng,
3916                    ));
3917                    Ok(())
3918                },
3919            )
3920            .unwrap();
3921
3922        async fn assert_seek_fast_forwards_iterator(
3923            table: &BTreeMap<Bytes, Bytes>,
3924            db: &Db,
3925            scan_range: &BytesRange,
3926            rng: &mut TestRng,
3927        ) {
3928            let mut iter = db
3929                .inner
3930                .scan_with_options(scan_range.clone(), &ScanOptions::default(), None)
3931                .await
3932                .unwrap();
3933
3934            let seek_key = sample::bytes_in_range(rng, scan_range);
3935            iter.seek(seek_key.clone()).await.unwrap();
3936
3937            let seek_range = BytesRange::new(
3938                Included(seek_key),
3939                std::ops::RangeBounds::end_bound(scan_range).cloned(),
3940            );
3941            test_utils::assert_ranged_db_scan(
3942                table,
3943                seek_range,
3944                IterationOrder::Ascending,
3945                &mut iter,
3946            )
3947            .await;
3948        }
3949    }
3950
3951    #[tokio::test]
3952    async fn test_write_batch() {
3953        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3954        let kv_store = Db::builder("/tmp/test_kv_store", object_store)
3955            .with_settings(test_db_options(0, 1024, None))
3956            .build()
3957            .await
3958            .unwrap();
3959
3960        // Create a new WriteBatch
3961        let mut batch = WriteBatch::new();
3962        batch.put(b"key1", b"value1");
3963        batch.put(b"key2", b"value2");
3964        batch.delete(b"key1");
3965
3966        // Write the batch
3967        kv_store.write(batch).await.expect("write batch failed");
3968
3969        // Read back keys
3970        assert_eq!(kv_store.get(b"key1").await.unwrap(), None);
3971        assert_eq!(
3972            kv_store.get(b"key2").await.unwrap(),
3973            Some(Bytes::from_static(b"value2"))
3974        );
3975
3976        kv_store.close().await.unwrap();
3977    }
3978
3979    #[cfg(feature = "wal_disable")]
3980    #[tokio::test]
3981    async fn test_write_batch_without_wal() {
3982        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3983        // Use a very small l0 size to force flushes so await is notified
3984        let mut options = test_db_options(0, 8, None);
3985
3986        // Disable WAL
3987        options.wal_enabled = false;
3988
3989        let kv_store = Db::builder("/tmp/test_kv_store_without_wal", object_store.clone())
3990            .with_settings(options)
3991            .build()
3992            .await
3993            .unwrap();
3994
3995        // Create a new WriteBatch
3996        let mut batch = WriteBatch::new();
3997        batch.put(b"key1", b"value1");
3998        batch.put(b"key2", b"value2");
3999        batch.delete(b"key1");
4000
4001        // Write the batch
4002        kv_store.write(batch).await.expect("write batch failed");
4003
4004        // Read back keys
4005        assert_eq!(kv_store.get(b"key1").await.unwrap(), None);
4006        assert_eq!(kv_store.get(b"key2").await.unwrap(), Some("value2".into()));
4007
4008        kv_store.close().await.unwrap();
4009    }
4010
4011    #[tokio::test]
4012    async fn test_write_batch_with_empty_key() {
4013        let mut batch = WriteBatch::new();
4014        let result = std::panic::catch_unwind(move || {
4015            batch.put(b"", b"value");
4016        });
4017        assert!(
4018            result.is_err(),
4019            "Expected panic when using empty key in put operation"
4020        );
4021
4022        let mut batch = WriteBatch::new();
4023        let result = std::panic::catch_unwind(move || {
4024            batch.delete(b"");
4025        });
4026        assert!(
4027            result.is_err(),
4028            "Expected panic when using empty key in delete operation"
4029        );
4030    }
4031
4032    /// Test that batch writes are atomic. Test does the following:
4033    ///
4034    /// - A set of, say 100 keys, 1-100
4035    /// - Two tasks writing, one writing value to be same key, and other
4036    ///   writing it to be key*2.
4037    /// - We wait for both to complete.
4038    /// - Assert that either all values are same as key, or all values key*2.
4039    /// - Repeat above loop few times.
4040    ///
4041    /// _Note: This test is non-deterministic because it depends on the async
4042    /// runtime to schedule the tasks in a way that the writes are concurrent._
4043    #[tokio::test]
4044    async fn test_concurrent_batch_writes_consistency() {
4045        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
4046        let kv_store = Arc::new(
4047            Db::builder("/tmp/test_concurrent_kv_store", object_store)
4048                .with_settings(test_db_options(
4049                    0,
4050                    1024,
4051                    // Enable compactor to prevent l0 from filling up and
4052                    // applying backpressure indefinitely.
4053                    Some(CompactorOptions {
4054                        poll_interval: Duration::from_millis(100),
4055                        max_concurrent_compactions: 1,
4056                        manifest_update_timeout: Duration::from_secs(300),
4057                        worker: Some(CompactionWorkerOptions {
4058                            max_sst_size: 256,
4059                            ..Default::default()
4060                        }),
4061                        ..Default::default()
4062                    }),
4063                ))
4064                .build()
4065                .await
4066                .unwrap(),
4067        );
4068
4069        const NUM_KEYS: usize = 100;
4070        const NUM_ROUNDS: usize = 20;
4071
4072        for _ in 0..NUM_ROUNDS {
4073            // Write two tasks that write to the same keys
4074            let task1 = {
4075                let store = kv_store.clone();
4076                tokio::spawn(async move {
4077                    let mut batch = WriteBatch::new();
4078                    for key in 1..=NUM_KEYS {
4079                        batch.put(key.to_be_bytes(), key.to_be_bytes());
4080                    }
4081                    store.write(batch).await.expect("write batch failed");
4082                })
4083            };
4084
4085            let task2 = {
4086                let store = kv_store.clone();
4087                tokio::spawn(async move {
4088                    let mut batch = WriteBatch::new();
4089                    for key in 1..=NUM_KEYS {
4090                        let value = (key * 2).to_be_bytes();
4091                        batch.put(key.to_be_bytes(), value);
4092                    }
4093                    store.write(batch).await.expect("write batch failed");
4094                })
4095            };
4096
4097            // Wait for both tasks to complete
4098            join_all(vec![task1, task2]).await;
4099
4100            // Ensure consistency: all values must be either key or key * 2
4101            let mut all_key = true;
4102            let mut all_key2 = true;
4103
4104            for key in 1..=NUM_KEYS {
4105                let value = kv_store.get(key.to_be_bytes()).await.unwrap();
4106                let value = value.expect("Value should exist");
4107
4108                if value.as_ref() != key.to_be_bytes() {
4109                    all_key = false;
4110                }
4111                if value.as_ref() != (key * 2).to_be_bytes() {
4112                    all_key2 = false;
4113                }
4114            }
4115
4116            // Assert that the result is consistent: either all key or all key * 2
4117            assert!(
4118                all_key || all_key2,
4119                "Inconsistent state: not all values match either key or key * 2"
4120            );
4121        }
4122
4123        kv_store.close().await.unwrap();
4124    }
4125
4126    #[tokio::test]
4127    #[cfg(feature = "wal_disable")]
4128    async fn test_disable_wal_after_wal_enabled() {
4129        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
4130        let path = "/tmp/test_kv_store";
4131        // open a db and write a wal entry
4132        let options = test_db_options(0, 32, None);
4133        let db = Db::builder(path, object_store.clone())
4134            .with_settings(options)
4135            .build()
4136            .await
4137            .unwrap();
4138        db.put(&[b'a'; 4], &[b'j'; 4]).await.unwrap();
4139        db.put(&[b'b'; 4], &[b'k'; 4]).await.unwrap();
4140        db.close().await.unwrap();
4141
4142        // open a db with wal disabled and write a memtable
4143        let mut options = test_db_options(0, 32, None);
4144        options.wal_enabled = false;
4145        let db = Db::builder(path, object_store.clone())
4146            .with_settings(options.clone())
4147            .build()
4148            .await
4149            .unwrap();
4150        db.delete_with_options(
4151            &[b'b'; 4],
4152            &WriteOptions {
4153                await_durable: false,
4154                ..Default::default()
4155            },
4156        )
4157        .await
4158        .unwrap();
4159        db.put(&[b'a'; 4], &[b'z'; 64]).await.unwrap();
4160        db.close().await.unwrap();
4161
4162        // ensure we don't overwrite the values we just put on a reload
4163        let db = Db::builder(path, object_store.clone())
4164            .with_settings(options.clone())
4165            .build()
4166            .await
4167            .unwrap();
4168        let val = db.get(&[b'a'; 4]).await.unwrap();
4169        assert_eq!(val.unwrap(), Bytes::copy_from_slice(&[b'z'; 64]));
4170        let val = db.get(&[b'b'; 4]).await.unwrap();
4171        assert!(val.is_none());
4172    }
4173
4174    #[cfg(feature = "wal_disable")]
4175    #[tokio::test]
4176    async fn test_wal_disabled() {
4177        use crate::{test_utils::assert_iterator, types::RowEntry};
4178
4179        let clock = Arc::new(MockSystemClock::new());
4180        let mut options = test_db_options(0, 350, None);
4181        options.wal_enabled = false;
4182        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
4183        let path = Path::from("/tmp/test_kv_store");
4184        let sst_format = SsTableFormat::default();
4185        let table_store = Arc::new(TableStore::new(
4186            ObjectStores::new(object_store.clone(), None),
4187            sst_format,
4188            path.clone(),
4189            None,
4190            TableStoreKind::Main,
4191            BlockCachePolicy::default(),
4192        ));
4193        let db = Db::builder(path.clone(), object_store.clone())
4194            .with_settings(options)
4195            .with_system_clock(clock.clone())
4196            .build()
4197            .await
4198            .unwrap();
4199        let manifest_store = Arc::new(ManifestStore::new(&path, object_store.clone()));
4200        let mut stored_manifest =
4201            StoredManifest::load(manifest_store.clone(), Arc::new(DefaultSystemClock::new()))
4202                .await
4203                .unwrap();
4204        let write_options = WriteOptions {
4205            await_durable: false,
4206            ..Default::default()
4207        };
4208
4209        db.put_with_options(
4210            &[b'a'; 32],
4211            &[b'j'; 32],
4212            &PutOptions::default(),
4213            &write_options,
4214        )
4215        .await
4216        .unwrap();
4217        db.delete_with_options(&[b'b'; 31], &write_options)
4218            .await
4219            .unwrap();
4220
4221        // ensure the memtable's size is greater than l0_sst_size_bytes, or
4222        // the memtable will not be flushed to l0, and the test will hang
4223        // at this put_with_options call.
4224        let write_options = WriteOptions {
4225            await_durable: true,
4226            ..Default::default()
4227        };
4228        clock.set(10);
4229        db.put_with_options(
4230            &[b'c'; 32],
4231            &[b'l'; 32],
4232            &PutOptions::default(),
4233            &write_options,
4234        )
4235        .await
4236        .unwrap();
4237
4238        let state = wait_for_manifest_condition(
4239            &mut stored_manifest,
4240            |s| !s.tree.l0.is_empty(),
4241            Duration::from_secs(30),
4242        )
4243        .await;
4244        assert_eq!(state.tree.l0.len(), 1);
4245
4246        let l0 = state.tree.l0.front().unwrap();
4247        let mut iter = SstIterator::new_borrowed_initialized(
4248            ..,
4249            l0,
4250            table_store.clone(),
4251            SstIteratorOptions::default(),
4252        )
4253        .await
4254        .unwrap()
4255        .expect("Expected Some(iter) but got None");
4256        assert_iterator(
4257            &mut iter,
4258            vec![
4259                RowEntry::new_value(&[b'a'; 32], &[b'j'; 32], 1).with_create_ts(0),
4260                RowEntry::new_tombstone(&[b'b'; 31], 2).with_create_ts(0),
4261                RowEntry::new_value(&[b'c'; 32], &[b'l'; 32], 3).with_create_ts(10),
4262            ],
4263        )
4264        .await;
4265    }
4266
4267    #[tokio::test]
4268    async fn test_put_flushes_memtable() {
4269        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
4270        let path = "/tmp/test_kv_store";
4271        let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
4272        let kv_store = Db::builder(path, object_store.clone())
4273            .with_settings(test_db_options(0, 320, None))
4274            .with_metrics_recorder(metrics_recorder.clone())
4275            .build()
4276            .await
4277            .unwrap();
4278        let manifest_store = Arc::new(ManifestStore::new(&Path::from(path), object_store.clone()));
4279        let mut stored_manifest =
4280            StoredManifest::load(manifest_store.clone(), Arc::new(DefaultSystemClock::new()))
4281                .await
4282                .unwrap();
4283        let sst_format = SsTableFormat {
4284            min_filter_keys: 10,
4285            ..SsTableFormat::default()
4286        };
4287        let table_store = Arc::new(TableStore::new(
4288            ObjectStores::new(object_store.clone(), None),
4289            sst_format,
4290            path,
4291            None,
4292            TableStoreKind::Main,
4293            BlockCachePolicy::default(),
4294        ));
4295
4296        // Write data a few times such that each loop results in a memtable flush
4297        let mut last_wal_id = 0;
4298        for i in 0..3 {
4299            let key = [b'a' + i; 16];
4300            let value = [b'b' + i; 50];
4301            kv_store.put(&key, &value).await.unwrap();
4302            let key = [b'j' + i; 16];
4303            let value = [b'k' + i; 50];
4304            kv_store.put(&key, &value).await.unwrap();
4305            let db_state = wait_for_manifest_condition(
4306                &mut stored_manifest,
4307                |s| s.replay_after_wal_id > last_wal_id,
4308                Duration::from_secs(30),
4309            )
4310            .await;
4311
4312            // 2 wal per iteration.
4313            assert_eq!(db_state.replay_after_wal_id, (i as u64) * 2 + 2);
4314            last_wal_id = db_state.replay_after_wal_id
4315        }
4316
4317        let manifest = stored_manifest.refresh().await.unwrap();
4318        let l0 = &manifest.core.tree.l0;
4319        assert_eq!(l0.len(), 3);
4320        let sst_iter_options = SstIteratorOptions::default();
4321
4322        for i in 0u8..3u8 {
4323            let sst1 = l0.get(2 - i as usize).unwrap();
4324            let mut iter = SstIterator::new_borrowed_initialized(
4325                ..,
4326                sst1,
4327                table_store.clone(),
4328                sst_iter_options.clone(),
4329            )
4330            .await
4331            .unwrap()
4332            .expect("Expected Some(iter) but got None");
4333            let kv: KeyValue = iter.next().await.unwrap().unwrap().into();
4334            assert_eq!(kv.key.as_ref(), [b'a' + i; 16]);
4335            assert_eq!(kv.value.as_ref(), [b'b' + i; 50]);
4336            let kv: KeyValue = iter.next().await.unwrap().unwrap().into();
4337            assert_eq!(kv.key.as_ref(), [b'j' + i; 16]);
4338            assert_eq!(kv.value.as_ref(), [b'k' + i; 50]);
4339            let kv = iter.next().await.unwrap().map(KeyValue::from);
4340            assert!(kv.is_none());
4341        }
4342        assert!(lookup_metric(&metrics_recorder, IMMUTABLE_MEMTABLE_FLUSHES).is_some_and(|v| v > 0));
4343    }
4344
4345    #[tokio::test]
4346    async fn test_put_flushes_memtable_after_max_wal_flushes() {
4347        const MAX_WAL_FLUSHES_BEFORE_L0_FLUSH: u64 = 4096;
4348
4349        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
4350        let path = "/tmp/test_flush_memtable_max_wal_flushes";
4351
4352        let mut settings = test_db_options(0, 64 * 1024 * 1024, None);
4353        settings.flush_interval = None; // Disable flushing
4354        settings.max_wal_flushes_before_l0_flush = MAX_WAL_FLUSHES_BEFORE_L0_FLUSH;
4355
4356        let kv_store = Db::builder(path, object_store.clone())
4357            .with_settings(settings)
4358            .build()
4359            .await
4360            .unwrap();
4361
4362        let manifest_store = Arc::new(ManifestStore::new(&Path::from(path), object_store.clone()));
4363        let mut stored_manifest =
4364            StoredManifest::load(manifest_store.clone(), Arc::new(DefaultSystemClock::new()))
4365                .await
4366                .unwrap();
4367
4368        let write_options: WriteOptions = WriteOptions {
4369            await_durable: false,
4370            ..Default::default()
4371        };
4372        let put_options = PutOptions::default();
4373
4374        for i in 0..(MAX_WAL_FLUSHES_BEFORE_L0_FLUSH - 1) {
4375            let key = format!("key{:08}", i);
4376            kv_store
4377                .put_with_options(key.as_bytes(), b"v", &put_options, &write_options)
4378                .await
4379                .unwrap();
4380            kv_store.flush().await.unwrap();
4381        }
4382
4383        // Verify WALs flushes.
4384        let wal_id = kv_store
4385            .inner
4386            .wal_observer
4387            .status()
4388            .unwrap()
4389            .last_flushed_wal_id;
4390        assert_eq!(wal_id, MAX_WAL_FLUSHES_BEFORE_L0_FLUSH); // account for the empty WAL written for fencing
4391
4392        // Verify no memtable was frozen or L0 flush happened.
4393        {
4394            let guard = kv_store.inner.state.read();
4395            assert!(guard.state().imm_memtable.is_empty());
4396            assert_eq!(guard.state().core().tree.l0.len(), 0);
4397        }
4398
4399        // This put() triggers a freeze.
4400        let key = format!("key{:08}", MAX_WAL_FLUSHES_BEFORE_L0_FLUSH - 1);
4401        kv_store
4402            .put_with_options(key.as_bytes(), b"v", &put_options, &write_options)
4403            .await
4404            .unwrap();
4405        // Flush the WAL so the manifest writer can proceed (flush_interval is
4406        // disabled in this test, so there is no periodic WAL flush).
4407        kv_store.flush().await.unwrap();
4408
4409        // Verify that the WAL count threshold triggered a memtable freeze and L0 flush.
4410        // replay_after_wal_id should have advanced to the threshold, and there should
4411        // be exactly one L0 SST.
4412        let db_state = wait_for_manifest_condition(
4413            &mut stored_manifest,
4414            |s| s.replay_after_wal_id == MAX_WAL_FLUSHES_BEFORE_L0_FLUSH,
4415            Duration::from_secs(30),
4416        )
4417        .await;
4418        assert_eq!(db_state.tree.l0.len(), 1);
4419
4420        // Run MAX_WAL_FLUSHES_BEFORE_L0_FLUSH more put()/flush() cycles
4421        // and see if the threshold triggers again.
4422        for i in 0..(MAX_WAL_FLUSHES_BEFORE_L0_FLUSH - 1) {
4423            let key = format!("key{:08}", i);
4424            kv_store
4425                .put_with_options(key.as_bytes(), b"v", &put_options, &write_options)
4426                .await
4427                .unwrap();
4428            kv_store.flush().await.unwrap();
4429        }
4430
4431        // Verify no more memtables were frozen or L0 flush happened.
4432        {
4433            let guard = kv_store.inner.state.read();
4434            assert_eq!(guard.state().core().tree.l0.len(), 1);
4435        }
4436
4437        // This put() triggers a freeze.
4438        let key = format!("key{:08}", MAX_WAL_FLUSHES_BEFORE_L0_FLUSH);
4439        kv_store
4440            .put_with_options(key.as_bytes(), b"v", &put_options, &write_options)
4441            .await
4442            .unwrap();
4443        kv_store.flush().await.unwrap();
4444
4445        // Wait for the flush to happen.
4446        let db_state = wait_for_manifest_condition(
4447            &mut stored_manifest,
4448            |s| s.replay_after_wal_id == MAX_WAL_FLUSHES_BEFORE_L0_FLUSH * 2,
4449            Duration::from_secs(30),
4450        )
4451        .await;
4452        assert_eq!(db_state.tree.l0.len(), 2); // We should have two L0 flushes.
4453
4454        kv_store.close().await.unwrap();
4455    }
4456
4457    #[tokio::test]
4458    async fn test_flush_memtable_with_wal_enabled() {
4459        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
4460        let path = "/tmp/test_flush_with_options";
4461        let mut options = test_db_options(0, 256, None);
4462        options.flush_interval = Some(Duration::from_secs(u64::MAX));
4463        let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
4464        let kv_store = Db::builder(path, object_store.clone())
4465            .with_settings(options)
4466            .with_metrics_recorder(metrics_recorder.clone())
4467            .build()
4468            .await
4469            .unwrap();
4470
4471        let manifest_store = Arc::new(ManifestStore::new(&Path::from(path), object_store.clone()));
4472        let mut stored_manifest =
4473            StoredManifest::load(manifest_store.clone(), Arc::new(DefaultSystemClock::new()))
4474                .await
4475                .unwrap();
4476        let sst_format = SsTableFormat {
4477            min_filter_keys: 10,
4478            ..SsTableFormat::default()
4479        };
4480        let table_store = Arc::new(TableStore::new(
4481            ObjectStores::new(object_store.clone(), None),
4482            sst_format,
4483            path,
4484            None,
4485            TableStoreKind::Main,
4486            BlockCachePolicy::default(),
4487        ));
4488
4489        // Write some data to populate the memtable
4490        let key1 = b"test_key_1";
4491        let value1 = b"test_value_1";
4492        kv_store
4493            .put_with_options(
4494                key1,
4495                value1,
4496                &PutOptions::default(),
4497                &WriteOptions {
4498                    await_durable: false,
4499                    ..Default::default()
4500                },
4501            )
4502            .await
4503            .unwrap();
4504
4505        let key2 = b"test_key_2";
4506        let value2 = b"test_value_2";
4507        kv_store
4508            .put_with_options(
4509                key2,
4510                value2,
4511                &PutOptions::default(),
4512                &WriteOptions {
4513                    await_durable: false,
4514                    ..Default::default()
4515                },
4516            )
4517            .await
4518            .unwrap();
4519
4520        // Get initial state
4521        let initial_manifest = stored_manifest.refresh().await.unwrap();
4522        let initial_l0_count = initial_manifest.core.tree.l0.len();
4523
4524        let initial_flush_count =
4525            lookup_metric(&metrics_recorder, IMMUTABLE_MEMTABLE_FLUSHES).unwrap();
4526
4527        // Flush memtable using flush_with_options
4528        kv_store
4529            .flush_with_options(FlushOptions {
4530                flush_type: FlushType::MemTable,
4531            })
4532            .await
4533            .unwrap();
4534
4535        // Wait for the flush to complete and manifest to be updated
4536        let db_state = wait_for_manifest_condition(
4537            &mut stored_manifest,
4538            |s| s.tree.l0.len() > initial_l0_count,
4539            Duration::from_secs(30),
4540        )
4541        .await;
4542
4543        // Verify that a new SST was created in L0
4544        assert_eq!(db_state.tree.l0.len(), initial_l0_count + 1);
4545
4546        // Verify that the flush metrics were updated
4547        let final_flush_count =
4548            lookup_metric(&metrics_recorder, IMMUTABLE_MEMTABLE_FLUSHES).unwrap();
4549        assert!(final_flush_count > initial_flush_count);
4550
4551        // Verify that the WAL was also flushed since we guarantee
4552        // memtable data is persisted in the WAL prior to L0 flush.
4553        let recent_flushed_wal_id = kv_store
4554            .inner
4555            .wal_observer
4556            .status()
4557            .unwrap()
4558            .last_flushed_wal_id;
4559        assert_eq!(recent_flushed_wal_id, 2);
4560
4561        // Verify that the data is still accessible after flush
4562        let retrieved_value1 = kv_store.get(key1).await.unwrap().unwrap();
4563        assert_eq!(retrieved_value1.as_ref(), value1);
4564
4565        let retrieved_value2 = kv_store.get(key2).await.unwrap().unwrap();
4566        assert_eq!(retrieved_value2.as_ref(), value2);
4567
4568        // Verify the data exists in the newly created SST
4569        let latest_sst = db_state.tree.l0.back().unwrap();
4570        let sst_iter_options = SstIteratorOptions::default();
4571        let mut iter = SstIterator::new_borrowed_initialized(
4572            ..,
4573            latest_sst,
4574            table_store.clone(),
4575            sst_iter_options,
4576        )
4577        .await
4578        .unwrap()
4579        .expect("Expected Some(iter) but got None");
4580
4581        // Collect all key-value pairs from the SST
4582        let mut found_keys = std::collections::HashSet::new();
4583        while let Some(kv) = iter.next().await.unwrap().map(KeyValue::from) {
4584            found_keys.insert(kv.key.to_vec());
4585        }
4586
4587        // Verify our keys are in the SST
4588        assert!(found_keys.contains(key1.as_slice()));
4589        assert!(found_keys.contains(key2.as_slice()));
4590    }
4591
4592    #[tokio::test]
4593    async fn test_memtable_flush_also_flushes_wal() {
4594        let main_object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
4595        let wal_object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
4596        let path = "/tmp/test_memtable_flush_also_flushes_wal";
4597        let mut settings = test_db_options(0, 1024, None);
4598        settings.flush_interval = None;
4599
4600        let kv_store = Db::builder(path, main_object_store)
4601            .with_settings(settings)
4602            .with_wal_object_store(wal_object_store.clone())
4603            .build()
4604            .await
4605            .unwrap();
4606
4607        let key = b"wal_flush_key";
4608        let value = b"wal_flush_value";
4609        kv_store
4610            .put_with_options(
4611                key,
4612                value,
4613                &PutOptions::default(),
4614                &WriteOptions {
4615                    await_durable: false,
4616                    ..Default::default()
4617                },
4618            )
4619            .await
4620            .unwrap();
4621
4622        assert_eq!(
4623            kv_store
4624                .inner
4625                .wal_observer
4626                .status()
4627                .unwrap()
4628                .buffered_wal_entries_count,
4629            1
4630        );
4631
4632        kv_store
4633            .flush_with_options(FlushOptions {
4634                flush_type: FlushType::MemTable,
4635            })
4636            .await
4637            .unwrap();
4638
4639        assert_eq!(
4640            kv_store
4641                .inner
4642                .wal_observer
4643                .status()
4644                .unwrap()
4645                .buffered_wal_entries_count,
4646            0
4647        );
4648
4649        let wal_reader = WalReader::new(path, wal_object_store);
4650        let wal_files = wal_reader.list(..).await.unwrap();
4651        assert_eq!(wal_files.len(), 2); // first file is the fencing operation
4652        let mut rows = Vec::new();
4653        let mut wal_iter = wal_files[1] // second file contains the actual write
4654            .iterator()
4655            .await
4656            .expect("expected successful WAL iterator call");
4657        while let Some(entry) = wal_iter
4658            .next()
4659            .await
4660            .expect("expected successful WAL rows read")
4661        {
4662            rows.push(entry);
4663        }
4664        assert_eq!(rows.len(), 1);
4665        let row = &rows[0];
4666        assert_eq!(row.key.as_ref(), key);
4667        assert_eq!(
4668            row.value.as_bytes().expect("expected bytes").as_ref(),
4669            value
4670        );
4671        assert_eq!(row.seq, 1);
4672    }
4673
4674    async fn test_sequence_tracker_persisted_across_flush_and_reload_impl(wal_enabled: bool) {
4675        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
4676        let path = "/tmp/test_sequence_tracker_flush";
4677        let mut settings = test_db_options(0, 256, None);
4678        settings.flush_interval = None;
4679        #[cfg(feature = "wal_disable")]
4680        {
4681            settings.wal_enabled = wal_enabled;
4682        }
4683        #[cfg(not(feature = "wal_disable"))]
4684        let _ = wal_enabled;
4685        let system_clock = Arc::new(MockSystemClock::new());
4686
4687        let kv_store = Db::builder(path, object_store.clone())
4688            .with_settings(settings.clone())
4689            .with_system_clock(system_clock.clone())
4690            .build()
4691            .await
4692            .unwrap();
4693
4694        let manifest_store = Arc::new(ManifestStore::new(&Path::from(path), object_store.clone()));
4695        let mut stored_manifest =
4696            StoredManifest::load(manifest_store.clone(), Arc::new(DefaultSystemClock::new()))
4697                .await
4698                .unwrap();
4699        let write_options = WriteOptions {
4700            await_durable: false,
4701            ..Default::default()
4702        };
4703        let put_options = PutOptions::default();
4704
4705        let timestamps_ms = [0_i64, 60_000, 120_000];
4706        for (idx, ts) in timestamps_ms.iter().enumerate() {
4707            system_clock.set(*ts);
4708            let key = format!("key-{idx}").into_bytes();
4709            let value = format!("value-{idx}").into_bytes();
4710            kv_store
4711                .put_with_options(&key, &value, &put_options, &write_options)
4712                .await
4713                .unwrap();
4714        }
4715
4716        kv_store
4717            .flush_with_options(FlushOptions {
4718                flush_type: FlushType::MemTable,
4719            })
4720            .await
4721            .unwrap();
4722
4723        let target_ts = Utc.timestamp_opt(120, 0).single().unwrap();
4724        let persisted_state = wait_for_manifest_condition(
4725            &mut stored_manifest,
4726            move |core| {
4727                core.sequence_tracker
4728                    .find_seq(target_ts, FindOption::RoundDown)
4729                    == Some(3)
4730            },
4731            Duration::from_secs(5),
4732        )
4733        .await;
4734
4735        let tracker = persisted_state.sequence_tracker.clone();
4736        let live_tracker = kv_store
4737            .inner
4738            .state
4739            .read()
4740            .state()
4741            .core()
4742            .sequence_tracker
4743            .clone();
4744        assert_eq!(tracker, live_tracker);
4745
4746        let seq1_ts = tracker.find_ts(1, FindOption::RoundDown).unwrap();
4747        assert_eq!(seq1_ts.timestamp(), 0);
4748
4749        let seq2_ts = tracker.find_ts(2, FindOption::RoundDown).unwrap();
4750        assert_eq!(seq2_ts.timestamp(), 60);
4751
4752        let seq3_ts = tracker.find_ts(3, FindOption::RoundDown).unwrap();
4753        assert_eq!(seq3_ts.timestamp(), 120);
4754
4755        let ts_lookup = Utc.timestamp_opt(60, 0).single().unwrap();
4756        assert_eq!(tracker.find_seq(ts_lookup, FindOption::RoundDown), Some(2));
4757
4758        kv_store.close().await.unwrap();
4759
4760        let reopened = Db::builder(path, object_store.clone())
4761            .with_settings(settings)
4762            .with_system_clock(system_clock.clone())
4763            .build()
4764            .await
4765            .unwrap();
4766
4767        let reopened_tracker = reopened
4768            .inner
4769            .state
4770            .read()
4771            .state()
4772            .core()
4773            .sequence_tracker
4774            .clone();
4775        assert_eq!(tracker, reopened_tracker);
4776
4777        reopened.close().await.unwrap();
4778    }
4779
4780    #[tokio::test]
4781    async fn test_sequence_tracker_persisted_across_flush_and_reload_wal_enabled() {
4782        test_sequence_tracker_persisted_across_flush_and_reload_impl(true).await;
4783    }
4784
4785    #[tokio::test]
4786    #[cfg(feature = "wal_disable")]
4787    async fn test_sequence_tracker_persisted_across_flush_and_reload_wal_disabled() {
4788        test_sequence_tracker_persisted_across_flush_and_reload_impl(false).await;
4789    }
4790
4791    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
4792    async fn test_sequence_tracker_not_ahead_of_last_l0_seq_when_flush_races_with_writes() {
4793        let fp_registry = Arc::new(FailPointRegistry::new());
4794        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
4795        let mut settings = test_db_options(0, 2048, None);
4796        settings.flush_interval = None;
4797        // Don't trigger `flush_and_record` unless we explicitly ask for it.
4798        settings.manifest_poll_interval = Duration::from_secs(60 * 60);
4799
4800        let system_clock = Arc::new(MockSystemClock::new());
4801        let db = Db::builder(
4802            "/tmp/test_sequence_tracker_flush_race",
4803            object_store.clone(),
4804        )
4805        .with_settings(settings)
4806        .with_system_clock(system_clock.clone())
4807        .with_fp_registry(fp_registry.clone())
4808        .build()
4809        .await
4810        .unwrap();
4811
4812        let write_options = WriteOptions {
4813            await_durable: false,
4814            ..Default::default()
4815        };
4816
4817        async fn put_with_timestamp(
4818            db: &Db,
4819            system_clock: &Arc<MockSystemClock>,
4820            idx: usize,
4821            write_options: &WriteOptions,
4822        ) {
4823            system_clock.set((idx as i64) * 60_000);
4824            let key = format!("race-key-{idx}").into_bytes();
4825            let value = format!("race-value-{idx}").into_bytes();
4826            db.put_with_options(&key, &value, &PutOptions::default(), write_options)
4827                .await
4828                .unwrap();
4829        }
4830
4831        // These are the entries that should make it into the first L0 flush.
4832        put_with_timestamp(&db, &system_clock, 0, &write_options).await;
4833        put_with_timestamp(&db, &system_clock, 1, &write_options).await;
4834
4835        // Pause after the immutable memtable has been written to an L0 SST but before the manifest
4836        // is updated. That gives us a precise window where later writes can race with manifest state.
4837        fail_parallel::cfg(
4838            fp_registry.clone(),
4839            "after-flush-imm-to-l0-before-manifest",
4840            "pause",
4841        )
4842        .unwrap();
4843
4844        // Kick off the flush in the background so the test can interleave more writes while the
4845        // flusher is paused at the failpoint above.
4846        let flush_handle = {
4847            let inner = Arc::clone(&db.inner);
4848            tokio::spawn(async move { inner.flush_memtables(FlushTarget::All).await })
4849        };
4850
4851        let mut wrote_l0_sst = false;
4852        for _ in 0..6000 {
4853            // Waiting for the SST itself is more precise than watching imm_memtable state. We only
4854            // continue once the flusher has definitely crossed the "write SST, not manifest" boundary.
4855            let ssts = db.inner.table_store.list_compacted_ssts(..).await.unwrap();
4856            if !ssts.is_empty() {
4857                wrote_l0_sst = true;
4858                break;
4859            }
4860            tokio::time::sleep(Duration::from_millis(10)).await;
4861        }
4862        assert!(
4863            wrote_l0_sst,
4864            "L0 SST was not written before manifest update pause"
4865        );
4866
4867        // These writes land in the new active memtable while the first flush is paused.
4868        // They should not leak into the persisted sequence tracker state for the earlier flush.
4869        put_with_timestamp(&db, &system_clock, 2, &write_options).await;
4870        put_with_timestamp(&db, &system_clock, 3, &write_options).await;
4871
4872        // Let the original flush finish publishing its manifest update.
4873        fail_parallel::cfg(
4874            fp_registry.clone(),
4875            "after-flush-imm-to-l0-before-manifest",
4876            "off",
4877        )
4878        .unwrap();
4879
4880        flush_handle.await.unwrap().unwrap();
4881
4882        {
4883            let guard = db.inner.state.read();
4884            // The background flush should have drained the single immutable memtable we created.
4885            assert!(guard.state().imm_memtable.is_empty());
4886        }
4887
4888        let manifest_state = {
4889            let guard = db.inner.state.read();
4890            guard.state().manifest.value.core.clone()
4891        };
4892        let last_l0_seq = manifest_state.last_l0_seq;
4893        assert!(
4894            last_l0_seq >= 2,
4895            "expected flushed memtable to advance last_l0_seq"
4896        );
4897
4898        // The core invariant: once the first flush publishes last_l0_seq, the persisted tracker
4899        // must not contain timestamps for later sequence numbers from the second memtable.
4900        assert!(
4901            manifest_state
4902                .sequence_tracker
4903                .find_ts(last_l0_seq + 1, FindOption::RoundUp)
4904                .is_none(),
4905            "sequence tracker should not advance beyond last_l0_seq (last_l0_seq={})",
4906            last_l0_seq
4907        );
4908
4909        db.close().await.unwrap();
4910    }
4911
4912    #[tokio::test]
4913    async fn test_flush_with_options_wal() {
4914        let fp_registry = Arc::new(FailPointRegistry::new());
4915        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
4916        let path = "/tmp/test_flush_with_options_wal";
4917        let mut options = test_db_options(0, 1024, None);
4918        // Larger memtable to avoid memtable flushes
4919        options.flush_interval = Some(Duration::from_secs(u64::MAX));
4920        // Fail all memtable writes before the DB starts, so we can be sure that
4921        // only the WAL is flushed.
4922        fail_parallel::cfg(
4923            fp_registry.clone(),
4924            "write-compacted-sst-io-error",
4925            "return",
4926        )
4927        .unwrap();
4928        let kv_store = Db::builder(path, object_store.clone())
4929            .with_settings(options)
4930            .with_fp_registry(fp_registry.clone())
4931            .build()
4932            .await
4933            .unwrap();
4934
4935        // Write some data to populate the WAL buffer
4936        let key1 = b"wal_test_key_1";
4937        let value1 = b"wal_test_value_1";
4938        kv_store
4939            .put_with_options(
4940                key1,
4941                value1,
4942                &PutOptions::default(),
4943                &WriteOptions {
4944                    await_durable: false,
4945                    ..Default::default()
4946                },
4947            )
4948            .await
4949            .unwrap();
4950
4951        let key2 = b"wal_test_key_2";
4952        let value2 = b"wal_test_value_2";
4953        kv_store
4954            .put_with_options(
4955                key2,
4956                value2,
4957                &PutOptions::default(),
4958                &WriteOptions {
4959                    await_durable: false,
4960                    ..Default::default()
4961                },
4962            )
4963            .await
4964            .unwrap();
4965
4966        // Get initial WAL ID to verify flush occurred
4967        let initial_wal_id = kv_store
4968            .inner
4969            .wal_observer
4970            .status()
4971            .unwrap()
4972            .last_flushed_wal_id;
4973
4974        // Flush WAL using flush_with_options - this should succeed without error
4975        let flush_result = kv_store
4976            .flush_with_options(FlushOptions {
4977                flush_type: FlushType::Wal,
4978            })
4979            .await;
4980
4981        // Verify the flush operation completed successfully
4982        assert!(flush_result.is_ok(), "WAL flush should succeed");
4983
4984        // Verify that the data is still accessible after WAL flush
4985        let retrieved_value1 = kv_store.get(key1).await.unwrap().unwrap();
4986        assert_eq!(retrieved_value1.as_ref(), value1);
4987
4988        let retrieved_value2 = kv_store.get(key2).await.unwrap().unwrap();
4989        assert_eq!(retrieved_value2.as_ref(), value2);
4990
4991        // Verify that the WAL buffer is in a consistent state after flush
4992        // The recent_flushed_wal_id should be at least as high as before
4993        let final_wal_id = kv_store
4994            .inner
4995            .wal_observer
4996            .status()
4997            .unwrap()
4998            .last_flushed_wal_id;
4999        assert!(
5000            final_wal_id >= initial_wal_id,
5001            "WAL ID should not decrease after flush"
5002        );
5003
5004        // Verify that the memtable has not been flushed by checking the db for error state
5005        assert!(
5006            kv_store.inner.status().close_reason.is_none(),
5007            "DB should not have an error state"
5008        );
5009    }
5010
5011    #[tokio::test]
5012    #[cfg(feature = "wal_disable")]
5013    async fn test_flush_with_options_wal_disabled_error() {
5014        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5015        let path = "/tmp/test_flush_with_options_wal_disabled";
5016        let mut options = test_db_options(0, 1024, None);
5017        options.wal_enabled = false; // Disable WAL
5018        let kv_store = Db::builder(path, object_store.clone())
5019            .with_settings(options)
5020            .build()
5021            .await
5022            .unwrap();
5023
5024        // Write some data to the database
5025        let key1 = b"test_key_1";
5026        let value1 = b"test_value_1";
5027        kv_store
5028            .put_with_options(
5029                key1,
5030                value1,
5031                &PutOptions::default(),
5032                &WriteOptions {
5033                    await_durable: false,
5034                    ..Default::default()
5035                },
5036            )
5037            .await
5038            .unwrap();
5039
5040        // Attempt to flush WAL on a WAL-disabled database
5041        let flush_result = kv_store
5042            .flush_with_options(FlushOptions {
5043                flush_type: FlushType::Wal,
5044            })
5045            .await;
5046
5047        // Verify that we get the WalDisabled error
5048        assert!(flush_result.is_err(), "Expected WalDisabled error");
5049        let error = flush_result.unwrap_err();
5050
5051        assert!(
5052            error
5053                .to_string()
5054                .contains("attempted a WAL operation when the WAL is disabled"),
5055            "Expected WalDisabled error message, got: {}",
5056            error
5057        );
5058
5059        // Verify that memtable flush still works when WAL is disabled
5060        let memtable_flush_result = kv_store
5061            .flush_with_options(FlushOptions {
5062                flush_type: FlushType::MemTable,
5063            })
5064            .await;
5065        assert!(
5066            memtable_flush_result.is_ok(),
5067            "Memtable flush should work even when WAL is disabled"
5068        );
5069
5070        // Verify that the data is still accessible
5071        let retrieved_value1 = kv_store.get(key1).await.unwrap().unwrap();
5072        assert_eq!(retrieved_value1.as_ref(), value1);
5073    }
5074
5075    // 2 threads so we can can wait on the write_with_options (main) thread
5076    // while the write_batch (background) thread is blocked on writing the
5077    // WAL SST.
5078    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
5079    async fn test_apply_wal_memory_backpressure() {
5080        let fp_registry = Arc::new(FailPointRegistry::new());
5081        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5082        let path = Path::from("/tmp/test_kv_store");
5083        let mut options = test_db_options(0, 1, None);
5084        let first_entry = RowEntry::new_value(b"key1", b"val1", 1).with_create_ts(0);
5085        let sst_format = SsTableFormat {
5086            min_filter_keys: options.min_filter_keys,
5087            ..SsTableFormat::default()
5088        };
5089        let first_memtable_bytes =
5090            sst_format.estimate_encoded_size_compacted(1, first_entry.estimated_size());
5091        // Keep the memtable alone below the limit so this test only applies
5092        // backpressure when the WAL estimate is included.
5093        options.max_unflushed_bytes = first_memtable_bytes.saturating_add(1);
5094        let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
5095        let db = Db::builder(path, object_store.clone())
5096            .with_settings(options)
5097            .with_fp_registry(fp_registry.clone())
5098            .with_metrics_recorder(metrics_recorder.clone())
5099            .build()
5100            .await
5101            .unwrap();
5102        let metrics_recorder_clone = metrics_recorder.clone();
5103        let write_opts = WriteOptions {
5104            await_durable: false,
5105            ..Default::default()
5106        };
5107
5108        fail_parallel::cfg(fp_registry.clone(), "write-wal-sst-io-error", "pause").unwrap();
5109
5110        // Helper function to wait for a condition to be true.
5111        let wait_for = async move |condition: Box<dyn Fn() -> bool>| {
5112            for _ in 0..3000 {
5113                if condition() {
5114                    return;
5115                }
5116                tokio::time::sleep(Duration::from_millis(10)).await;
5117            }
5118        };
5119
5120        // 1 wal entry in memory
5121        db.put_with_options(b"key1", b"val1", &PutOptions::default(), &write_opts)
5122            .await
5123            .unwrap();
5124
5125        // Wait for put to end up in the WAL buffer
5126        let this_wal_buffer = db.inner.wal_observer.clone();
5127        wait_for(Box::new(move || {
5128            this_wal_buffer.status().unwrap().buffered_wal_entries_count > 0
5129        }))
5130        .await;
5131
5132        // Verify that there is now 1 WAL entry in memory.
5133        let wal_status = db.inner.wal_observer.status().unwrap();
5134        assert_eq!(wal_status.buffered_wal_entries_count, 1);
5135
5136        let (active_memtable_size_bytes, imm_memtable_size_bytes) = {
5137            let guard = db.inner.state.read();
5138            let estimate = |metadata: KVTableMetadata| {
5139                db.inner.table_store.estimate_encoded_size_compacted(
5140                    metadata.entry_num,
5141                    metadata.entries_size_in_bytes,
5142                )
5143            };
5144            let active_memtable_size_bytes = estimate(guard.memtable().table().metadata());
5145            let imm_memtable_size_bytes = guard
5146                .state()
5147                .imm_memtable
5148                .iter()
5149                .map(|imm| estimate(imm.table().metadata()))
5150                .fold(0usize, |total, size| total.saturating_add(size));
5151            (active_memtable_size_bytes, imm_memtable_size_bytes)
5152        };
5153        let memtable_size_bytes =
5154            active_memtable_size_bytes.saturating_add(imm_memtable_size_bytes);
5155        let total_mem_size_bytes = memtable_size_bytes.saturating_add(wal_status.estimated_bytes);
5156        assert!(
5157            memtable_size_bytes < db.inner.settings.max_unflushed_bytes,
5158            "test requires memtable bytes ({memtable_size_bytes}) to remain below \
5159             max_unflushed_bytes ({})",
5160            db.inner.settings.max_unflushed_bytes
5161        );
5162        assert!(
5163            total_mem_size_bytes >= db.inner.settings.max_unflushed_bytes,
5164            "test requires memtable plus WAL bytes ({total_mem_size_bytes}) to reach \
5165             max_unflushed_bytes ({})",
5166            db.inner.settings.max_unflushed_bytes
5167        );
5168
5169        // Put another WAL entry, which should trigger backpressure. Do this in a separate
5170        // task since the put() is blocked until the WAL is flushed, which isn't happening
5171        // due to the fail point.
5172        let join_handle = tokio::spawn(async move {
5173            db.put_with_options(b"key2", b"val2", &PutOptions::default(), &write_opts)
5174                .await
5175                .unwrap();
5176        });
5177
5178        let this_recorder = metrics_recorder_clone.clone();
5179        // Wait up to 30s for backpressure to be applied to the second write.
5180        wait_for(Box::new(move || {
5181            lookup_metric(&this_recorder, crate::db_stats::BACKPRESSURE_COUNT)
5182                .is_some_and(|v| v > 0)
5183        }))
5184        .await;
5185
5186        // Verify that backpressure is applied.
5187        assert!(
5188            lookup_metric(&metrics_recorder_clone, crate::db_stats::BACKPRESSURE_COUNT).unwrap()
5189                >= 1
5190        );
5191
5192        // Unblock so put_with_options in join_handle can complete and join_handle.await returns
5193        fail_parallel::cfg(fp_registry.clone(), "write-wal-sst-io-error", "off").unwrap();
5194
5195        // Shutdown the background task
5196        join_handle.abort();
5197        let _ = join_handle.await;
5198    }
5199
5200    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
5201    async fn test_backpressure_waiter_exits_when_db_is_fenced() {
5202        // Pause the L0 upload so a frozen memtable can't drain, keeping unflushed
5203        // bytes above the backpressure threshold indefinitely.
5204        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5205        let fp_registry = Arc::new(FailPointRegistry::new());
5206        fail_parallel::cfg(fp_registry.clone(), "write-compacted-sst-io-error", "pause").unwrap();
5207
5208        let mut options = test_db_options(0, 4 * 1024, None);
5209        options.flush_interval = None;
5210        options.max_unflushed_bytes = 8 * 1024;
5211
5212        // Use a metrics recorder so the test can observe when the spawned task
5213        // has actually entered maybe_apply_backpressure().
5214        let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
5215        let db = Db::builder(
5216            "/tmp/test_backpressure_waiter_exits_when_db_is_fenced",
5217            object_store,
5218        )
5219        .with_settings(options)
5220        .with_fp_registry(fp_registry.clone())
5221        .with_metrics_recorder(metrics_recorder.clone())
5222        .build()
5223        .await
5224        .unwrap();
5225        let write_opts = WriteOptions {
5226            await_durable: false,
5227            ..Default::default()
5228        };
5229
5230        let large_value = vec![b'x'; 16 * 1024];
5231        db.put_with_options(b"key1", &large_value, &PutOptions::default(), &write_opts)
5232            .await
5233            .unwrap();
5234        assert!(!db.inner.state.read().state().imm_memtable.is_empty());
5235
5236        // Start backpressure on a cloned inner handle. This parks the task on
5237        // the same wait path used by writers before they enqueue a batch.
5238        let inner = db.inner.clone();
5239        let mut backpressure_task =
5240            tokio::spawn(async move { inner.maybe_apply_backpressure().await });
5241
5242        // Wait until the task has observed the unflushed memtable and incremented
5243        // the backpressure counter, proving it is inside the wait path.
5244        tokio::time::timeout(Duration::from_secs(60), async {
5245            loop {
5246                if lookup_metric(&metrics_recorder, crate::db_stats::BACKPRESSURE_COUNT)
5247                    .is_some_and(|v| v > 0)
5248                {
5249                    break;
5250                }
5251                tokio::time::sleep(Duration::from_millis(10)).await;
5252            }
5253        })
5254        .await
5255        .expect("timed out waiting for backpressure to be applied");
5256
5257        // Simulate the DB being fenced while the writer is already parked in
5258        // backpressure.
5259        db.inner
5260            .status_manager
5261            .write_result(Err(SlateDBError::Fenced));
5262
5263        // The lifecycle signal should wake the waiter promptly even though no
5264        // WAL flush or memtable upload will notify it.
5265        let result = tokio::time::timeout(Duration::from_secs(5), &mut backpressure_task).await;
5266        if result.is_err() {
5267            backpressure_task.abort();
5268            let _ = backpressure_task.await;
5269        }
5270
5271        // Resume the L0 upload so the pending memtable can drain and close can
5272        // complete cleanly.
5273        fail_parallel::cfg(fp_registry.clone(), "write-compacted-sst-io-error", "off").unwrap();
5274        let _ = db.close().await;
5275
5276        // Assert that the waiter exits with the terminal fenced error, not a
5277        // successful write path or some unrelated task failure.
5278        let backpressure_result = result
5279            .expect("backpressure waiter did not exit after DB was fenced")
5280            .expect("backpressure task panicked");
5281        assert!(
5282            matches!(backpressure_result, Err(SlateDBError::Fenced)),
5283            "expected fenced error, got {:?}",
5284            backpressure_result
5285        );
5286    }
5287
5288    #[tokio::test]
5289    async fn test_apply_backpressure_to_memtable_flush() {
5290        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5291        let mut options = test_db_options(0, 1, None);
5292        options.l0_max_ssts = 4;
5293        let db = Db::builder("/tmp/test_kv_store", object_store.clone())
5294            .with_settings(options)
5295            .build()
5296            .await
5297            .unwrap();
5298        db.put(b"key1", b"val1").await.unwrap();
5299        db.put(b"key2", b"val2").await.unwrap();
5300        db.put(b"key3", b"val3").await.unwrap();
5301        db.put(b"key4", b"val4").await.unwrap();
5302        db.put(b"key5", b"val5").await.unwrap();
5303
5304        db.flush().await.unwrap();
5305
5306        let db_state = db.inner.state.read().view();
5307        assert_eq!(db_state.state.imm_memtable.len(), 1);
5308    }
5309
5310    #[tokio::test]
5311    async fn test_put_empty_value() {
5312        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5313        let kv_store = Db::builder("/tmp/test_kv_store", object_store.clone())
5314            .with_settings(test_db_options(0, 1024, None))
5315            .build()
5316            .await
5317            .unwrap();
5318        let key = b"test_key";
5319        let value = b"";
5320        kv_store.put(key, value).await.unwrap();
5321        kv_store.flush().await.unwrap();
5322
5323        assert_eq!(
5324            kv_store.get(key).await.unwrap(),
5325            Some(Bytes::from_static(value))
5326        );
5327    }
5328
5329    #[tokio::test]
5330    async fn test_flush_while_iterating() {
5331        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5332        let kv_store = Db::builder("/tmp/test_kv_store", object_store.clone())
5333            .with_settings(test_db_options(0, 1024, None))
5334            .with_system_clock(Arc::new(MockSystemClock::new()))
5335            .build()
5336            .await
5337            .unwrap();
5338
5339        let memtable = {
5340            let lock = kv_store.inner.state.read();
5341            lock.memtable()
5342                .put(RowEntry::new_value(b"abc1111", b"value1111", 1));
5343            lock.memtable()
5344                .put(RowEntry::new_value(b"abc2222", b"value2222", 2));
5345            lock.memtable()
5346                .put(RowEntry::new_value(b"abc3333", b"value3333", 3));
5347            lock.memtable().table().clone()
5348        };
5349
5350        let mut iter = memtable.iter();
5351        let kv: KeyValue = iter.next().await.unwrap().unwrap().into();
5352        assert_eq!(kv.key, b"abc1111".as_slice());
5353
5354        kv_store.flush().await.unwrap();
5355
5356        let kv: KeyValue = iter.next().await.unwrap().unwrap().into();
5357        assert_eq!(kv.key, b"abc2222".as_slice());
5358
5359        let kv: KeyValue = iter.next().await.unwrap().unwrap().into();
5360        assert_eq!(kv.key, b"abc3333".as_slice());
5361    }
5362
5363    #[tokio::test]
5364    async fn test_basic_restore() {
5365        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5366        let path = "/tmp/test_kv_store";
5367        let mut next_wal_id = 1;
5368        let kv_store = Db::builder(path, object_store.clone())
5369            // with l0_sst_size_bytes = 600 all large puts should be flushed in one L0 SST
5370            .with_settings(test_db_options(0, 600, None))
5371            .with_system_clock(Arc::new(MockSystemClock::new()))
5372            .build()
5373            .await
5374            .unwrap();
5375        // increment wal id for the empty wal
5376        next_wal_id += 1;
5377
5378        // do all flushes manually
5379        let write_opts = WriteOptions {
5380            await_durable: false,
5381            ..Default::default()
5382        };
5383
5384        // do a few writes that will result in l0 flushes
5385        let l0_count: u64 = 3;
5386        for i in 0..l0_count {
5387            kv_store
5388                .put_with_options(
5389                    &[b'a' + i as u8; 16],
5390                    &[b'b' + i as u8; 48],
5391                    &PutOptions::default(),
5392                    &write_opts,
5393                )
5394                .await
5395                .unwrap();
5396            kv_store.flush().await.unwrap();
5397            kv_store
5398                .put_with_options(
5399                    &[b'j' + i as u8; 16],
5400                    &[b'k' + i as u8; 48],
5401                    &PutOptions::default(),
5402                    &write_opts,
5403                )
5404                .await
5405                .unwrap();
5406            kv_store.flush().await.unwrap();
5407            next_wal_id += 2;
5408        }
5409
5410        // write some smaller keys so that we populate wal without flushing to l0
5411        let sst_count: u64 = 5;
5412        for i in 0..sst_count {
5413            kv_store
5414                .put_with_options(
5415                    &i.to_be_bytes(),
5416                    &i.to_be_bytes(),
5417                    &PutOptions::default(),
5418                    &write_opts,
5419                )
5420                .await
5421                .unwrap();
5422            kv_store.flush().await.unwrap();
5423            next_wal_id += 1;
5424        }
5425
5426        kv_store.close().await.unwrap();
5427
5428        // recover and validate that sst files are loaded on recovery.
5429        let kv_store_restored = Db::builder(path, object_store.clone())
5430            .with_settings(test_db_options(0, 128, None))
5431            .with_system_clock(Arc::new(MockSystemClock::new()))
5432            .build()
5433            .await
5434            .unwrap();
5435        // increment wal id for the empty wal
5436        next_wal_id += 1;
5437
5438        for i in 0..l0_count {
5439            let val = kv_store_restored.get([b'a' + i as u8; 16]).await.unwrap();
5440            assert_eq!(val, Some(Bytes::copy_from_slice(&[b'b' + i as u8; 48])));
5441            let val = kv_store_restored.get([b'j' + i as u8; 16]).await.unwrap();
5442            assert_eq!(val, Some(Bytes::copy_from_slice(&[b'k' + i as u8; 48])));
5443        }
5444        for i in 0..sst_count {
5445            let val = kv_store_restored.get(i.to_be_bytes()).await.unwrap();
5446            assert_eq!(val, Some(Bytes::copy_from_slice(&i.to_be_bytes())));
5447        }
5448        kv_store_restored.close().await.unwrap();
5449
5450        // validate that the manifest file exists.
5451        let manifest_store = Arc::new(ManifestStore::new(&Path::from(path), object_store.clone()));
5452        let stored_manifest =
5453            StoredManifest::load(manifest_store, Arc::new(DefaultSystemClock::new()))
5454                .await
5455                .unwrap();
5456        let db_state = stored_manifest.db_state();
5457        assert_eq!(db_state.next_wal_sst_id, next_wal_id);
5458    }
5459
5460    #[tokio::test]
5461    #[allow(clippy::await_holding_lock)]
5462    async fn test_restore_seq_number() {
5463        let fp_registry = Arc::new(FailPointRegistry::new());
5464        // Block L0 uploads so the data remains only in the WAL. The
5465        // uploader gives up on shutdown when the WAL is enabled, so
5466        // close() will complete without flushing memtables to L0.
5467        fail_parallel::cfg(
5468            fp_registry.clone(),
5469            "write-compacted-sst-io-error",
5470            "return",
5471        )
5472        .unwrap();
5473        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5474        let path = "/tmp/test_kv_store";
5475        let db = Db::builder(path, object_store.clone())
5476            .with_settings(test_db_options(0, 512, None))
5477            .with_system_clock(Arc::new(MockSystemClock::new()))
5478            .with_fp_registry(fp_registry.clone())
5479            .build()
5480            .await
5481            .unwrap();
5482
5483        db.put_with_options(
5484            b"key1",
5485            b"val1",
5486            &PutOptions::default(),
5487            &WriteOptions {
5488                await_durable: false,
5489                ..Default::default()
5490            },
5491        )
5492        .await
5493        .unwrap();
5494        db.put_with_options(
5495            b"key2",
5496            b"val2",
5497            &PutOptions::default(),
5498            &WriteOptions {
5499                await_durable: false,
5500                ..Default::default()
5501            },
5502        )
5503        .await
5504        .unwrap();
5505        db.put_with_options(
5506            b"key3",
5507            b"val3",
5508            &PutOptions::default(),
5509            &WriteOptions {
5510                await_durable: false,
5511                ..Default::default()
5512            },
5513        )
5514        .await
5515        .unwrap();
5516        db.flush().await.unwrap();
5517        // expect to fail as l0 upload is blocked
5518        assert!(db.close().await.is_err());
5519
5520        // Disable the failpoint so the restored DB can flush normally.
5521        fail_parallel::cfg(fp_registry.clone(), "write-compacted-sst-io-error", "off").unwrap();
5522
5523        let db_restored = Db::builder(path, object_store.clone())
5524            .with_settings(test_db_options(0, 512, None))
5525            .with_system_clock(Arc::new(MockSystemClock::new()))
5526            .with_fp_registry(fp_registry.clone())
5527            .build()
5528            .await
5529            .unwrap();
5530
5531        let state = db_restored.inner.state.read();
5532        let memtable = state.memtable();
5533        let mut iter = memtable.table().iter();
5534        assert_iterator(
5535            &mut iter,
5536            vec![
5537                RowEntry::new_value(b"key1", b"val1", 1).with_create_ts(0),
5538                RowEntry::new_value(b"key2", b"val2", 2).with_create_ts(0),
5539                RowEntry::new_value(b"key3", b"val3", 3).with_create_ts(0),
5540            ],
5541        )
5542        .await;
5543    }
5544
5545    #[tokio::test]
5546    async fn test_read_merges_from_snapshot_across_compaction() {
5547        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5548        let path = "/tmp/testdb";
5549        let should_compact_l0 = Arc::new(AtomicBool::new(false));
5550        let this_should_compact_l0 = should_compact_l0.clone();
5551        let compaction_scheduler = Arc::new(OnDemandCompactionSchedulerSupplier::new(Arc::new(
5552            move |_state| this_should_compact_l0.swap(false, Ordering::SeqCst),
5553        )));
5554        let db = Db::builder(path, object_store.clone())
5555            .with_settings(test_db_options(0, 1024 * 1024, None))
5556            .with_merge_operator(Arc::new(StringConcatMergeOperator {}))
5557            .with_compactor_builder(
5558                CompactorBuilder::new(path, object_store.clone())
5559                    .with_scheduler_supplier(compaction_scheduler.clone())
5560                    .with_options(fast_compactor_options()),
5561            )
5562            .build()
5563            .await
5564            .unwrap();
5565        let db = Arc::new(db);
5566
5567        db.merge(b"foo", b"0").await.unwrap();
5568        let snapshot = db.snapshot().await.unwrap();
5569        db.flush_with_options(FlushOptions {
5570            flush_type: FlushType::MemTable,
5571        })
5572        .await
5573        .unwrap();
5574        db.merge(b"foo", b"1").await.unwrap();
5575        db.flush_with_options(FlushOptions {
5576            flush_type: FlushType::MemTable,
5577        })
5578        .await
5579        .unwrap();
5580
5581        // await a compaction
5582        should_compact_l0.store(true, Ordering::SeqCst);
5583        let db_poll = db.clone();
5584        tokio::time::timeout(Duration::from_secs(10), async move {
5585            loop {
5586                {
5587                    let db_state = db_poll.inner.state.read();
5588                    if !db_state.state().core().tree.compacted.is_empty() {
5589                        return;
5590                    }
5591                }
5592                tokio::time::sleep(Duration::from_millis(10)).await;
5593            }
5594        })
5595        .await
5596        .unwrap();
5597
5598        let result = snapshot.get(b"foo").await.unwrap();
5599        assert_eq!(result, Some(Bytes::copy_from_slice(b"0")));
5600        let result = db.get(b"foo").await.unwrap();
5601        assert_eq!(result, Some(Bytes::copy_from_slice(b"01")));
5602    }
5603
5604    #[tokio::test]
5605    async fn test_all_kv_seq_num_are_greater_than_0() {
5606        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5607        let path = "/tmp/test_kv_store_seq_num";
5608        let db = Db::builder(path, object_store.clone())
5609            .with_settings(test_db_options(0, 1024 * 1024, None))
5610            .build()
5611            .await
5612            .unwrap();
5613
5614        // Write some data to memtable
5615        db.put(b"key1", b"value1").await.unwrap();
5616
5617        let val = db.get(b"key1").await.unwrap();
5618        assert_eq!(val, Some(Bytes::from_static(b"value1")));
5619
5620        let state = db.inner.state.read();
5621        let memtable = state.memtable();
5622        assert_eq!(memtable.table().last_seq(), Some(1));
5623    }
5624
5625    #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
5626    async fn test_should_read_uncommitted_data_if_read_level_uncommitted() {
5627        let fp_registry = Arc::new(FailPointRegistry::new());
5628        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5629        let path = "/tmp/test_kv_store";
5630        let kv_store = Db::builder(path, object_store.clone())
5631            .with_settings(test_db_options(0, 1024, None))
5632            .with_fp_registry(fp_registry.clone())
5633            .build()
5634            .await
5635            .unwrap();
5636
5637        fail_parallel::cfg(fp_registry.clone(), "write-wal-sst-io-error", "pause").unwrap();
5638        kv_store
5639            .put_with_options(
5640                "foo".as_bytes(),
5641                "bar".as_bytes(),
5642                &PutOptions::default(),
5643                &WriteOptions {
5644                    await_durable: false,
5645                    ..Default::default()
5646                },
5647            )
5648            .await
5649            .unwrap();
5650
5651        // Validate uncommitted read
5652        let val = kv_store
5653            .get_with_options(
5654                "foo".as_bytes(),
5655                &ReadOptions::new().with_durability_filter(Memory),
5656            )
5657            .await
5658            .unwrap();
5659        assert_eq!(val, Some("bar".into()));
5660
5661        // Validate committed read should still return None
5662        let val = kv_store
5663            .get_with_options(
5664                "foo".as_bytes(),
5665                &ReadOptions::new().with_durability_filter(Remote),
5666            )
5667            .await
5668            .unwrap();
5669        assert_eq!(val, None);
5670
5671        fail_parallel::cfg(fp_registry.clone(), "write-wal-sst-io-error", "off").unwrap();
5672        kv_store.close().await.unwrap();
5673    }
5674
5675    #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
5676    async fn test_should_read_only_committed_data() {
5677        let fp_registry = Arc::new(FailPointRegistry::new());
5678        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5679        let path = "/tmp/test_kv_store";
5680        let kv_store = Db::builder(path, object_store.clone())
5681            .with_settings(test_db_options(0, 1024, None))
5682            .with_fp_registry(fp_registry.clone())
5683            .build()
5684            .await
5685            .unwrap();
5686
5687        kv_store
5688            .put("foo".as_bytes(), "bar".as_bytes())
5689            .await
5690            .unwrap();
5691        fail_parallel::cfg(fp_registry.clone(), "write-wal-sst-io-error", "pause").unwrap();
5692        kv_store
5693            .put_with_options(
5694                "foo".as_bytes(),
5695                "bla".as_bytes(),
5696                &PutOptions::default(),
5697                &WriteOptions {
5698                    await_durable: false,
5699                    ..Default::default()
5700                },
5701            )
5702            .await
5703            .unwrap();
5704
5705        let val = kv_store
5706            .get_with_options(
5707                "foo".as_bytes(),
5708                &ReadOptions::new().with_durability_filter(Remote),
5709            )
5710            .await
5711            .unwrap();
5712        assert_eq!(val, Some("bar".into()));
5713        let val = kv_store
5714            .get_with_options(
5715                "foo".as_bytes(),
5716                &ReadOptions::new().with_durability_filter(Memory),
5717            )
5718            .await
5719            .unwrap();
5720        assert_eq!(val, Some("bla".into()));
5721
5722        fail_parallel::cfg(fp_registry.clone(), "write-wal-sst-io-error", "off").unwrap();
5723        kv_store.close().await.unwrap();
5724    }
5725
5726    #[tokio::test]
5727    async fn test_should_delete_without_awaiting_flush() {
5728        let fp_registry = Arc::new(FailPointRegistry::new());
5729        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5730        let path = "/tmp/test_kv_store";
5731        let kv_store = Db::builder(path, object_store.clone())
5732            .with_settings(test_db_options(0, 1024, None))
5733            .with_fp_registry(fp_registry.clone())
5734            .build()
5735            .await
5736            .unwrap();
5737
5738        kv_store
5739            .put("foo".as_bytes(), "bar".as_bytes())
5740            .await
5741            .unwrap();
5742        fail_parallel::cfg(fp_registry.clone(), "write-wal-sst-io-error", "pause").unwrap();
5743        kv_store
5744            .delete_with_options(
5745                "foo".as_bytes(),
5746                &WriteOptions {
5747                    await_durable: false,
5748                    ..Default::default()
5749                },
5750            )
5751            .await
5752            .unwrap();
5753
5754        let val = kv_store
5755            .get_with_options(
5756                "foo".as_bytes(),
5757                &ReadOptions::new().with_durability_filter(Remote),
5758            )
5759            .await
5760            .unwrap();
5761        assert_eq!(val, Some("bar".into()));
5762        let val = kv_store
5763            .get_with_options(
5764                "foo".as_bytes(),
5765                &ReadOptions::new().with_durability_filter(Memory),
5766            )
5767            .await
5768            .unwrap();
5769        assert_eq!(val, None);
5770
5771        fail_parallel::cfg(fp_registry.clone(), "write-wal-sst-io-error", "off").unwrap();
5772        kv_store.close().await.unwrap();
5773    }
5774
5775    #[tokio::test]
5776    async fn test_scan_should_read_only_committed_data() {
5777        let fp_registry = Arc::new(FailPointRegistry::new());
5778        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5779        let path = "/tmp/test_kv_store";
5780        let kv_store = Db::builder(path, object_store.clone())
5781            .with_settings(test_db_options(0, 1024, None))
5782            .with_fp_registry(fp_registry.clone())
5783            .build()
5784            .await
5785            .unwrap();
5786
5787        // Write and commit some initial data
5788        kv_store
5789            .put("key1".as_bytes(), "committed1".as_bytes())
5790            .await
5791            .unwrap();
5792        kv_store
5793            .put("key2".as_bytes(), "committed2".as_bytes())
5794            .await
5795            .unwrap();
5796        kv_store
5797            .put("key3".as_bytes(), "committed3".as_bytes())
5798            .await
5799            .unwrap();
5800
5801        // Pause WAL writes to prevent new writes from being committed
5802        fail_parallel::cfg(fp_registry.clone(), "write-wal-sst-io-error", "pause").unwrap();
5803
5804        // Write uncommitted data
5805        kv_store
5806            .put_with_options(
5807                "key2".as_bytes(),
5808                "uncommitted2".as_bytes(),
5809                &PutOptions::default(),
5810                &WriteOptions {
5811                    await_durable: false,
5812                    ..Default::default()
5813                },
5814            )
5815            .await
5816            .unwrap();
5817        kv_store
5818            .put_with_options(
5819                "key4".as_bytes(),
5820                "uncommitted4".as_bytes(),
5821                &PutOptions::default(),
5822                &WriteOptions {
5823                    await_durable: false,
5824                    ..Default::default()
5825                },
5826            )
5827            .await
5828            .unwrap();
5829
5830        // Scan with Remote filter should only see committed data
5831        let mut iter = kv_store
5832            .scan_with_options(
5833                "key1".as_bytes().."key5".as_bytes(),
5834                &ScanOptions::new().with_durability_filter(Remote),
5835            )
5836            .await
5837            .unwrap();
5838
5839        let kv = iter.next().await.unwrap().unwrap();
5840        assert_eq!(kv.key.as_ref(), b"key1");
5841        assert_eq!(kv.value.as_ref(), b"committed1");
5842
5843        let kv = iter.next().await.unwrap().unwrap();
5844        assert_eq!(kv.key.as_ref(), b"key2");
5845        assert_eq!(kv.value.as_ref(), b"committed2"); // Old committed value
5846
5847        let kv = iter.next().await.unwrap().unwrap();
5848        assert_eq!(kv.key.as_ref(), b"key3");
5849        assert_eq!(kv.value.as_ref(), b"committed3");
5850
5851        // key4 should not be visible with Remote filter
5852        assert_eq!(iter.next().await.unwrap(), None);
5853
5854        // Scan with Memory filter should see uncommitted data
5855        let mut iter = kv_store
5856            .scan_with_options(
5857                "key1".as_bytes().."key5".as_bytes(),
5858                &ScanOptions::new().with_durability_filter(Memory),
5859            )
5860            .await
5861            .unwrap();
5862
5863        let kv = iter.next().await.unwrap().unwrap();
5864        assert_eq!(kv.key.as_ref(), b"key1");
5865        assert_eq!(kv.value.as_ref(), b"committed1");
5866
5867        let kv = iter.next().await.unwrap().unwrap();
5868        assert_eq!(kv.key.as_ref(), b"key2");
5869        assert_eq!(kv.value.as_ref(), b"uncommitted2"); // New uncommitted value
5870
5871        let kv = iter.next().await.unwrap().unwrap();
5872        assert_eq!(kv.key.as_ref(), b"key3");
5873        assert_eq!(kv.value.as_ref(), b"committed3");
5874
5875        let kv = iter.next().await.unwrap().unwrap();
5876        assert_eq!(kv.key.as_ref(), b"key4");
5877        assert_eq!(kv.value.as_ref(), b"uncommitted4"); // Uncommitted key visible
5878
5879        assert_eq!(iter.next().await.unwrap(), None);
5880
5881        fail_parallel::cfg(fp_registry.clone(), "write-wal-sst-io-error", "off").unwrap();
5882        kv_store.close().await.unwrap();
5883    }
5884
5885    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
5886    async fn test_should_recover_imm_from_wal() {
5887        let fp_registry = Arc::new(FailPointRegistry::new());
5888        fail_parallel::cfg(fp_registry.clone(), "write-compacted-sst-io-error", "pause").unwrap();
5889
5890        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5891        let path = "/tmp/test_kv_store";
5892        let mut next_wal_id = 1;
5893        let db = Db::builder(path, object_store.clone())
5894            .with_settings(test_db_options(0, 128, None))
5895            .with_fp_registry(fp_registry.clone())
5896            .build()
5897            .await
5898            .unwrap();
5899        next_wal_id += 1;
5900
5901        // subscribe to status manager to get notified when db is closed
5902        let mut rx = db.inner.status_manager.subscribe();
5903
5904        // write a few keys that will result in memtable flushes
5905        let key1 = [b'a'; 32];
5906        let value1 = [b'b'; 96];
5907        db.put(key1, value1).await.unwrap();
5908        next_wal_id += 1;
5909        let key2 = [b'c'; 32];
5910        let value2 = [b'd'; 96];
5911        db.put(key2, value2).await.unwrap();
5912        next_wal_id += 1;
5913
5914        let reader = Db::builder(path, object_store.clone())
5915            .with_settings(test_db_options(0, 128, None))
5916            .with_fp_registry(fp_registry.clone())
5917            .build()
5918            .await
5919            .unwrap();
5920
5921        // increment wal id for the empty wal
5922        next_wal_id += 1;
5923
5924        // verify that we reload imm
5925        let db_state = reader.inner.state.read().view();
5926        assert_eq!(db_state.state.imm_memtable.len(), 2);
5927
5928        // one empty wal and two wals for the puts
5929        assert_eq!(
5930            db_state
5931                .state
5932                .imm_memtable
5933                .front()
5934                .unwrap()
5935                .recent_flushed_wal_id(),
5936            1 + 2
5937        );
5938        assert_eq!(
5939            db_state
5940                .state
5941                .imm_memtable
5942                .get(1)
5943                .unwrap()
5944                .recent_flushed_wal_id(),
5945            2
5946        );
5947        assert_eq!(db_state.state.core().next_wal_sst_id, next_wal_id);
5948        assert_eq!(
5949            reader.get(key1).await.unwrap(),
5950            Some(Bytes::copy_from_slice(&value1))
5951        );
5952        assert_eq!(
5953            reader.get(key2).await.unwrap(),
5954            Some(Bytes::copy_from_slice(&value2))
5955        );
5956
5957        fail_parallel::cfg(fp_registry.clone(), "write-compacted-sst-io-error", "off").unwrap();
5958
5959        // wait for the background task to report the Fenced error
5960        rx.wait_for(|status| status.close_reason.is_some())
5961            .await
5962            .unwrap();
5963        assert_eq!(
5964            db.inner.status_manager.status().close_reason,
5965            Some(crate::error::CloseReason::Fenced)
5966        );
5967
5968        db.close().await.unwrap();
5969        reader.close().await.unwrap();
5970    }
5971
5972    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
5973    async fn test_should_recover_imm_from_wal_after_flush_error() {
5974        let fp_registry = Arc::new(FailPointRegistry::new());
5975        fail_parallel::cfg(
5976            fp_registry.clone(),
5977            "write-compacted-sst-io-error",
5978            "return",
5979        )
5980        .unwrap();
5981        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5982        let path = "/tmp/test_kv_store";
5983        let db = Db::builder(path, object_store.clone())
5984            .with_settings(test_db_options(0, 4096, None))
5985            .with_fp_registry(fp_registry.clone())
5986            .build()
5987            .await
5988            .unwrap();
5989
5990        // write data to the WAL, but not enough to trigger a memtable flush
5991        let key1 = [b'a'; 32];
5992        let value1 = [b'b'; 96];
5993        let result = db.put(&key1, &value1).await;
5994        assert!(result.is_ok(), "Failed to write key1");
5995        assert_eq!(
5996            db.inner.wal_observer.status().unwrap().last_flushed_wal_id,
5997            2
5998        );
5999
6000        // Let background flush attempts fail while WAL durability preserves recovery.
6001        // expect to fail as l0 upload is blocked
6002        assert!(db.close().await.is_err());
6003
6004        // pause write-compacted-sst-io-error to prevent immutable tables
6005        // from being flushed, so we can snapshot the state when there is
6006        // an immutable table to verify its contents.
6007        fail_parallel::cfg(fp_registry.clone(), "write-compacted-sst-io-error", "pause").unwrap();
6008
6009        // reload the db
6010        let db = Db::builder(path, object_store.clone())
6011            .with_settings(test_db_options(0, 128, None))
6012            .with_fp_registry(fp_registry.clone())
6013            .build()
6014            .await
6015            .unwrap();
6016
6017        let db_state = db.inner.state.read().view();
6018
6019        // resume write-compacted-sst-io-error since we got a snapshot and
6020        // want to let the test finish.
6021        fail_parallel::cfg(fp_registry.clone(), "write-compacted-sst-io-error", "off").unwrap();
6022
6023        // verify that we reload imm
6024        assert_eq!(db_state.state.imm_memtable.len(), 1);
6025
6026        // verify that we have no L0 SSTs because memtables should have failed to flush
6027        assert_eq!(db_state.state.core().tree.l0.len(), 0);
6028        assert_eq!(db_state.state.core().tree.compacted.len(), 0);
6029
6030        // one empty wal and one wal for the first put
6031        assert_eq!(
6032            db_state
6033                .state
6034                .imm_memtable
6035                .front()
6036                .unwrap()
6037                .recent_flushed_wal_id(),
6038            1 + 1
6039        );
6040        assert!(db_state.state.imm_memtable.get(1).is_none());
6041
6042        assert_eq!(db_state.state.core().next_wal_sst_id, 4);
6043        assert_eq!(
6044            db.get(key1).await.unwrap(),
6045            Some(Bytes::copy_from_slice(&value1))
6046        );
6047    }
6048
6049    #[tokio::test]
6050    async fn test_should_fail_write_if_wal_flush_task_panics() {
6051        let fp_registry = Arc::new(FailPointRegistry::new());
6052        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6053        let path = "/tmp/test_kv_store";
6054        let db = Arc::new(
6055            Db::builder(path, object_store.clone())
6056                .with_settings(test_db_options(0, 128, None))
6057                .with_fp_registry(fp_registry.clone())
6058                .build()
6059                .await
6060                .unwrap(),
6061        );
6062
6063        fail_parallel::cfg(fp_registry.clone(), "write-wal-sst-io-error", "panic").unwrap();
6064        let result = db.put(b"foo", b"bar").await.unwrap_err();
6065        assert!(result.to_string().contains("background task panicked"));
6066    }
6067
6068    #[tokio::test]
6069    async fn test_wal_id_last_seen_should_only_reflect_flushed_wals() {
6070        let fp_registry = Arc::new(FailPointRegistry::new());
6071        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6072        let path = "/tmp/test_kv_store";
6073        let db = Arc::new(
6074            Db::builder(path, object_store.clone())
6075                .with_settings(test_db_options(0, 128, None))
6076                .with_fp_registry(fp_registry.clone())
6077                .build()
6078                .await
6079                .unwrap(),
6080        );
6081        // Trigger a WAL write and block until durable so WAL is written
6082        db.put(b"foo", b"bar").await.unwrap();
6083
6084        fail_parallel::cfg(fp_registry.clone(), "write-wal-sst-io-error", "panic").unwrap();
6085
6086        // Trigger a WAL write, which should not advance the manifest WAL ID
6087        let result = db.put(b"foo", b"bar").await.unwrap_err();
6088        assert_eq!(result.kind(), crate::ErrorKind::Closed(CloseReason::Panic));
6089        assert!(result
6090            .to_string()
6091            .contains("background task panicked. name=`wal_writer`"));
6092
6093        // Close, which flushes the latest manifest to the object store
6094        // TODO: it might make sense to return an error if there're unflushed wals in memory
6095        // on close().
6096        db.close().await.unwrap();
6097
6098        let manifest_store = ManifestStore::new(&Path::from(path), object_store.clone());
6099        let table_store = Arc::new(TableStore::new(
6100            ObjectStores::new(object_store.clone(), None),
6101            SsTableFormat::default(),
6102            path,
6103            None,
6104            TableStoreKind::Main,
6105            BlockCachePolicy::default(),
6106        ));
6107
6108        // Get the next WAL SST ID based on what's currently in the object store
6109        let next_wal_sst_id = table_store.next_wal_sst_id(0).await.unwrap();
6110
6111        // Get the latest manifest
6112        let manifest = manifest_store.read_latest_manifest().await.unwrap();
6113
6114        // Assert that the manifest reflects only the flushed WAL
6115        assert_eq!(manifest.manifest.core.next_wal_sst_id, next_wal_sst_id);
6116    }
6117
6118    #[tokio::test]
6119    async fn test_close_should_return_error_if_wal_flush_fails() {
6120        let fp_registry = Arc::new(FailPointRegistry::new());
6121        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6122        let path = "/tmp/test_kv_store";
6123
6124        let mut settings = test_db_options(0, 256, None);
6125        // Disable automatic flush
6126        settings.flush_interval = None;
6127
6128        let db = Db::builder(path, object_store.clone())
6129            .with_settings(settings)
6130            .with_fp_registry(fp_registry.clone())
6131            .build()
6132            .await
6133            .unwrap();
6134
6135        // Turn on the io error failpoint for WAL
6136        fail_parallel::cfg(fp_registry.clone(), "write-wal-sst-io-error", "return").unwrap();
6137
6138        // Write data without awaiting durable so it goes into the WAL buffer
6139        db.put_with_options(
6140            b"foo",
6141            b"bar",
6142            &PutOptions::default(),
6143            &WriteOptions {
6144                await_durable: false,
6145                ..Default::default()
6146            },
6147        )
6148        .await
6149        .unwrap();
6150
6151        // Close triggers the WAL flush, which should fail due to the io error
6152        db.close()
6153            .await
6154            .expect_err("close should error out due to WAL IO error");
6155    }
6156
6157    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
6158    async fn test_await_durable_write_returns_error_if_db_closes_before_durable() {
6159        let fp_registry = Arc::new(FailPointRegistry::new());
6160        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6161        let mut settings = test_db_options(0, 1024, None);
6162        settings.flush_interval = None;
6163        let db = Arc::new(
6164            Db::builder(
6165                "/tmp/test_await_durable_write_returns_error_if_db_closes_before_durable",
6166                object_store,
6167            )
6168            .with_settings(settings)
6169            .with_fp_registry(fp_registry.clone())
6170            .build()
6171            .await
6172            .unwrap(),
6173        );
6174        // pause writes so that we can force the write to fail on the close status before the
6175        // final flush causes the write to become durable
6176        fail_parallel::cfg(fp_registry.clone(), "write-wal-sst-io-error", "pause").unwrap();
6177        let write_db = db.clone();
6178        let write_task = tokio::spawn(async move {
6179            write_db
6180                .put_with_options(
6181                    b"foo",
6182                    b"bar",
6183                    &PutOptions::default(),
6184                    &WriteOptions::default(),
6185                )
6186                .await
6187        });
6188        tokio::time::timeout(Duration::from_secs(10), async {
6189            loop {
6190                if db
6191                    .inner
6192                    .wal_observer
6193                    .status()
6194                    .unwrap()
6195                    .buffered_wal_entries_count
6196                    == 1
6197                {
6198                    break;
6199                }
6200                tokio::task::yield_now().await;
6201            }
6202        })
6203        .await
6204        .expect("write was not buffered");
6205        let close_db = db.clone();
6206        let close_task = tokio::spawn(async move { close_db.close().await });
6207
6208        let write_error = tokio::time::timeout(Duration::from_secs(10), write_task)
6209            .await
6210            .expect("timed out waiting for write")
6211            .expect("write task panicked")
6212            .expect_err("write unexpectedly reported success");
6213
6214        assert_eq!(
6215            write_error.kind(),
6216            crate::ErrorKind::Closed(CloseReason::Clean)
6217        );
6218        fail_parallel::cfg(fp_registry, "write-wal-sst-io-error", "off").unwrap();
6219        let _ = close_task.await.unwrap();
6220    }
6221
6222    async fn do_test_should_read_compacted_db(mut options: Settings) {
6223        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6224        let path = "/tmp/test_kv_store";
6225        let should_compact_l0 = Arc::new(AtomicBool::new(false));
6226        let this_should_compact_l0 = should_compact_l0.clone();
6227        let compaction_scheduler = Arc::new(OnDemandCompactionSchedulerSupplier::new(Arc::new(
6228            move |_state| this_should_compact_l0.swap(false, Ordering::SeqCst),
6229        )));
6230
6231        let compactor_options = options.compactor_options.take();
6232        let db = Db::builder(path, object_store.clone())
6233            .with_settings(options)
6234            .with_compactor_builder(
6235                CompactorBuilder::new(path, object_store.clone())
6236                    .with_scheduler_supplier(compaction_scheduler.clone())
6237                    .with_options(compactor_options.unwrap()),
6238            )
6239            .build()
6240            .await
6241            .unwrap();
6242        let ms = ManifestStore::new(&Path::from(path), object_store.clone());
6243        let mut sm = StoredManifest::load(Arc::new(ms), Arc::new(DefaultSystemClock::new()))
6244            .await
6245            .unwrap();
6246
6247        // write enough to fill up a few l0 SSTs
6248        for i in 0..4 {
6249            db.put(&[b'a' + i; 32], &[1u8 + i; 32]).await.unwrap();
6250            db.put(&[b'm' + i; 32], &[13u8 + i; 32]).await.unwrap();
6251        }
6252        // wait for compactor to compact them
6253        wait_for_manifest_condition(
6254            &mut sm,
6255            |s| {
6256                // compact after writing values. include in loop since the on demand scheduler
6257                // only runs once per `should_compact`, and memtables might still be getting
6258                // flushed (await_durable in the put()'s above only wait for the writes to hit
6259                // the WAL before returning).
6260                should_compact_l0.store(true, Ordering::SeqCst);
6261                s.tree.last_compacted_l0_sst_view_id.is_some() && s.tree.l0.is_empty()
6262            },
6263            Duration::from_secs(10),
6264        )
6265        .await;
6266        let manifest = db.manifest();
6267        info!(
6268            "1 l0: {} {}",
6269            manifest.manifest.core.tree.l0.len(),
6270            manifest.manifest.core.tree.compacted.len()
6271        );
6272
6273        // write more l0s and wait for compaction
6274        for i in 0..4 {
6275            db.put(&[b'f' + i; 32], &[6u8 + i; 32]).await.unwrap();
6276            db.put(&[b's' + i; 32], &[19u8 + i; 32]).await.unwrap();
6277        }
6278        // wait for compactor to compact them
6279        wait_for_manifest_condition(
6280            &mut sm,
6281            |s| {
6282                // compact after writing values. include in loop since the on demand scheduler
6283                // only runs once per `should_compact`, and memtables might still be getting
6284                // flushed (await_durable in the put()'s above only wait for the writes to hit
6285                // the WAL before returning).
6286                should_compact_l0.store(true, Ordering::SeqCst);
6287                s.tree.last_compacted_l0_sst_view_id.is_some() && s.tree.l0.is_empty()
6288            },
6289            Duration::from_secs(10),
6290        )
6291        .await;
6292        let manifest = db.manifest();
6293        info!(
6294            "2 l0: {} {}",
6295            manifest.manifest.core.tree.l0.len(),
6296            manifest.manifest.core.tree.compacted.len()
6297        );
6298        // write another l0
6299        db.put(&[b'a'; 32], &[128u8; 32]).await.unwrap();
6300        db.put(&[b'm'; 32], &[129u8; 32]).await.unwrap();
6301
6302        let val = db.get([b'a'; 32]).await.unwrap();
6303        assert_eq!(val, Some(Bytes::copy_from_slice(&[128u8; 32])));
6304        let val = db.get([b'm'; 32]).await.unwrap();
6305        assert_eq!(val, Some(Bytes::copy_from_slice(&[129u8; 32])));
6306        for i in 1..4 {
6307            let manifest = db.manifest();
6308            info!(
6309                "3 l0: {} {}",
6310                manifest.manifest.core.tree.l0.len(),
6311                manifest.manifest.core.tree.compacted.len()
6312            );
6313            let val = db.get([b'a' + i; 32]).await.unwrap();
6314            assert_eq!(val, Some(Bytes::copy_from_slice(&[1u8 + i; 32])));
6315            let val = db.get([b'm' + i; 32]).await.unwrap();
6316            assert_eq!(val, Some(Bytes::copy_from_slice(&[13u8 + i; 32])));
6317        }
6318        for i in 0..4 {
6319            let val = db.get([b'f' + i; 32]).await.unwrap();
6320            assert_eq!(val, Some(Bytes::copy_from_slice(&[6u8 + i; 32])));
6321            let val = db.get([b's' + i; 32]).await.unwrap();
6322            assert_eq!(val, Some(Bytes::copy_from_slice(&[19u8 + i; 32])));
6323        }
6324        let neg_lookup = db.get(b"abc").await;
6325        assert!(neg_lookup.unwrap().is_none());
6326    }
6327
6328    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
6329    async fn test_should_read_from_compacted_db() {
6330        do_test_should_read_compacted_db(test_db_options(
6331            0,
6332            127,
6333            Some(CompactorOptions {
6334                poll_interval: Duration::from_millis(100),
6335                max_concurrent_compactions: 1,
6336                manifest_update_timeout: Duration::from_secs(300),
6337                worker: Some(CompactionWorkerOptions {
6338                    max_sst_size: 256,
6339                    compactions_poll_interval: Duration::from_millis(100),
6340                    ..Default::default()
6341                }),
6342                ..Default::default()
6343            }),
6344        ))
6345        .await;
6346    }
6347
6348    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
6349    async fn test_should_read_from_compacted_db_no_filters() {
6350        do_test_should_read_compacted_db(test_db_options(
6351            u32::MAX,
6352            127,
6353            Some(CompactorOptions {
6354                poll_interval: Duration::from_millis(100),
6355                manifest_update_timeout: Duration::from_secs(300),
6356                max_concurrent_compactions: 1,
6357                worker: Some(CompactionWorkerOptions {
6358                    max_sst_size: 256,
6359                    compactions_poll_interval: Duration::from_millis(100),
6360                    ..Default::default()
6361                }),
6362                ..Default::default()
6363            }),
6364        ))
6365        .await
6366    }
6367
6368    #[tokio::test]
6369    async fn test_db_open_should_write_empty_wal() {
6370        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6371        let path = "/tmp/test_kv_store";
6372        // assert that open db writes an empty wal.
6373        let db = Db::builder(path, object_store.clone())
6374            .with_settings(test_db_options(0, 128, None))
6375            .build()
6376            .await
6377            .unwrap();
6378        assert_eq!(db.inner.state.read().state().core().next_wal_sst_id, 2);
6379        let wal_ssts = db.inner.table_store.list_wal_ssts(..).await.unwrap();
6380        assert_eq!(wal_ssts.len(), 1);
6381        assert_eq!(wal_ssts[0].metadata.size, 0);
6382        db.put(b"1", b"1").await.unwrap();
6383        // assert that second open writes another empty wal.
6384        let db = Db::builder(path, object_store.clone())
6385            .with_settings(test_db_options(0, 128, None))
6386            .build()
6387            .await
6388            .unwrap();
6389        assert_eq!(db.inner.state.read().state().core().next_wal_sst_id, 4);
6390    }
6391
6392    #[tokio::test]
6393    async fn test_empty_wal_should_fence_old_writer() {
6394        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6395        let path = "/tmp/test_kv_store";
6396
6397        async fn do_put(db: &Db, key: &[u8], val: &[u8]) -> Result<WriteHandle, crate::Error> {
6398            db.put_with_options(
6399                key,
6400                val,
6401                &PutOptions::default(),
6402                &WriteOptions {
6403                    await_durable: true,
6404                    ..Default::default()
6405                },
6406            )
6407            .await
6408        }
6409
6410        // open db1 and assert that it can write.
6411        let db1 = Db::builder(path, object_store.clone())
6412            .with_settings(test_db_options(0, 128, None))
6413            .build()
6414            .await
6415            .unwrap();
6416        do_put(&db1, b"1", b"1").await.unwrap();
6417
6418        // open db2, causing it to write an empty wal and fence db1.
6419        let db2 = Db::builder(path, object_store.clone())
6420            .with_settings(test_db_options(0, 128, None))
6421            .build()
6422            .await
6423            .unwrap();
6424
6425        // assert that db1 can no longer write.
6426        let err = do_put(&db1, b"1", b"1").await.unwrap_err();
6427        assert_eq!(err.to_string(), "Closed error: detected newer DB client");
6428
6429        do_put(&db2, b"2", b"2").await.unwrap();
6430        assert_eq!(db2.inner.state.read().state().core().next_wal_sst_id, 5);
6431    }
6432
6433    #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
6434    async fn test_writer_paused_in_replay_wal_should_be_fenced_by_concurrent_open() {
6435        // Race we're trying to reproduce:
6436        // - W1 starts opening: claims writer_epoch=1, writes its fence WAL, then
6437        //   enters replay_wal. We pause it inside replay_wal.
6438        // - W2 starts opening: claims writer_epoch=2, writes its own fence WAL
6439        //   (above W1's), replays, and finishes init.
6440        // - W1 unpauses, finishes replay_wal, and returns its Db handle.
6441        // - W1 issues a put. This put should fail because W1's next WAL id is taken.
6442        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6443        let path = "/tmp/test_writer_paused_in_replay_wal_race";
6444        let fp_registry = Arc::new(FailPointRegistry::new());
6445
6446        // Pause replay_wal for any writer that holds writer_epoch == 1
6447        // (the condition is enforced inside the fail point body).
6448        fail_parallel::cfg(fp_registry.clone(), "replay-wal-pause", "pause").unwrap();
6449
6450        // Kick off W1 in the background. It will park inside replay_wal.
6451        // Use a long manifest poll interval on W1 so its background poller
6452        // doesn't independently observe W2's epoch bump and trip the
6453        // closed/fenced check while replay is paused.
6454        let w1_settings = {
6455            let mut s = test_db_options(0, 128, None);
6456            s.manifest_poll_interval = Duration::from_secs(600);
6457            s
6458        };
6459        let w1_handle = {
6460            let object_store = object_store.clone();
6461            let fp_registry = fp_registry.clone();
6462            tokio::spawn(async move {
6463                Db::builder(path, object_store)
6464                    .with_settings(w1_settings)
6465                    .with_fp_registry(fp_registry)
6466                    .build()
6467                    .await
6468            })
6469        };
6470
6471        // Wait for W1 to write its fence WAL — at that point W1 has finished
6472        // fence_writers and has either entered or is about to enter the paused
6473        // replay_wal call.
6474        let probe_table_store = Arc::new(TableStore::new(
6475            ObjectStores::new(object_store.clone(), None),
6476            SsTableFormat::default(),
6477            path,
6478            None,
6479            TableStoreKind::Main,
6480            BlockCachePolicy::default(),
6481        ));
6482        let mut w1_paused = false;
6483        for _ in 0..600 {
6484            let wals = probe_table_store.list_wal_ssts(..).await.unwrap();
6485            if !wals.is_empty() {
6486                w1_paused = true;
6487                break;
6488            }
6489            tokio::time::sleep(Duration::from_millis(10)).await;
6490        }
6491        assert!(w1_paused, "W1 did not reach replay_wal pause in time");
6492        // Small additional wait for W1 to transition from fence_writers into
6493        // the paused replay_wal block.
6494        tokio::time::sleep(Duration::from_millis(100)).await;
6495
6496        // While W1 is paused, open W2. W2's epoch is 2, so replay_wal is not
6497        // paused for W2. W2 writes its own fence WAL above W1's and finishes
6498        // init.
6499        let db2 = Db::builder(path, object_store.clone())
6500            .with_settings(test_db_options(0, 128, None))
6501            .with_fp_registry(fp_registry.clone())
6502            .build()
6503            .await
6504            .unwrap();
6505        assert_eq!(
6506            db2.inner.state.read().state().manifest.value.writer_epoch,
6507            2
6508        );
6509
6510        // Release W1. It finishes replay_wal and returns a Db handle.
6511        fail_parallel::cfg(fp_registry.clone(), "replay-wal-pause", "off").unwrap();
6512        let db1 = w1_handle.await.unwrap().unwrap();
6513        assert_eq!(
6514            db1.inner.state.read().state().manifest.value.writer_epoch,
6515            1
6516        );
6517
6518        // W1's put should fail because its now fenced
6519        let result = db1
6520            .put_with_options(
6521                b"w1",
6522                b"value",
6523                &PutOptions::default(),
6524                &WriteOptions {
6525                    await_durable: true,
6526                    ..Default::default()
6527                },
6528            )
6529            .await;
6530        assert!(result.is_err());
6531    }
6532
6533    async fn wait_for_wal_sst_count(table_store: &TableStore, min_count: usize, context: &str) {
6534        for _ in 0..6000 {
6535            let wals = table_store.list_wal_ssts(..).await.unwrap();
6536            if wals.len() >= min_count {
6537                return;
6538            }
6539            tokio::time::sleep(Duration::from_millis(10)).await;
6540        }
6541        panic!("{context}");
6542    }
6543
6544    #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
6545    async fn wal_replay_not_found_should_be_fenced_when_writer_epoch_advanced() {
6546        let base_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6547        let gated_store = Arc::new(GatedObjectStore::new(base_store.clone()));
6548        let gated_object_store: Arc<dyn ObjectStore> = gated_store.clone();
6549        let path = "/tmp/wal_replay_not_found_should_be_fenced_when_writer_epoch_advanced";
6550        let fp_registry = Arc::new(FailPointRegistry::new());
6551
6552        fail_parallel::cfg(fp_registry.clone(), "replay-wal-pause", "pause").unwrap();
6553
6554        let w1_settings = {
6555            let mut s = test_db_options(0, 128, None);
6556            s.manifest_poll_interval = Duration::from_secs(600);
6557            s
6558        };
6559        let w1_handle = {
6560            let object_store = gated_object_store.clone();
6561            let fp_registry = fp_registry.clone();
6562            tokio::spawn(async move {
6563                Db::builder(path, object_store)
6564                    .with_settings(w1_settings)
6565                    .with_fp_registry(fp_registry)
6566                    .build()
6567                    .await
6568            })
6569        };
6570
6571        let probe_table_store = TableStore::new(
6572            ObjectStores::new(base_store.clone(), None),
6573            SsTableFormat::default(),
6574            path,
6575            None,
6576            TableStoreKind::Main,
6577            BlockCachePolicy::default(),
6578        );
6579        wait_for_wal_sst_count(
6580            &probe_table_store,
6581            1,
6582            "W1 did not write its fence WAL in time",
6583        )
6584        .await;
6585
6586        let head_arrivals_before = gated_store.head_gate.arrivals();
6587        gated_store.head_gate.close();
6588        fail_parallel::cfg(fp_registry.clone(), "replay-wal-pause", "off").unwrap();
6589        gated_store
6590            .head_gate
6591            .wait_for_arrivals(head_arrivals_before + 1)
6592            .await;
6593
6594        let db2 = Db::builder(path, base_store.clone())
6595            .with_settings(test_db_options(0, 128, None))
6596            .with_fp_registry(fp_registry.clone())
6597            .build()
6598            .await
6599            .unwrap();
6600        assert_eq!(
6601            db2.inner.state.read().state().manifest.value.writer_epoch,
6602            2
6603        );
6604
6605        probe_table_store
6606            .delete_sst(&SsTableId::Wal(1))
6607            .await
6608            .unwrap();
6609        gated_store.head_gate.release();
6610
6611        let err = match w1_handle.await.unwrap() {
6612            Ok(_) => panic!("expected W1 open to fail"),
6613            Err(err) => err,
6614        };
6615        assert!(
6616            matches!(err.kind(), crate::ErrorKind::Closed(CloseReason::Fenced)),
6617            "expected fenced error, got {err:?}"
6618        );
6619
6620        db2.close().await.unwrap();
6621    }
6622
6623    #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
6624    async fn wal_replay_not_found_should_remain_not_found_when_writer_epoch_unchanged() {
6625        let base_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6626        let gated_store = Arc::new(GatedObjectStore::new(base_store.clone()));
6627        let gated_object_store: Arc<dyn ObjectStore> = gated_store.clone();
6628        let path = "/tmp/wal_replay_not_found_should_remain_not_found_when_writer_epoch_unchanged";
6629        let fp_registry = Arc::new(FailPointRegistry::new());
6630
6631        fail_parallel::cfg(fp_registry.clone(), "replay-wal-pause", "pause").unwrap();
6632
6633        let w1_settings = {
6634            let mut s = test_db_options(0, 128, None);
6635            s.manifest_poll_interval = Duration::from_secs(600);
6636            s
6637        };
6638        let w1_handle = {
6639            let object_store = gated_object_store.clone();
6640            let fp_registry = fp_registry.clone();
6641            tokio::spawn(async move {
6642                Db::builder(path, object_store)
6643                    .with_settings(w1_settings)
6644                    .with_fp_registry(fp_registry)
6645                    .build()
6646                    .await
6647            })
6648        };
6649
6650        let probe_table_store = TableStore::new(
6651            ObjectStores::new(base_store.clone(), None),
6652            SsTableFormat::default(),
6653            path,
6654            None,
6655            TableStoreKind::Main,
6656            BlockCachePolicy::default(),
6657        );
6658        wait_for_wal_sst_count(
6659            &probe_table_store,
6660            1,
6661            "W1 did not write its fence WAL in time",
6662        )
6663        .await;
6664
6665        let head_arrivals_before = gated_store.head_gate.arrivals();
6666        gated_store.head_gate.close();
6667        fail_parallel::cfg(fp_registry.clone(), "replay-wal-pause", "off").unwrap();
6668        gated_store
6669            .head_gate
6670            .wait_for_arrivals(head_arrivals_before + 1)
6671            .await;
6672
6673        probe_table_store
6674            .delete_sst(&SsTableId::Wal(1))
6675            .await
6676            .unwrap();
6677        gated_store.head_gate.release();
6678
6679        let err = match w1_handle.await.unwrap() {
6680            Ok(_) => panic!("expected W1 open to fail"),
6681            Err(err) => err,
6682        };
6683        assert_eq!(err.kind(), crate::ErrorKind::Data);
6684    }
6685
6686    #[tokio::test]
6687    async fn test_invalid_clock_progression() {
6688        // Given:
6689        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6690        let path = "/tmp/test_kv_store";
6691
6692        let clock = Arc::new(MockSystemClock::new());
6693        let db = Db::builder(path, object_store.clone())
6694            .with_settings(test_db_options(0, 128, None))
6695            .with_system_clock(clock.clone())
6696            .build()
6697            .await
6698            .unwrap();
6699
6700        // When:
6701        // put with time = 10
6702        clock.set(10);
6703        db.put_with_options(
6704            b"1",
6705            b"1",
6706            &PutOptions::default(),
6707            &WriteOptions {
6708                await_durable: false,
6709                ..Default::default()
6710            },
6711        )
6712        .await
6713        .unwrap();
6714
6715        // Then:
6716        // put with time goes backwards, should fail
6717        clock.set(5);
6718        match db
6719            .put_with_options(
6720                b"1",
6721                b"1",
6722                &PutOptions::default(),
6723                &WriteOptions {
6724                    await_durable: false, ..Default::default()
6725                },
6726            )
6727            .await
6728        {
6729            Ok(_) => panic!("expected an error on inserting backwards time"),
6730            Err(e) => assert_eq!(e.to_string(), "Invalid error: invalid clock tick, must be monotonic. last_tick=`10`, next_tick=`5`"),
6731        }
6732    }
6733
6734    #[tokio::test]
6735    async fn test_invalid_clock_progression_across_db_instances() {
6736        // Given:
6737        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6738        let path = "/tmp/test_kv_store";
6739
6740        let clock = Arc::new(MockSystemClock::new());
6741        let db = Db::builder(path, object_store.clone())
6742            .with_settings(test_db_options(0, 128, None))
6743            .with_system_clock(clock.clone())
6744            .build()
6745            .await
6746            .unwrap();
6747
6748        // When:
6749        // put with time = 10
6750        clock.set(10);
6751        db.put_with_options(
6752            b"1",
6753            b"1",
6754            &PutOptions::default(),
6755            &WriteOptions {
6756                await_durable: false,
6757                ..Default::default()
6758            },
6759        )
6760        .await
6761        .unwrap();
6762        db.flush().await.unwrap();
6763
6764        let db2 = Db::builder(path, object_store.clone())
6765            .with_settings(test_db_options(0, 128, None))
6766            .with_system_clock(clock.clone())
6767            .build()
6768            .await
6769            .unwrap();
6770        clock.set(5);
6771        match db2
6772            .put_with_options(
6773                b"1",
6774                b"1",
6775                &PutOptions::default(),
6776                &WriteOptions {
6777                    await_durable: false, ..Default::default()
6778                },
6779            )
6780            .await
6781        {
6782            Ok(_) => panic!("expected an error on inserting backwards time"),
6783            Err(e) => assert_eq!(e.to_string(), "Invalid error: invalid clock tick, must be monotonic. last_tick=`10`, next_tick=`5`"),
6784        }
6785    }
6786
6787    #[tokio::test]
6788    #[cfg(feature = "wal_disable")]
6789    async fn should_flush_all_memtables_when_wal_disabled() {
6790        // Given:
6791        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6792        let path = "/tmp/test_kv_store";
6793
6794        let db_options = Settings {
6795            wal_enabled: false,
6796            flush_interval: Some(Duration::from_secs(10)),
6797            ..Settings::default()
6798        };
6799
6800        let db = Db::builder(path, object_store.clone())
6801            .with_settings(db_options.clone())
6802            .build()
6803            .await
6804            .unwrap();
6805
6806        let mut rng = proptest_util::rng::new_test_rng(None);
6807        let table = sample::table(&mut rng, 1000, 5);
6808        test_utils::seed_database(&db, &table, false).await.unwrap();
6809        db.flush().await.unwrap();
6810
6811        // When: reopen the database without closing the old instance
6812        let reopened_db = Db::builder(path, object_store.clone())
6813            .with_settings(db_options.clone())
6814            .build()
6815            .await
6816            .unwrap();
6817
6818        // Then:
6819        assert_records_in_range(
6820            &table,
6821            &reopened_db,
6822            &ScanOptions::default(),
6823            BytesRange::from(..),
6824        )
6825        .await
6826    }
6827
6828    #[tokio::test]
6829    async fn test_recover_clock_tick_from_wal() {
6830        let fp_registry = Arc::new(FailPointRegistry::new());
6831        // Block L0 uploads so the data remains only in the WAL. The
6832        // uploader gives up on shutdown when the WAL is enabled, so
6833        // close() will complete without flushing memtables to L0.
6834        fail_parallel::cfg(
6835            fp_registry.clone(),
6836            "write-compacted-sst-io-error",
6837            "return",
6838        )
6839        .unwrap();
6840        let clock = Arc::new(MockSystemClock::new());
6841        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6842        let path = "/tmp/test_kv_store";
6843
6844        let db = Db::builder(path, object_store.clone())
6845            .with_settings(test_db_options(0, 1024, None))
6846            .with_system_clock(clock.clone())
6847            .with_fp_registry(fp_registry.clone())
6848            .build()
6849            .await
6850            .unwrap();
6851
6852        clock.set(10);
6853        db.put_with_options(
6854            &[b'a'; 4],
6855            &[b'j'; 8],
6856            &PutOptions::default(),
6857            &WriteOptions {
6858                await_durable: false,
6859                ..Default::default()
6860            },
6861        )
6862        .await
6863        .expect("write batch failed");
6864        clock.set(11);
6865        db.put_with_options(
6866            &[b'b'; 4],
6867            &[b'k'; 8],
6868            &PutOptions::default(),
6869            &WriteOptions {
6870                await_durable: false,
6871                ..Default::default()
6872            },
6873        )
6874        .await
6875        .expect("write batch failed");
6876
6877        db.flush().await.unwrap();
6878        // expect to fail as l0 upload is blocked
6879        assert!(db.close().await.is_err());
6880
6881        // check the last_l0_clock_tick persisted in the manifest, it should be
6882        // i64::MIN because no WAL SST has yet made its way into L0
6883        let manifest_store = Arc::new(ManifestStore::new(&Path::from(path), object_store.clone()));
6884        let stored_manifest =
6885            StoredManifest::load(manifest_store, Arc::new(DefaultSystemClock::new()))
6886                .await
6887                .unwrap();
6888        let db_state = stored_manifest.db_state();
6889        let last_clock_tick = db_state.last_l0_clock_tick;
6890        assert_eq!(last_clock_tick, i64::MIN);
6891
6892        // Disable the failpoint so the restored DB can flush normally.
6893        fail_parallel::cfg(fp_registry.clone(), "write-compacted-sst-io-error", "off").unwrap();
6894
6895        let clock = Arc::new(MockSystemClock::new());
6896        let db = Db::builder(path, object_store.clone())
6897            .with_settings(test_db_options(0, 1024, None))
6898            .with_system_clock(clock.clone())
6899            .with_fp_registry(fp_registry.clone())
6900            .build()
6901            .await
6902            .unwrap();
6903
6904        assert_eq!(db.inner.mono_clock.last_tick.load(Ordering::SeqCst), 11);
6905    }
6906
6907    #[tokio::test]
6908    async fn test_should_update_manifest_clock_tick_on_l0_flush() {
6909        let clock = Arc::new(MockSystemClock::new());
6910        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6911        let path = "/tmp/test_kv_store";
6912
6913        let db = Db::builder(path, object_store.clone())
6914            .with_settings(test_db_options(0, 32, None))
6915            .with_system_clock(clock.clone())
6916            .build()
6917            .await
6918            .unwrap();
6919
6920        // this will exceed the l0_sst_size_bytes, meaning a clean shutdown
6921        // will update the manifest
6922        clock.set(10);
6923        db.put(&[b'a'; 4], &[b'j'; 8])
6924            .await
6925            .expect("write batch failed");
6926        clock.set(11);
6927        db.put(&[b'b'; 4], &[b'k'; 8])
6928            .await
6929            .expect("write batch failed");
6930
6931        // close the db to flush the manifest
6932        db.flush().await.unwrap();
6933        db.close().await.unwrap();
6934
6935        // check the last_clock_tick persisted in the manifest, it should be
6936        // i64::MIN because no WAL SST has yet made its way into L0
6937        let manifest_store = Arc::new(ManifestStore::new(&Path::from(path), object_store.clone()));
6938        let stored_manifest =
6939            StoredManifest::load(manifest_store, Arc::new(DefaultSystemClock::new()))
6940                .await
6941                .unwrap();
6942        let db_state = stored_manifest.db_state();
6943        let last_clock_tick = db_state.last_l0_clock_tick;
6944        assert_eq!(last_clock_tick, 11);
6945    }
6946
6947    #[tokio::test]
6948    #[cfg(feature = "wal_disable")]
6949    async fn test_recover_clock_tick_from_manifest() {
6950        let clock = Arc::new(MockSystemClock::new());
6951        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6952        let path = "/tmp/test_kv_store";
6953        let mut options = test_db_options(0, 32, None);
6954        options.wal_enabled = false;
6955
6956        let db = Db::builder(path, object_store.clone())
6957            .with_settings(options)
6958            .with_system_clock(clock.clone())
6959            .build()
6960            .await
6961            .unwrap();
6962
6963        clock.set(10);
6964        db.put(&[b'a'; 4], &[b'j'; 28])
6965            .await
6966            .expect("write batch failed");
6967        clock.set(11);
6968        db.put(&[b'b'; 4], &[b'k'; 28])
6969            .await
6970            .expect("write batch failed");
6971
6972        // close the db to flush the manifest
6973        db.flush().await.unwrap();
6974        db.close().await.unwrap();
6975
6976        let clock = Arc::new(MockSystemClock::new());
6977        let mut options = test_db_options(0, 32, None);
6978        options.wal_enabled = false;
6979        let db = Db::builder(path, object_store.clone())
6980            .with_settings(options)
6981            .with_system_clock(clock.clone())
6982            .build()
6983            .await
6984            .unwrap();
6985
6986        assert_eq!(db.inner.mono_clock.last_tick.load(Ordering::SeqCst), 11);
6987    }
6988
6989    #[tokio::test]
6990    async fn test_put_get_reopen_delete_with_separate_wal_store() {
6991        async fn count_ssts_in(store: &Arc<InMemory>) -> usize {
6992            store
6993                .list(None)
6994                .filter(|r| {
6995                    future::ready(
6996                        r.as_ref()
6997                            .unwrap()
6998                            .location
6999                            .extension()
7000                            .unwrap()
7001                            .to_lowercase()
7002                            == "sst",
7003                    )
7004                })
7005                .count()
7006                .await
7007        }
7008
7009        let fp_registry = Arc::new(FailPointRegistry::new());
7010
7011        let main_object_store = Arc::new(InMemory::new());
7012        let wal_object_store = Arc::new(InMemory::new());
7013        let kv_store = Db::builder("/tmp/test_kv_store", main_object_store.clone())
7014            .with_settings(test_db_options(0, 1024, None))
7015            .with_wal_object_store(wal_object_store.clone())
7016            .with_fp_registry(fp_registry.clone())
7017            .build()
7018            .await
7019            .unwrap();
7020        assert_eq!(count_ssts_in(&main_object_store).await, 0);
7021        assert_eq!(count_ssts_in(&wal_object_store).await, 1);
7022
7023        let key = b"test_key";
7024        let value = b"test_value";
7025
7026        // pause memtable flushes
7027        fail_parallel::cfg(fp_registry.clone(), "write-compacted-sst-io-error", "pause").unwrap();
7028        kv_store.put(key, value).await.unwrap();
7029        kv_store.flush().await.unwrap();
7030        assert_eq!(count_ssts_in(&main_object_store).await, 0);
7031        assert_eq!(count_ssts_in(&wal_object_store).await, 2);
7032        assert_eq!(
7033            kv_store.get(key).await.unwrap(),
7034            Some(Bytes::from_static(value))
7035        );
7036        // resume memtable flushes
7037        fail_parallel::cfg(fp_registry.clone(), "write-compacted-sst-io-error", "off").unwrap();
7038
7039        // write some data to force L0 SST creation
7040        let mut batch = WriteBatch::default();
7041        for i in 0u32..128 {
7042            batch.put(i.to_be_bytes(), i.to_be_bytes());
7043        }
7044        kv_store.write(batch).await.unwrap();
7045        kv_store.flush().await.unwrap();
7046        assert_eq!(count_ssts_in(&main_object_store).await, 1);
7047        assert_eq!(count_ssts_in(&wal_object_store).await, 3);
7048        assert_eq!(
7049            kv_store.get(key).await.unwrap(),
7050            Some(Bytes::from_static(value))
7051        );
7052
7053        kv_store.close().await.unwrap();
7054        assert_eq!(count_ssts_in(&wal_object_store).await, 3);
7055
7056        let kv_store = Db::builder("/tmp/test_kv_store", main_object_store)
7057            .with_settings(test_db_options(0, 1024, None))
7058            .with_wal_object_store(wal_object_store.clone())
7059            .build()
7060            .await
7061            .unwrap();
7062
7063        assert_eq!(
7064            kv_store.get(key).await.unwrap(),
7065            Some(Bytes::from_static(value))
7066        );
7067
7068        kv_store.delete(key).await.unwrap();
7069        assert_eq!(None, kv_store.get(key).await.unwrap());
7070
7071        kv_store.close().await.unwrap();
7072    }
7073
7074    #[tokio::test]
7075    async fn test_wal_store_reconfiguration_fails() {
7076        let object_store = Arc::new(InMemory::new());
7077        let wal_object_store = Arc::new(InMemory::new());
7078
7079        let kv_store = Db::builder("/tmp/test_kv_store", object_store.clone())
7080            .with_settings(test_db_options(0, 1024, None))
7081            .with_wal_object_store(wal_object_store.clone())
7082            .build()
7083            .await
7084            .unwrap();
7085        kv_store.close().await.unwrap();
7086
7087        let result = Db::builder("/tmp/test_kv_store", object_store)
7088            .with_settings(test_db_options(0, 1024, None))
7089            .build()
7090            .await;
7091        match result {
7092            Err(err) => {
7093                assert!(err.to_string().contains("unsupported"));
7094            }
7095            _ => panic!("expected Unsupported error"),
7096        }
7097    }
7098
7099    #[test]
7100    fn test_write_option_defaults() {
7101        // This is a regression test for a bug where the defaults for WriteOptions were not being
7102        // set correctly due to visibility issues.
7103        let write_options = WriteOptions::default();
7104        assert!(write_options.await_durable);
7105    }
7106
7107    #[tokio::test]
7108    #[cfg(feature = "zstd")]
7109    async fn test_compression_overflow_bug() {
7110        // This test reproduces the bug reported in https://github.com/slatedb/slatedb/issues/555
7111        // where re-opening a DB using zstd compression causes "attempt to subtract with overflow"
7112        // error in Block::decode
7113
7114        use crate::config::CompressionCodec;
7115        use std::str::FromStr;
7116
7117        // Create and load initial database
7118        let os: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
7119        let compress = CompressionCodec::from_str("zstd").unwrap();
7120        let db_builder = Db::builder("/tmp/test_kv_store", os.clone()).with_settings(Settings {
7121            compression_codec: Some(compress),
7122            ..Settings::default()
7123        });
7124        let db = db_builder.build().await.unwrap();
7125
7126        for i in 0..1000 {
7127            let key = format!("k{}", i);
7128            let value = format!("{}{}", "v".repeat(i), i);
7129            let put_option = PutOptions::default();
7130            let write_option = WriteOptions {
7131                await_durable: false,
7132                ..Default::default()
7133            };
7134            db.put_with_options(key.as_bytes(), value.clone(), &put_option, &write_option)
7135                .await
7136                .expect("failed to put");
7137        }
7138        db.flush().await.expect("flush failed");
7139        db.close().await.expect("failed to close db");
7140
7141        // Reload DB and read a value to trigger error
7142        let db_builder = Db::builder("/tmp/test_kv_store", os.clone()).with_settings(Settings {
7143            compression_codec: Some(compress),
7144            ..Settings::default()
7145        });
7146        let db = db_builder.build().await.unwrap();
7147        let v = db.get("k1").await.expect("get failed").unwrap();
7148        assert_eq!(v.as_ref(), b"v1");
7149
7150        db.close().await.expect("failed to close db");
7151    }
7152
7153    async fn wait_for_manifest_condition(
7154        sm: &mut StoredManifest,
7155        cond: impl Fn(&ManifestCore) -> bool,
7156        timeout: Duration,
7157    ) -> ManifestCore {
7158        let start = tokio::time::Instant::now();
7159        while start.elapsed() < timeout {
7160            let manifest = sm.refresh().await.unwrap();
7161            if cond(&manifest.core) {
7162                return manifest.core.clone();
7163            }
7164            tokio::time::sleep(Duration::from_millis(10)).await;
7165        }
7166        panic!("manifest condition took longer than timeout")
7167    }
7168
7169    fn test_db_options(
7170        min_filter_keys: u32,
7171        l0_sst_size_bytes: usize,
7172        compactor_options: Option<CompactorOptions>,
7173    ) -> Settings {
7174        test_db_options_with_ttl(min_filter_keys, l0_sst_size_bytes, compactor_options, None)
7175    }
7176
7177    /// Compactor options with fast poll intervals. With the defaults (5s
7178    /// coordinator poll + 5s worker claim poll, jittered up to 1.5x), a
7179    /// compaction can take longer to land in the manifest than the 10s the
7180    /// tests using this helper wait for one.
7181    fn fast_compactor_options() -> CompactorOptions {
7182        CompactorOptions {
7183            poll_interval: Duration::from_millis(100),
7184            worker: Some(CompactionWorkerOptions {
7185                compactions_poll_interval: Duration::from_millis(100),
7186                ..Default::default()
7187            }),
7188            ..Default::default()
7189        }
7190    }
7191
7192    fn test_db_options_with_ttl(
7193        min_filter_keys: u32,
7194        l0_sst_size_bytes: usize,
7195        compactor_options: Option<CompactorOptions>,
7196        ttl: Option<u64>,
7197    ) -> Settings {
7198        Settings {
7199            flush_interval: Some(Duration::from_millis(100)),
7200            #[cfg(feature = "wal_disable")]
7201            wal_enabled: true,
7202            manifest_poll_interval: Duration::from_millis(100),
7203            manifest_update_timeout: Duration::from_secs(300),
7204            max_unflushed_bytes: 134_217_728,
7205            l0_max_ssts: 8,
7206            l0_max_ssts_per_key: 8,
7207            l0_flush_parallelism: 1,
7208            min_filter_keys,
7209            l0_sst_size_bytes,
7210            max_wal_flushes_before_l0_flush: 4096,
7211            compactor_options,
7212            compression_codec: None,
7213            object_store_cache_options: ObjectStoreCacheOptions::default(),
7214            garbage_collector_options: None,
7215            metric_level: MetricLevel::default(),
7216            default_ttl: ttl,
7217            object_store_max_retries: None,
7218            block_format: None,
7219        }
7220    }
7221
7222    #[tokio::test]
7223    async fn test_snapshot_basic_functionality() {
7224        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
7225        let db = Db::open("test_db", object_store).await.unwrap();
7226
7227        // Write some data
7228        db.put(b"key1", b"value1").await.unwrap();
7229        db.put(b"key2", b"value2").await.unwrap();
7230
7231        // Create a snapshot
7232        let snapshot = db.snapshot().await.unwrap();
7233
7234        // Verify snapshot can read the data
7235        assert_eq!(
7236            snapshot.get(b"key1").await.unwrap(),
7237            Some(Bytes::from(b"value1".as_ref()))
7238        );
7239        assert_eq!(
7240            snapshot.get(b"key2").await.unwrap(),
7241            Some(Bytes::from(b"value2".as_ref()))
7242        );
7243
7244        // Write more data to the original database
7245        db.put(b"key3", b"value3").await.unwrap();
7246
7247        // Snapshot should not see the new data
7248        assert_eq!(snapshot.get(b"key3").await.unwrap(), None);
7249
7250        // Original database should see the new data
7251        assert_eq!(
7252            db.get(b"key3").await.unwrap(),
7253            Some(Bytes::from(b"value3".as_ref()))
7254        );
7255    }
7256
7257    #[tokio::test]
7258    async fn test_recent_snapshot_min_seq_monotonic() {
7259        use crate::oracle::Oracle;
7260
7261        let path = "/tmp/test_recent_snapshot_min_seq_monotonic";
7262        let object_store = Arc::new(InMemory::new());
7263        let settings = Settings {
7264            l0_sst_size_bytes: 2 * 1024,   // Smaller to trigger flush more easily
7265            max_unflushed_bytes: 4 * 1024, // Smaller to trigger flush more easily
7266            min_filter_keys: 0,
7267            flush_interval: Some(Duration::from_millis(100)),
7268            ..Default::default()
7269        };
7270
7271        let db = Db::builder(path, object_store)
7272            .with_settings(settings)
7273            .build()
7274            .await
7275            .unwrap();
7276
7277        // Initial state: recent_snapshot_min_seq should be 0
7278        {
7279            let state = db.inner.state.read();
7280            assert_eq!(state.state().core().recent_snapshot_min_seq, 0);
7281        }
7282
7283        // Test 1: Force memtable flush to update recent_snapshot_min_seq
7284        db.put(b"key1", b"value1").await.unwrap();
7285        db.inner.flush_memtables(FlushTarget::All).await.unwrap();
7286
7287        {
7288            let state = db.inner.state.read();
7289            let recent_min_seq = state.state().core().recent_snapshot_min_seq;
7290            // After flush, recent_snapshot_min_seq should be updated (no active snapshots)
7291            assert!(
7292                recent_min_seq > 0,
7293                "recent_snapshot_min_seq should be > 0 after flush"
7294            );
7295        }
7296
7297        // Test 2: With active snapshots
7298        let _snapshot = db.snapshot().await.unwrap();
7299        let snapshot_seq = db.inner.oracle.last_committed_seq();
7300
7301        // Write more data and force flush
7302        db.put(b"key2", b"value2").await.unwrap();
7303        db.inner.flush_memtables(FlushTarget::All).await.unwrap();
7304
7305        // Verify that snapshot_manager.min_active_seq() returns the snapshot seq
7306        let min_active_seq = db.inner.snapshot_manager.min_active_seq();
7307        assert!(min_active_seq.is_some());
7308        assert_eq!(min_active_seq.unwrap(), snapshot_seq);
7309
7310        {
7311            let state = db.inner.state.read();
7312            let recent_min_seq = state.state().core().recent_snapshot_min_seq;
7313            assert_eq!(
7314                recent_min_seq,
7315                min_active_seq.unwrap(),
7316                "recent_snapshot_min_seq should equal snapshot_manager.min_active_seq() after flush"
7317            );
7318        }
7319
7320        // Test 3: Drop snapshot and check update
7321        drop(_snapshot);
7322
7323        // Write more data and flush to trigger update
7324        db.put(b"key3", b"value3").await.unwrap();
7325        db.inner.flush_memtables(FlushTarget::All).await.unwrap();
7326
7327        // Now recent_snapshot_min_seq should be updated to higher value (no active snapshots)
7328        {
7329            let state = db.inner.state.read();
7330            let recent_min_seq = state.state().core().recent_snapshot_min_seq;
7331            let last_l0_seq = state.state().core().last_l0_seq;
7332
7333            // Should be updated to last_l0_seq since no active snapshots
7334            assert_eq!(
7335                recent_min_seq, last_l0_seq,
7336                "recent_snapshot_min_seq should equal last_l0_seq when no active snapshots"
7337            );
7338            assert!(recent_min_seq > snapshot_seq);
7339        }
7340    }
7341
7342    #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
7343    async fn test_compaction_resume_loses_merge_operands_after_snapshot_retention_advances() {
7344        let path =
7345            "/tmp/test_compaction_resume_loses_merge_operands_after_snapshot_retention_advances";
7346        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
7347        let fp_registry = Arc::new(FailPointRegistry::new());
7348        let should_compact = Arc::new(AtomicBool::new(false));
7349        let compactor_options = CompactorOptions {
7350            poll_interval: Duration::from_millis(10),
7351            commit_compacted_interval: Duration::from_millis(10),
7352            worker: Some(CompactionWorkerOptions {
7353                compactions_poll_interval: Duration::from_millis(10),
7354                heartbeat_interval: Duration::from_millis(10),
7355                max_sst_size: 1,
7356                ..Default::default()
7357            }),
7358            ..Default::default()
7359        };
7360        let settings_without_compactor = test_db_options(0, 1024, None);
7361
7362        // The compactor's SST I/O runs through a gated store so the test can
7363        // freeze the job after its first output SSTs upload. Manifest and
7364        // `.compactions` I/O use the ungated store passed to `Db::builder`,
7365        // so the worker's heartbeats keep flowing while the job is frozen.
7366        let gated = Arc::new(crate::test_utils::GatedObjectStore::new(
7367            object_store.clone(),
7368        ));
7369        let gated_store: Arc<dyn ObjectStore> = gated.clone();
7370
7371        let db = Db::builder(path, object_store.clone())
7372            .with_settings(settings_without_compactor.clone())
7373            .with_sst_block_size(SstBlockSize::Other(1))
7374            .with_fp_registry(fp_registry.clone())
7375            .with_merge_operator(Arc::new(StringConcatMergeOperator))
7376            .with_compactor_builder(
7377                CompactorBuilder::new(path, gated_store)
7378                    .with_options(compactor_options.clone())
7379                    .with_scheduler_supplier(Arc::new(OnDemandCompactionSchedulerSupplier::new({
7380                        let should_compact = should_compact.clone();
7381                        Arc::new(move |_| should_compact.load(Ordering::SeqCst))
7382                    }))),
7383            )
7384            .build()
7385            .await
7386            .unwrap();
7387
7388        db.merge(b"k", b"1").await.unwrap();
7389        let snapshot = db.snapshot().await.unwrap();
7390        let snapshot_seq = db.inner.oracle.last_committed_seq();
7391        db.flush().await.unwrap();
7392
7393        for operand in [b"2", b"3", b"4", b"5", b"6"] {
7394            db.merge(b"k", operand).await.unwrap();
7395            db.flush().await.unwrap();
7396        }
7397
7398        let manifest_store = Arc::new(ManifestStore::new(&Path::from(path), object_store.clone()));
7399        let mut stored_manifest =
7400            StoredManifest::load(manifest_store.clone(), Arc::new(DefaultSystemClock::new()))
7401                .await
7402                .unwrap();
7403        let staged = wait_for_manifest_condition(
7404            &mut stored_manifest,
7405            |core| core.last_l0_seq >= 6 && core.recent_snapshot_min_seq == snapshot_seq,
7406            Duration::from_secs(10),
7407        )
7408        .await;
7409        assert!(
7410            staged.tree.l0.len() > 1,
7411            "the test requires multiple L0s so compaction has work to resume"
7412        );
7413
7414        // Fence the worker on the first heartbeat that publishes progress
7415        // carrying an output SST. This leaves a partially-complete compaction
7416        // (its first output SST plus the retention_min_seq captured while
7417        // the snapshot was live) persisted for the resumed attempt.
7418        fail_parallel::cfg(
7419            fp_registry.clone(),
7420            "compactor-heartbeat-after-output-sst",
7421            "return",
7422        )
7423        .unwrap();
7424
7425        let mut status_rx = db.subscribe();
7426
7427        // Admit exactly one output SST upload through the closed gate, so the
7428        // job reports progress with one SST and then freezes on its next
7429        // upload — it cannot finish before the fencing heartbeat observes
7430        // that progress.
7431        let baseline_puts = gated.put_opts_gate.arrivals();
7432        gated.put_opts_gate.close();
7433        should_compact.store(true, Ordering::SeqCst);
7434        tokio::time::timeout(
7435            Duration::from_secs(10),
7436            gated.put_opts_gate.wait_for_arrivals(baseline_puts + 1),
7437        )
7438        .await
7439        .expect("compaction should upload output SSTs");
7440        gated.put_opts_gate.admit(1);
7441
7442        tokio::time::timeout(Duration::from_secs(10), async {
7443            loop {
7444                match status_rx.borrow().close_reason {
7445                    Some(CloseReason::Fenced) => break,
7446                    Some(reason) => {
7447                        panic!("expected compactor failpoint to fence DB, got {reason:?}")
7448                    }
7449                    None => {}
7450                }
7451                status_rx.changed().await.expect("db status channel closed");
7452            }
7453        })
7454        .await
7455        .expect("compactor did not hit the failpoint");
7456
7457        drop(snapshot);
7458        gated.put_opts_gate.release();
7459        db.close().await.unwrap();
7460        fail_parallel::cfg(
7461            fp_registry.clone(),
7462            "compactor-heartbeat-after-output-sst",
7463            "off",
7464        )
7465        .unwrap();
7466
7467        let db = Db::builder(path, object_store.clone())
7468            .with_settings(settings_without_compactor.clone())
7469            .with_sst_block_size(SstBlockSize::Other(1))
7470            .with_fp_registry(fp_registry.clone())
7471            .with_merge_operator(Arc::new(StringConcatMergeOperator))
7472            .build()
7473            .await
7474            .unwrap();
7475        db.put(b"zz-advance-retention", b"x").await.unwrap();
7476        db.flush().await.unwrap();
7477        wait_for_manifest_condition(
7478            &mut stored_manifest,
7479            |core| core.last_l0_seq >= 7 && core.recent_snapshot_min_seq > snapshot_seq,
7480            Duration::from_secs(10),
7481        )
7482        .await;
7483        db.close().await.unwrap();
7484
7485        let db = Db::builder(path, object_store.clone())
7486            .with_settings(settings_without_compactor)
7487            .with_sst_block_size(SstBlockSize::Other(1))
7488            .with_fp_registry(fp_registry)
7489            .with_merge_operator(Arc::new(StringConcatMergeOperator))
7490            .with_compactor_builder(
7491                CompactorBuilder::new(path, object_store.clone())
7492                    .with_options(compactor_options)
7493                    .with_scheduler_supplier(Arc::new(OnDemandCompactionSchedulerSupplier::new(
7494                        Arc::new(|_| false),
7495                    ))),
7496            )
7497            .build()
7498            .await
7499            .unwrap();
7500
7501        let manifest_store = Arc::new(ManifestStore::new(&Path::from(path), object_store.clone()));
7502        let mut stored_manifest =
7503            StoredManifest::load(manifest_store, Arc::new(DefaultSystemClock::new()))
7504                .await
7505                .unwrap();
7506        wait_for_manifest_condition(
7507            &mut stored_manifest,
7508            |core| !core.tree.compacted.is_empty(),
7509            Duration::from_secs(10),
7510        )
7511        .await;
7512        db.refresh_manifest().await.unwrap();
7513
7514        let actual = db.get(b"k").await.unwrap();
7515        db.close().await.unwrap();
7516
7517        assert_eq!(actual, Some(Bytes::from_static(b"123456")));
7518    }
7519
7520    #[tokio::test]
7521    async fn test_recent_snapshot_min_seq_uses_transaction_seq() {
7522        let path = "/tmp/test_recent_snapshot_min_seq_uses_transaction_seq";
7523        let object_store = Arc::new(InMemory::new());
7524        let db = Db::builder(path, object_store).build().await.unwrap();
7525
7526        {
7527            let state = db.inner.state.read();
7528            assert_eq!(state.state().core().recent_snapshot_min_seq, 0);
7529        }
7530
7531        db.put(b"key1", b"value1").await.unwrap();
7532        db.inner
7533            .flush_memtables(crate::memtable_flusher::FlushTarget::All)
7534            .await
7535            .unwrap();
7536
7537        let txn = db.begin(IsolationLevel::Snapshot).await.unwrap();
7538        let txn_seq = txn.seqnum();
7539
7540        db.put(b"key2", b"value2").await.unwrap();
7541        db.inner
7542            .flush_memtables(crate::memtable_flusher::FlushTarget::All)
7543            .await
7544            .unwrap();
7545
7546        let min_active_seq = db.inner.txn_manager.min_active_seq();
7547        assert_eq!(min_active_seq, Some(txn_seq));
7548
7549        {
7550            let state = db.inner.state.read();
7551            let recent_min_seq = state.state().core().recent_snapshot_min_seq;
7552            assert_eq!(
7553                recent_min_seq, txn_seq,
7554                "recent_snapshot_min_seq should equal txn_manager.min_active_seq() after flush"
7555            );
7556        }
7557
7558        drop(txn);
7559
7560        db.put(b"key3", b"value3").await.unwrap();
7561        db.inner
7562            .flush_memtables(crate::memtable_flusher::FlushTarget::All)
7563            .await
7564            .unwrap();
7565
7566        {
7567            let state = db.inner.state.read();
7568            let recent_min_seq = state.state().core().recent_snapshot_min_seq;
7569            let last_l0_seq = state.state().core().last_l0_seq;
7570            assert_eq!(
7571                recent_min_seq, last_l0_seq,
7572                "recent_snapshot_min_seq should equal last_l0_seq when no active transactions"
7573            );
7574            assert!(recent_min_seq > txn_seq);
7575        }
7576    }
7577
7578    #[tokio::test]
7579    async fn test_recent_snapshot_min_seq_prefers_snapshot_when_snapshot_seq_is_lower() {
7580        let path = "/tmp/test_recent_snapshot_min_seq_prefers_snapshot_when_snapshot_seq_is_lower";
7581        let object_store = Arc::new(InMemory::new());
7582        let db = Db::builder(path, object_store).build().await.unwrap();
7583
7584        db.put(b"key1", b"value1").await.unwrap();
7585        db.inner
7586            .flush_memtables(crate::memtable_flusher::FlushTarget::All)
7587            .await
7588            .unwrap();
7589
7590        let snapshot = db.snapshot().await.unwrap();
7591        let snapshot_seq = snapshot.seq();
7592
7593        db.put(b"key2", b"value2").await.unwrap();
7594        db.inner
7595            .flush_memtables(crate::memtable_flusher::FlushTarget::All)
7596            .await
7597            .unwrap();
7598
7599        let txn = db.begin(IsolationLevel::Snapshot).await.unwrap();
7600        let txn_seq = txn.seqnum();
7601
7602        assert_eq!(
7603            db.inner.snapshot_manager.min_active_seq(),
7604            Some(snapshot_seq)
7605        );
7606        assert_eq!(db.inner.txn_manager.min_active_seq(), Some(txn_seq));
7607        assert!(snapshot_seq < txn_seq);
7608
7609        db.put(b"key3", b"value3").await.unwrap();
7610        db.inner
7611            .flush_memtables(crate::memtable_flusher::FlushTarget::All)
7612            .await
7613            .unwrap();
7614
7615        {
7616            let state = db.inner.state.read();
7617            let recent_min_seq = state.state().core().recent_snapshot_min_seq;
7618            assert_eq!(
7619                recent_min_seq,
7620                snapshot_seq,
7621                "recent_snapshot_min_seq should use the snapshot seq when it is smaller than the transaction seq"
7622            );
7623        }
7624
7625        drop(snapshot);
7626
7627        db.put(b"key4", b"value4").await.unwrap();
7628        db.inner
7629            .flush_memtables(crate::memtable_flusher::FlushTarget::All)
7630            .await
7631            .unwrap();
7632
7633        assert_eq!(db.inner.snapshot_manager.min_active_seq(), None);
7634        assert_eq!(db.inner.txn_manager.min_active_seq(), Some(txn_seq));
7635
7636        {
7637            let state = db.inner.state.read();
7638            let recent_min_seq = state.state().core().recent_snapshot_min_seq;
7639            assert_eq!(
7640                recent_min_seq,
7641                txn_seq,
7642                "recent_snapshot_min_seq should move to the transaction seq after the snapshot is dropped"
7643            );
7644        }
7645    }
7646
7647    #[tokio::test]
7648    async fn test_recent_snapshot_min_seq_prefers_transaction_when_transaction_seq_is_lower() {
7649        let path =
7650            "/tmp/test_recent_snapshot_min_seq_prefers_transaction_when_transaction_seq_is_lower";
7651        let object_store = Arc::new(InMemory::new());
7652        let db = Db::builder(path, object_store).build().await.unwrap();
7653
7654        db.put(b"key1", b"value1").await.unwrap();
7655        db.inner
7656            .flush_memtables(crate::memtable_flusher::FlushTarget::All)
7657            .await
7658            .unwrap();
7659
7660        let txn = db.begin(IsolationLevel::Snapshot).await.unwrap();
7661        let txn_seq = txn.seqnum();
7662
7663        db.put(b"key2", b"value2").await.unwrap();
7664        db.inner
7665            .flush_memtables(crate::memtable_flusher::FlushTarget::All)
7666            .await
7667            .unwrap();
7668
7669        let snapshot = db.snapshot().await.unwrap();
7670        let snapshot_seq = snapshot.seq();
7671
7672        assert_eq!(db.inner.txn_manager.min_active_seq(), Some(txn_seq));
7673        assert_eq!(
7674            db.inner.snapshot_manager.min_active_seq(),
7675            Some(snapshot_seq)
7676        );
7677        assert!(txn_seq < snapshot_seq);
7678
7679        db.put(b"key3", b"value3").await.unwrap();
7680        db.inner
7681            .flush_memtables(crate::memtable_flusher::FlushTarget::All)
7682            .await
7683            .unwrap();
7684
7685        {
7686            let state = db.inner.state.read();
7687            let recent_min_seq = state.state().core().recent_snapshot_min_seq;
7688            assert_eq!(
7689                recent_min_seq,
7690                txn_seq,
7691                "recent_snapshot_min_seq should use the transaction seq when it is smaller than the snapshot seq"
7692            );
7693        }
7694
7695        drop(txn);
7696
7697        db.put(b"key4", b"value4").await.unwrap();
7698        db.inner
7699            .flush_memtables(crate::memtable_flusher::FlushTarget::All)
7700            .await
7701            .unwrap();
7702
7703        assert_eq!(db.inner.txn_manager.min_active_seq(), None);
7704        assert_eq!(
7705            db.inner.snapshot_manager.min_active_seq(),
7706            Some(snapshot_seq)
7707        );
7708
7709        {
7710            let state = db.inner.state.read();
7711            let recent_min_seq = state.state().core().recent_snapshot_min_seq;
7712            assert_eq!(
7713                recent_min_seq,
7714                snapshot_seq,
7715                "recent_snapshot_min_seq should move to the snapshot seq after the transaction is dropped"
7716            );
7717        }
7718    }
7719
7720    #[tokio::test]
7721    async fn test_memtable_flush_updates_last_remote_persisted_seq() {
7722        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
7723        let path = "/tmp/test";
7724        let mut opts = test_db_options(0, 256, None);
7725        opts.flush_interval = Some(Duration::MAX);
7726        let db = Db::builder(path, object_store.clone())
7727            .with_settings(opts)
7728            .build()
7729            .await
7730            .unwrap();
7731
7732        // do a write and flush memtable only (not wal)
7733        let write_opts = WriteOptions {
7734            await_durable: false,
7735            ..Default::default()
7736        };
7737        db.put_with_options(&b"foo", &b"bar", &PutOptions::default(), &write_opts)
7738            .await
7739            .unwrap();
7740        db.flush_with_options(FlushOptions {
7741            flush_type: FlushType::MemTable,
7742        })
7743        .await
7744        .unwrap();
7745
7746        // check that read with durability level remote returns value
7747        let v = db
7748            .get_with_options(&b"foo", &ReadOptions::new().with_durability_filter(Memory))
7749            .await
7750            .unwrap();
7751        assert_eq!(v, Some(Bytes::from(b"bar".as_ref())));
7752        let v = db
7753            .get_with_options(&b"foo", &ReadOptions::new().with_durability_filter(Remote))
7754            .await
7755            .unwrap();
7756        assert_eq!(v, Some(Bytes::from(b"bar".as_ref())));
7757    }
7758
7759    #[tokio::test]
7760    async fn should_merge_operand_into_empty_key() {
7761        // Given: Database with merge operator, empty key
7762        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
7763        let db = Db::builder("/tmp/test_merge_1", object_store.clone())
7764            .with_settings(test_db_options(0, 1024, None))
7765            .with_merge_operator(Arc::new(StringConcatMergeOperator))
7766            .build()
7767            .await
7768            .unwrap();
7769
7770        // When: Merging a value
7771        db.merge(b"key1", b"value1").await.unwrap();
7772
7773        // Then: Value is stored and retrievable
7774        let result = db.get(b"key1").await.unwrap();
7775        assert_eq!(result, Some(Bytes::from("value1")));
7776    }
7777
7778    #[tokio::test]
7779    async fn should_merge_multiple_operands_into_same_key() {
7780        // Given: Database with merge operator, key with initial merge
7781        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
7782        let db = Db::builder("/tmp/test_merge_2", object_store.clone())
7783            .with_settings(test_db_options(0, 1024, None))
7784            .with_merge_operator(Arc::new(StringConcatMergeOperator))
7785            .build()
7786            .await
7787            .unwrap();
7788
7789        // When: Merging multiple operands to the same key
7790        db.merge(b"key1", b"a").await.unwrap();
7791        db.merge(b"key1", b"b").await.unwrap();
7792        db.merge(b"key1", b"c").await.unwrap();
7793
7794        // Then: All operands are merged correctly when read
7795        let result = db.get(b"key1").await.unwrap();
7796        assert_eq!(result, Some(Bytes::from("abc")));
7797    }
7798
7799    #[tokio::test]
7800    async fn should_persist_merge_operands_across_flush() {
7801        // Given: Database with merge operator, merge operands in memtable
7802        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
7803        let db = Db::builder("/tmp/test_merge_3", object_store.clone())
7804            .with_settings(test_db_options(0, 1024, None))
7805            .with_merge_operator(Arc::new(StringConcatMergeOperator))
7806            .build()
7807            .await
7808            .unwrap();
7809
7810        db.merge(b"key1", b"a").await.unwrap();
7811        db.merge(b"key1", b"b").await.unwrap();
7812
7813        // When: Flushing memtable and reading
7814        db.flush().await.unwrap();
7815
7816        // Then: Merged result is correct after flush
7817        let result = db.get(b"key1").await.unwrap();
7818        assert_eq!(result, Some(Bytes::from("ab")));
7819
7820        // Verify it still works after additional merges post-flush
7821        db.merge(b"key1", b"c").await.unwrap();
7822        let result = db.get(b"key1").await.unwrap();
7823        assert_eq!(result, Some(Bytes::from("abc")));
7824    }
7825
7826    #[tokio::test]
7827    async fn should_error_when_merging_without_merge_operator() {
7828        // Given: Database with merge operator, merge operands written
7829        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
7830        let path = "/tmp/test_merge_4";
7831        let db = Db::builder(path, object_store.clone())
7832            .with_settings(test_db_options(0, 1024, None))
7833            .with_merge_operator(Arc::new(StringConcatMergeOperator))
7834            .build()
7835            .await
7836            .unwrap();
7837
7838        db.merge(b"key1", b"value1").await.unwrap();
7839        db.flush().await.unwrap();
7840        db.close().await.unwrap();
7841
7842        // When: Reopening the DB without a merge operator and then reading
7843        let db = Db::builder(path, object_store.clone())
7844            .with_settings(test_db_options(0, 1024, None))
7845            .build()
7846            .await
7847            .unwrap();
7848
7849        // Then: Reading should fail because merge operands require a merge operator
7850        let err = db.get(b"key1").await.unwrap_err();
7851        assert_eq!(err.kind(), crate::ErrorKind::Invalid);
7852    }
7853
7854    #[tokio::test]
7855    async fn should_error_when_writing_merge_without_merge_operator() {
7856        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
7857        let db = Db::builder("/tmp/test_merge_4_write_fails", object_store.clone())
7858            .with_settings(test_db_options(0, 1024, None))
7859            .build()
7860            .await
7861            .unwrap();
7862
7863        let err = db.merge(b"key1", b"value1").await.unwrap_err();
7864        assert_eq!(err.kind(), crate::ErrorKind::Invalid);
7865
7866        let result = db.get(b"key1").await.unwrap();
7867        assert_eq!(result, None);
7868    }
7869
7870    #[tokio::test]
7871    async fn should_error_when_writing_batch_with_merge_without_merge_operator() {
7872        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
7873        let db = Db::builder("/tmp/test_merge_4_batch_write_fails", object_store.clone())
7874            .with_settings(test_db_options(0, 1024, None))
7875            .build()
7876            .await
7877            .unwrap();
7878
7879        let mut batch = WriteBatch::new();
7880        batch.put(b"key1", b"value1");
7881        batch.merge(b"key2", b"value2");
7882
7883        let err = db.write(batch).await.unwrap_err();
7884        assert_eq!(err.kind(), crate::ErrorKind::Invalid);
7885
7886        assert_eq!(db.get(b"key1").await.unwrap(), None);
7887        assert_eq!(db.get(b"key2").await.unwrap(), None);
7888    }
7889
7890    #[tokio::test]
7891    async fn should_merge_operands_after_reopen() {
7892        // Given: Database with merge operator, merge operands written
7893        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
7894        let path = "/tmp/test_merge_5";
7895        let db = Db::builder(path, object_store.clone())
7896            .with_settings(test_db_options(0, 1024, None))
7897            .with_merge_operator(Arc::new(StringConcatMergeOperator))
7898            .build()
7899            .await
7900            .unwrap();
7901
7902        db.merge(b"key1", b"a").await.unwrap();
7903        db.merge(b"key1", b"b").await.unwrap();
7904        db.flush().await.unwrap();
7905        db.close().await.unwrap();
7906
7907        // When: Closing and reopening database, then reading
7908        let db_reopened = Db::builder(path, object_store.clone())
7909            .with_settings(test_db_options(0, 1024, None))
7910            .with_merge_operator(Arc::new(StringConcatMergeOperator))
7911            .build()
7912            .await
7913            .unwrap();
7914
7915        // Then: Merged result is correct after reopen
7916        let result = db_reopened.get(b"key1").await.unwrap();
7917        assert_eq!(result, Some(Bytes::from("ab")));
7918
7919        // Verify additional merges work after reopen
7920        db_reopened.merge(b"key1", b"c").await.unwrap();
7921        let result = db_reopened.get(b"key1").await.unwrap();
7922        assert_eq!(result, Some(Bytes::from("abc")));
7923    }
7924
7925    /// Reproduces a race where GC can delete an L0 SST before the manifest
7926    /// is updated to reference it at the DB level:
7927    /// 1. New L0 is written
7928    /// 2. 100ms passes
7929    /// 3. GC lists SSTs
7930    /// 4. GC sees L0 SST from (1)
7931    /// 5. GC deletes L0 SST from (1) (it is > min_age=100ms old and is not in any manifests)
7932    /// 6. L0 is added to in-memory manifest
7933    /// 7. Manifest is written to object storage
7934    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
7935    async fn test_gc_race_deletes_l0_before_manifest_update() {
7936        let fp_registry = Arc::new(FailPointRegistry::new());
7937        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
7938        let path = Path::from("/tmp/test_gc_race_deletes_l0_before_manifest_update");
7939
7940        let mut settings = test_db_options(0, 1024, None);
7941        settings.flush_interval = None;
7942
7943        let db = Db::builder(path.clone(), object_store.clone())
7944            .with_settings(settings)
7945            .with_fp_registry(fp_registry.clone())
7946            .build()
7947            .await
7948            .expect("failed to build DB");
7949        let db = Arc::new(db);
7950
7951        // Pause after the L0 SST is written but before the manifest is updated.
7952        fail_parallel::cfg(
7953            fp_registry.clone(),
7954            "after-flush-imm-to-l0-before-manifest",
7955            "pause",
7956        )
7957        .expect("failed to set failpoint");
7958
7959        // Write some data so we have an immutable memtable to flush to L0.
7960        db.put_with_options(
7961            b"key1",
7962            b"value1",
7963            &PutOptions::default(),
7964            &WriteOptions {
7965                await_durable: false,
7966                ..Default::default()
7967            },
7968        )
7969        .await
7970        .expect("failed to put");
7971
7972        // Trigger a memtable flush in the background; it will block at the failpoint.
7973        let this_db = db.clone();
7974        let flush_handle = tokio::spawn(async move {
7975            this_db
7976                .flush_with_options(FlushOptions {
7977                    flush_type: FlushType::MemTable,
7978                })
7979                .await
7980        });
7981
7982        // Wait for the L0 SST to appear in the table store, indicating it has been written.
7983        let mut ssts = Vec::new();
7984        for _ in 0..200 {
7985            ssts = db
7986                .inner
7987                .table_store
7988                .list_compacted_ssts(..)
7989                .await
7990                .expect("failed to list compacted ssts");
7991            if !ssts.is_empty() {
7992                break;
7993            }
7994            tokio::time::sleep(Duration::from_millis(10)).await;
7995        }
7996        assert_eq!(
7997            ssts.len(),
7998            1,
7999            "expected exactly one L0 SST after GC, but found {:?}",
8000            ssts.iter().map(|sst| sst.id).collect::<Vec<_>>()
8001        );
8002
8003        // Run a manual GC with aggressive settings to delete the L0 SST while
8004        // the manifest is still not updated.
8005        let gc_options = GarbageCollectorOptions {
8006            wal_options: Some(GarbageCollectorDirectoryOptions {
8007                interval: None,
8008                min_age: Duration::from_millis(0),
8009                dry_run: false,
8010            }),
8011            wal_fence_options: None,
8012            manifest_options: Some(GarbageCollectorDirectoryOptions {
8013                interval: None,
8014                min_age: Duration::from_millis(0),
8015                dry_run: false,
8016            }),
8017            compacted_options: Some(GarbageCollectorDirectoryOptions {
8018                interval: None,
8019                min_age: Duration::from_millis(0),
8020                dry_run: false,
8021            }),
8022            compactions_options: Some(GarbageCollectorDirectoryOptions {
8023                interval: None,
8024                min_age: Duration::from_millis(0),
8025                dry_run: false,
8026            }),
8027            detach_options: None,
8028            metric_level: None,
8029            boundary_files_enabled: true,
8030            object_store_max_retries: None,
8031        };
8032
8033        let gc = GarbageCollectorBuilder::new(path.clone(), object_store.clone())
8034            .with_options(gc_options)
8035            .with_system_clock(db.inner.system_clock.clone())
8036            .build();
8037
8038        // Run the GC a few times so it sees the L0 SST (and hopefully doesn't delete it)
8039        for _ in 0..5 {
8040            gc.run_gc_once().await;
8041            ssts = db
8042                .inner
8043                .table_store
8044                .list_compacted_ssts(..)
8045                .await
8046                .expect("failed to list compacted ssts after manual GC");
8047            if ssts.is_empty() {
8048                break;
8049            }
8050            tokio::time::sleep(Duration::from_millis(10)).await;
8051        }
8052        assert_eq!(
8053            ssts.len(),
8054            1,
8055            "expected exactly one L0 SST after GC, but found {:?}",
8056            ssts.iter().map(|sst| sst.id).collect::<Vec<_>>()
8057        );
8058
8059        // Now allow the memtable flush to resume and persist the manifest referencing the deleted SST.
8060        fail_parallel::cfg(
8061            fp_registry.clone(),
8062            "after-flush-imm-to-l0-before-manifest",
8063            "off",
8064        )
8065        .expect("failed to set failpoint");
8066        flush_handle
8067            .await
8068            .expect("failed to join flush handle")
8069            .expect("flush failed");
8070
8071        // Read the latest manifest and verify it references the L0 SST.
8072        let manifest_store = ManifestStore::new(&path, object_store.clone());
8073        let manifest = manifest_store
8074            .read_latest_manifest()
8075            .await
8076            .expect("failed to read latest manifest");
8077        assert_eq!(
8078            manifest.manifest.core.tree.l0.len(),
8079            1,
8080            "expected exactly one L0 SST in manifest"
8081        );
8082        let l0_id = manifest.manifest.core.tree.l0[0].sst.id;
8083        assert_eq!(
8084            l0_id, ssts[0].id,
8085            "expected SST {:?} but found SST {:?}",
8086            ssts[0].id, l0_id,
8087        );
8088
8089        // Build a read-only TableStore sharing the same underlying object store
8090        // and assert that the referenced L0 SST still exists.
8091        let table_store = TableStore::new(
8092            ObjectStores::new(object_store.clone(), None),
8093            SsTableFormat::default(),
8094            path.clone(),
8095            None,
8096            TableStoreKind::Main,
8097            BlockCachePolicy::default(),
8098        );
8099        let compacted_ssts = table_store
8100            .list_compacted_ssts(..)
8101            .await
8102            .expect("failed to list compacted ssts");
8103        let still_exists = compacted_ssts.iter().any(|m| m.id == l0_id);
8104        assert!(
8105            still_exists,
8106            "manifest references L0 SST {:?} that GC has already deleted",
8107            l0_id
8108        );
8109
8110        db.close().await.expect("failed to close DB");
8111    }
8112
8113    #[tokio::test]
8114    async fn test_write_handle() {
8115        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
8116        let path = "/tmp/test_write_handle_db";
8117        let clock = Arc::new(MockSystemClock::new());
8118        let db = Db::builder(path, object_store)
8119            .with_settings(test_db_options(0, 1024, None))
8120            .with_system_clock(clock.clone())
8121            .build()
8122            .await
8123            .unwrap();
8124
8125        // Put
8126        let key = b"key1";
8127        let value = b"value1";
8128        clock.set(100);
8129        let handle = db
8130            .put_with_options(
8131                key,
8132                value,
8133                &PutOptions::default(),
8134                &WriteOptions {
8135                    await_durable: false,
8136                    ..Default::default()
8137                },
8138            )
8139            .await
8140            .unwrap();
8141        assert_eq!(handle.seqnum(), 1);
8142        assert_eq!(handle.create_ts(), 100);
8143
8144        // Put with options (TTL)
8145        clock.set(200);
8146        let put_opts = PutOptions {
8147            ttl: Ttl::ExpireAfter(1000),
8148        };
8149        let handle = db
8150            .put_with_options(
8151                b"key2",
8152                b"value2",
8153                &put_opts,
8154                &WriteOptions {
8155                    await_durable: false,
8156                    ..Default::default()
8157                },
8158            )
8159            .await
8160            .unwrap();
8161        assert_eq!(handle.seqnum(), 2);
8162        assert_eq!(handle.create_ts(), 200);
8163
8164        // Delete
8165        clock.set(300);
8166        let handle = db
8167            .delete_with_options(
8168                b"key1",
8169                &WriteOptions {
8170                    await_durable: false,
8171                    ..Default::default()
8172                },
8173            )
8174            .await
8175            .unwrap();
8176        assert_eq!(handle.seqnum(), 3);
8177        assert_eq!(handle.create_ts(), 300);
8178
8179        // Write Batch
8180        clock.set(400);
8181        let mut batch = WriteBatch::new();
8182        batch.put(b"key3", b"value3");
8183        batch.delete(b"key2");
8184        let handle = db
8185            .write_with_options(
8186                batch,
8187                &WriteOptions {
8188                    await_durable: false,
8189                    ..Default::default()
8190                },
8191            )
8192            .await
8193            .unwrap();
8194        assert_eq!(handle.seqnum(), 4);
8195        assert_eq!(handle.create_ts(), 400);
8196    }
8197
8198    #[tokio::test]
8199    async fn test_write_handle_with_batch() {
8200        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
8201        let path = "/tmp/test_write_batch_handle";
8202        let clock = Arc::new(MockSystemClock::new());
8203        let db = Db::builder(path, object_store)
8204            .with_settings(test_db_options(0, 1024, None))
8205            .with_system_clock(clock.clone())
8206            .build()
8207            .await
8208            .unwrap();
8209
8210        // Write Batch 1
8211        clock.set(100);
8212        let mut batch = WriteBatch::new();
8213        batch.put(b"key1", b"value1");
8214        batch.delete(b"key2");
8215        let handle = db
8216            .write_with_options(
8217                batch,
8218                &WriteOptions {
8219                    await_durable: false,
8220                    ..Default::default()
8221                },
8222            )
8223            .await
8224            .unwrap();
8225        assert_eq!(handle.seqnum(), 1);
8226        assert_eq!(handle.create_ts(), 100);
8227
8228        // Write Batch 2
8229        clock.set(200);
8230        let mut batch = WriteBatch::new();
8231        batch.put(b"key3", b"value3");
8232        batch.put(b"key4", b"value4");
8233        let handle = db
8234            .write_with_options(
8235                batch,
8236                &WriteOptions {
8237                    await_durable: false,
8238                    ..Default::default()
8239                },
8240            )
8241            .await
8242            .unwrap();
8243        assert_eq!(handle.seqnum(), 2);
8244        assert_eq!(handle.create_ts(), 200);
8245
8246        // Write Batch 3
8247        clock.set(300);
8248        let mut batch = WriteBatch::new();
8249        batch.delete(b"key1");
8250        let handle = db
8251            .write_with_options(
8252                batch,
8253                &WriteOptions {
8254                    await_durable: false,
8255                    ..Default::default()
8256                },
8257            )
8258            .await
8259            .unwrap();
8260        assert_eq!(handle.seqnum(), 3);
8261        assert_eq!(handle.create_ts(), 300);
8262    }
8263
8264    #[tokio::test]
8265    async fn test_write_with_options_empty_batch_returns_empty_batch_error() {
8266        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
8267        let db = Db::builder("/tmp/test_write_with_options_empty_batch", object_store)
8268            .with_settings(test_db_options(0, 1024, None))
8269            .build()
8270            .await
8271            .unwrap();
8272
8273        let err = db
8274            .inner
8275            .write_with_options(
8276                WriteBatch::new(),
8277                &WriteOptions {
8278                    await_durable: false,
8279                    ..Default::default()
8280                },
8281                None,
8282            )
8283            .await
8284            .unwrap_err();
8285        assert!(matches!(err, SlateDBError::EmptyBatch));
8286    }
8287
8288    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
8289    async fn test_txn_conflict_when_first_commit_paused_post_commit() {
8290        // This test reproduces the error in #1301. Befor the fix, the commited seqnum
8291        // in the oracle was advanced outside the commit lock. This caused a race where
8292        // another transaction could start, see the original seqnum (pre-commit), but not
8293        // see conflicts. See #1301 for more details.
8294        let fp_registry = Arc::new(FailPointRegistry::new());
8295        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
8296        let db = Db::builder("/tmp/test_txn_conflict_post_commit_pause", object_store)
8297            .with_settings(test_db_options(0, 1024, None))
8298            .with_fp_registry(fp_registry.clone())
8299            .build()
8300            .await
8301            .unwrap();
8302
8303        // 1-2. Create txn1 and write k1=v1.
8304        let txn1 = db
8305            .begin(IsolationLevel::SerializableSnapshot)
8306            .await
8307            .unwrap();
8308        txn1.put(b"k1", b"v1").unwrap();
8309
8310        // 3. Pause on write-batch-post-commit so txn1 blocks after conflict metadata is tracked.
8311        fail_parallel::cfg(fp_registry.clone(), "write-batch-post-commit", "pause").unwrap();
8312
8313        let txn1_start_seq = txn1.seqnum();
8314
8315        // 4. Commit txn1 in the background; it should pause at write-batch-post-commit.
8316        let txn1_commit_task = tokio::spawn(async move { txn1.commit().await });
8317
8318        // 5. Wait until txn1 reaches post-commit pause:
8319        // - txn1 is no longer active in txn_manager
8320        let pause_reached = tokio::time::timeout(Duration::from_secs(5), async {
8321            loop {
8322                let txn1_removed_from_active = db.inner.txn_manager.min_active_seq().is_none();
8323                if txn1_removed_from_active {
8324                    break;
8325                }
8326                tokio::time::sleep(Duration::from_millis(10)).await;
8327            }
8328        })
8329        .await
8330        .is_ok();
8331        if !pause_reached {
8332            fail_parallel::cfg(fp_registry.clone(), "write-batch-post-commit", "off").unwrap();
8333            let _ = txn1_commit_task.await;
8334            panic!("txn1 did not pause at write-batch-post-commit");
8335        }
8336
8337        // 5.1. Add/drop txn to trigger a recycle that removes txn1 from recent commits.
8338        let txn_dropped = db
8339            .begin(IsolationLevel::SerializableSnapshot)
8340            .await
8341            .unwrap();
8342        drop(txn_dropped);
8343
8344        // 6. Create txn2 after txn1 is committed but before batch_write is complete.
8345        // The seqnum should advance transactionally with the commit, so txn2 should
8346        // see txn1's post-write seqnum.
8347        let txn2 = db
8348            .begin(IsolationLevel::SerializableSnapshot)
8349            .await
8350            .unwrap();
8351
8352        // 6.1. txn2 should see k1=v1 since it started after txn1's commit, even though the
8353        // batch write is not fully complete until after txn2 starts.
8354        assert_eq!(
8355            txn2.get(b"k1").await.unwrap(),
8356            Some(Bytes::from_static(b"v1"))
8357        );
8358
8359        // 7. Unpause write-batch-post-commit, advance seqnum.
8360        fail_parallel::cfg(fp_registry.clone(), "write-batch-post-commit", "off").unwrap();
8361
8362        // 8. Wait for txn1 to finish committing. txn1 is dropped when this finishes.
8363        let _ = txn1_commit_task
8364            .await
8365            .expect("failed to join txn1 commit task")
8366            .expect("txn1 commit should succeed");
8367        assert_eq!(
8368            txn2.seqnum(),
8369            txn1_start_seq + 1, // 1 row was written
8370            "txn2 should see the commit seqnum after txn1's commit"
8371        );
8372
8373        // 9-10. txn2 writes k1=v2 then attempts to commit (should not conflict).
8374        txn2.put(b"k1", b"v2").unwrap();
8375        txn2.put(b"k2", b"v2").unwrap();
8376        assert!(txn2.commit().await.is_ok());
8377
8378        // 11. txn2 committed, so the db should show it.
8379        assert_eq!(
8380            db.get(b"k1").await.unwrap(),
8381            Some(Bytes::from_static(b"v2"))
8382        );
8383
8384        db.close().await.unwrap();
8385    }
8386
8387    /// Regression test: cancelling a commit future after the write batch has been
8388    /// sent to the writer (but before it is processed) drops the DbTransaction,
8389    /// which removes it from active_txns. The writer then:
8390    ///   1. Skips conflict detection (check_has_conflict returns false for
8391    ///      missing txn_ids)
8392    ///   2. Skips tracking the committed state (track_recent_committed_txn
8393    ///      silently no-ops)
8394    ///
8395    /// This allows a second transaction writing the same key to commit without
8396    /// detecting a write-write conflict — a lost update anomaly.
8397    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
8398    async fn test_commit_future_cancel_bypasses_conflict_detection() {
8399        let fp_registry = Arc::new(FailPointRegistry::new());
8400        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
8401        let db = Db::builder(
8402            "/tmp/test_commit_future_cancel_bypasses_conflict_detection",
8403            object_store,
8404        )
8405        .with_fp_registry(fp_registry.clone())
8406        .build()
8407        .await
8408        .unwrap();
8409
8410        // Write initial data.
8411        db.put(b"x", b"v0").await.unwrap();
8412        let initial_last_seq = db.inner.oracle.last_seq();
8413
8414        // Start txn1 and buffer a write to key "x".
8415        let txn1 = db.begin(IsolationLevel::Snapshot).await.unwrap();
8416        let txn1_started_seq = txn1.seqnum();
8417        txn1.put(b"x", b"txn1_value").unwrap();
8418
8419        // Pause the writer *before* it tracks the committed transaction state.
8420        // At the pause point the WAL + memtable writes have already happened,
8421        // but track_recent_committed_txn has not been called yet.
8422        fail_parallel::cfg(fp_registry.clone(), "write-batch-pre-commit", "pause").unwrap();
8423
8424        // Commit txn1 in a background task. It will send the batch to the
8425        // writer, which will process it up to the pause point and block.
8426        let txn1_commit = tokio::spawn(async move { txn1.commit().await });
8427
8428        // Wait until the writer has started processing txn1's batch.
8429        // oracle.last_seq() advances at the very start of write_batch. No
8430        // further synchronization is needed: the failpoint guarantees the
8431        // writer cannot advance past pre-commit before we cancel below.
8432        let reached = tokio::time::timeout(Duration::from_secs(30), async {
8433            while db.inner.oracle.last_seq() <= initial_last_seq {
8434                tokio::task::yield_now().await;
8435            }
8436        })
8437        .await;
8438        assert!(reached.is_ok(), "writer did not start processing txn1");
8439
8440        // Cancel txn1's commit future. The DbTransaction was moved into the
8441        // writer's queue message at commit time, so cancelling the future does
8442        // NOT drop it on the caller side and therefore cannot call drop_txn.
8443        txn1_commit.abort();
8444        let _ = txn1_commit.await;
8445
8446        // txn1 stays in active_txns (the writer now owns it, not the caller).
8447        assert!(
8448            db.inner.txn_manager.min_active_seq().is_some(),
8449            "txn1 should still be in active_txns (writer owns the in-flight txn)"
8450        );
8451
8452        // Start txn2 while the writer is still paused (committed_seq has not
8453        // been advanced yet), so txn2 starts at the same snapshot as txn1.
8454        let txn2 = db.begin(IsolationLevel::Snapshot).await.unwrap();
8455        assert_eq!(
8456            txn2.seqnum(),
8457            txn1_started_seq,
8458            "txn2 should start at the same seq as txn1 (committed_seq not yet advanced)"
8459        );
8460        txn2.put(b"x", b"txn2_value").unwrap();
8461
8462        // Un-pause the writer. It will track txn1's committed state properly
8463        // because txn1 is still in active_txns.
8464        fail_parallel::cfg(fp_registry.clone(), "write-batch-pre-commit", "off").unwrap();
8465
8466        // Commit txn2. Its batch is enqueued behind txn1's, so the writer
8467        // finishes tracking txn1's committed state before it runs conflict
8468        // detection for txn2.
8469        // Both txn1 and txn2 wrote key "x" from the same snapshot.
8470        // A correct implementation detects a write-write conflict here.
8471        let result = txn2.commit().await;
8472        assert!(
8473            result.is_err(),
8474            "txn2 should detect a WW conflict with txn1"
8475        );
8476
8477        db.close().await.unwrap();
8478    }
8479
8480    /// Verify that the writer cleans up active_txns when a cancelled commit's
8481    /// batch fails (e.g., TransactionConflict). Because commit moves the
8482    /// DbTransaction into the writer's queue message, cancelling the future does
8483    /// not drop it caller-side; the writer owns it and drops it (running drop_txn)
8484    /// when it finishes processing the message. Without that ownership transfer
8485    /// the txn would leak in active_txns, pinning min_active_seq and blocking
8486    /// compaction.
8487    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
8488    async fn test_cancelled_commit_writer_error_cleans_up_active_txn() {
8489        let fp_registry = Arc::new(FailPointRegistry::new());
8490        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
8491        let db = Db::builder(
8492            "/tmp/test_cancelled_commit_writer_error_cleans_up_active_txn",
8493            object_store,
8494        )
8495        .with_fp_registry(fp_registry.clone())
8496        .build()
8497        .await
8498        .unwrap();
8499
8500        // Start txn_a and txn_b at the same snapshot. Both write the same key,
8501        // so txn_b will hit a WW conflict once txn_a's commit is tracked.
8502        let txn_a = db.begin(IsolationLevel::Snapshot).await.unwrap();
8503        let txn_b = db.begin(IsolationLevel::Snapshot).await.unwrap();
8504        txn_a.put(b"y", b"txn_a_value").unwrap();
8505        txn_b.put(b"y", b"txn_b_value").unwrap();
8506
8507        // Commit txn_a fully so its committed state is tracked. From here on,
8508        // txn_b is the only active transaction.
8509        txn_a.commit().await.expect("txn_a commit should succeed");
8510        let seq_after_a = db.inner.oracle.last_seq();
8511
8512        // Park the writer on a filler write: pause at post-commit and wait for
8513        // the filler's processing to start (oracle.last_seq() advances at the
8514        // very start of write_batch). With the writer held, txn_b's commit
8515        // below cannot complete before we cancel it.
8516        fail_parallel::cfg(fp_registry.clone(), "write-batch-post-commit", "pause").unwrap();
8517        let filler_db = db.clone();
8518        let filler = tokio::spawn(async move { filler_db.put(b"z", b"filler_value").await });
8519        let reached = tokio::time::timeout(Duration::from_secs(30), async {
8520            while db.inner.oracle.last_seq() <= seq_after_a {
8521                tokio::task::yield_now().await;
8522            }
8523        })
8524        .await;
8525        assert!(
8526            reached.is_ok(),
8527            "writer did not start processing the filler"
8528        );
8529        let seq_after_filler = db.inner.oracle.last_seq();
8530
8531        // Start committing txn_b, then cancel the commit. The first poll of the
8532        // commit future enqueues the batch — moving the DbTransaction into the
8533        // queued message — and then parks waiting on the (paused) writer, so
8534        // now_or_never() drops the future exactly like a cancelled in-flight
8535        // commit.
8536        assert!(
8537            txn_b.commit().now_or_never().is_none(),
8538            "commit should be pending while the writer is paused"
8539        );
8540
8541        // txn_b should still be in active_txns: ownership moved to the writer
8542        // at enqueue, so cancelling the future must not run drop_txn.
8543        assert!(
8544            db.inner.txn_manager.min_active_seq().is_some(),
8545            "txn_b should still be in active_txns (writer owns the in-flight txn)"
8546        );
8547
8548        // Un-pause the writer. It finishes the filler, then processes txn_b's
8549        // batch, which is rejected with a WW conflict; dropping the rejected
8550        // message drops the owned DbTransaction, which runs drop_txn.
8551        fail_parallel::cfg(fp_registry.clone(), "write-batch-post-commit", "off").unwrap();
8552        filler
8553            .await
8554            .expect("failed to join filler task")
8555            .expect("filler write should succeed");
8556
8557        // The writer picks up txn_b's batch (last_seq advances at the start of
8558        // write_batch, before conflict detection rejects it)...
8559        let processed = tokio::time::timeout(Duration::from_secs(30), async {
8560            while db.inner.oracle.last_seq() <= seq_after_filler {
8561                tokio::task::yield_now().await;
8562            }
8563        })
8564        .await;
8565        assert!(processed.is_ok(), "writer did not start processing txn_b");
8566
8567        // ...and cleans it up from active_txns.
8568        let cleaned = tokio::time::timeout(Duration::from_secs(30), async {
8569            while db.inner.txn_manager.min_active_seq().is_some() {
8570                tokio::task::yield_now().await;
8571            }
8572        })
8573        .await;
8574        assert!(
8575            cleaned.is_ok(),
8576            "txn_b should have been cleaned up from active_txns by the writer"
8577        );
8578
8579        // txn_b was rejected, so its write must not be visible.
8580        assert_eq!(
8581            db.get(b"y").await.unwrap(),
8582            Some(Bytes::from_static(b"txn_a_value"))
8583        );
8584
8585        db.close().await.unwrap();
8586    }
8587
8588    /// When the commit's enqueue fails (e.g. the writer channel is closed), the
8589    /// DbTransaction was moved into `enqueue_write_batch` but never made it onto
8590    /// the queue. Ownership therefore stays on the caller side: the moved-in txn
8591    /// drops inside the failing enqueue and runs drop_txn, so the txn must not
8592    /// leak in active_txns and commit must surface the error.
8593    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
8594    async fn test_commit_enqueue_failure_cleans_up_active_txn() {
8595        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
8596        let db = Db::builder(
8597            "/tmp/test_commit_enqueue_failure_cleans_up_active_txn",
8598            object_store,
8599        )
8600        .build()
8601        .await
8602        .unwrap();
8603
8604        // Capture a handle to the txn manager before closing the db so we can
8605        // assert on active_txns afterwards.
8606        let txn_manager = db.inner.txn_manager.clone();
8607
8608        let txn = db.begin(IsolationLevel::Snapshot).await.unwrap();
8609        txn.put(b"k", b"v").unwrap();
8610        assert!(
8611            txn_manager.min_active_seq().is_some(),
8612            "txn should be registered in active_txns before commit"
8613        );
8614
8615        // Close the db, shutting down the writer and its channel. The txn keeps
8616        // its own Arc<DbInner>, so it remains usable for the commit attempt.
8617        db.close().await.unwrap();
8618
8619        // Commit now fails to enqueue. The moved-in DbTransaction drops on the
8620        // caller side and cleans itself up.
8621        let result = txn.commit().await;
8622        assert!(
8623            result.is_err(),
8624            "commit should fail once the writer is closed"
8625        );
8626        assert!(
8627            txn_manager.min_active_seq().is_none(),
8628            "txn must be cleaned up on the caller side when enqueue fails"
8629        );
8630    }
8631
8632    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
8633    async fn test_txn_conflict_commit_seq_gap_does_not_block_l0_retirement() {
8634        const REPRO_SEED: u64 = 2_985_011_763_506_195_159;
8635        const MAX_UNFLUSHED_BYTES: usize = 8 * 1024;
8636        const LARGE_VALUE_BYTES: usize = 16 * 1024;
8637
8638        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
8639        let mut options = test_db_options(0, 1024, None);
8640        options.flush_interval = None;
8641        options.manifest_poll_interval = Duration::from_millis(10);
8642        options.max_unflushed_bytes = MAX_UNFLUSHED_BYTES;
8643        options.l0_max_ssts = 16;
8644
8645        let db = Db::builder(
8646            "/tmp/test_txn_conflict_commit_seq_gap_blocks_l0_manifest_retirement",
8647            object_store,
8648        )
8649        .with_seed(REPRO_SEED)
8650        .with_settings(options)
8651        .build()
8652        .await
8653        .unwrap();
8654        let write_opts = WriteOptions {
8655            await_durable: false,
8656            ..Default::default()
8657        };
8658
8659        // Establish and flush seq=1 so the L0 manifest writer has a known
8660        // contiguous frontier before the conflict sequence gap is created.
8661        db.put_with_options(b"conflict-key", b"v1", &PutOptions::default(), &write_opts)
8662            .await
8663            .unwrap();
8664        tokio::time::timeout(
8665            Duration::from_secs(5),
8666            db.flush_with_options(FlushOptions {
8667                flush_type: FlushType::MemTable,
8668            }),
8669        )
8670        .await
8671        .expect("timed out flushing base memtable")
8672        .expect("base memtable flush should succeed");
8673
8674        // Start a serializable transaction at seq=1 and record a read on
8675        // conflict-key. The next committed write to that key will conflict
8676        // with this transaction when it tries to commit.
8677        let txn = db
8678            .begin(IsolationLevel::SerializableSnapshot)
8679            .await
8680            .unwrap();
8681        assert_eq!(
8682            txn.get(b"conflict-key").await.unwrap(),
8683            Some(Bytes::from_static(b"v1"))
8684        );
8685
8686        // Commit and flush seq=2 for the same key. This advances the L0
8687        // manifest writer through seq=2 and creates the transaction conflict.
8688        db.put_with_options(b"conflict-key", b"v2", &PutOptions::default(), &write_opts)
8689            .await
8690            .unwrap();
8691        tokio::time::timeout(
8692            Duration::from_secs(5),
8693            db.flush_with_options(FlushOptions {
8694                flush_type: FlushType::MemTable,
8695            }),
8696        )
8697        .await
8698        .expect("timed out flushing conflict-producing write")
8699        .expect("conflict-producing write flush should succeed");
8700
8701        // The transaction commit must fail, but the bug is that write_batch
8702        // allocates commit_seq=3 before conflict detection and never writes it
8703        // into a memtable. This leaves a durable-memtable sequence hole.
8704        txn.put(b"txn-write", b"will-conflict").unwrap();
8705        let err = txn.commit().await.expect_err("transaction should conflict");
8706        assert_eq!(err.kind(), crate::ErrorKind::Transaction);
8707
8708        // Write enough data to freeze an immutable memtable. Its first sequence
8709        // is 4 because seq=3 was consumed by the failed transaction commit.
8710        let large_value = vec![b'x'; LARGE_VALUE_BYTES];
8711        let large_handle = db
8712            .put_with_options(
8713                b"large-after-conflict",
8714                &large_value,
8715                &PutOptions::default(),
8716                &write_opts,
8717            )
8718            .await
8719            .expect("large write after conflict should be accepted");
8720        assert_eq!(
8721            large_handle.seqnum(),
8722            4,
8723            "the conflicted commit should have consumed commit_seq=3"
8724        );
8725        tokio::time::timeout(
8726            Duration::from_secs(5),
8727            db.flush_with_options(FlushOptions {
8728                flush_type: FlushType::Wal,
8729            }),
8730        )
8731        .await
8732        .expect("timed out flushing WAL after large write")
8733        .expect("WAL flush after large write should succeed");
8734
8735        // Wait until the large write either reaches L0 or is visible as an
8736        // immutable memtable. On the buggy path it is uploaded, but cannot be
8737        // retired because the manifest writer is still waiting for seq=3.
8738        let large_seq = large_handle.seqnum();
8739        tokio::time::timeout(Duration::from_secs(5), async {
8740            loop {
8741                let (last_l0_seq, has_immutable_memtable) = {
8742                    let guard = db.inner.state.read();
8743                    (
8744                        guard.state().core().last_l0_seq,
8745                        !guard.state().imm_memtable.is_empty(),
8746                    )
8747                };
8748                if last_l0_seq >= large_seq || has_immutable_memtable {
8749                    break;
8750                }
8751                tokio::time::sleep(Duration::from_millis(10)).await;
8752            }
8753        })
8754        .await
8755        .expect("large write never froze or flushed");
8756
8757        // This write enters backpressure because the stuck immutable memtable
8758        // remains charged against max_unflushed_bytes. The expected failure is
8759        // this timeout, which proves the hang without letting the test run
8760        // forever.
8761        tokio::time::timeout(
8762            Duration::from_secs(5),
8763            db.put_with_options(
8764                b"write-after-gap",
8765                b"v",
8766                &PutOptions::default(),
8767                &write_opts,
8768            ),
8769        )
8770        .await
8771        .expect("timed out waiting for write after conflicted transaction consumed commit_seq=3")
8772        .expect("write after conflicted commit sequence gap should succeed");
8773
8774        db.close().await.unwrap();
8775    }
8776
8777    #[tokio::test]
8778    async fn should_notify_seq_watcher_on_wal_flush() {
8779        // Given: a DB with WAL enabled and a seq watcher
8780        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
8781        let db = Db::builder("/tmp/test_watch_wal", object_store)
8782            .build()
8783            .await
8784            .unwrap();
8785        let mut watcher = db.subscribe();
8786
8787        // When: writing multiple keys and flushing the WAL
8788        db.put(b"key1", b"value1").await.unwrap();
8789        db.put(b"key2", b"value2").await.unwrap();
8790        db.put(b"key3", b"value3").await.unwrap();
8791        db.flush_with_options(FlushOptions {
8792            flush_type: FlushType::Wal,
8793        })
8794        .await
8795        .unwrap();
8796
8797        // Then: the watcher should report durable_seq >= 3
8798        let status = tokio::time::timeout(
8799            Duration::from_secs(10),
8800            watcher.wait_for(|s| s.durable_seq >= 3),
8801        )
8802        .await
8803        .expect("timed out waiting for seq update")
8804        .expect("watch channel closed")
8805        .clone();
8806        assert!(
8807            status.durable_seq >= 3,
8808            "expected durable seq >= 3, got {}",
8809            status.durable_seq
8810        );
8811
8812        db.close().await.unwrap();
8813    }
8814
8815    #[tokio::test]
8816    async fn should_subscribe_to_current_manifest_updates_after_flush() {
8817        // Given: a DB with a watcher and manifest access
8818        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
8819        let path = Path::from("/tmp/test_watch_current_manifest");
8820        let db = Db::builder(path.clone(), object_store.clone())
8821            .with_settings(test_db_options(0, 1024, None))
8822            .build()
8823            .await
8824            .unwrap();
8825        let mut watcher = db.subscribe();
8826        let manifest_store = Arc::new(ManifestStore::new(&path, object_store));
8827        let mut stored_manifest =
8828            StoredManifest::load(manifest_store, Arc::new(DefaultSystemClock::new()))
8829                .await
8830                .unwrap();
8831
8832        assert_eq!(watcher.borrow().current_manifest, db.manifest());
8833
8834        // When: writes are flushed to an L0 and the manifest is updated
8835        db.put(b"key1", b"value1").await.unwrap();
8836        db.put(b"key2", b"value2").await.unwrap();
8837        db.flush_with_options(FlushOptions {
8838            flush_type: FlushType::MemTable,
8839        })
8840        .await
8841        .unwrap();
8842        wait_for_manifest_condition(
8843            &mut stored_manifest,
8844            |manifest| manifest.last_l0_seq >= 2,
8845            Duration::from_secs(10),
8846        )
8847        .await;
8848
8849        // Then: subscribe reports the updated manifest and durability frontier
8850        let status = tokio::time::timeout(
8851            Duration::from_secs(10),
8852            watcher.wait_for(|s| {
8853                s.current_manifest.manifest.core.last_l0_seq >= 2 && s.durable_seq >= 2
8854            }),
8855        )
8856        .await
8857        .expect("timed out waiting for manifest update")
8858        .expect("watch channel closed")
8859        .clone();
8860        assert!(status.durable_seq >= 2);
8861        assert_eq!(status.current_manifest.manifest.core.last_l0_seq, 2);
8862
8863        db.close().await.unwrap();
8864    }
8865
8866    #[tokio::test]
8867    async fn should_publish_remote_manifest_updates_via_poll() {
8868        // Given: a DB with a watcher
8869        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
8870        let path = Path::from("/tmp/test_watch_remote_manifest");
8871        let db = Db::builder(path.clone(), object_store.clone())
8872            .with_settings(test_db_options(0, 1024, None))
8873            .build()
8874            .await
8875            .unwrap();
8876        let mut watcher = db.subscribe();
8877        let initial_checkpoint_count = watcher
8878            .borrow()
8879            .current_manifest
8880            .manifest
8881            .core
8882            .checkpoints
8883            .len();
8884
8885        let manifest_store = Arc::new(ManifestStore::new(&path, object_store));
8886        let mut stored_manifest =
8887            StoredManifest::load(manifest_store, Arc::new(DefaultSystemClock::new()))
8888                .await
8889                .unwrap();
8890
8891        // When: another writer updates the manifest
8892        stored_manifest
8893            .write_checkpoint(uuid::Uuid::new_v4(), &CheckpointOptions::default())
8894            .await
8895            .unwrap();
8896        wait_for_manifest_condition(
8897            &mut stored_manifest,
8898            |manifest| manifest.checkpoints.len() > initial_checkpoint_count,
8899            Duration::from_secs(10),
8900        )
8901        .await;
8902
8903        // Then: subscribe eventually reports the merged manifest
8904        let status = tokio::time::timeout(
8905            Duration::from_secs(10),
8906            watcher.wait_for(|s| {
8907                s.current_manifest.manifest.core.checkpoints.len() > initial_checkpoint_count
8908            }),
8909        )
8910        .await
8911        .expect("timed out waiting for remote manifest update")
8912        .expect("watch channel closed")
8913        .clone();
8914        assert_eq!(
8915            status.current_manifest.manifest.core.checkpoints.len(),
8916            initial_checkpoint_count + 1
8917        );
8918
8919        db.close().await.unwrap();
8920    }
8921
8922    #[tokio::test]
8923    async fn should_close_watcher_on_db_drop() {
8924        // Given: a DB with a watcher
8925        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
8926        let db = Db::builder("/tmp/test_watch_drop", object_store)
8927            .build()
8928            .await
8929            .unwrap();
8930        let mut watcher = db.subscribe();
8931
8932        // When: the DB is closed
8933        db.close().await.unwrap();
8934
8935        // Then: the watcher should report close_reason = Clean
8936        let status = watcher
8937            .wait_for(|s| s.close_reason.is_some())
8938            .await
8939            .expect("watch channel closed")
8940            .clone();
8941        assert_eq!(
8942            status.close_reason,
8943            Some(CloseReason::Clean),
8944            "expected close_reason = Clean after db close",
8945        );
8946
8947        // When: the DB is dropped (drops the watch sender)
8948        drop(db);
8949
8950        // Then: the watcher's changed() should return Err (channel closed)
8951        let result = watcher.changed().await;
8952        assert!(
8953            result.is_err(),
8954            "expected watch channel closed after db drop, got Ok",
8955        );
8956    }
8957
8958    #[tokio::test]
8959    async fn should_report_close_reason_clean_on_db_close() {
8960        // Given: a DB with a watcher
8961        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
8962        let db = Db::builder("/tmp/test_close_reason_clean", object_store)
8963            .build()
8964            .await
8965            .unwrap();
8966        let mut watcher = db.subscribe();
8967
8968        // When: the DB is closed cleanly
8969        db.close().await.unwrap();
8970
8971        // Then: the watcher should report close_reason = Clean
8972        let status = watcher
8973            .wait_for(|s| s.close_reason.is_some())
8974            .await
8975            .expect("watch channel closed")
8976            .clone();
8977        assert_eq!(status.close_reason, Some(CloseReason::Clean));
8978    }
8979
8980    #[tokio::test]
8981    async fn should_report_close_reason_panic_on_background_task_failure() {
8982        // Given: a DB with a failpoint on WAL flush and a watcher
8983        let fp_registry = Arc::new(FailPointRegistry::new());
8984        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
8985        let db = Db::builder("/tmp/test_close_reason_panic", object_store)
8986            .with_settings(test_db_options(0, 128, None))
8987            .with_fp_registry(fp_registry.clone())
8988            .build()
8989            .await
8990            .unwrap();
8991        let mut watcher = db.subscribe();
8992
8993        // When: a background task panics
8994        fail_parallel::cfg(fp_registry.clone(), "write-wal-sst-io-error", "panic").unwrap();
8995        let _ = db.put(b"foo", b"bar").await;
8996
8997        // Then: the watcher should report close_reason = Panic
8998        let status = tokio::time::timeout(
8999            Duration::from_secs(10),
9000            watcher.wait_for(|s| s.close_reason.is_some()),
9001        )
9002        .await
9003        .expect("timed out waiting for close reason")
9004        .expect("watch channel closed")
9005        .clone();
9006        assert_eq!(status.close_reason, Some(CloseReason::Panic));
9007    }
9008
9009    #[tokio::test]
9010    async fn should_report_close_reason_fenced_on_fenced_error() {
9011        // Given: a DB with a watcher
9012        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9013        let db = Db::builder("/tmp/test_close_reason_fenced", object_store)
9014            .with_settings(test_db_options(0, 1024, None))
9015            .build()
9016            .await
9017            .unwrap();
9018        let mut watcher = db.subscribe();
9019
9020        // When: the DB is fenced (simulated via closed_result)
9021        db.inner
9022            .status_manager
9023            .write_result(Err(crate::error::SlateDBError::Fenced));
9024
9025        // Then: the watcher should report close_reason = Fenced
9026        let status = tokio::time::timeout(
9027            Duration::from_secs(10),
9028            watcher.wait_for(|s| s.close_reason.is_some()),
9029        )
9030        .await
9031        .expect("timed out waiting for close reason")
9032        .expect("watch channel closed")
9033        .clone();
9034        assert_eq!(status.close_reason, Some(CloseReason::Fenced));
9035    }
9036
9037    #[cfg(feature = "wal_disable")]
9038    #[tokio::test]
9039    async fn should_notify_seq_watcher_on_l0_flush_when_wal_disabled() {
9040        // Given: a DB with WAL disabled and a seq watcher
9041        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9042        let mut options = test_db_options(0, 256, None);
9043        options.wal_enabled = false;
9044        let db = Db::builder("/tmp/test_watch_l0", object_store)
9045            .with_settings(options)
9046            .build()
9047            .await
9048            .unwrap();
9049        let mut watcher = db.subscribe();
9050
9051        // When: writing multiple keys and flushing the memtable to L0
9052        db.put_with_options(
9053            b"key1",
9054            b"value1",
9055            &PutOptions::default(),
9056            &WriteOptions {
9057                await_durable: false,
9058                ..Default::default()
9059            },
9060        )
9061        .await
9062        .unwrap();
9063        db.put_with_options(
9064            b"key2",
9065            b"value2",
9066            &PutOptions::default(),
9067            &WriteOptions {
9068                await_durable: false,
9069                ..Default::default()
9070            },
9071        )
9072        .await
9073        .unwrap();
9074        db.flush_with_options(FlushOptions {
9075            flush_type: FlushType::MemTable,
9076        })
9077        .await
9078        .unwrap();
9079
9080        // Then: the watcher should report durable_seq >= 2
9081        let status = tokio::time::timeout(
9082            Duration::from_secs(10),
9083            watcher.wait_for(|s| s.durable_seq >= 2),
9084        )
9085        .await
9086        .expect("timed out waiting for seq update")
9087        .expect("watch channel closed")
9088        .clone();
9089        assert!(
9090            status.durable_seq >= 2,
9091            "expected durable seq >= 2, got {}",
9092            status.durable_seq
9093        );
9094
9095        db.close().await.unwrap();
9096    }
9097
9098    #[cfg(feature = "wal_disable")]
9099    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
9100    async fn reads_succeed_when_compacted_sr_splits_same_key_across_ssts() {
9101        use crate::SstBlockSize;
9102        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9103        let path = "/tmp/test_merge_split_sr_repro";
9104        let should_compact = Arc::new(AtomicBool::new(false));
9105        let should_compact2 = should_compact.clone();
9106        let compaction_scheduler = Arc::new(OnDemandCompactionSchedulerSupplier::new(Arc::new(
9107            move |_state| {
9108                let result = should_compact2
9109                    .compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst)
9110                    .unwrap_or(false);
9111                if result {
9112                    info!("TRIGGER COMPACT");
9113                }
9114                result
9115            },
9116        )));
9117
9118        // Force frequent L0 flushes and tiny compacted SSTs so a single SR ends up with multiple SSTs.
9119        let mut settings = test_db_options(
9120            0,
9121            128,
9122            Some(CompactorOptions {
9123                poll_interval: Duration::from_millis(20),
9124                max_concurrent_compactions: 1,
9125                manifest_update_timeout: Duration::from_secs(300),
9126                worker: Some(CompactionWorkerOptions {
9127                    max_sst_size: 128,
9128                    ..Default::default()
9129                }),
9130                ..Default::default()
9131            }),
9132        );
9133        settings.l0_max_ssts = 10_000;
9134        settings.l0_max_ssts_per_key = 10_000;
9135        settings.flush_interval = None;
9136        settings.wal_enabled = false;
9137
9138        let compactor_options = settings.compactor_options.take().unwrap();
9139        let db = Db::builder(path, object_store.clone())
9140            .with_settings(settings)
9141            .with_sst_block_size(SstBlockSize::Other(64))
9142            .with_merge_operator(Arc::new(StringConcatMergeOperator))
9143            .with_compactor_builder(
9144                CompactorBuilder::new(path, object_store.clone())
9145                    .with_scheduler_supplier(compaction_scheduler)
9146                    .with_options(compactor_options),
9147            )
9148            .build()
9149            .await
9150            .unwrap();
9151
9152        // Write a base value and keep a snapshot alive so later versions are retained by compaction.
9153        db.put_with_options(
9154            b"k",
9155            b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0",
9156            &PutOptions::default(),
9157            &WriteOptions {
9158                await_durable: false,
9159                ..Default::default()
9160            },
9161        )
9162        .await
9163        .unwrap();
9164        let _snapshot = db.snapshot().await.unwrap();
9165
9166        // Write many merge operands for the same key, each forced into L0.
9167        for i in 0..16u16 {
9168            let val = format!("{}{}", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", i + 1);
9169            db.put_with_options(
9170                b"k",
9171                val.as_bytes(),
9172                &PutOptions::default(),
9173                &WriteOptions {
9174                    await_durable: false,
9175                    ..Default::default()
9176                },
9177            )
9178            .await
9179            .unwrap();
9180        }
9181
9182        // Flush all pending writes to L0 before triggering compaction.
9183        db.flush_with_options(FlushOptions {
9184            flush_type: FlushType::MemTable,
9185        })
9186        .await
9187        .unwrap();
9188
9189        // Compact until we observe a sorted run where a single logical key spans
9190        // multiple SSTs. Re-arm should_compact each iteration so partial compactions
9191        // can be followed by additional rounds.
9192        tokio::time::timeout(Duration::from_secs(60), async {
9193            loop {
9194                should_compact.store(true, Ordering::SeqCst);
9195                {
9196                    let state = db.inner.state.read();
9197                    info!(
9198                        "l0: {:?}",
9199                        state
9200                            .state()
9201                            .core()
9202                            .tree
9203                            .l0
9204                            .iter()
9205                            .map(|t| t.estimate_size())
9206                            .collect::<Vec<_>>()
9207                    );
9208                    info!(
9209                        "compacted: {:?}",
9210                        state
9211                            .state()
9212                            .core()
9213                            .tree
9214                            .compacted
9215                            .iter()
9216                            .map(|t| t.estimate_size())
9217                            .collect::<Vec<_>>()
9218                    );
9219                    if state
9220                        .state()
9221                        .core()
9222                        .tree
9223                        .compacted
9224                        .first()
9225                        .is_some_and(|sr| sr.sst_views.len() > 1)
9226                    {
9227                        break;
9228                    }
9229                }
9230                tokio::time::sleep(Duration::from_millis(3000)).await;
9231            }
9232        })
9233        .await
9234        .expect("timed out waiting for compacted SR where one key spans multiple SSTs");
9235
9236        let data = db.get(b"k").await.unwrap().unwrap();
9237        let expected = Bytes::from(b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa16".as_ref());
9238        let data_scan = db
9239            .scan(b"k".as_slice()..)
9240            .await
9241            .unwrap()
9242            .next()
9243            .await
9244            .unwrap()
9245            .unwrap();
9246        info!("data: {:?}", data);
9247        info!("data (scan): {:?}", data_scan.value);
9248        assert_eq!(data, expected);
9249        assert_eq!(data_scan.value, expected);
9250    }
9251
9252    #[cfg(feature = "wal_disable")]
9253    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
9254    async fn reads_succeed_when_compacted_sr_splits_same_merge_key_across_ssts() {
9255        use crate::SstBlockSize;
9256        use bytes::{BufMut as _, BytesMut};
9257        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9258        let path = "/tmp/test_merge_split_sr_repro";
9259        let should_compact = Arc::new(AtomicBool::new(false));
9260        let should_compact2 = should_compact.clone();
9261        let compaction_scheduler = Arc::new(OnDemandCompactionSchedulerSupplier::new(Arc::new(
9262            move |_state| {
9263                let result = should_compact2
9264                    .compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst)
9265                    .unwrap_or(false);
9266                if result {
9267                    info!("TRIGGER COMPACT");
9268                }
9269                result
9270            },
9271        )));
9272
9273        // Force frequent L0 flushes and tiny compacted SSTs so a single SR ends up with multiple SSTs.
9274        let mut settings = test_db_options(
9275            0,
9276            128,
9277            Some(CompactorOptions {
9278                poll_interval: Duration::from_millis(20),
9279                max_concurrent_compactions: 1,
9280                manifest_update_timeout: Duration::from_secs(300),
9281                worker: Some(CompactionWorkerOptions {
9282                    max_sst_size: 128,
9283                    ..Default::default()
9284                }),
9285                ..Default::default()
9286            }),
9287        );
9288        settings.l0_max_ssts = 10_000;
9289        settings.l0_max_ssts_per_key = 10_000;
9290        settings.flush_interval = None;
9291        settings.wal_enabled = false;
9292
9293        let compactor_options = settings.compactor_options.take().unwrap();
9294        let db = Db::builder(path, object_store.clone())
9295            .with_settings(settings)
9296            .with_sst_block_size(SstBlockSize::Other(64))
9297            .with_merge_operator(Arc::new(StringConcatMergeOperator))
9298            .with_compactor_builder(
9299                CompactorBuilder::new(path, object_store.clone())
9300                    .with_scheduler_supplier(compaction_scheduler)
9301                    .with_options(compactor_options),
9302            )
9303            .build()
9304            .await
9305            .unwrap();
9306
9307        // Write a base value and keep a snapshot alive so later versions are retained by compaction.
9308        let mut expected = BytesMut::new();
9309        db.put_with_options(
9310            b"k",
9311            b"base",
9312            &PutOptions::default(),
9313            &WriteOptions {
9314                await_durable: false,
9315                ..Default::default()
9316            },
9317        )
9318        .await
9319        .unwrap();
9320        expected.put(b"base".as_slice());
9321        let _snapshot = db.snapshot().await.unwrap();
9322
9323        // Write distinct merge operands so the final value also verifies operand ordering.
9324        for i in 0..16u8 {
9325            let operand = vec![b'a' + i; 32];
9326            expected.put(operand.as_slice());
9327            db.merge_with_options(
9328                b"k",
9329                operand,
9330                &MergeOptions::default(),
9331                &WriteOptions {
9332                    await_durable: false,
9333                    ..Default::default()
9334                },
9335            )
9336            .await
9337            .unwrap();
9338        }
9339
9340        // Flush all pending writes to L0 before triggering compaction.
9341        db.flush_with_options(FlushOptions {
9342            flush_type: FlushType::MemTable,
9343        })
9344        .await
9345        .unwrap();
9346
9347        // Compact until we observe a sorted run where a single logical key spans
9348        // multiple SSTs. Re-arm should_compact each iteration so partial compactions
9349        // can be followed by additional rounds.
9350        tokio::time::timeout(Duration::from_secs(60), async {
9351            loop {
9352                should_compact.store(true, Ordering::SeqCst);
9353                {
9354                    let state = db.inner.state.read();
9355                    info!(
9356                        "l0: {:?}",
9357                        state
9358                            .state()
9359                            .core()
9360                            .tree
9361                            .l0
9362                            .iter()
9363                            .map(|t| t.estimate_size())
9364                            .collect::<Vec<_>>()
9365                    );
9366                    info!(
9367                        "compacted: {:?}",
9368                        state
9369                            .state()
9370                            .core()
9371                            .tree
9372                            .compacted
9373                            .iter()
9374                            .map(|t| t.estimate_size())
9375                            .collect::<Vec<_>>()
9376                    );
9377                    if state
9378                        .state()
9379                        .core()
9380                        .tree
9381                        .compacted
9382                        .first()
9383                        .is_some_and(|sr| sr.sst_views.len() > 1)
9384                    {
9385                        break;
9386                    }
9387                }
9388                tokio::time::sleep(Duration::from_millis(3000)).await;
9389            }
9390        })
9391        .await
9392        .expect("timed out waiting for compacted SR where one key spans multiple SSTs");
9393
9394        let data = db.get(b"k").await.unwrap().unwrap();
9395        let expected = expected.freeze();
9396        let data_scan = db
9397            .scan(b"k".as_slice()..)
9398            .await
9399            .unwrap()
9400            .next()
9401            .await
9402            .unwrap()
9403            .unwrap();
9404        info!("data: {:?}", data);
9405        info!("data (scan): {:?}", data_scan.value);
9406        assert_eq!(data, expected);
9407        assert_eq!(data_scan.value, expected);
9408    }
9409
9410    #[tokio::test]
9411    async fn test_get_key_value() {
9412        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9413        let path = "/tmp/test_get_key_value";
9414        let clock = Arc::new(MockSystemClock::new());
9415        let db = Db::builder(path, object_store)
9416            .with_settings(test_db_options(0, 1024, None))
9417            .with_system_clock(clock.clone())
9418            .build()
9419            .await
9420            .unwrap();
9421
9422        clock.set(100);
9423        let key = b"key1";
9424        let value = b"value1";
9425        db.put_with_options(
9426            key,
9427            value,
9428            &PutOptions {
9429                ttl: Ttl::ExpireAfter(50),
9430            },
9431            &WriteOptions {
9432                await_durable: false,
9433                ..Default::default()
9434            },
9435        )
9436        .await
9437        .unwrap();
9438
9439        let kv = db.get_key_value(key).await.unwrap().unwrap();
9440        assert_eq!(kv.key, Bytes::from_static(key));
9441        assert_eq!(kv.value, Bytes::from_static(value));
9442        assert_eq!(kv.seq, 1);
9443        assert_eq!(kv.create_ts, 100);
9444        assert_eq!(kv.expire_ts, Some(150));
9445    }
9446
9447    #[tokio::test]
9448    async fn test_scan_row_entry() {
9449        use crate::types::ValueDeletable;
9450
9451        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9452        let path = "/tmp/test_scan_row_entry";
9453        let clock = Arc::new(MockSystemClock::new());
9454        let db = Db::builder(path, object_store)
9455            .with_settings(test_db_options(0, 1024, None))
9456            .with_system_clock(clock.clone())
9457            .build()
9458            .await
9459            .unwrap();
9460
9461        let put_opts = PutOptions {
9462            ttl: Ttl::ExpireAfter(50),
9463        };
9464        let write_opts = WriteOptions {
9465            await_durable: false,
9466            ..Default::default()
9467        };
9468
9469        clock.set(100);
9470        db.put_with_options(b"key1", b"value1", &put_opts, &write_opts)
9471            .await
9472            .unwrap();
9473
9474        clock.set(110);
9475        db.put_with_options(b"key2", b"value2", &put_opts, &write_opts)
9476            .await
9477            .unwrap();
9478
9479        clock.set(120);
9480        db.put_with_options(b"key3", b"value3", &put_opts, &write_opts)
9481            .await
9482            .unwrap();
9483
9484        let mut iter = db.scan(..).await.unwrap();
9485
9486        let row_entry1 = iter.next_entry().await.unwrap().unwrap();
9487        assert_eq!(row_entry1.key, Bytes::from_static(b"key1"));
9488        assert_eq!(
9489            row_entry1.value,
9490            ValueDeletable::Value(Bytes::from_static(b"value1"))
9491        );
9492        assert_eq!(row_entry1.seq, 1);
9493        assert_eq!(row_entry1.create_ts, Some(100));
9494        assert_eq!(row_entry1.expire_ts, Some(150));
9495
9496        let row_entry2 = iter.next_entry().await.unwrap().unwrap();
9497        assert_eq!(row_entry2.key, Bytes::from_static(b"key2"));
9498        assert_eq!(
9499            row_entry2.value,
9500            ValueDeletable::Value(Bytes::from_static(b"value2"))
9501        );
9502        assert_eq!(row_entry2.seq, 2);
9503        assert_eq!(row_entry2.create_ts, Some(110));
9504        assert_eq!(row_entry2.expire_ts, Some(160));
9505
9506        let row_entry3 = iter.next_entry().await.unwrap().unwrap();
9507        assert_eq!(row_entry3.key, Bytes::from_static(b"key3"));
9508        assert_eq!(
9509            row_entry3.value,
9510            ValueDeletable::Value(Bytes::from_static(b"value3"))
9511        );
9512        assert_eq!(row_entry3.seq, 3);
9513        assert_eq!(row_entry3.create_ts, Some(120));
9514        assert_eq!(row_entry3.expire_ts, Some(170));
9515
9516        assert!(iter.next_entry().await.unwrap().is_none());
9517    }
9518
9519    #[tokio::test]
9520    async fn should_get_key_value_with_expire_at() {
9521        // given
9522        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9523        let path = "/tmp/test_get_key_value_expire_at";
9524        let clock = Arc::new(MockSystemClock::new());
9525        let db = Db::builder(path, object_store)
9526            .with_settings(test_db_options(0, 1024, None))
9527            .with_system_clock(clock.clone())
9528            .build()
9529            .await
9530            .unwrap();
9531
9532        // when: write with ExpireAt at different clock times
9533        clock.set(100);
9534        db.put_with_options(
9535            b"key1",
9536            b"value1",
9537            &PutOptions {
9538                ttl: Ttl::ExpireAt(500),
9539            },
9540            &WriteOptions {
9541                await_durable: false,
9542                ..Default::default()
9543            },
9544        )
9545        .await
9546        .unwrap();
9547
9548        clock.set(200);
9549        db.put_with_options(
9550            b"key2",
9551            b"value2",
9552            &PutOptions {
9553                ttl: Ttl::ExpireAt(500),
9554            },
9555            &WriteOptions {
9556                await_durable: false,
9557                ..Default::default()
9558            },
9559        )
9560        .await
9561        .unwrap();
9562
9563        // then: both keys have the same expire_ts regardless of write time
9564        let kv1 = db.get_key_value(b"key1").await.unwrap().unwrap();
9565        assert_eq!(kv1.expire_ts, Some(500));
9566        assert_eq!(kv1.create_ts, 100);
9567
9568        let kv2 = db.get_key_value(b"key2").await.unwrap().unwrap();
9569        assert_eq!(kv2.expire_ts, Some(500));
9570        assert_eq!(kv2.create_ts, 200);
9571    }
9572
9573    #[tokio::test]
9574    async fn test_should_record_scan_request_count() {
9575        // given:
9576        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9577        let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
9578        let db = Db::builder("/tmp/test_should_record_scan_request_count", object_store)
9579            .with_metrics_recorder(metrics_recorder.clone())
9580            .build()
9581            .await
9582            .unwrap();
9583        db.put(b"k1", b"v1").await.unwrap();
9584
9585        // when:
9586        let mut iter = db.scan(..).await.unwrap();
9587        let _ = iter.next().await;
9588
9589        // then:
9590        assert_eq!(
9591            lookup_metric_with_labels(
9592                &metrics_recorder,
9593                crate::db_stats::REQUEST_COUNT,
9594                &[("op", "scan")]
9595            ),
9596            Some(1)
9597        );
9598        db.close().await.unwrap();
9599    }
9600
9601    #[tokio::test]
9602    async fn test_should_record_flush_request_count() {
9603        // given:
9604        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9605        let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
9606        let db = Db::builder("/tmp/test_should_record_flush_request_count", object_store)
9607            .with_metrics_recorder(metrics_recorder.clone())
9608            .build()
9609            .await
9610            .unwrap();
9611        db.put(b"k1", b"v1").await.unwrap();
9612
9613        // when:
9614        db.flush().await.unwrap();
9615
9616        // then:
9617        assert_eq!(
9618            lookup_metric_with_labels(
9619                &metrics_recorder,
9620                crate::db_stats::REQUEST_COUNT,
9621                &[("op", "flush")]
9622            ),
9623            Some(1)
9624        );
9625        db.close().await.unwrap();
9626    }
9627
9628    #[tokio::test]
9629    async fn test_should_record_write_ops_and_batch_count() {
9630        // given:
9631        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9632        let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
9633        let db = Db::builder(
9634            "/tmp/test_should_record_write_ops_and_batch_count",
9635            object_store,
9636        )
9637        .with_metrics_recorder(metrics_recorder.clone())
9638        .build()
9639        .await
9640        .unwrap();
9641
9642        // when:
9643        db.put(b"k1", b"v1").await.unwrap();
9644        db.put(b"k2", b"v2").await.unwrap();
9645
9646        // then:
9647        assert_eq!(
9648            lookup_metric(&metrics_recorder, crate::db_stats::WRITE_OPS),
9649            Some(2)
9650        );
9651        assert_eq!(
9652            lookup_metric(&metrics_recorder, crate::db_stats::WRITE_BATCH_COUNT),
9653            Some(2)
9654        );
9655        db.close().await.unwrap();
9656    }
9657
9658    #[tokio::test]
9659    async fn test_should_record_merge_operator_operands_on_flush_path_during_batch_write() {
9660        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9661        let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
9662        let path =
9663            "/tmp/test_should_record_merge_operator_operands_on_flush_path_during_batch_write";
9664        let mut options = test_db_options(0, 1024, None);
9665        options.flush_interval = None;
9666        options.max_unflushed_bytes = 1024 * 1024;
9667        let db = Db::builder(path, object_store.clone())
9668            .with_settings(options)
9669            .with_metrics_recorder(metrics_recorder.clone())
9670            .with_merge_operator(Arc::new(StringConcatMergeOperator))
9671            .build()
9672            .await
9673            .unwrap();
9674
9675        let mut batch = WriteBatch::new();
9676        batch.merge(b"key1", b"a");
9677        batch.merge(b"key1", b"b");
9678        db.write_with_options(
9679            batch,
9680            &WriteOptions {
9681                await_durable: false,
9682                ..Default::default()
9683            },
9684        )
9685        .await
9686        .unwrap();
9687
9688        assert_eq!(
9689            lookup_merge_operator_operands(&metrics_recorder, MERGE_OPERATOR_READ_PATH),
9690            Some(0)
9691        );
9692        assert_eq!(
9693            lookup_merge_operator_operands(&metrics_recorder, MERGE_OPERATOR_FLUSH_PATH),
9694            Some(3)
9695        );
9696        assert!(
9697            lookup_merge_operator_operands(&metrics_recorder, MERGE_OPERATOR_COMPACT_PATH)
9698                .is_none_or(|value| value == 0)
9699        );
9700
9701        db.close().await.unwrap();
9702    }
9703
9704    #[tokio::test]
9705    async fn should_reject_batch_local_merges_across_differing_ttls() {
9706        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9707        let db = Db::builder(
9708            "/tmp/test_reject_batch_local_merges_across_differing_ttls",
9709            object_store,
9710        )
9711        .with_settings(test_db_options(0, 1024, None))
9712        .with_merge_operator(Arc::new(StringConcatMergeOperator))
9713        .build()
9714        .await
9715        .unwrap();
9716
9717        let mut batch = WriteBatch::new();
9718        batch.merge_with_options(
9719            b"key1",
9720            b"a",
9721            &MergeOptions {
9722                ttl: Ttl::ExpireAfter(3600),
9723            },
9724        );
9725        batch.merge_with_options(
9726            b"key1",
9727            b"b",
9728            &MergeOptions {
9729                ttl: Ttl::ExpireAfter(7200),
9730            },
9731        );
9732
9733        let err = db.write(batch).await.unwrap_err();
9734        assert_eq!(err.kind(), crate::ErrorKind::Invalid);
9735        assert!(
9736            err.to_string()
9737                .contains("only one merge TTL per-key allowed"),
9738            "unexpected error: {err}"
9739        );
9740        assert_eq!(db.get(b"key1").await.unwrap(), None);
9741
9742        db.close().await.unwrap();
9743    }
9744
9745    #[cfg(feature = "wal_disable")]
9746    #[tokio::test]
9747    async fn test_should_record_total_mem_size_bytes_with_wal_disabled() {
9748        // given: WAL disabled, so writes land only in the active memtable.
9749        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9750        let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
9751        let mut opts = test_db_options(0, 1024, None);
9752        opts.flush_interval = None;
9753        opts.max_unflushed_bytes = 1024 * 1024;
9754        opts.wal_enabled = false;
9755        let db = Db::builder(
9756            "/tmp/test_should_record_total_mem_size_bytes_with_wal_disabled",
9757            object_store,
9758        )
9759        .with_settings(opts)
9760        .with_metrics_recorder(metrics_recorder.clone())
9761        .build()
9762        .await
9763        .unwrap();
9764
9765        // when: two writes (the second triggers maybe_apply_backpressure for the first's bytes)
9766        let write_opts = WriteOptions {
9767            await_durable: false,
9768            ..Default::default()
9769        };
9770        db.put_with_options(b"k1", b"v1", &PutOptions::default(), &write_opts)
9771            .await
9772            .unwrap();
9773        db.put_with_options(b"k2", b"v2", &PutOptions::default(), &write_opts)
9774            .await
9775            .unwrap();
9776
9777        // then: total_mem_size_bytes reflects the active memtable even with the WAL off
9778        let mem_size = lookup_metric(&metrics_recorder, crate::db_stats::TOTAL_MEM_SIZE_BYTES);
9779        assert!(
9780            mem_size.is_some_and(|v| v > 0),
9781            "expected total_mem_size_bytes > 0 with WAL disabled, got {:?}",
9782            mem_size
9783        );
9784        db.close().await.unwrap();
9785    }
9786
9787    #[tokio::test]
9788    async fn test_should_record_total_mem_size_bytes() {
9789        // given:
9790        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9791        let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
9792        let mut opts = test_db_options(0, 1024, None);
9793        opts.flush_interval = None;
9794        opts.max_unflushed_bytes = 1024 * 1024;
9795        let db = Db::builder("/tmp/test_should_record_total_mem_size_bytes", object_store)
9796            .with_settings(opts)
9797            .with_metrics_recorder(metrics_recorder.clone())
9798            .build()
9799            .await
9800            .unwrap();
9801
9802        // when: write without awaiting durability so data stays in WAL buffer
9803        db.put_with_options(
9804            b"k1",
9805            b"v1",
9806            &PutOptions::default(),
9807            &WriteOptions {
9808                await_durable: false,
9809                ..Default::default()
9810            },
9811        )
9812        .await
9813        .unwrap();
9814        // Second write so maybe_apply_backpressure sees the first write's bytes
9815        db.put_with_options(
9816            b"k2",
9817            b"v2",
9818            &PutOptions::default(),
9819            &WriteOptions {
9820                await_durable: false,
9821                ..Default::default()
9822            },
9823        )
9824        .await
9825        .unwrap();
9826
9827        // then: total_mem_size_bytes is updated via maybe_apply_backpressure
9828        let mem_size = lookup_metric(&metrics_recorder, crate::db_stats::TOTAL_MEM_SIZE_BYTES);
9829        assert!(
9830            mem_size.is_some_and(|v| v > 0),
9831            "expected total_mem_size_bytes > 0, got {:?}",
9832            mem_size
9833        );
9834        db.close().await.unwrap();
9835    }
9836
9837    #[tokio::test]
9838    async fn test_should_record_wal_buffer_estimated_bytes() {
9839        // given:
9840        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9841        let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
9842        let mut opts = test_db_options(0, 1024, None);
9843        opts.flush_interval = None;
9844        let db = Db::builder(
9845            "/tmp/test_should_record_wal_buffer_estimated_bytes",
9846            object_store,
9847        )
9848        .with_settings(opts)
9849        .with_metrics_recorder(metrics_recorder.clone())
9850        .build()
9851        .await
9852        .unwrap();
9853
9854        // when:
9855        db.put_with_options(
9856            b"k1",
9857            b"v1",
9858            &PutOptions::default(),
9859            &WriteOptions {
9860                await_durable: false,
9861                ..Default::default()
9862            },
9863        )
9864        .await
9865        .unwrap();
9866
9867        // then:
9868        let estimated = lookup_metric(
9869            &metrics_recorder,
9870            crate::wal_buffer::stats::WAL_BUFFER_ESTIMATED_BYTES,
9871        );
9872        assert!(
9873            estimated.is_some_and(|v| v > 0),
9874            "expected wal_buffer_estimated_bytes > 0, got {:?}",
9875            estimated
9876        );
9877        db.close().await.unwrap();
9878    }
9879
9880    #[tokio::test]
9881    async fn test_should_record_manifest_structural_counts() {
9882        // given:
9883        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9884        let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
9885        let db = Db::builder(
9886            "/tmp/test_should_record_manifest_structural_counts",
9887            object_store,
9888        )
9889        .with_settings(test_db_options(0, 1024, None))
9890        .with_metrics_recorder(metrics_recorder.clone())
9891        .build()
9892        .await
9893        .unwrap();
9894
9895        // when: write data and flush memtable to L0
9896        db.put(b"k1", b"v1").await.unwrap();
9897        db.flush_with_options(FlushOptions {
9898            flush_type: FlushType::MemTable,
9899        })
9900        .await
9901        .unwrap();
9902
9903        // Wait for manifest poll to update l0_sst_count (poll interval is 100ms)
9904        tokio::time::sleep(Duration::from_millis(500)).await;
9905
9906        // then:
9907        let l0_count = lookup_metric(&metrics_recorder, crate::db_stats::L0_SST_COUNT);
9908        assert!(
9909            l0_count.is_some_and(|v| v > 0),
9910            "expected l0_sst_count > 0, got {:?}",
9911            l0_count
9912        );
9913        // No segment extractor configured → root is the only tree, so the
9914        // per-tree max equals the total. Both gauges are updated at the
9915        // same call site in `merge_remote_manifest`.
9916        let segment_max =
9917            lookup_metric(&metrics_recorder, crate::db_stats::SEGMENT_MAX_L0_SST_COUNT);
9918        assert_eq!(
9919            segment_max, l0_count,
9920            "expected segment_max_l0_sst_count == l0_sst_count for an unsegmented DB"
9921        );
9922
9923        // The single flushed L0 SST shows up as one SST view, with no sorted
9924        // runs and no external DBs. These gauges are set at the same call site.
9925        assert_eq!(
9926            lookup_metric(&metrics_recorder, crate::db_stats::SST_VIEW_COUNT),
9927            Some(1),
9928            "expected sst_view_count == 1 for a single flushed L0 SST"
9929        );
9930        assert_eq!(
9931            lookup_metric(&metrics_recorder, crate::db_stats::SST_COUNT),
9932            Some(1),
9933            "expected sst_count == 1 (one distinct physical SST) for a single flushed L0 SST"
9934        );
9935        assert_eq!(
9936            lookup_metric(&metrics_recorder, crate::db_stats::SORTED_RUN_COUNT),
9937            Some(0),
9938            "expected sorted_run_count == 0 before any compaction"
9939        );
9940        assert_eq!(
9941            lookup_metric(&metrics_recorder, crate::db_stats::EXTERNAL_DB_COUNT),
9942            Some(0),
9943            "expected external_db_count == 0 for a standalone DB"
9944        );
9945        db.close().await.unwrap();
9946    }
9947
9948    #[tokio::test]
9949    async fn test_should_record_segment_max_l0_sst_count_with_extractor() {
9950        // With a segment extractor configured, `l0_sst_count` sums L0 SSTs
9951        // across every tree (root + each named segment) while
9952        // `segment_max_l0_sst_count` reports the largest single tree. The
9953        // two values diverge whenever segments accumulate L0 SSTs unevenly
9954        // — the case the new gauge exists to catch, since `l0_max_ssts`
9955        // backpressure is enforced per-tree.
9956        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9957        let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
9958        let extractor = Arc::new(test_utils::FixedThreeBytePrefixExtractor);
9959        let db = Db::builder(
9960            "/tmp/test_should_record_segment_max_l0_sst_count_with_extractor",
9961            object_store,
9962        )
9963        .with_settings(test_db_options(0, 1024, None))
9964        .with_segment_extractor(extractor)
9965        .with_metrics_recorder(metrics_recorder.clone())
9966        .build()
9967        .await
9968        .unwrap();
9969
9970        // Flush 1: two prefixes → each segment tree gets one L0 SST.
9971        db.put(b"aaa-1", b"v").await.unwrap();
9972        db.put(b"bbb-1", b"v").await.unwrap();
9973        db.flush_with_options(FlushOptions {
9974            flush_type: FlushType::MemTable,
9975        })
9976        .await
9977        .unwrap();
9978
9979        // Flush 2: only "aaa" → that tree grows to 2 L0 SSTs while "bbb"
9980        // stays at 1. Final state: total = 3, per-tree max = 2.
9981        db.put(b"aaa-2", b"v").await.unwrap();
9982        db.flush_with_options(FlushOptions {
9983            flush_type: FlushType::MemTable,
9984        })
9985        .await
9986        .unwrap();
9987
9988        // Wait for the manifest poll to refresh both gauges (interval 100ms).
9989        tokio::time::sleep(Duration::from_millis(500)).await;
9990
9991        let total = lookup_metric(&metrics_recorder, crate::db_stats::L0_SST_COUNT);
9992        let segment_max =
9993            lookup_metric(&metrics_recorder, crate::db_stats::SEGMENT_MAX_L0_SST_COUNT);
9994        assert_eq!(
9995            total,
9996            Some(3),
9997            "expected l0_sst_count to sum across trees, got {:?}",
9998            total
9999        );
10000        assert_eq!(
10001            segment_max,
10002            Some(2),
10003            "expected segment_max_l0_sst_count to track the largest tree, got {:?}",
10004            segment_max
10005        );
10006        db.close().await.unwrap();
10007    }
10008
10009    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
10010    async fn test_wal_replay_l0_boundary_does_not_skip_unflushed_replay_batches() {
10011        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10012        let path = "/tmp/test_wal_replay_l0_boundary_does_not_skip_unflushed_replay_batches";
10013
10014        // Start with a large L0/WAL size so the source writer only creates WAL
10015        // files. The data stays out of L0 until recovery replays it.
10016        let mut source_settings = test_db_options(0, 16 * 1024, None);
10017        source_settings.flush_interval = None;
10018
10019        let source = Db::builder(path, object_store.clone())
10020            .with_settings(source_settings)
10021            .build()
10022            .await
10023            .unwrap();
10024
10025        let write_opts = WriteOptions {
10026            await_durable: false,
10027            ..Default::default()
10028        };
10029
10030        // Write two records, each flushed into its own WAL. With the smaller
10031        // replay settings below, these WALs are replayed into the first
10032        // memtable, and that memtable is the only one that reaches L0 before
10033        // the second simulated crash.
10034        let l0_flushed_records = [
10035            (b"l0-flushed-replay-batch-1".as_slice(), vec![b'a'; 128]),
10036            (b"l0-flushed-replay-batch-2".as_slice(), vec![b'b'; 1024]),
10037        ];
10038        let mut l0_flushed_seq = 0;
10039        let mut first_l0_flushed_wal_id = 0;
10040        for (i, (key, value)) in l0_flushed_records.iter().enumerate() {
10041            let write = source
10042                .put_with_options(*key, value, &PutOptions::default(), &write_opts)
10043                .await
10044                .unwrap();
10045            source
10046                .flush_with_options(FlushOptions {
10047                    flush_type: FlushType::Wal,
10048                })
10049                .await
10050                .unwrap();
10051            if i == 0 {
10052                first_l0_flushed_wal_id = source
10053                    .inner
10054                    .wal_observer
10055                    .status()
10056                    .unwrap()
10057                    .last_flushed_wal_id;
10058            }
10059            l0_flushed_seq = write.seqnum();
10060        }
10061        let l0_flushed_boundary_wal_id = source
10062            .inner
10063            .wal_observer
10064            .status()
10065            .unwrap()
10066            .last_flushed_wal_id;
10067        assert!(l0_flushed_boundary_wal_id > first_l0_flushed_wal_id);
10068
10069        // Write several smaller records, each flushed into a separate WAL. On
10070        // replay, these WALs remain after the first replayed memtable's WAL
10071        // boundary, so they must still be eligible for replay after the next
10072        // reopen.
10073        let unflushed_replay_records = [
10074            (b"unflushed-replay-batch-1".as_slice(), vec![b'c'; 128]),
10075            (b"unflushed-replay-batch-2".as_slice(), vec![b'd'; 128]),
10076            (b"unflushed-replay-batch-3".as_slice(), vec![b'e'; 128]),
10077        ];
10078        for (key, value) in &unflushed_replay_records {
10079            source
10080                .put_with_options(*key, value, &PutOptions::default(), &write_opts)
10081                .await
10082                .unwrap();
10083            source
10084                .flush_with_options(FlushOptions {
10085                    flush_type: FlushType::Wal,
10086                })
10087                .await
10088                .unwrap();
10089        }
10090        let final_source_wal_id = source
10091            .inner
10092            .wal_observer
10093            .status()
10094            .unwrap()
10095            .last_flushed_wal_id;
10096        assert!(final_source_wal_id >= l0_flushed_boundary_wal_id + 2);
10097
10098        // Recover with a much smaller replay target so WAL replay splits into
10099        // multiple replayed memtables. Limit L0 to one table so only the first
10100        // replayed memtable can be flushed before the next reopen.
10101        let mut replay_settings = test_db_options(0, 512, None);
10102        replay_settings.flush_interval = None;
10103        replay_settings.l0_max_ssts = 1;
10104        replay_settings.l0_max_ssts_per_key = 1;
10105
10106        // First recovery: replay the WAL and allow the first replayed memtable
10107        // to publish to L0.
10108        let _first_recovery = Db::builder(path, object_store.clone())
10109            .with_settings(replay_settings.clone())
10110            .build()
10111            .await
10112            .unwrap();
10113
10114        // Wait until the manifest has durably published that first replayed
10115        // memtable. The manifest now has one L0 with last_l0_seq and
10116        // replay_after_wal_id matching the first replayed memtable's actual
10117        // sequence and WAL boundaries.
10118        let manifest_store = Arc::new(ManifestStore::new(&Path::from(path), object_store.clone()));
10119        let mut stored_manifest =
10120            StoredManifest::load(manifest_store, Arc::new(DefaultSystemClock::new()))
10121                .await
10122                .unwrap();
10123        let first_l0_manifest = wait_for_manifest_condition(
10124            &mut stored_manifest,
10125            |state| !state.tree.l0.is_empty() && state.last_l0_seq >= l0_flushed_seq,
10126            Duration::from_secs(60),
10127        )
10128        .await;
10129        assert_eq!(first_l0_manifest.last_l0_seq, l0_flushed_seq);
10130        assert_eq!(
10131            first_l0_manifest.replay_after_wal_id,
10132            l0_flushed_boundary_wal_id
10133        );
10134        assert!(final_source_wal_id >= first_l0_manifest.replay_after_wal_id + 2);
10135        assert_eq!(first_l0_manifest.tree.l0.len(), 1);
10136
10137        // Second recovery: simulate crashing after only the first replayed
10138        // memtable reached L0. Recovery must resume after that first
10139        // memtable's actual WAL boundary, not after the later replay batch's
10140        // boundary.
10141        let recovered = Db::builder(path, object_store.clone())
10142            .with_settings(replay_settings)
10143            .build()
10144            .await
10145            .unwrap();
10146
10147        // The L0-flushed keys are present from L0. The later keys must still
10148        // come from WAL replay because they were not covered by the published
10149        // L0 boundary.
10150        for (key, value) in &l0_flushed_records {
10151            assert_eq!(
10152                recovered.get(*key).await.unwrap(),
10153                Some(Bytes::copy_from_slice(value))
10154            );
10155        }
10156        for (key, value) in &unflushed_replay_records {
10157            assert_eq!(
10158                recovered.get(*key).await.unwrap(),
10159                Some(Bytes::copy_from_slice(value))
10160            );
10161        }
10162    }
10163
10164    #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
10165    async fn test_wal_replay_flushes_oversized_active_memtable_before_backpressure() {
10166        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10167        let path = "/tmp/test_wal_replay_flushes_oversized_active_memtable";
10168
10169        // Leave a single WAL SST whose replayed table is smaller than the
10170        // source's freeze threshold. Keeping the source open simulates a crash:
10171        // the recovery writer fences it without first flushing its memtable to L0.
10172        let mut source_settings = test_db_options(0, 64 * 1024, None);
10173        source_settings.flush_interval = None;
10174        let source = Db::builder(path, object_store.clone())
10175            .with_settings(source_settings)
10176            .build()
10177            .await
10178            .unwrap();
10179        let value = vec![b'x'; 16 * 1024];
10180        source
10181            .put_with_options(
10182                b"oversized-replay-value",
10183                &value,
10184                &PutOptions::default(),
10185                &WriteOptions {
10186                    await_durable: false,
10187                    ..Default::default()
10188                },
10189            )
10190            .await
10191            .unwrap();
10192        source
10193            .flush_with_options(FlushOptions {
10194                flush_type: FlushType::Wal,
10195            })
10196            .await
10197            .unwrap();
10198
10199        // On recovery, one complete WAL SST exceeds both thresholds. Replay
10200        // must freeze it before backpressure; otherwise open spins forever with
10201        // an oversized active memtable and nothing for the flusher to drain.
10202        let mut replay_settings = test_db_options(0, 1024, None);
10203        replay_settings.flush_interval = None;
10204        replay_settings.max_unflushed_bytes = 2 * 1024;
10205        let recovered = tokio::time::timeout(
10206            Duration::from_secs(5),
10207            Db::builder(path, object_store)
10208                .with_settings(replay_settings)
10209                .build(),
10210        )
10211        .await
10212        .expect("WAL replay deadlocked on an oversized active memtable")
10213        .expect("failed to recover database");
10214
10215        assert_eq!(
10216            recovered.get(b"oversized-replay-value").await.unwrap(),
10217            Some(Bytes::from(value))
10218        );
10219        recovered
10220            .put_with_options(
10221                b"write-after-replay",
10222                b"value",
10223                &PutOptions::default(),
10224                &WriteOptions {
10225                    await_durable: false,
10226                    ..Default::default()
10227                },
10228            )
10229            .await
10230            .expect("write after oversized WAL replay should succeed");
10231
10232        recovered.close().await.unwrap();
10233    }
10234
10235    /// RFC-0024: WAL replay through a conforming extractor preserves the
10236    /// keys and lets segment-aware writes resume after the next open.
10237    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
10238    async fn test_wal_replay_with_extractor_preserves_keys() {
10239        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10240        let path = "/tmp/test_wal_replay_with_extractor_preserves_keys";
10241
10242        let extractor = Arc::new(test_utils::FixedThreeBytePrefixExtractor);
10243        let mut settings = test_db_options(0, 16 * 1024, None);
10244        settings.flush_interval = None;
10245
10246        let source = Db::builder(path, object_store.clone())
10247            .with_settings(settings.clone())
10248            .with_segment_extractor(extractor.clone())
10249            .build()
10250            .await
10251            .unwrap();
10252        let write_opts = WriteOptions {
10253            await_durable: false,
10254            ..Default::default()
10255        };
10256        for (key, value) in [
10257            (b"aaa-1".as_slice(), b"v1".as_slice()),
10258            (b"bbb-1".as_slice(), b"v2".as_slice()),
10259        ] {
10260            source
10261                .put_with_options(key, value, &PutOptions::default(), &write_opts)
10262                .await
10263                .unwrap();
10264        }
10265        source
10266            .flush_with_options(FlushOptions {
10267                flush_type: FlushType::Wal,
10268            })
10269            .await
10270            .unwrap();
10271        // Drop without close so writes remain in WAL only.
10272        drop(source);
10273
10274        let recovered = Db::builder(path, object_store.clone())
10275            .with_settings(settings)
10276            .with_segment_extractor(extractor)
10277            .build()
10278            .await
10279            .unwrap();
10280        assert_eq!(
10281            recovered.get(b"aaa-1").await.unwrap().unwrap().as_ref(),
10282            b"v1"
10283        );
10284        assert_eq!(
10285            recovered.get(b"bbb-1").await.unwrap().unwrap().as_ref(),
10286            b"v2"
10287        );
10288        recovered.close().await.unwrap();
10289    }
10290
10291    /// RFC-0024: WAL replay rejects entries whose prefix under the
10292    /// current extractor would be empty. We reach this path via the
10293    /// silent-swap pattern: the source writes through a conforming
10294    /// fixed-3 extractor, then a reopen substitutes an aliased
10295    /// `fixed-3` extractor whose `prefix_len` always returns `Some(0)`.
10296    /// The open-time name check passes; replay catches the empty
10297    /// prefix as `EmptySegmentPrefix`.
10298    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
10299    async fn test_wal_replay_rejects_empty_extractor_prefix() {
10300        #[derive(Debug)]
10301        struct AliasedAlwaysEmptyExtractor;
10302        impl crate::PrefixExtractor for AliasedAlwaysEmptyExtractor {
10303            fn name(&self) -> &str {
10304                "fixed-3"
10305            }
10306            fn prefix_len(&self, _target: &crate::PrefixTarget) -> Option<usize> {
10307                Some(0)
10308            }
10309        }
10310
10311        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10312        let path = "/tmp/test_wal_replay_rejects_empty_extractor_prefix";
10313
10314        // Source with a conforming extractor — keys get a 3-byte
10315        // prefix and reach the WAL cleanly.
10316        let mut source_settings = test_db_options(0, 16 * 1024, None);
10317        source_settings.flush_interval = None;
10318        let source = Db::builder(path, object_store.clone())
10319            .with_settings(source_settings)
10320            .with_segment_extractor(Arc::new(test_utils::FixedThreeBytePrefixExtractor))
10321            .build()
10322            .await
10323            .unwrap();
10324        let write_opts = WriteOptions {
10325            await_durable: false,
10326            ..Default::default()
10327        };
10328        source
10329            .put_with_options(b"abc-1", b"v1", &PutOptions::default(), &write_opts)
10330            .await
10331            .unwrap();
10332        source
10333            .flush_with_options(FlushOptions {
10334                flush_type: FlushType::Wal,
10335            })
10336            .await
10337            .unwrap();
10338        drop(source);
10339
10340        // Reopen with the same `name()`, but the swapped extractor's
10341        // logic returns `Some(0)` for every key — replay must reject.
10342        let result = Db::builder(path, object_store)
10343            .with_settings(test_db_options(0, 16 * 1024, None))
10344            .with_segment_extractor(Arc::new(AliasedAlwaysEmptyExtractor))
10345            .build()
10346            .await;
10347        let err = match result {
10348            Ok(_) => panic!("expected empty-prefix rejection at replay, got Ok"),
10349            Err(e) => e,
10350        };
10351        assert!(matches!(err.kind(), crate::error::ErrorKind::Invalid));
10352        assert!(
10353            err.to_string().contains("empty prefix"),
10354            "expected empty-prefix error, got: {err}"
10355        );
10356    }
10357
10358    #[tokio::test]
10359    async fn should_report_new_memtable_segments_in_subscription() {
10360        // given
10361        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10362        let path = "/tmp/test_subscribe_reports_memtable_segments";
10363        let mut settings = test_db_options(0, 16 * 1024, None);
10364        settings.flush_interval = None;
10365        let db = Db::builder(path, object_store.clone())
10366            .with_settings(settings)
10367            .with_segment_extractor(Arc::new(test_utils::FixedThreeBytePrefixExtractor))
10368            .build()
10369            .await
10370            .unwrap();
10371        let mut rx = db.subscribe();
10372        assert!(rx.borrow_and_update().list_segments().is_empty());
10373        let write_opts = WriteOptions {
10374            await_durable: false,
10375            ..Default::default()
10376        };
10377
10378        // when
10379        db.put_with_options(b"abc-1", b"v1", &PutOptions::default(), &write_opts)
10380            .await
10381            .unwrap();
10382
10383        // then
10384        rx.wait_for(|s| {
10385            s.list_segments()
10386                .iter()
10387                .any(|seg| seg.prefix.as_ref() == b"abc")
10388        })
10389        .await
10390        .unwrap();
10391
10392        // when
10393        db.put_with_options(b"xyz-1", b"v2", &PutOptions::default(), &write_opts)
10394            .await
10395            .unwrap();
10396
10397        // then
10398        rx.wait_for(|s| {
10399            s.list_segments()
10400                .into_iter()
10401                .map(|seg| seg.prefix)
10402                .collect::<Vec<_>>()
10403                == vec![Bytes::from_static(b"abc"), Bytes::from_static(b"xyz")]
10404        })
10405        .await
10406        .unwrap();
10407    }
10408
10409    #[tokio::test]
10410    async fn should_report_segments_in_manifest_after_flush() {
10411        // given
10412        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10413        let path = "/tmp/test_report_segments_in_manifest_after_flush";
10414        let mut settings = test_db_options(0, 16 * 1024, None);
10415        settings.flush_interval = None;
10416        let db = Db::builder(path, object_store.clone())
10417            .with_settings(settings)
10418            .with_segment_extractor(Arc::new(test_utils::FixedThreeBytePrefixExtractor))
10419            .build()
10420            .await
10421            .unwrap();
10422        let mut rx = db.subscribe();
10423        rx.borrow_and_update();
10424
10425        // when
10426        db.put_with_options(
10427            b"abc-1",
10428            b"v1",
10429            &PutOptions::default(),
10430            &WriteOptions {
10431                await_durable: false,
10432                ..Default::default()
10433            },
10434        )
10435        .await
10436        .unwrap();
10437
10438        // then
10439        rx.wait_for(|s| {
10440            s.list_segments()
10441                .iter()
10442                .any(|seg| seg.prefix.as_ref() == b"abc")
10443        })
10444        .await
10445        .unwrap();
10446
10447        // when
10448        db.flush_with_options(FlushOptions {
10449            flush_type: FlushType::MemTable,
10450        })
10451        .await
10452        .unwrap();
10453
10454        // then
10455        rx.wait_for(|s| {
10456            s.list_segments()
10457                .into_iter()
10458                .map(|seg| seg.prefix)
10459                .collect::<Vec<_>>()
10460                == vec![Bytes::from_static(b"abc")]
10461        })
10462        .await
10463        .unwrap();
10464
10465        // when
10466        // the segments are deleted from the manifest (as a full compaction would)
10467        db.inner
10468            .state
10469            .write()
10470            .modify(|m| m.state.manifest.value.core.segments.clear());
10471        let manifest = db.inner.state.read().state().manifest.clone();
10472        db.inner.status_manager.report_manifest(manifest.into());
10473
10474        // then
10475        assert!(db.status().list_segments().is_empty());
10476    }
10477
10478    #[tokio::test]
10479    async fn should_not_report_segments_without_extractor() {
10480        // given
10481        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10482        let path = "/tmp/test_no_segments_without_extractor";
10483        let mut settings = test_db_options(0, 16 * 1024, None);
10484        settings.flush_interval = None;
10485        let db = Db::builder(path, object_store.clone())
10486            .with_settings(settings)
10487            .build()
10488            .await
10489            .unwrap();
10490        let mut rx = db.subscribe();
10491        assert!(rx.borrow_and_update().list_segments().is_empty());
10492
10493        // when
10494        db.put_with_options(
10495            b"abc-1",
10496            b"v1",
10497            &PutOptions::default(),
10498            &WriteOptions {
10499                await_durable: false,
10500                ..Default::default()
10501            },
10502        )
10503        .await
10504        .unwrap();
10505        // the flush drains the write path and folds the memtable into the
10506        // manifest, so both reporting paths have run by the time it returns.
10507        db.flush_with_options(FlushOptions {
10508            flush_type: FlushType::MemTable,
10509        })
10510        .await
10511        .unwrap();
10512
10513        // then
10514        assert!(db.status().list_segments().is_empty());
10515    }
10516
10517    #[derive(Clone, Copy, Debug)]
10518    enum ExtractorConfig {
10519        None,
10520        Fixed3,
10521        Other,
10522    }
10523
10524    impl ExtractorConfig {
10525        fn to_extractor(self) -> Option<Arc<dyn crate::PrefixExtractor>> {
10526            #[derive(Debug)]
10527            struct OtherExtractor;
10528            impl crate::PrefixExtractor for OtherExtractor {
10529                fn name(&self) -> &str {
10530                    "other"
10531                }
10532                fn prefix_len(&self, _target: &crate::PrefixTarget) -> Option<usize> {
10533                    Some(3)
10534                }
10535            }
10536            match self {
10537                ExtractorConfig::None => None,
10538                ExtractorConfig::Fixed3 => {
10539                    Some(Arc::new(test_utils::FixedThreeBytePrefixExtractor))
10540                }
10541                ExtractorConfig::Other => Some(Arc::new(OtherExtractor)),
10542            }
10543        }
10544    }
10545
10546    /// RFC-0024 open-time reconciliation: the configured extractor on
10547    /// reopen must agree with what was persisted at creation.
10548    /// `(persisted, configured) → outcome` for every combination of
10549    /// `None`, the conforming `Fixed3` extractor, and a differently-named
10550    /// `Other` extractor.
10551    #[rstest::rstest]
10552    #[case::no_extractor_round_trip(ExtractorConfig::None, ExtractorConfig::None, true)]
10553    #[case::same_extractor_round_trip(ExtractorConfig::Fixed3, ExtractorConfig::Fixed3, true)]
10554    #[case::name_mismatch(ExtractorConfig::Fixed3, ExtractorConfig::Other, false)]
10555    #[case::removed(ExtractorConfig::Fixed3, ExtractorConfig::None, false)]
10556    #[case::added(ExtractorConfig::None, ExtractorConfig::Fixed3, false)]
10557    #[tokio::test]
10558    async fn test_open_extractor_reconciliation(
10559        #[case] initial: ExtractorConfig,
10560        #[case] reopen: ExtractorConfig,
10561        #[case] expect_ok: bool,
10562    ) {
10563        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10564        let path = format!("/tmp/test_open_extractor_reconciliation_{initial:?}_{reopen:?}");
10565
10566        let mut builder = Db::builder(path.clone(), object_store.clone());
10567        if let Some(extractor) = initial.to_extractor() {
10568            builder = builder.with_segment_extractor(extractor);
10569        }
10570        builder.build().await.unwrap().close().await.unwrap();
10571
10572        let mut builder = Db::builder(path, object_store);
10573        if let Some(extractor) = reopen.to_extractor() {
10574            builder = builder.with_segment_extractor(extractor);
10575        }
10576        match (expect_ok, builder.build().await) {
10577            (true, Ok(reopened)) => reopened.close().await.unwrap(),
10578            (true, Err(err)) => panic!("expected reopen to succeed, got {err:?}"),
10579            (false, Ok(_)) => panic!("expected reopen to fail"),
10580            (false, Err(err)) => {
10581                assert!(matches!(err.kind(), crate::error::ErrorKind::Invalid));
10582            }
10583        }
10584    }
10585
10586    /// RFC-0024 open-time per-segment check: every persisted segment
10587    /// prefix `p` must satisfy `prefix_len(Prefix(p)) == Some(p.len())`
10588    /// under the configured extractor. This catches a silent-swap case
10589    /// the name check misses — same `name()`, but the new logic no
10590    /// longer treats an existing prefix as a complete segment boundary.
10591    #[tokio::test]
10592    async fn test_open_rejects_when_segment_prefix_unrecognized() {
10593        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10594        let path = "/tmp/test_open_rejects_unrecognized_segment_prefix";
10595
10596        let db = Db::builder(path, object_store.clone())
10597            .with_segment_extractor(Arc::new(test_utils::FixedThreeBytePrefixExtractor))
10598            .build()
10599            .await
10600            .unwrap();
10601        // Persist segments "abc" and "ab-": both produce 3-byte
10602        // prefixes under fixed-3 and route to disjoint segments. The
10603        // close() call flushes the memtable to L0, which is what
10604        // creates the persisted segment entries.
10605        db.put(b"abc-1", b"v1").await.unwrap();
10606        db.put(b"ab--1", b"v2").await.unwrap();
10607        db.close().await.unwrap();
10608
10609        // Reopen with an aliased extractor — same name, different
10610        // logic. Under the swapped logic, `Prefix("ab-")` returns
10611        // Some(2) (prefix "ab"), which does not equal `"ab-".len()`.
10612        // The per-segment check must reject.
10613        let result = Db::builder(path, object_store)
10614            .with_segment_extractor(Arc::new(test_utils::AliasedFixed3PrefixExtractor))
10615            .build()
10616            .await;
10617        let err = match result {
10618            Ok(_) => panic!("expected unrecognized-prefix rejection, got Ok"),
10619            Err(e) => e,
10620        };
10621        assert!(matches!(err.kind(), crate::error::ErrorKind::Invalid));
10622        assert!(
10623            err.to_string().contains("not recognized"),
10624            "expected error to mention recognition, got: {err}"
10625        );
10626    }
10627
10628    /// Helper: collect the segment prefix list from `db`'s in-memory
10629    /// manifest snapshot.
10630    fn segment_prefixes(db: &Db) -> Vec<Bytes> {
10631        let guard = db.inner.state.read();
10632        let cow = guard.state();
10633        cow.core()
10634            .segments
10635            .iter()
10636            .map(|s| s.prefix.clone())
10637            .collect()
10638    }
10639
10640    /// RFC-0024: a DB created with both an extractor and a separate
10641    /// WAL object store writes a V2 manifest, which intentionally
10642    /// drops `wal_object_store_uri` (commit 52cead43). The reopen
10643    /// path must skip the WAL-store reconfiguration check when the
10644    /// persisted URI is absent, so a matching configuration on
10645    /// reopen still succeeds.
10646    #[tokio::test]
10647    async fn test_open_with_extractor_and_wal_store_round_trips() {
10648        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10649        let wal_object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10650        let path = "/tmp/test_open_extractor_with_wal_store";
10651        let extractor = Arc::new(test_utils::FixedThreeBytePrefixExtractor);
10652
10653        let db = Db::builder(path, object_store.clone())
10654            .with_segment_extractor(extractor.clone())
10655            .with_wal_object_store(wal_object_store.clone())
10656            .build()
10657            .await
10658            .unwrap();
10659        db.close().await.unwrap();
10660
10661        let reopened = Db::builder(path, object_store)
10662            .with_segment_extractor(extractor)
10663            .with_wal_object_store(wal_object_store)
10664            .build()
10665            .await
10666            .unwrap();
10667        reopened.close().await.unwrap();
10668    }
10669
10670    /// End-to-end: write to multiple segments, flush, close, reopen,
10671    /// read every key back. The manifest carries one segment per
10672    /// touched prefix and survives the encode/decode cycle.
10673    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
10674    async fn test_segments_round_trip_through_flush_and_reopen() {
10675        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10676        let path = "/tmp/test_segments_round_trip";
10677        let extractor = Arc::new(test_utils::FixedThreeBytePrefixExtractor);
10678
10679        let db = Db::builder(path, object_store.clone())
10680            .with_segment_extractor(extractor.clone())
10681            .build()
10682            .await
10683            .unwrap();
10684
10685        let entries: &[(&[u8], &[u8])] = &[
10686            (b"aaa-1", b"v1"),
10687            (b"aaa-2", b"v2"),
10688            (b"bbb-1", b"v3"),
10689            (b"ccc-1", b"v4"),
10690            (b"ccc-2", b"v5"),
10691        ];
10692        for (k, v) in entries {
10693            db.put(*k, *v).await.unwrap();
10694        }
10695        db.flush_with_options(FlushOptions {
10696            flush_type: FlushType::MemTable,
10697        })
10698        .await
10699        .unwrap();
10700
10701        let prefixes = segment_prefixes(&db);
10702        assert_eq!(
10703            prefixes,
10704            vec![
10705                Bytes::from_static(b"aaa"),
10706                Bytes::from_static(b"bbb"),
10707                Bytes::from_static(b"ccc"),
10708            ],
10709            "expected one segment per touched prefix, in sorted order"
10710        );
10711        db.close().await.unwrap();
10712
10713        let reopened = Db::builder(path, object_store)
10714            .with_segment_extractor(extractor)
10715            .build()
10716            .await
10717            .unwrap();
10718        for (k, v) in entries {
10719            assert_eq!(
10720                reopened.get(*k).await.unwrap().unwrap().as_ref(),
10721                *v,
10722                "round-trip mismatch for key {:?}",
10723                k
10724            );
10725        }
10726        // Segments survived encode/decode.
10727        assert_eq!(
10728            segment_prefixes(&reopened),
10729            vec![
10730                Bytes::from_static(b"aaa"),
10731                Bytes::from_static(b"bbb"),
10732                Bytes::from_static(b"ccc"),
10733            ]
10734        );
10735        reopened.close().await.unwrap();
10736    }
10737
10738    /// One batch covering N segments must reach the manifest atomically:
10739    /// after the flush, every touched segment has exactly one L0 SST
10740    /// from this flush — no partial visibility.
10741    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
10742    async fn test_mixed_batch_publishes_atomically() {
10743        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10744        let path = "/tmp/test_mixed_batch_atomic";
10745
10746        let db = Db::builder(path, object_store)
10747            .with_segment_extractor(Arc::new(test_utils::FixedThreeBytePrefixExtractor))
10748            .build()
10749            .await
10750            .unwrap();
10751        let mut batch = WriteBatch::new();
10752        batch.put(b"aaa-1", b"v1");
10753        batch.put(b"bbb-1", b"v2");
10754        batch.put(b"ccc-1", b"v3");
10755        db.write(batch).await.unwrap();
10756
10757        // Pre-flush: segments are still empty (data is in memtable / WAL).
10758        assert!(segment_prefixes(&db).is_empty());
10759
10760        db.flush_with_options(FlushOptions {
10761            flush_type: FlushType::MemTable,
10762        })
10763        .await
10764        .unwrap();
10765
10766        // Scope the read guard so it is dropped before the `await` below.
10767        {
10768            let guard = db.inner.state.read();
10769            let cow = guard.state();
10770            let core = cow.core();
10771            assert_eq!(core.segments.len(), 3);
10772            for segment in &core.segments {
10773                assert_eq!(
10774                    segment.tree.l0.len(),
10775                    1,
10776                    "expected exactly one L0 SST in segment {:?}, got {}",
10777                    segment.prefix,
10778                    segment.tree.l0.len()
10779                );
10780                assert!(
10781                    segment.tree.compacted.is_empty(),
10782                    "no compaction expected yet"
10783                );
10784            }
10785        }
10786        db.close().await.unwrap();
10787    }
10788
10789    /// WAL replay reconstructs segment state when the source process
10790    /// dropped before flushing. After reopen, replayed entries land in
10791    /// the memtable; a subsequent flush then publishes them as
10792    /// per-segment L0 SSTs.
10793    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
10794    async fn test_replay_reconstructs_segments_from_wal() {
10795        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10796        let path = "/tmp/test_replay_reconstructs_segments";
10797        let extractor = Arc::new(test_utils::FixedThreeBytePrefixExtractor);
10798
10799        let mut settings = test_db_options(0, 16 * 1024, None);
10800        settings.flush_interval = None;
10801
10802        let source = Db::builder(path, object_store.clone())
10803            .with_settings(settings.clone())
10804            .with_segment_extractor(extractor.clone())
10805            .build()
10806            .await
10807            .unwrap();
10808        let write_opts = WriteOptions {
10809            await_durable: false,
10810            ..Default::default()
10811        };
10812        for (k, v) in [(b"aaa-1".as_slice(), b"v1"), (b"bbb-1".as_slice(), b"v2")] {
10813            source
10814                .put_with_options(k, v, &PutOptions::default(), &write_opts)
10815                .await
10816                .unwrap();
10817        }
10818        source
10819            .flush_with_options(FlushOptions {
10820                flush_type: FlushType::Wal,
10821            })
10822            .await
10823            .unwrap();
10824        // Drop without close so entries stay in WAL only.
10825        drop(source);
10826
10827        // Reopen — the manifest still has no segments at this point.
10828        let recovered = Db::builder(path, object_store)
10829            .with_settings(settings)
10830            .with_segment_extractor(extractor)
10831            .build()
10832            .await
10833            .unwrap();
10834        assert!(
10835            segment_prefixes(&recovered).is_empty(),
10836            "replay should not stamp segments until the memtable flushes"
10837        );
10838        // Reads see the replayed data via the memtable.
10839        assert_eq!(
10840            recovered.get(b"aaa-1").await.unwrap().unwrap().as_ref(),
10841            b"v1"
10842        );
10843        assert_eq!(
10844            recovered.get(b"bbb-1").await.unwrap().unwrap().as_ref(),
10845            b"v2"
10846        );
10847
10848        // Force the memtable through, segments should appear.
10849        recovered
10850            .flush_with_options(FlushOptions {
10851                flush_type: FlushType::MemTable,
10852            })
10853            .await
10854            .unwrap();
10855        assert_eq!(
10856            segment_prefixes(&recovered),
10857            vec![Bytes::from_static(b"aaa"), Bytes::from_static(b"bbb")]
10858        );
10859        recovered.close().await.unwrap();
10860    }
10861
10862    /// A timeseries-style workload: hot writes to a "current" segment
10863    /// interleaved with occasional backfill into an "older" one. Both
10864    /// segments end up correctly populated and reads succeed.
10865    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
10866    async fn test_backfill_alongside_active_segment() {
10867        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10868        let path = "/tmp/test_backfill_active";
10869
10870        let db = Db::builder(path, object_store)
10871            .with_segment_extractor(Arc::new(test_utils::FixedThreeBytePrefixExtractor))
10872            .build()
10873            .await
10874            .unwrap();
10875
10876        // Active segment "cur" sees most writes; segment "old" gets a
10877        // sprinkle of backfill. Interleaved on purpose so several
10878        // batches touch both.
10879        let mut expected: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
10880        for i in 0..20u32 {
10881            let cur_key = format!("cur-{i:03}").into_bytes();
10882            let cur_val = format!("c{i}").into_bytes();
10883            db.put(&cur_key, &cur_val).await.unwrap();
10884            expected.push((cur_key, cur_val));
10885            if i % 7 == 0 {
10886                let old_key = format!("old-{i:03}").into_bytes();
10887                let old_val = format!("o{i}").into_bytes();
10888                db.put(&old_key, &old_val).await.unwrap();
10889                expected.push((old_key, old_val));
10890            }
10891        }
10892        db.flush_with_options(FlushOptions {
10893            flush_type: FlushType::MemTable,
10894        })
10895        .await
10896        .unwrap();
10897
10898        assert_eq!(
10899            segment_prefixes(&db),
10900            vec![Bytes::from_static(b"cur"), Bytes::from_static(b"old")]
10901        );
10902        for (k, v) in &expected {
10903            assert_eq!(
10904                db.get(k).await.unwrap().unwrap().as_ref(),
10905                v.as_slice(),
10906                "missing value for {:?}",
10907                k
10908            );
10909        }
10910        db.close().await.unwrap();
10911    }
10912
10913    /// After a clean restart, writes resume into existing segments and
10914    /// both the prior (in compacted/L0) and new (in fresh L0) entries
10915    /// remain readable.
10916    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
10917    async fn test_restart_resumes_writes_into_existing_segment() {
10918        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10919        let path = "/tmp/test_restart_resume";
10920        let extractor = Arc::new(test_utils::FixedThreeBytePrefixExtractor);
10921
10922        let initial = Db::builder(path, object_store.clone())
10923            .with_segment_extractor(extractor.clone())
10924            .build()
10925            .await
10926            .unwrap();
10927        initial.put(b"aaa-1", b"v1").await.unwrap();
10928        initial.put(b"aaa-2", b"v2").await.unwrap();
10929        initial.close().await.unwrap();
10930
10931        let reopened = Db::builder(path, object_store)
10932            .with_segment_extractor(extractor)
10933            .build()
10934            .await
10935            .unwrap();
10936        // Prior writes still readable after reopen.
10937        assert_eq!(
10938            reopened.get(b"aaa-1").await.unwrap().unwrap().as_ref(),
10939            b"v1"
10940        );
10941        assert_eq!(
10942            reopened.get(b"aaa-2").await.unwrap().unwrap().as_ref(),
10943            b"v2"
10944        );
10945
10946        // Resume writes into the same segment.
10947        reopened.put(b"aaa-3", b"v3").await.unwrap();
10948        reopened.put(b"aaa-4", b"v4").await.unwrap();
10949        reopened
10950            .flush_with_options(FlushOptions {
10951                flush_type: FlushType::MemTable,
10952            })
10953            .await
10954            .unwrap();
10955
10956        // Still exactly the one "aaa" segment, now with two L0 SSTs.
10957        let prefixes = segment_prefixes(&reopened);
10958        assert_eq!(prefixes, vec![Bytes::from_static(b"aaa")]);
10959        {
10960            let guard = reopened.inner.state.read();
10961            let cow = guard.state();
10962            let segment = &cow.core().segments[0];
10963            assert_eq!(
10964                segment.tree.l0.len(),
10965                2,
10966                "expected two L0 SSTs after the second flush, got {}",
10967                segment.tree.l0.len()
10968            );
10969        }
10970
10971        for (k, v) in [
10972            (b"aaa-1".as_slice(), b"v1".as_slice()),
10973            (b"aaa-2".as_slice(), b"v2".as_slice()),
10974            (b"aaa-3".as_slice(), b"v3".as_slice()),
10975            (b"aaa-4".as_slice(), b"v4".as_slice()),
10976        ] {
10977            assert_eq!(
10978                reopened.get(k).await.unwrap().unwrap().as_ref(),
10979                v,
10980                "missing value for {:?}",
10981                k
10982            );
10983        }
10984        reopened.close().await.unwrap();
10985    }
10986
10987    async fn create_segmented_scan_fixture(
10988        path: &str,
10989        object_store: Arc<dyn ObjectStore>,
10990    ) -> (Db, BTreeMap<Bytes, Bytes>) {
10991        let db = Db::builder(path, object_store)
10992            .with_segment_extractor(Arc::new(test_utils::FixedThreeBytePrefixExtractor))
10993            .build()
10994            .await
10995            .unwrap();
10996
10997        let table = BTreeMap::from([
10998            (Bytes::from_static(b"aaa-001"), Bytes::from_static(b"v1")),
10999            (Bytes::from_static(b"aaa-003"), Bytes::from_static(b"v2")),
11000            (Bytes::from_static(b"bbb-001"), Bytes::from_static(b"v3")),
11001            (Bytes::from_static(b"bbb-002"), Bytes::from_static(b"v4")),
11002            (Bytes::from_static(b"ddd-001"), Bytes::from_static(b"v5")),
11003            (Bytes::from_static(b"ddd-004"), Bytes::from_static(b"v6")),
11004        ]);
11005        test_utils::seed_database(&db, &table, true).await.unwrap();
11006        db.flush_with_options(FlushOptions {
11007            flush_type: FlushType::MemTable,
11008        })
11009        .await
11010        .unwrap();
11011
11012        (db, table)
11013    }
11014
11015    async fn assert_segmented_scan_matrix<R>(reader: &R, table: &BTreeMap<Bytes, Bytes>)
11016    where
11017        R: DbReadOps + Sync,
11018    {
11019        let mut prefix_iter = reader.scan_prefix(b"bbb", ..).await.unwrap();
11020        test_utils::assert_ranged_db_scan(
11021            table,
11022            Bytes::from_static(b"bbb")..Bytes::from_static(b"bbc"),
11023            IterationOrder::Ascending,
11024            &mut prefix_iter,
11025        )
11026        .await;
11027
11028        // Bounded subranges compose with the prefix on every read surface:
11029        // a start bound that excludes earlier suffixes...
11030        let mut subrange_iter = reader
11031            .scan_prefix(b"bbb", b"-002".as_slice()..)
11032            .await
11033            .unwrap();
11034        test_utils::assert_ranged_db_scan(
11035            table,
11036            Bytes::from_static(b"bbb-002")..Bytes::from_static(b"bbc"),
11037            IterationOrder::Ascending,
11038            &mut subrange_iter,
11039        )
11040        .await;
11041
11042        // ...and an end bound that excludes later suffixes.
11043        let mut subrange_iter = reader
11044            .scan_prefix(b"ddd", b"-001".as_slice()..b"-004".as_slice())
11045            .await
11046            .unwrap();
11047        test_utils::assert_ranged_db_scan(
11048            table,
11049            Bytes::from_static(b"ddd-001")..Bytes::from_static(b"ddd-004"),
11050            IterationOrder::Ascending,
11051            &mut subrange_iter,
11052        )
11053        .await;
11054
11055        let mut asc_iter = reader
11056            .scan(b"aaa".to_vec()..=b"ddd-999".to_vec())
11057            .await
11058            .unwrap();
11059        test_utils::assert_ranged_db_scan(
11060            table,
11061            Bytes::from_static(b"aaa")..=Bytes::from_static(b"ddd-999"),
11062            IterationOrder::Ascending,
11063            &mut asc_iter,
11064        )
11065        .await;
11066
11067        let desc_options = ScanOptions::default().with_order(IterationOrder::Descending);
11068        let mut desc_iter = reader
11069            .scan_with_options(b"aaa".to_vec()..=b"ddd-999".to_vec(), &desc_options)
11070            .await
11071            .unwrap();
11072        test_utils::assert_ranged_db_scan(
11073            table,
11074            Bytes::from_static(b"aaa")..=Bytes::from_static(b"ddd-999"),
11075            IterationOrder::Descending,
11076            &mut desc_iter,
11077        )
11078        .await;
11079
11080        let mut gap_iter = reader
11081            .scan(b"bbc".to_vec()..=b"ddd-002".to_vec())
11082            .await
11083            .unwrap();
11084        test_utils::assert_ranged_db_scan(
11085            table,
11086            Bytes::from_static(b"bbc")..=Bytes::from_static(b"ddd-002"),
11087            IterationOrder::Ascending,
11088            &mut gap_iter,
11089        )
11090        .await;
11091    }
11092
11093    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
11094    async fn test_segmented_scans_on_db() {
11095        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
11096        let (db, table) =
11097            create_segmented_scan_fixture("/tmp/test_segmented_scans_on_db", object_store).await;
11098
11099        assert_segmented_scan_matrix(&db, &table).await;
11100        db.close().await.unwrap();
11101    }
11102
11103    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
11104    async fn test_segmented_scans_on_snapshot() {
11105        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
11106        let (db, table) =
11107            create_segmented_scan_fixture("/tmp/test_segmented_scans_on_snapshot", object_store)
11108                .await;
11109
11110        let snapshot = db.snapshot().await.unwrap();
11111        assert_segmented_scan_matrix(snapshot.as_ref(), &table).await;
11112        drop(snapshot);
11113        db.close().await.unwrap();
11114    }
11115
11116    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
11117    async fn test_segmented_scans_on_db_reader() {
11118        let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
11119        let path = "/tmp/test_segmented_scans_on_db_reader";
11120        let (db, table) = create_segmented_scan_fixture(path, object_store.clone()).await;
11121        db.close().await.unwrap();
11122
11123        let reader = DbReaderBuilder::new(path, object_store)
11124            .with_segment_extractor(Arc::new(test_utils::FixedThreeBytePrefixExtractor))
11125            .build()
11126            .await
11127            .unwrap();
11128        assert_segmented_scan_matrix(&reader, &table).await;
11129    }
11130
11131    #[tokio::test]
11132    async fn test_db_reader_cache_scoping() {
11133        use crate::db_cache::{DbCache, SplitCache};
11134
11135        // Create two separate databases
11136        let object_store_a: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
11137        let object_store_b: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
11138
11139        // Write different data to each database
11140        let db_a = Db::builder("/tmp/test_reader_cache_a", object_store_a.clone())
11141            .with_settings(test_db_options(0, 1024, None))
11142            .build()
11143            .await
11144            .unwrap();
11145        db_a.put(b"key1", b"value_from_db_a").await.unwrap();
11146        db_a.flush().await.unwrap();
11147        db_a.close().await.unwrap();
11148
11149        let db_b = Db::builder("/tmp/test_reader_cache_b", object_store_b.clone())
11150            .with_settings(test_db_options(0, 1024, None))
11151            .build()
11152            .await
11153            .unwrap();
11154        db_b.put(b"key1", b"value_from_db_b").await.unwrap();
11155        db_b.flush().await.unwrap();
11156        db_b.close().await.unwrap();
11157
11158        // Create a shared cache
11159        let shared_cache: Arc<dyn DbCache> = Arc::new(SplitCache::new().build());
11160
11161        // Open both databases as readers with the shared cache
11162        let reader_a = DbReaderBuilder::new("/tmp/test_reader_cache_a", object_store_a)
11163            .with_db_cache(shared_cache.clone())
11164            .build()
11165            .await
11166            .unwrap();
11167
11168        let reader_b = DbReaderBuilder::new("/tmp/test_reader_cache_b", object_store_b)
11169            .with_db_cache(shared_cache.clone())
11170            .build()
11171            .await
11172            .unwrap();
11173
11174        // Verify each reader returns its own data, not the other's
11175        let value_a = reader_a.get(b"key1").await.unwrap();
11176        assert_eq!(value_a, Some(Bytes::from("value_from_db_a")));
11177
11178        let value_b = reader_b.get(b"key1").await.unwrap();
11179        assert_eq!(value_b, Some(Bytes::from("value_from_db_b")));
11180
11181        // Read again to exercise cached paths
11182        let value_a_cached = reader_a.get(b"key1").await.unwrap();
11183        assert_eq!(value_a_cached, Some(Bytes::from("value_from_db_a")));
11184
11185        // Close reader_a; the shared cache must remain usable for reader_b.
11186        reader_a.close().await.unwrap();
11187
11188        let value_b_cached = reader_b.get(b"key1").await.unwrap();
11189        assert_eq!(value_b_cached, Some(Bytes::from("value_from_db_b")));
11190
11191        reader_b.close().await.unwrap();
11192    }
11193
11194    #[cfg(feature = "foyer")]
11195    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
11196    async fn test_close_does_not_kill_shared_hybrid_cache() {
11197        use crate::db_cache::foyer_hybrid::FoyerHybridCache;
11198        use crate::db_cache::{CachedEntry, CachedKey, DbCache};
11199        use crate::db_state::SsTableId;
11200        use crate::format::sst::BlockBuilder;
11201        use foyer::{
11202            BlockEngineConfig, DeviceBuilder, FsDeviceBuilder, HybridCacheBuilder,
11203            PsyncIoEngineConfig,
11204        };
11205
11206        fn probe_entry() -> CachedEntry {
11207            use rand::RngCore;
11208            let mut rng = rand::rng();
11209            let mut builder = BlockBuilder::new_latest(1024);
11210            loop {
11211                let mut k = vec![0u8; 32];
11212                rng.fill_bytes(&mut k);
11213                let mut v = vec![0u8; 128];
11214                rng.fill_bytes(&mut v);
11215                if builder.add_value(&k, &v, None, None) {
11216                    break;
11217                }
11218            }
11219            CachedEntry::with_block(Arc::new(builder.build().unwrap()))
11220        }
11221
11222        async fn open_shared_cache(path: &std::path::Path) -> Arc<dyn DbCache> {
11223            let hybrid = HybridCacheBuilder::new()
11224                .with_name("shared_hybrid")
11225                .memory(1024 * 1024)
11226                .with_weighter(|_, v: &CachedEntry| v.size())
11227                .storage()
11228                .with_io_engine_config(PsyncIoEngineConfig::new())
11229                .with_engine_config(
11230                    BlockEngineConfig::new(
11231                        FsDeviceBuilder::new(path)
11232                            .with_capacity(4 * 1024 * 1024)
11233                            .build()
11234                            .unwrap(),
11235                    )
11236                    .with_block_size(64 * 1024),
11237                )
11238                .build()
11239                .await
11240                .unwrap();
11241            Arc::new(FoyerHybridCache::new_with_cache(hybrid))
11242        }
11243
11244        let cache_dir = tempfile::tempdir().unwrap();
11245        let shared_cache = open_shared_cache(cache_dir.path()).await;
11246
11247        let object_store_a: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
11248        let object_store_b: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
11249
11250        let db_a = Db::builder("/tmp/test_shared_hybrid_a", object_store_a)
11251            .with_settings(test_db_options(0, 1024, None))
11252            .with_db_cache(shared_cache.clone())
11253            .build()
11254            .await
11255            .unwrap();
11256        let db_b = Db::builder("/tmp/test_shared_hybrid_b", object_store_b)
11257            .with_settings(test_db_options(0, 1024, None))
11258            .with_db_cache(shared_cache.clone())
11259            .build()
11260            .await
11261            .unwrap();
11262
11263        db_a.put(b"key1", b"value_from_db_a").await.unwrap();
11264        db_a.flush().await.unwrap();
11265        db_b.put(b"key1", b"value_from_db_b").await.unwrap();
11266        db_b.flush().await.unwrap();
11267
11268        // Close db_a; the shared cache must remain usable for db_b.
11269        db_a.close().await.unwrap();
11270
11271        // db_b reads still succeed (they can fall back to the object store)...
11272        let value_b = db_b.get(b"key1").await.unwrap();
11273        assert_eq!(value_b, Some(Bytes::from("value_from_db_b")));
11274
11275        // ...and entries cached on behalf of db_b after db_a closed should
11276        // still persist across the caller's own graceful shutdown sequence
11277        // (close all DBs, then close the cache we own) + cache reopen, exactly
11278        // like `should_persist_blocks_to_disk_on_close` proves for a cache
11279        // that nobody else closed. If db_a's close had closed the shared
11280        // cache, these entries could never reach disk: the disk engine drops
11281        // post-close writes and the final close below becomes a no-op.
11282        let mut keys = Vec::new();
11283        for b in 0u64..64 {
11284            let k = CachedKey::from((SsTableId::Wal(u64::MAX - 2), b));
11285            shared_cache.insert(k.clone(), probe_entry()).await;
11286            keys.push(k);
11287        }
11288
11289        db_b.close().await.unwrap();
11290        // The caller owns the injected cache and closes it once all DBs are
11291        // closed; this flushes the memory tier to disk.
11292        shared_cache.close().await.unwrap();
11293        drop(db_a);
11294        drop(db_b);
11295        drop(shared_cache);
11296
11297        let reopened = open_shared_cache(cache_dir.path()).await;
11298        let mut found = 0;
11299        for k in &keys {
11300            if reopened.get_block(k).await.unwrap().is_some() {
11301                found += 1;
11302            }
11303        }
11304        assert_eq!(
11305            found,
11306            keys.len(),
11307            "entries cached after another DB closed the shared cache were lost \
11308             ({}/{} survived cache close + reopen)",
11309            found,
11310            keys.len()
11311        );
11312    }
11313
11314    mod object_store_cache {
11315        use super::*;
11316        use crate::cached_object_store::stats::{PART_ACCESS_COUNT, PART_HIT_COUNT};
11317        use crate::cached_object_store::CachedObjectStore;
11318        use object_store::ObjectStoreExt;
11319
11320        /// Fixture for the object store cache tests.
11321        struct ObjectStoreCacheTest {
11322            db: Db,
11323            upstream: Arc<dyn ObjectStore>,
11324            /// The typed handle to the cache passed to the db as its object
11325            /// store; `None` when built `without_object_store_cache`.
11326            cache: Option<Arc<CachedObjectStore>>,
11327            cache_root: std::path::PathBuf,
11328            db_path: String,
11329            should_compact: Option<Arc<AtomicBool>>,
11330        }
11331
11332        /// Builder for [`ObjectStoreCacheTest`]. Defaults: 1 KiB cache parts, a
11333        /// 1 KiB L0 size, both write sources uncached, and no compactor.
11334        struct ObjectStoreCacheTestBuilder {
11335            db_path: String,
11336            object_store_cache: bool,
11337            cache_on_flush: bool,
11338            cache_on_compaction: bool,
11339            part_size: usize,
11340            l0_sst_size_bytes: usize,
11341            on_demand_compactor: bool,
11342            custom_compactor_store: bool,
11343            metrics_recorder: Option<Arc<DefaultMetricsRecorder>>,
11344        }
11345
11346        impl ObjectStoreCacheTestBuilder {
11347            fn new(db_path: &str) -> Self {
11348                Self {
11349                    db_path: db_path.to_string(),
11350                    object_store_cache: true,
11351                    cache_on_flush: false,
11352                    cache_on_compaction: false,
11353                    part_size: 1024,
11354                    l0_sst_size_bytes: 1024,
11355                    on_demand_compactor: false,
11356                    custom_compactor_store: false,
11357                    metrics_recorder: None,
11358                }
11359            }
11360
11361            /// Leaves the object store cache unconfigured (no root folder).
11362            fn without_object_store_cache(mut self) -> Self {
11363                self.object_store_cache = false;
11364                self
11365            }
11366
11367            fn metrics_recorder(mut self, recorder: Arc<DefaultMetricsRecorder>) -> Self {
11368                self.metrics_recorder = Some(recorder);
11369                self
11370            }
11371
11372            fn cache_on_flush(mut self) -> Self {
11373                self.cache_on_flush = true;
11374                self
11375            }
11376
11377            fn cache_on_compaction(mut self) -> Self {
11378                self.cache_on_compaction = true;
11379                self
11380            }
11381
11382            fn part_size(mut self, part_size: usize) -> Self {
11383                self.part_size = part_size;
11384                self
11385            }
11386
11387            fn l0_sst_size_bytes(mut self, bytes: usize) -> Self {
11388                self.l0_sst_size_bytes = bytes;
11389                self
11390            }
11391
11392            /// Adds an embedded compactor that compacts once each time
11393            /// [`ObjectStoreCacheTest::compact_and_wait`] is called.
11394            fn on_demand_compactor(mut self) -> Self {
11395                self.on_demand_compactor = true;
11396                self
11397            }
11398
11399            /// Like `on_demand_compactor`, but the compactor holds its own
11400            /// handle to upstream, so the db builder keeps it off the cached store.
11401            fn on_demand_compactor_with_custom_store(mut self) -> Self {
11402                self.on_demand_compactor = true;
11403                self.custom_compactor_store = true;
11404                self
11405            }
11406
11407            async fn build(self) -> ObjectStoreCacheTest {
11408                let Self {
11409                    db_path,
11410                    object_store_cache,
11411                    cache_on_flush,
11412                    cache_on_compaction,
11413                    part_size,
11414                    l0_sst_size_bytes,
11415                    on_demand_compactor,
11416                    custom_compactor_store,
11417                    metrics_recorder,
11418                } = self;
11419
11420                let upstream: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
11421                let temp_dir = tempfile::Builder::new()
11422                    .prefix("objstore_cache_test_")
11423                    .tempdir()
11424                    .unwrap();
11425                let cache_root = temp_dir.keep();
11426
11427                let opts = test_db_options(0, l0_sst_size_bytes, None);
11428
11429                // The cache is user-constructed and passed to the db as the
11430                // object store itself.
11431                let cache = if object_store_cache {
11432                    Some(
11433                        CachedObjectStore::builder(cache_root.clone(), upstream.clone())
11434                            .with_part_size_bytes(part_size)
11435                            .with_cache_on_flush(cache_on_flush)
11436                            .with_cache_on_compaction(cache_on_compaction)
11437                            .build()
11438                            .await
11439                            .unwrap(),
11440                    )
11441                } else {
11442                    None
11443                };
11444                let main_store: Arc<dyn ObjectStore> = match &cache {
11445                    Some(cache) => cache.clone(),
11446                    None => upstream.clone(),
11447                };
11448
11449                let mut builder =
11450                    Db::builder(db_path.as_str(), main_store.clone()).with_settings(opts);
11451                if let Some(recorder) = metrics_recorder {
11452                    builder = builder.with_metrics_recorder(recorder);
11453                }
11454                let should_compact = if on_demand_compactor {
11455                    let flag = Arc::new(AtomicBool::new(false));
11456                    let flag_clone = flag.clone();
11457                    let scheduler = Arc::new(OnDemandCompactionSchedulerSupplier::new(Arc::new(
11458                        move |_state| flag_clone.swap(false, Ordering::SeqCst),
11459                    )));
11460                    // A custom compactor store bypasses the cache entirely;
11461                    // otherwise the compactor shares the db's (possibly
11462                    // cached) store. Open gates make GatedObjectStore a
11463                    // pass-through.
11464                    let compactor_store: Arc<dyn ObjectStore> = if custom_compactor_store {
11465                        Arc::new(GatedObjectStore::new(upstream.clone()))
11466                    } else {
11467                        main_store.clone()
11468                    };
11469                    // One subcompaction writes one output SST, keeping exact
11470                    // part counts deterministic.
11471                    let mut compactor_options = fast_compactor_options();
11472                    if let Some(worker) = compactor_options.worker.as_mut() {
11473                        worker.max_subcompactions = 1;
11474                    }
11475                    builder = builder.with_compactor_builder(
11476                        CompactorBuilder::new(db_path.as_str(), compactor_store)
11477                            .with_scheduler_supplier(scheduler)
11478                            .with_options(compactor_options),
11479                    );
11480                    Some(flag)
11481                } else {
11482                    None
11483                };
11484                let db = builder.build().await.unwrap();
11485
11486                ObjectStoreCacheTest {
11487                    db,
11488                    upstream,
11489                    cache,
11490                    cache_root,
11491                    db_path,
11492                    should_compact,
11493                }
11494            }
11495        }
11496
11497        impl ObjectStoreCacheTest {
11498            fn builder(db_path: &str) -> ObjectStoreCacheTestBuilder {
11499                ObjectStoreCacheTestBuilder::new(db_path)
11500            }
11501
11502            fn db(&self) -> &Db {
11503                &self.db
11504            }
11505
11506            /// A path under the db root, e.g. `sub_path("wal/00..002.sst")`.
11507            fn sub_path(&self, suffix: &str) -> object_store::path::Path {
11508                object_store::path::Path::from(format!("{}/{}", self.db_path, suffix))
11509            }
11510
11511            /// Number of cached part files for an object.
11512            fn cached_part_count(&self, path: &object_store::path::Path) -> usize {
11513                let dir = self.cache_root.join(path.to_string());
11514                let Ok(entries) = std::fs::read_dir(dir) else {
11515                    return 0;
11516                };
11517                entries
11518                    .filter(|e| {
11519                        e.as_ref()
11520                            .unwrap()
11521                            .file_name()
11522                            .to_string_lossy()
11523                            .starts_with("_part")
11524                    })
11525                    .count()
11526            }
11527
11528            fn assert_cached(&self, path: &object_store::path::Path, expected_parts: usize) {
11529                assert_eq!(
11530                    self.cached_part_count(path),
11531                    expected_parts,
11532                    "expected {path} to be cached as {expected_parts} part(s)"
11533                );
11534            }
11535
11536            /// Asserts each of `suffixes` (relative to the db root) is uncached.
11537            fn assert_uncached(&self, suffixes: &[&str]) {
11538                for suffix in suffixes {
11539                    let path = self.sub_path(suffix);
11540                    assert_eq!(
11541                        self.cached_part_count(&path),
11542                        0,
11543                        "expected {suffix} to be uncached"
11544                    );
11545                }
11546            }
11547
11548            /// Lists the compacted SSTs currently in the object store.
11549            async fn compacted_locations(&self) -> Vec<object_store::path::Path> {
11550                let prefix = self.sub_path("compacted");
11551                self.upstream
11552                    .list(Some(&prefix))
11553                    .map(|meta| meta.unwrap().location)
11554                    .collect()
11555                    .await
11556            }
11557
11558            /// The size of an object as stored upstream, in bytes.
11559            async fn object_size(&self, path: &object_store::path::Path) -> u64 {
11560                self.upstream.head(path).await.unwrap().size
11561            }
11562
11563            /// The upstream path of a compacted SST id.
11564            fn compacted_sst_path(&self, id: &SsTableId) -> object_store::path::Path {
11565                crate::paths::PathResolver::from_root(self.db_path.as_str()).sst_path(id)
11566            }
11567
11568            fn l0_ids(&self) -> Vec<SsTableId> {
11569                self.db.manifest().l0().iter().map(|v| v.sst.id).collect()
11570            }
11571
11572            /// Triggers one on-demand compaction and waits for a sorted run to
11573            /// land in the manifest. Requires `on_demand_compactor`.
11574            async fn compact_and_wait(&self) {
11575                self.should_compact
11576                    .as_ref()
11577                    .expect("fixture built without on_demand_compactor")
11578                    .store(true, Ordering::SeqCst);
11579                tokio::time::timeout(Duration::from_secs(30), async {
11580                    loop {
11581                        if !self.db.manifest().compacted().is_empty() {
11582                            return;
11583                        }
11584                        tokio::time::sleep(Duration::from_millis(10)).await;
11585                    }
11586                })
11587                .await
11588                .expect("compaction did not land within timeout");
11589            }
11590
11591            /// The SSTs the compaction wrote: sorted run members that were not
11592            /// among the flushed L0s.
11593            fn compaction_output_ids(&self, l0_ids: &[SsTableId]) -> Vec<SsTableId> {
11594                self.db
11595                    .manifest()
11596                    .compacted()
11597                    .iter()
11598                    .flat_map(|sr| sr.sst_views.iter())
11599                    .map(|v| v.sst.id)
11600                    .filter(|id| !l0_ids.contains(id))
11601                    .collect()
11602            }
11603
11604            async fn close(self) {
11605                self.db.close().await.unwrap();
11606            }
11607        }
11608
11609        #[tokio::test]
11610        async fn test_get_with_object_store_cache_metrics() {
11611            let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
11612            let mut opts = test_db_options(0, 1024, None);
11613            let temp_dir = tempfile::Builder::new()
11614                .prefix("objstore_cache_test_")
11615                .tempdir()
11616                .unwrap();
11617
11618            opts.object_store_cache_options.root_folder = Some(temp_dir.keep());
11619            opts.object_store_cache_options.part_size_bytes = 1024;
11620            let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
11621            let kv_store = Db::builder(
11622                "/tmp/test_kv_store_with_cache_metrics",
11623                object_store.clone(),
11624            )
11625            .with_settings(opts)
11626            .with_db_cache_disabled()
11627            .with_metrics_recorder(metrics_recorder.clone())
11628            .build()
11629            .await
11630            .unwrap();
11631
11632            let access_count0 = lookup_metric(&metrics_recorder, PART_ACCESS_COUNT).unwrap();
11633            let key = b"test_key";
11634            let value = b"test_value";
11635            kv_store.put(key, value).await.unwrap();
11636            kv_store
11637                .flush_with_options(FlushOptions {
11638                    flush_type: FlushType::MemTable,
11639                })
11640                .await
11641                .unwrap();
11642
11643            // First (cold) get. cache_on_flush is off, so the SST is not cached on the
11644            // write. The whole SST is a single cache part, read as three sub-ranges
11645            // (index, filter and block). The first is a cold read that fetches and
11646            // caches the part (a miss) and the next two are served from the cache
11647            // (hits). So three accesses, two hits.
11648            let val = kv_store.get(key).await.unwrap();
11649            assert_eq!(val, Some(Bytes::from_static(value)));
11650            let access_count1 = lookup_metric(&metrics_recorder, PART_ACCESS_COUNT).unwrap();
11651            let hit_count1 = lookup_metric(&metrics_recorder, PART_HIT_COUNT).unwrap();
11652            assert_eq!(
11653                access_count1 - access_count0,
11654                3,
11655                "one point get reads the single-part SST in three sub-ranges"
11656            );
11657            assert_eq!(
11658                hit_count1, 2,
11659                "the cold read is a miss; the next two reads hit the warm cache"
11660            );
11661
11662            // Second (warm) get: the object is fully cached, so all three reads hit.
11663            let got = kv_store.get(key).await.unwrap();
11664            assert_eq!(got, Some(Bytes::from_static(value)));
11665            let access_count2 = lookup_metric(&metrics_recorder, PART_ACCESS_COUNT).unwrap();
11666            let hit_count2 = lookup_metric(&metrics_recorder, PART_HIT_COUNT).unwrap();
11667            assert_eq!(
11668                access_count2 - access_count1,
11669                3,
11670                "the second get reads the same three sub-ranges"
11671            );
11672            assert_eq!(
11673                hit_count2 - hit_count1,
11674                3,
11675                "every read in the warm get is a cache hit"
11676            );
11677        }
11678
11679        #[tokio::test]
11680        async fn test_db_records_read_calls_into_cached_object_store() {
11681            let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
11682            let mut opts = test_db_options(0, 1024, None);
11683            let temp_dir = tempfile::Builder::new()
11684                .prefix("objstore_metrics_test_")
11685                .tempdir()
11686                .unwrap();
11687
11688            opts.manifest_poll_interval = Duration::from_secs(3600);
11689            let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
11690            let path = "/tmp/test_db_records_read_calls_into_cached_object_store";
11691            let cached_store = CachedObjectStore::builder(temp_dir.keep(), object_store)
11692                .with_part_size_bytes(1024)
11693                .build()
11694                .await
11695                .unwrap();
11696            // Disable the in-memory block cache so reads reach the object store
11697            // cache layer (the subject of this test) instead of being served from
11698            // decoded blocks in memory.
11699            let kv_store = Db::builder(path, cached_store)
11700                .with_settings(opts)
11701                .with_db_cache_disabled()
11702                .with_metrics_recorder(metrics_recorder.clone())
11703                .build()
11704                .await
11705                .unwrap();
11706
11707            kv_store.put(b"test_key", b"test_value").await.unwrap();
11708            kv_store.flush().await.unwrap();
11709            kv_store
11710                .flush_with_options(FlushOptions {
11711                    flush_type: FlushType::MemTable,
11712                })
11713                .await
11714                .unwrap();
11715
11716            let requests_before =
11717                lookup_object_store_op_request_count(&metrics_recorder, "db", "main", "get");
11718            let _val = kv_store.get(b"test_key").await.unwrap();
11719            let requests_after_first =
11720                lookup_object_store_op_request_count(&metrics_recorder, "db", "main", "get");
11721            let got = kv_store.get(b"test_key").await.unwrap();
11722            let requests_after_second =
11723                lookup_object_store_op_request_count(&metrics_recorder, "db", "main", "get");
11724
11725            // The instrumented store sits above the object store cache and counts
11726            // logical read calls whether they are served from the cache or the
11727            // remote store.
11728            // A point get reads the single-part SST in three sub-ranges (index,
11729            // filter and block).
11730            assert_eq!(requests_after_first, requests_before + 3);
11731            assert_eq!(got, Some(Bytes::from_static(b"test_value")));
11732            assert_eq!(requests_after_second, requests_after_first + 3);
11733            assert_eq!(
11734                lookup_object_store_op_histogram_count(&metrics_recorder, "db", "main", "get"),
11735                requests_after_second as u64
11736            );
11737            kv_store.close().await.unwrap();
11738        }
11739
11740        /// Warming a disk cache by enumerating SSTs from the manifest,
11741        /// resolving their paths with `PathResolver`, and loading their raw
11742        /// bytes with `load_files_to_cache`.
11743        #[tokio::test]
11744        async fn test_preload_disk_cache_from_manifest() {
11745            let fixture =
11746                ObjectStoreCacheTest::builder("/tmp/test_preload_disk_cache_from_manifest")
11747                    .build()
11748                    .await;
11749
11750            // Two flushed L0 SSTs, not admitted on write.
11751            for (key, value) in [(b"k1", b"v1"), (b"k2", b"v2")] {
11752                fixture.db().put(key, value).await.unwrap();
11753                fixture.db().flush().await.unwrap();
11754                fixture
11755                    .db()
11756                    .flush_with_options(FlushOptions {
11757                        flush_type: FlushType::MemTable,
11758                    })
11759                    .await
11760                    .unwrap();
11761            }
11762
11763            let ids = fixture.l0_ids();
11764            assert_eq!(ids.len(), 2);
11765            let paths: Vec<_> = ids
11766                .iter()
11767                .map(|id| fixture.compacted_sst_path(id))
11768                .collect();
11769            for path in &paths {
11770                fixture.assert_cached(path, 0);
11771            }
11772
11773            let cache = fixture.cache.as_ref().unwrap();
11774            cache
11775                .load_files_to_cache(paths.clone(), usize::MAX)
11776                .await
11777                .unwrap();
11778
11779            // Each small SST fits in a single 1 KiB part.
11780            for path in &paths {
11781                fixture.assert_cached(path, 1);
11782            }
11783            fixture.close().await;
11784        }
11785
11786        /// A flushed L0 SST is a compacted SST written by the main store, so
11787        /// cache_on_flush admits it. The manifest (untagged) and the WAL
11788        /// (skipped by policy) are never cached.
11789        #[tokio::test]
11790        async fn test_object_store_cache_caches_flushed_sst_only() {
11791            let fixture = ObjectStoreCacheTest::builder("/tmp/test_object_store_cache_flush_only")
11792                .cache_on_flush()
11793                .build()
11794                .await;
11795
11796            // A foreground write plus a memtable flush produces a WAL entry, a
11797            // manifest, and an L0 SST.
11798            fixture.db().put(b"test_key", b"test_value").await.unwrap();
11799            fixture.db().flush().await.unwrap();
11800            fixture
11801                .db()
11802                .flush_with_options(FlushOptions {
11803                    flush_type: FlushType::MemTable,
11804                })
11805                .await
11806                .unwrap();
11807
11808            fixture.assert_uncached(&[
11809                "manifest/00000000000000000001.manifest",
11810                "manifest/00000000000000000002.manifest",
11811                "wal/00000000000000000001.sst",
11812                "wal/00000000000000000002.sst",
11813            ]);
11814
11815            // The single explicit memtable flush produces one L0 SST, cached as one
11816            // part (the key/value is well under the 1 KiB part size).
11817            let compacted = fixture.compacted_locations().await;
11818            assert_eq!(compacted.len(), 1, "expected exactly one flushed SST");
11819            fixture.assert_cached(&compacted[0], 1);
11820            fixture.close().await;
11821        }
11822
11823        /// A flushed SST above the 10 MiB multipart threshold is written with a
11824        /// multipart upload, whose parts are now mirrored into the cache, so the
11825        /// whole SST is cached (one part per part_size chunk).
11826        #[tokio::test]
11827        async fn test_object_store_cache_caches_large_multipart_flush() {
11828            const MIB: usize = 1024 * 1024;
11829
11830            let fixture = ObjectStoreCacheTest::builder("/tmp/test_object_store_cache_large_flush")
11831                .cache_on_flush()
11832                .part_size(MIB)
11833                // Large enough that the whole write flushes as a single L0 SST.
11834                .l0_sst_size_bytes(64 * MIB)
11835                .build()
11836                .await;
11837
11838            // ~20 MiB in one memtable, flushed as a single L0 SST above the 10 MiB
11839            // multipart threshold.
11840            for i in 0..20u32 {
11841                let key = format!("k{:04}", i);
11842                fixture
11843                    .db()
11844                    .put(key.as_bytes(), &vec![i as u8; MIB])
11845                    .await
11846                    .unwrap();
11847            }
11848            fixture
11849                .db()
11850                .flush_with_options(FlushOptions {
11851                    flush_type: FlushType::MemTable,
11852                })
11853                .await
11854                .unwrap();
11855
11856            let compacted = fixture.compacted_locations().await;
11857            assert_eq!(compacted.len(), 1, "expected exactly one flushed SST");
11858            // The SST is written with a multipart upload (each 1 MiB part teed into
11859            // the cache), so every part of the SST is cached.
11860            let expected_parts = (fixture.object_size(&compacted[0]).await as usize).div_ceil(MIB);
11861            assert!(
11862                expected_parts > 10,
11863                "expected a large multipart SST, got {expected_parts} part(s)"
11864            );
11865            fixture.assert_cached(&compacted[0], expected_parts);
11866            fixture.close().await;
11867        }
11868
11869        /// cache_on_compaction admits the embedded compactor's output; with
11870        /// cache_on_flush off, the flushed L0 inputs stay uncached.
11871        #[tokio::test]
11872        async fn test_object_store_cache_caches_compaction_output() {
11873            let t = ObjectStoreCacheTest::builder("/tmp/test_object_store_cache_compaction_output")
11874                .cache_on_compaction()
11875                .on_demand_compactor()
11876                .build()
11877                .await;
11878
11879            for i in 0..2u32 {
11880                let key = format!("key{:04}", i);
11881                t.db().put(key.as_bytes(), &[b'v'; 64]).await.unwrap();
11882                t.db()
11883                    .flush_with_options(FlushOptions {
11884                        flush_type: FlushType::MemTable,
11885                    })
11886                    .await
11887                    .unwrap();
11888            }
11889            let l0_ids = t.l0_ids();
11890            assert_eq!(l0_ids.len(), 2);
11891
11892            t.compact_and_wait().await;
11893
11894            for id in &l0_ids {
11895                t.assert_cached(&t.compacted_sst_path(id), 0);
11896            }
11897
11898            let output_ids = t.compaction_output_ids(&l0_ids);
11899            assert!(!output_ids.is_empty(), "expected compaction output SSTs");
11900            for id in &output_ids {
11901                let path = t.compacted_sst_path(id);
11902                assert!(
11903                    t.cached_part_count(&path) > 0,
11904                    "expected compaction output {path} to be cached"
11905                );
11906            }
11907            t.close().await;
11908        }
11909
11910        /// Compaction output above the multipart threshold is cached in full.
11911        #[tokio::test]
11912        async fn test_object_store_cache_caches_large_multipart_compaction_output() {
11913            const MIB: usize = 1024 * 1024;
11914
11915            let t = ObjectStoreCacheTest::builder(
11916                "/tmp/test_object_store_cache_large_compaction_output",
11917            )
11918            .cache_on_compaction()
11919            .on_demand_compactor()
11920            .part_size(MIB)
11921            // Large enough that each write batch flushes as a single L0 SST.
11922            .l0_sst_size_bytes(64 * MIB)
11923            .build()
11924            .await;
11925
11926            // Two ~10 MiB L0s; the ~20 MiB output crosses the multipart threshold.
11927            for sst in 0..2u32 {
11928                for i in 0..10u32 {
11929                    let key = format!("k{:04}", sst * 10 + i);
11930                    t.db()
11931                        .put(key.as_bytes(), &vec![i as u8; MIB])
11932                        .await
11933                        .unwrap();
11934                }
11935                t.db()
11936                    .flush_with_options(FlushOptions {
11937                        flush_type: FlushType::MemTable,
11938                    })
11939                    .await
11940                    .unwrap();
11941            }
11942            let l0_ids = t.l0_ids();
11943            assert_eq!(l0_ids.len(), 2);
11944
11945            t.compact_and_wait().await;
11946
11947            let output_ids = t.compaction_output_ids(&l0_ids);
11948            assert_eq!(output_ids.len(), 1, "expected one output SST");
11949            let path = t.compacted_sst_path(&output_ids[0]);
11950            let expected_parts = (t.object_size(&path).await as usize).div_ceil(MIB);
11951            assert_eq!(
11952                expected_parts, 21,
11953                "update this count if an SST encoding change shifts the size"
11954            );
11955            t.assert_cached(&path, expected_parts);
11956            t.close().await;
11957        }
11958
11959        /// A compactor builder with its own object store stays cacheless:
11960        /// output is not admitted even with cache_on_compaction on.
11961        #[tokio::test]
11962        async fn test_object_store_cache_skips_compaction_output_from_custom_store() {
11963            let t = ObjectStoreCacheTest::builder(
11964                "/tmp/test_object_store_cache_custom_compactor_store",
11965            )
11966            .cache_on_compaction()
11967            .on_demand_compactor_with_custom_store()
11968            .build()
11969            .await;
11970
11971            for i in 0..2u32 {
11972                let key = format!("key{:04}", i);
11973                t.db().put(key.as_bytes(), &[b'v'; 64]).await.unwrap();
11974                t.db()
11975                    .flush_with_options(FlushOptions {
11976                        flush_type: FlushType::MemTable,
11977                    })
11978                    .await
11979                    .unwrap();
11980            }
11981            let l0_ids = t.l0_ids();
11982            assert_eq!(l0_ids.len(), 2);
11983
11984            t.compact_and_wait().await;
11985
11986            let output_ids = t.compaction_output_ids(&l0_ids);
11987            assert!(!output_ids.is_empty(), "expected compaction output SSTs");
11988            for id in &output_ids {
11989                let path = t.compacted_sst_path(id);
11990                assert_eq!(
11991                    t.cached_part_count(&path),
11992                    0,
11993                    "expected compaction output {path} to stay uncached"
11994                );
11995            }
11996            t.close().await;
11997        }
11998
11999        /// An embedded compactor on the DB's own store records its object
12000        /// store I/O under the compactor component, with and without the
12001        /// object store cache.
12002        #[tokio::test]
12003        async fn test_embedded_compactor_io_recorded_under_compactor_component() {
12004            for object_store_cache in [true, false] {
12005                let recorder = Arc::new(DefaultMetricsRecorder::new());
12006                let mut builder =
12007                    ObjectStoreCacheTest::builder("/tmp/test_compactor_component_metrics")
12008                        .cache_on_compaction()
12009                        .on_demand_compactor()
12010                        .metrics_recorder(recorder.clone());
12011                if !object_store_cache {
12012                    builder = builder.without_object_store_cache();
12013                }
12014                let t = builder.build().await;
12015
12016                for i in 0..2u32 {
12017                    let key = format!("key{:04}", i);
12018                    t.db().put(key.as_bytes(), &[b'v'; 64]).await.unwrap();
12019                    t.db()
12020                        .flush_with_options(FlushOptions {
12021                            flush_type: FlushType::MemTable,
12022                        })
12023                        .await
12024                        .unwrap();
12025                }
12026                t.compact_and_wait().await;
12027
12028                let gets =
12029                    lookup_object_store_op_request_count(&recorder, "compactor", "main", "get");
12030                let puts =
12031                    lookup_object_store_op_request_count(&recorder, "compactor", "main", "put");
12032                assert!(gets > 0, "no compactor gets [cache={object_store_cache}]");
12033                assert!(puts > 0, "no compactor puts [cache={object_store_cache}]");
12034                t.close().await;
12035            }
12036        }
12037    }
12038}