1#![doc(
2 html_logo_url = "https://raw.githubusercontent.com/sourcenetwork/regolith/main/art/regolith_banner_2048x512.png"
3)]
4#![deny(unsafe_code)]
54#![warn(missing_docs)]
55
56mod backup;
57mod checkpoint;
58mod column_family;
59mod engine;
60pub mod env;
61mod error;
62mod event_listener;
63mod iter;
64mod mvcc;
65mod options;
66mod perf_context;
67mod portability;
68mod rate_limiter;
69mod slice;
70mod sst_file_writer;
71mod statistics;
72mod stream_writer;
73mod sync;
74mod tailing;
75mod transaction;
76mod ttl;
77mod txn_buffer;
78
79pub use backup::{BackupEngine, BackupId, BackupInfo};
80pub use checkpoint::Checkpoint;
81pub use column_family::{ColumnFamilyHandle, DEFAULT_CF_NAME};
82#[cfg(target_os = "wasi")]
83pub use env::WasiEnv;
84pub use env::{Capabilities, Env, MemEnv, StdEnv};
85pub use error::Error;
86pub use event_listener::{
87 BackgroundErrorReason, CompactionJobInfo, EventListener, ExternalFileIngestionInfo,
88 FlushJobInfo, TableFileCreationInfo, TableFileCreationReason, TableFileDeletionInfo,
89 WalFullInfo,
90};
91pub use iter::Iter;
92pub use options::{
93 ArenaProfile, CompactionDecision, CompactionFilter, CompactionStyle, CompressionType,
94 DEFAULT_MAX_BACKGROUND_COMPACTIONS, DEFAULT_MAX_KEY_SIZE, DEFAULT_MAX_VALUE_SIZE,
95 DEFAULT_TRANSACTION_KEYS_INLINE, DurabilityMode, FifoCompactionOptions, FixedLengthPrefix,
96 MAX_BLOCK_CACHE_SHARD_BITS, MAX_BLOOM_BITS_PER_KEY, MergeOperator, Options, PrefixExtractor,
97 UniversalCompactionOptions, WriteOptions,
98};
99pub use perf_context::{PerfContext, PerfContextSnapshot, PerfLevel};
100pub use rate_limiter::{Priority, RateLimiter, TokenBucketRateLimiter};
101pub use slice::DbSlice;
102pub use sst_file_writer::{IngestOptions, SstFileMeta, SstFileWriter};
103pub use statistics::{Histogram, HistogramSnapshot, Statistics, Ticker};
104pub use stream_writer::{StreamOptions, StreamingWriter};
105pub use tailing::TailingIter;
106pub use transaction::{
107 IsolationLevel, OptimisticTransactionDb, OwnedTransaction, ScanDirection, Transaction,
108 TransactionDb, TransactionError, TxResult, TxnScanStream,
109};
110pub use ttl::{DbWithTtl, TtlCompactionFilter, strip_timestamp};
111
112#[cfg(loom)]
113#[doc(hidden)]
114pub mod loom_exports {
115 pub use crate::engine::loom_model::{handoff, skiplist, slice, version};
126}
127
128#[cfg(feature = "fuzzing")]
129#[doc(hidden)]
130pub mod fuzzing {
131 use std::fs;
137 use std::path::{Path, PathBuf};
138 use std::sync::atomic::{AtomicU64, Ordering};
139
140 static NEXT_TEMP_ID: AtomicU64 = AtomicU64::new(0);
141
142 pub fn decode_block(data: &[u8]) {
144 let _ = crate::engine::block::Block::decode_data_block(data.to_vec());
145 }
146
147 pub fn decode_range_tombstones(data: &[u8]) {
149 let _ = crate::engine::sstable::decode_range_tombstone_block(data);
150 }
151
152 pub fn replay_wal(data: &[u8]) {
154 with_temp_file("wal", "log", data, |path| {
155 let Ok(mut iter) = crate::engine::wal_replay::WalReplayIter::open(
156 &crate::env::std_env(),
157 path,
158 crate::engine::wal_replay::WalPosition::Newest,
159 ) else {
160 return;
161 };
162 while matches!(iter.next_entry(), Ok(Some(_))) {}
163 });
164 }
165
166 pub fn open_sst(data: &[u8]) {
168 with_temp_file("sst", "sst", data, |path| {
169 let _ = crate::engine::sstable::SsTableReader::open_with(
172 &crate::env::std_env(),
173 path,
174 0,
175 crate::engine::sstable::MetadataPolicy::Pinned,
176 );
177 });
178 }
179
180 pub fn replay_manifest(data: &[u8]) {
182 with_temp_dir("manifest", |db_dir| {
183 let sst_dir = db_dir.join("sst");
184 let manifest_path = db_dir.join("MANIFEST");
185 if fs::create_dir_all(&sst_dir).is_ok() && fs::write(&manifest_path, data).is_ok() {
186 let _ = crate::engine::manifest::VersionSet::open(db_dir, &sst_dir);
187 }
188 });
189 }
190
191 fn with_temp_file(label: &str, extension: &str, data: &[u8], f: impl FnOnce(&Path)) {
192 let path = temp_path(label).with_extension(extension);
193 if fs::write(&path, data).is_ok() {
194 f(&path);
195 }
196 let _ = fs::remove_file(path);
197 }
198
199 fn with_temp_dir(label: &str, f: impl FnOnce(&Path)) {
200 let path = temp_path(label);
201 if fs::create_dir_all(&path).is_ok() {
202 f(&path);
203 }
204 let _ = fs::remove_dir_all(path);
205 }
206
207 fn temp_path(label: &str) -> PathBuf {
208 let id = NEXT_TEMP_ID.fetch_add(1, Ordering::Relaxed);
209 std::env::temp_dir().join(format!("regolith-fuzz-{label}-{}-{id}", std::process::id()))
210 }
211}
212
213use column_family::{
214 CfRegistry, DEFAULT_CF_ID, META_CF_ID, cf_lower_bound, cf_upper_bound, meta, prefix_key,
215};
216use engine::lookup_key::LookupKey;
217
218use std::collections::BTreeMap;
219use std::path::Path;
220use std::sync::Arc;
221
222use engine::RegolithEngine;
223
224fn invalid_cf_handle_error(cf: &ColumnFamilyHandle) -> Error {
225 Error::invalid_column_family(format!(
226 "column family handle '{}' with id {} is not live",
227 cf.name(),
228 cf.id()
229 ))
230}
231
232fn invalid_input_error(message: impl Into<String>) -> Error {
233 Error::invalid_argument(message)
234}
235
236fn invalid_cf_id_error(cf_id: u32) -> Error {
237 Error::invalid_column_family(format!("column family id {cf_id} is not live"))
238}
239
240fn invalid_cf_id_io_error(cf_id: u32) -> std::io::Error {
241 std::io::Error::new(
242 std::io::ErrorKind::InvalidInput,
243 format!("column family id {cf_id} is not live"),
244 )
245}
246
247fn map_point_read_error(err: std::io::Error, prefixed_key: &[u8]) -> Error {
248 let user_key = prefixed_key.get(4..).unwrap_or(prefixed_key);
249 map_point_read_error_for(err, user_key)
250}
251
252fn map_point_read_error_for(err: std::io::Error, user_key: &[u8]) -> Error {
258 if err.kind() == std::io::ErrorKind::InvalidData
259 && err.to_string().starts_with("merge operator ")
260 {
261 Error::MergeFailed(user_key.to_vec())
262 } else {
263 Error::from(err)
264 }
265}
266
267fn strip_cf_prefix_key(key: &[u8]) -> Result<Vec<u8>> {
268 key.get(4..)
269 .map(|user_key| user_key.to_vec())
270 .ok_or_else(|| Error::corruption("internal key is shorter than the column-family prefix"))
271}
272
273fn strip_cf_prefix_entries(raw: Vec<(Vec<u8>, Vec<u8>)>) -> Result<Vec<(Vec<u8>, Vec<u8>)>> {
274 raw.into_iter()
275 .map(|(k, v)| strip_cf_prefix_key(&k).map(|user_key| (user_key, v)))
276 .collect()
277}
278
279#[derive(Debug, Clone, PartialEq, Eq)]
285pub struct ScanPage {
286 pub entries: Vec<(Vec<u8>, Vec<u8>)>,
288 pub next_start: Option<Vec<u8>>,
294}
295
296fn prefixed_cf_id(prefixed_key: &[u8]) -> std::io::Result<u32> {
297 let prefix = prefixed_key.get(..4).ok_or_else(|| {
298 std::io::Error::new(
299 std::io::ErrorKind::InvalidInput,
300 "prefixed key is shorter than the column-family id",
301 )
302 })?;
303 let mut bytes = [0; 4];
304 bytes.copy_from_slice(prefix);
305 Ok(u32::from_be_bytes(bytes))
306}
307
308#[derive(Debug)]
313#[allow(dead_code)]
314struct OptionsSnapshot {
315 durability: engine::DurabilityMode,
316 default_cf: &'static str,
317 read_only: bool,
318 max_key_size: usize,
319 max_value_size: usize,
320 transaction_keys_inline: usize,
321}
322
323fn format_key_for_display(key: &[u8]) -> String {
329 let payload = if key.len() > 4 { &key[4..] } else { key };
330 if payload.iter().all(|&b| b.is_ascii_graphic() || b == b' ') {
331 format!("\"{}\"", String::from_utf8_lossy(payload))
332 } else {
333 let hex: String = payload.iter().map(|b| format!("{b:02x}")).collect();
334 format!("0x{hex}")
335 }
336}
337
338#[derive(Debug, Clone, Copy, PartialEq, Eq)]
341pub struct Range<'a> {
342 pub start: &'a [u8],
344 pub end: &'a [u8],
346}
347
348impl<'a> Range<'a> {
349 pub fn new(start: &'a [u8], end: &'a [u8]) -> Self {
351 Self { start, end }
352 }
353}
354
355#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
363pub struct MemTableStats {
364 pub count: u64,
366 pub size: u64,
368}
369
370pub type Result<T> = std::result::Result<T, Error>;
372
373pub struct Db {
375 engine: Arc<RegolithEngine>,
376 durability: engine::DurabilityMode,
377 cfs: Arc<CfRegistry>,
378 read_only: bool,
379 max_key_size: usize,
380 max_value_size: usize,
381 transaction_keys_inline: usize,
382}
383
384impl std::fmt::Debug for Db {
385 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
386 f.debug_struct("Db")
387 .field("durability", &self.durability)
388 .field("read_only", &self.read_only)
389 .finish_non_exhaustive()
390 }
391}
392
393impl Db {
394 pub fn open<P: AsRef<Path>>(path: P, opts: Options) -> Result<Self> {
407 opts.validate()?;
408 let read_only = opts.read_only;
409 let max_key_size = opts.max_key_size;
410 let max_value_size = opts.max_value_size;
411 let transaction_keys_inline = opts.transaction_keys_inline;
412 let durability = match opts.durability {
413 DurabilityMode::Immediate => engine::DurabilityMode::Immediate,
414 DurabilityMode::Eventual => engine::DurabilityMode::Eventual,
415 };
416 let engine_opts = opts.to_engine_options();
417 let engine = if read_only {
418 RegolithEngine::open_read_only(path.as_ref(), engine_opts)?
419 } else {
420 RegolithEngine::open(path.as_ref(), engine_opts)?
421 };
422 let cfs = Arc::new(CfRegistry::new());
423 let db = Self {
424 engine,
425 durability,
426 cfs,
427 read_only,
428 max_key_size,
429 max_value_size,
430 transaction_keys_inline,
431 };
432 db.load_cf_registry()?;
433 Ok(db)
434 }
435
436 pub fn open_read_only<P: AsRef<Path>>(path: P, mut opts: Options) -> Result<Self> {
443 opts.read_only = true;
444 Self::open(path, opts)
445 }
446
447 fn load_cf_registry(&self) -> Result<()> {
451 let mut entries: Vec<(String, u32)> = Vec::new();
458 let pairs = collect_range(
459 self.engine.new_iter_latest(),
460 Some(&meta::name_scan_prefix()),
461 Some(&meta::name_scan_upper()),
462 )?;
463 for (key, value) in pairs {
464 let Ok(id_bytes) = <[u8; 4]>::try_from(value.as_slice()) else {
466 continue;
467 };
468 let Some(name) = meta::name_from_key(&key) else {
469 continue;
470 };
471 entries.push((name.to_string(), u32::from_be_bytes(id_bytes)));
472 }
473
474 let next_id_raw = self
476 .engine
477 .get_latest(&meta::next_id_key())
478 .map_err(Error::from)?;
479 let next_id = next_id_raw
480 .and_then(|bytes| <[u8; 4]>::try_from(bytes.as_slice()).ok())
481 .map_or(DEFAULT_CF_ID + 1, u32::from_be_bytes);
482
483 entries.push((DEFAULT_CF_NAME.to_string(), DEFAULT_CF_ID));
487 self.cfs.load(entries, next_id);
488 Ok(())
489 }
490
491 pub fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>> {
496 Ok(self.get_slice(key)?.map(DbSlice::into_vec))
497 }
498
499 pub fn get_slice(&self, key: &[u8]) -> Result<Option<DbSlice>> {
509 self.lookup_slice_latest(DEFAULT_CF_ID, key)
510 }
511
512 pub fn get_slice_cf(&self, cf: &ColumnFamilyHandle, key: &[u8]) -> Result<Option<DbSlice>> {
514 self.validate_cf_handle(cf)?;
515 self.lookup_slice_latest(cf.id(), key)
516 }
517
518 pub fn has(&self, key: &[u8]) -> Result<bool> {
533 Ok(self.get_size(key)?.is_some())
534 }
535
536 pub fn has_cf(&self, cf: &ColumnFamilyHandle, key: &[u8]) -> Result<bool> {
538 Ok(self.get_size_cf(cf, key)?.is_some())
539 }
540
541 pub fn get_size(&self, key: &[u8]) -> Result<Option<usize>> {
548 self.lookup_size_latest(DEFAULT_CF_ID, key)
549 }
550
551 pub fn get_size_cf(&self, cf: &ColumnFamilyHandle, key: &[u8]) -> Result<Option<usize>> {
553 self.validate_cf_handle(cf)?;
554 self.lookup_size_latest(cf.id(), key)
555 }
556
557 fn lookup_slice_latest(&self, cf_id: u32, key: &[u8]) -> Result<Option<DbSlice>> {
565 let stats = self.stats();
566 let _scope = statistics::TimeScope::new(stats, Histogram::DbGet);
567 if let Some(s) = stats {
568 s.add(Ticker::KeysRead, 1);
569 }
570 perf_context::record_get_call();
571 let result = self
572 .engine
573 .get_slice_latest(cf_id, key)
574 .map_err(|err| map_point_read_error_for(err, key));
575 if let (Some(s), Ok(Some(v))) = (stats, &result) {
576 s.add(Ticker::BytesRead, v.len() as u64);
577 s.record(Histogram::BytesPerRead, v.len() as u64);
578 }
579 result
580 }
581
582 fn lookup_size_latest(&self, cf_id: u32, key: &[u8]) -> Result<Option<usize>> {
584 let stats = self.stats();
585 let _scope = statistics::TimeScope::new(stats, Histogram::DbGet);
586 if let Some(s) = stats {
587 s.add(Ticker::KeysRead, 1);
588 }
589 perf_context::record_get_call();
590 self.engine
591 .get_size_latest(cf_id, key)
592 .map_err(|err| map_point_read_error_for(err, key))
593 }
594
595 fn stats(&self) -> Option<&Statistics> {
600 self.engine.statistics()
601 }
602
603 pub fn multi_get(&self, keys: &[&[u8]]) -> Result<Vec<Option<Vec<u8>>>> {
612 let owned: Vec<Vec<u8>> = keys.iter().map(|k| prefix_key(DEFAULT_CF_ID, k)).collect();
613 let refs: Vec<&[u8]> = owned.iter().map(|k| k.as_slice()).collect();
614 self.engine.multi_get_latest(&refs).map_err(Error::from)
615 }
616
617 pub fn put(&self, key: &[u8], value: &[u8]) -> Result<()> {
621 self.put_opt(&WriteOptions::default(), key, value)
622 }
623
624 pub fn put_opt(&self, opts: &WriteOptions, key: &[u8], value: &[u8]) -> Result<()> {
627 self.ensure_writable()?;
628 self.validate_write_kv_sizes(key, value)?;
629 self.wait_for_write_capacity(opts)?;
630 let stats = self.stats();
631 let _scope = statistics::TimeScope::new(stats, Histogram::DbWrite);
632 let bytes = (key.len() + value.len()) as u64;
633 if let Some(s) = stats {
634 s.add(Ticker::KeysWritten, 1);
635 s.add(Ticker::BytesWritten, bytes);
636 s.record(Histogram::BytesPerWrite, bytes);
637 }
638 perf_context::record_write_call();
639 let (dm, disable_wal) = self.resolve_write_opts(opts);
640 self.engine
641 .apply_single_put(
642 prefix_key(DEFAULT_CF_ID, key),
643 value.to_vec(),
644 dm,
645 disable_wal,
646 )
647 .map_err(Error::from)
648 .map(|_| ())
649 }
650
651 pub fn delete(&self, key: &[u8]) -> Result<()> {
654 self.delete_opt(&WriteOptions::default(), key)
655 }
656
657 pub fn delete_opt(&self, opts: &WriteOptions, key: &[u8]) -> Result<()> {
660 self.ensure_writable()?;
661 self.validate_key_size(key)?;
662 self.wait_for_write_capacity(opts)?;
663 if let Some(s) = self.stats() {
664 s.add(Ticker::KeysDeleted, 1);
665 }
666 let mut batch = BTreeMap::new();
667 batch.insert(prefix_key(DEFAULT_CF_ID, key), None);
668 let (dm, disable_wal) = self.resolve_write_opts(opts);
669 self.engine
670 .apply_grouped_batch(batch, Vec::new(), Vec::new(), dm, disable_wal)
671 .map(|_| ())
672 .map_err(Error::from)
673 }
674
675 pub fn merge(&self, key: &[u8], operand: &[u8]) -> Result<()> {
683 self.merge_opt(&WriteOptions::default(), key, operand)
684 }
685
686 pub fn merge_opt(&self, opts: &WriteOptions, key: &[u8], operand: &[u8]) -> Result<()> {
688 self.ensure_writable()?;
689 self.validate_write_kv_sizes(key, operand)?;
690 self.wait_for_write_capacity(opts)?;
691 if let Some(s) = self.stats() {
692 s.add(Ticker::MergesWritten, 1);
693 }
694 let (dm, disable_wal) = self.resolve_write_opts(opts);
695 self.engine
696 .apply_grouped_batch(
697 BTreeMap::new(),
698 Vec::new(),
699 vec![(prefix_key(DEFAULT_CF_ID, key), operand.to_vec())],
700 dm,
701 disable_wal,
702 )
703 .map(|_| ())
704 .map_err(Error::from)
705 }
706
707 pub fn delete_range(&self, start: &[u8], end: &[u8]) -> Result<()> {
718 self.delete_range_opt(&WriteOptions::default(), start, end)
719 }
720
721 pub fn delete_range_opt(&self, opts: &WriteOptions, start: &[u8], end: &[u8]) -> Result<()> {
728 self.ensure_writable()?;
729 if start >= end {
730 return Ok(());
731 }
732 self.validate_key_size(start)?;
733 self.validate_key_size(end)?;
734 self.wait_for_write_capacity(opts)?;
735 if let Some(s) = self.stats() {
736 s.add(Ticker::RangeDeletesWritten, 1);
737 }
738 let (dm, disable_wal) = self.resolve_write_opts(opts);
739 self.engine
740 .apply_grouped_batch(
741 BTreeMap::new(),
742 vec![(
743 prefix_key(DEFAULT_CF_ID, start),
744 prefix_key(DEFAULT_CF_ID, end),
745 )],
746 Vec::new(),
747 dm,
748 disable_wal,
749 )
750 .map(|_| ())
751 .map_err(Error::from)
752 }
753
754 pub(crate) fn transaction_keys_inline(&self) -> usize {
757 self.transaction_keys_inline
758 }
759
760 pub fn streaming_writer(&self, opts: StreamOptions) -> StreamingWriter<'_> {
769 StreamingWriter::new(self, opts)
770 }
771
772 pub fn write(&self, batch: WriteBatch) -> Result<()> {
775 self.write_opt(&WriteOptions::default(), batch)
776 }
777
778 pub fn write_sequenced(&self, batch: WriteBatch) -> Result<u64> {
792 self.write_sequenced_opt(&WriteOptions::default(), batch)
793 }
794
795 pub fn write_sequenced_opt(&self, opts: &WriteOptions, batch: WriteBatch) -> Result<u64> {
797 self.write_opt_inner(opts, batch)
798 }
799
800 pub fn latest_sequence(&self) -> u64 {
804 self.engine.snapshot_seq()
805 }
806
807 pub fn write_opt(&self, opts: &WriteOptions, batch: WriteBatch) -> Result<()> {
812 self.write_opt_inner(opts, batch).map(|_| ())
813 }
814
815 fn write_opt_inner(&self, opts: &WriteOptions, batch: WriteBatch) -> Result<u64> {
816 self.ensure_writable()?;
820 if batch.is_empty() {
821 return Ok(self.engine.snapshot_seq());
824 }
825 self.validate_batch_cf_liveness(&batch)?;
826 self.validate_batch_sizes(&batch)?;
827 self.wait_for_write_capacity(opts)?;
828 perf_context::record_write_call();
829 let stats = self.stats();
830 let _scope = statistics::TimeScope::new(stats, Histogram::DbWrite);
831 if let Some(s) = stats {
832 let mut bytes: u64 = 0;
833 let mut puts: u64 = 0;
834 let mut deletes: u64 = 0;
835 let mut range_deletes: u64 = 0;
836 let mut merges: u64 = 0;
837 for op in &batch.ops {
838 match op {
839 WriteBatchOp::Put { key, value } => {
840 puts += 1;
841 bytes += (key.len() + value.len()) as u64;
842 }
843 WriteBatchOp::Delete { key } => {
844 deletes += 1;
845 bytes += key.len() as u64;
846 }
847 WriteBatchOp::DeleteRange { .. } => {
848 range_deletes += 1;
849 }
850 WriteBatchOp::Merge { .. } => {
851 merges += 1;
852 }
853 }
854 }
855 s.add(Ticker::KeysWritten, puts);
856 s.add(Ticker::KeysDeleted, deletes);
857 s.add(Ticker::BytesWritten, bytes);
858 s.add(Ticker::RangeDeletesWritten, range_deletes);
859 s.add(Ticker::MergesWritten, merges);
860 s.record(Histogram::BytesPerWrite, bytes);
861 }
862 let (dm, disable_wal) = self.resolve_write_opts(opts);
863 self.engine
864 .apply_batch(batch.ops, dm, disable_wal)
865 .map_err(Error::from)
866 }
867
868 pub fn write_with_durability(
872 &self,
873 batch: WriteBatch,
874 durability: DurabilityMode,
875 ) -> Result<()> {
876 let opts = WriteOptions {
877 sync: matches!(durability, DurabilityMode::Immediate),
878 ..WriteOptions::default()
879 };
880 self.write_opt(&opts, batch)
881 }
882
883 fn resolve_write_opts(&self, opts: &WriteOptions) -> (engine::DurabilityMode, bool) {
891 let dm = if opts.sync {
892 engine::DurabilityMode::Immediate
893 } else {
894 self.durability
895 };
896 (dm, opts.disable_wal)
897 }
898
899 fn wait_for_write_capacity(&self, opts: &WriteOptions) -> Result<()> {
904 self.engine.wait_for_write_capacity(opts.no_slowdown)?;
905 Ok(())
906 }
907
908 fn ensure_open(&self) -> Result<()> {
909 if self.engine.is_closed() {
910 Err(Error::Closed)
911 } else {
912 Ok(())
913 }
914 }
915
916 fn ensure_writable(&self) -> Result<()> {
917 self.ensure_open()?;
918 if self.read_only {
919 Err(Error::ReadOnly)
920 } else {
921 Ok(())
922 }
923 }
924
925 fn validate_key_size(&self, key: &[u8]) -> Result<()> {
926 if key.len() <= self.max_key_size {
927 return Ok(());
928 }
929
930 Err(invalid_input_error(format!(
931 "key length {} exceeds configured max_key_size {}",
932 key.len(),
933 self.max_key_size
934 )))
935 }
936
937 fn validate_value_size(&self, value: &[u8]) -> Result<()> {
938 if value.len() <= self.max_value_size {
939 return Ok(());
940 }
941
942 Err(invalid_input_error(format!(
943 "value length {} exceeds configured max_value_size {}",
944 value.len(),
945 self.max_value_size
946 )))
947 }
948
949 fn validate_write_kv_sizes(&self, key: &[u8], value: &[u8]) -> Result<()> {
950 self.validate_key_size(key)?;
951 self.validate_value_size(value)
952 }
953
954 fn validate_prefixed_key_size(&self, prefixed_key: &[u8]) -> Result<()> {
955 let user_key_len = prefixed_key.len().saturating_sub(4);
956 if user_key_len <= self.max_key_size {
957 return Ok(());
958 }
959
960 Err(invalid_input_error(format!(
961 "key length {} exceeds configured max_key_size {}",
962 user_key_len, self.max_key_size
963 )))
964 }
965
966 fn validate_batch_sizes(&self, batch: &WriteBatch) -> Result<()> {
967 for op in &batch.ops {
968 match op {
969 WriteBatchOp::Put { key, value } => {
970 self.validate_prefixed_key_size(key)?;
971 self.validate_value_size(value)?;
972 }
973 WriteBatchOp::Delete { key } => self.validate_prefixed_key_size(key)?,
974 WriteBatchOp::DeleteRange { start, end } => {
975 self.validate_prefixed_key_size(start)?;
976 self.validate_prefixed_key_size(end)?;
977 }
978 WriteBatchOp::Merge { key, operand } => {
979 self.validate_prefixed_key_size(key)?;
980 self.validate_value_size(operand)?;
981 }
982 }
983 }
984 Ok(())
985 }
986
987 fn validate_cf_handle(&self, cf: &ColumnFamilyHandle) -> Result<()> {
988 if self.cfs.is_live_handle(cf) {
989 Ok(())
990 } else {
991 Err(invalid_cf_handle_error(cf))
992 }
993 }
994
995 fn is_live_cf_handle(&self, cf: &ColumnFamilyHandle) -> bool {
996 self.cfs.is_live_handle(cf)
997 }
998
999 fn validate_prefixed_cf_io(&self, prefixed_key: &[u8]) -> std::io::Result<()> {
1000 let cf_id = prefixed_cf_id(prefixed_key)?;
1001 if self.cfs.contains_id(cf_id) {
1002 Ok(())
1003 } else {
1004 Err(invalid_cf_id_io_error(cf_id))
1005 }
1006 }
1007
1008 fn validate_prefixed_cf(&self, prefixed_key: &[u8]) -> Result<()> {
1009 let cf_id = prefixed_cf_id(prefixed_key).map_err(Error::from)?;
1010 if self.cfs.contains_id(cf_id) {
1011 Ok(())
1012 } else {
1013 Err(invalid_cf_id_error(cf_id))
1014 }
1015 }
1016
1017 fn validate_batch_cf_liveness(&self, batch: &WriteBatch) -> Result<()> {
1018 for op in &batch.ops {
1019 match op {
1020 WriteBatchOp::Put { key, .. }
1021 | WriteBatchOp::Delete { key }
1022 | WriteBatchOp::Merge { key, .. } => self.validate_prefixed_cf(key)?,
1023 WriteBatchOp::DeleteRange { start, end } => {
1024 self.validate_prefixed_cf(start)?;
1025 self.validate_prefixed_cf(end)?;
1026 }
1027 }
1028 }
1029 Ok(())
1030 }
1031
1032 pub fn snapshot(&self) -> Snapshot {
1040 let seq = self.engine.register_snapshot_at_horizon();
1041 Snapshot {
1042 engine: Arc::clone(&self.engine),
1043 cfs: Arc::clone(&self.cfs),
1044 seq,
1045 }
1046 }
1047
1048 pub fn scan_stream(&self, start: Option<&[u8]>, end: Option<&[u8]>) -> Result<ScanStream> {
1067 Ok(self.snapshot().into_scan_stream(start, end))
1068 }
1069
1070 pub fn scan(
1079 &self,
1080 start: Option<&[u8]>,
1081 end: Option<&[u8]>,
1082 ) -> Result<Vec<(Vec<u8>, Vec<u8>)>> {
1083 let lo = match start {
1084 Some(s) => prefix_key(DEFAULT_CF_ID, s),
1085 None => cf_lower_bound(DEFAULT_CF_ID),
1086 };
1087 let hi = match end {
1088 Some(e) => prefix_key(DEFAULT_CF_ID, e),
1089 None => cf_upper_bound(DEFAULT_CF_ID),
1090 };
1091 let raw = collect_range(self.engine.new_iter_latest(), Some(&lo), Some(&hi))?;
1097 strip_cf_prefix_entries(raw)
1098 }
1099
1100 pub fn scan_page(
1109 &self,
1110 start: Option<&[u8]>,
1111 end: Option<&[u8]>,
1112 limit: usize,
1113 ) -> Result<ScanPage> {
1114 let lo = match start {
1115 Some(s) => prefix_key(DEFAULT_CF_ID, s),
1116 None => cf_lower_bound(DEFAULT_CF_ID),
1117 };
1118 let hi = match end {
1119 Some(e) => prefix_key(DEFAULT_CF_ID, e),
1120 None => cf_upper_bound(DEFAULT_CF_ID),
1121 };
1122 collect_page(self.engine.new_iter_latest(), &lo, &hi, limit).and_then(strip_cf_prefix_page)
1123 }
1124
1125 pub fn iter(&self) -> CfIter<'_> {
1137 let default = self.default_cf();
1138 self.iter_cf(&default)
1139 }
1140
1141 fn raw_iter(&self) -> Iter<'_> {
1145 Iter::from_internal(self.engine.new_iter_latest()).with_stats(self.engine.statistics_arc())
1146 }
1147
1148 pub fn drop_all(&self) -> Result<()> {
1150 self.ensure_writable()?;
1151 self.engine.drop_all().map_err(Error::from)
1152 }
1153
1154 pub fn compact_range(&self, start: Option<&[u8]>, end: Option<&[u8]>) -> Result<()> {
1167 self.ensure_writable()?;
1168 if let Some(start) = start {
1169 self.validate_key_size(start)?;
1170 }
1171 if let Some(end) = end {
1172 self.validate_key_size(end)?;
1173 }
1174 let lower = match start {
1175 Some(s) => prefix_key(DEFAULT_CF_ID, s),
1176 None => cf_lower_bound(DEFAULT_CF_ID),
1177 };
1178 let upper = match end {
1179 Some(e) => prefix_key(DEFAULT_CF_ID, e),
1180 None => cf_upper_bound(DEFAULT_CF_ID),
1181 };
1182 self.engine
1183 .compact_range(Some(&lower), Some(&upper))
1184 .map_err(Error::from)
1185 }
1186
1187 pub fn flush(&self) -> Result<()> {
1201 self.ensure_writable()?;
1202 self.engine.flush_active_memtable().map_err(Error::from)
1203 }
1204
1205 pub fn compact_step(&self) -> Result<bool> {
1228 self.ensure_writable()?;
1229 let outcome = self.engine.run_one_compaction_pass().map_err(Error::from)?;
1230 Ok(outcome == engine::compaction::CompactionOutcome::DidWork)
1231 }
1232
1233 pub fn get_property(&self, name: &str) -> Option<String> {
1239 match name {
1240 "regolith.stats" => Some(self.format_stats_property()),
1241 "regolith.sstables" => Some(self.format_sstables_property()),
1242 "regolith.levelstats" => Some(self.format_levelstats_property()),
1243 "regolith.options" => Some(format!("{:#?}", self.options_snapshot())),
1244 _ => {
1245 self.get_int_property(name).map(|v| v.to_string())
1249 }
1250 }
1251 }
1252
1253 pub fn capabilities(&self) -> env::Capabilities {
1264 self.engine.capabilities()
1265 }
1266
1267 pub fn get_int_property(&self, name: &str) -> Option<u64> {
1270 if let Some(level_str) = name.strip_prefix("regolith.num-files-at-level") {
1271 let level: usize = level_str.parse().ok()?;
1272 return Some(self.engine.num_files_at_level(level));
1273 }
1274 match name {
1275 "regolith.total-sst-files-size" => Some(self.engine.total_sst_size()),
1276 "regolith.cur-size-active-mem-table" => Some(self.engine.active_memtable_size()),
1277 "regolith.memtable-reserved-bytes" => Some(self.engine.memtables_reserved_size()),
1278 "regolith.arena-pool-bytes" => Some(self.engine.arena_pool_size()),
1279 "regolith.cur-size-all-mem-tables" => {
1280 Some(self.engine.active_memtable_size() + self.engine.frozen_memtables_size())
1281 }
1282 "regolith.num-entries-active-mem-table" => {
1283 let bytes = self.engine.active_memtable_size();
1289 Some(bytes / 48)
1290 }
1291 "regolith.num-entries-imm-mem-tables" => {
1292 let bytes = self.engine.frozen_memtables_size();
1293 Some(bytes / 48)
1294 }
1295 "regolith.estimate-num-keys" => {
1296 let sst = self.engine.total_sst_num_entries();
1299 let mem_bytes =
1300 self.engine.active_memtable_size() + self.engine.frozen_memtables_size();
1301 Some(sst + mem_bytes / 48)
1302 }
1303 "regolith.estimate-live-data-size" => Some(self.engine.total_sst_size()),
1304 "regolith.num-snapshots" => Some(self.engine.live_snapshot_count()),
1305 "regolith.oldest-snapshot-time" => self.engine.oldest_snapshot_time_unix(),
1306 "regolith.block-cache-usage" => Some(self.engine.block_cache_usage() as u64),
1307 "regolith.block-cache-capacity" => Some(self.engine.block_cache_capacity() as u64),
1308 "regolith.pinned-metadata-bytes" => Some(self.engine.pinned_metadata_bytes() as u64),
1309 "regolith.background-errors" => Some(0),
1315 _ => None,
1316 }
1317 }
1318
1319 fn format_stats_property(&self) -> String {
1323 let mut out = String::new();
1324 out.push_str("== regolith engine stats ==\n");
1325 out.push_str(&self.format_levelstats_property());
1326 if let Some(stats) = self.engine.statistics() {
1327 out.push('\n');
1328 out.push_str(&stats.dump());
1329 } else {
1330 out.push_str("\n(no Statistics object configured - see Options::statistics)\n");
1331 }
1332 out
1333 }
1334
1335 fn format_levelstats_property(&self) -> String {
1338 let version = self.engine.current_version();
1339 let mut out = String::from("Level Files Size(B)\n");
1340 for (lvl, files) in version.levels.iter().enumerate() {
1341 let count = files.len();
1342 let size: u64 = files.iter().map(|f| f.meta.file_size).sum();
1343 out.push_str(&format!("{lvl:5} {count:5} {size:10}\n"));
1344 }
1345 out
1346 }
1347
1348 fn format_sstables_property(&self) -> String {
1351 let version = self.engine.current_version();
1352 let mut out =
1353 String::from("Level FileID Size(B) Entries SmallestKey..LargestKey\n");
1354 for (lvl, files) in version.levels.iter().enumerate() {
1355 for f in files {
1356 let smallest = format_key_for_display(&f.meta.smallest_key);
1359 let largest = format_key_for_display(&f.meta.largest_key);
1360 out.push_str(&format!(
1361 "{lvl:5} {:8} {:12} {:10} {}..{}\n",
1362 f.meta.file_id, f.meta.file_size, f.meta.num_entries, smallest, largest
1363 ));
1364 }
1365 }
1366 out
1367 }
1368
1369 fn options_snapshot(&self) -> OptionsSnapshot {
1374 OptionsSnapshot {
1375 durability: self.durability,
1376 default_cf: DEFAULT_CF_NAME,
1377 read_only: self.read_only,
1378 max_key_size: self.max_key_size,
1379 max_value_size: self.max_value_size,
1380 transaction_keys_inline: self.transaction_keys_inline,
1381 }
1382 }
1383
1384 pub fn get_approximate_sizes(&self, ranges: &[Range<'_>]) -> Vec<u64> {
1396 ranges
1397 .iter()
1398 .map(|r| self.approximate_size_in_range(&self.default_cf(), r))
1399 .collect()
1400 }
1401
1402 pub fn get_approximate_sizes_cf(
1404 &self,
1405 cf: &ColumnFamilyHandle,
1406 ranges: &[Range<'_>],
1407 ) -> Vec<u64> {
1408 if !self.is_live_cf_handle(cf) {
1409 return vec![0; ranges.len()];
1410 }
1411 ranges
1412 .iter()
1413 .map(|r| self.approximate_size_in_range(cf, r))
1414 .collect()
1415 }
1416
1417 fn approximate_size_in_range(&self, cf: &ColumnFamilyHandle, r: &Range<'_>) -> u64 {
1418 if r.start >= r.end {
1419 return 0;
1420 }
1421 let lo = prefix_key(cf.id(), r.start);
1422 let hi = prefix_key(cf.id(), r.end);
1423 self.engine.approximate_size_in_range(&lo, &hi)
1424 }
1425
1426 pub fn get_approximate_memtable_stats(&self, range: Range<'_>) -> MemTableStats {
1430 self.memtable_stats_in(&self.default_cf(), &range)
1431 }
1432
1433 pub fn get_approximate_memtable_stats_cf(
1435 &self,
1436 cf: &ColumnFamilyHandle,
1437 range: Range<'_>,
1438 ) -> MemTableStats {
1439 if !self.is_live_cf_handle(cf) {
1440 return MemTableStats::default();
1441 }
1442 self.memtable_stats_in(cf, &range)
1443 }
1444
1445 fn memtable_stats_in(&self, cf: &ColumnFamilyHandle, range: &Range<'_>) -> MemTableStats {
1446 if range.start >= range.end {
1447 return MemTableStats::default();
1448 }
1449 let lo = prefix_key(cf.id(), range.start);
1450 let hi = prefix_key(cf.id(), range.end);
1451 let (count, size) = self.engine.approximate_memtable_stats(&lo, &hi);
1452 MemTableStats { count, size }
1453 }
1454
1455 pub fn ingest_external_files(
1467 &self,
1468 files: &[std::path::PathBuf],
1469 opts: IngestOptions,
1470 ) -> Result<()> {
1471 self.ensure_writable()?;
1472 self.engine
1473 .ingest_external_files(files, &opts, |user_key| {
1474 self.validate_prefixed_cf_io(user_key)
1475 })
1476 .map_err(Error::from)
1477 }
1478
1479 pub fn close(&self) -> Result<()> {
1485 self.engine.close().map_err(Error::from)
1486 }
1487
1488 pub fn wait_for_snapshots(&self, timeout: std::time::Duration) -> u64 {
1529 self.engine.wait_for_snapshots(timeout)
1530 }
1531
1532 #[cfg(test)]
1534 pub(crate) fn level_file_count(&self, level: usize) -> usize {
1535 self.engine.level_file_count(level)
1536 }
1537
1538 pub fn checkpoint<P: AsRef<Path>>(&self, target_dir: P) -> Result<()> {
1545 self.ensure_writable()?;
1546 let cp = Checkpoint::new(self)?;
1547 cp.create(target_dir)
1548 }
1549
1550 pub fn default_cf(&self) -> ColumnFamilyHandle {
1556 ColumnFamilyHandle {
1557 name: Arc::new(DEFAULT_CF_NAME.to_string()),
1558 id: DEFAULT_CF_ID,
1559 }
1560 }
1561
1562 pub fn column_family(&self, name: &str) -> Option<ColumnFamilyHandle> {
1565 self.cfs.get(name)
1566 }
1567
1568 pub fn list_column_families(&self) -> Vec<String> {
1571 let mut names = self.cfs.names();
1572 names.sort();
1573 names
1574 }
1575
1576 pub fn create_column_family(&self, name: &str) -> Result<ColumnFamilyHandle> {
1583 self.ensure_writable()?;
1584 if name.is_empty() {
1585 return Err(Error::invalid_argument(
1586 "column family name must not be empty",
1587 ));
1588 }
1589 if let Some(existing) = self.cfs.get(name) {
1590 return Ok(existing);
1591 }
1592 self.validate_prefixed_key_size(&meta::name_key(name))?;
1593 let Some((handle, next_id)) = self.cfs.allocate(name) else {
1594 return Err(Error::invalid_argument(
1595 "the column-family id space is exhausted",
1596 ));
1597 };
1598 let mut batch = BTreeMap::new();
1599 batch.insert(
1600 meta::name_key(name),
1601 Some(handle.id().to_be_bytes().to_vec()),
1602 );
1603 batch.insert(meta::next_id_key(), Some(next_id.to_be_bytes().to_vec()));
1604 self.engine
1605 .apply_grouped_batch(batch, Vec::new(), Vec::new(), self.durability, false)
1606 .map(|_| ())
1607 .map_err(Error::from)?;
1608 Ok(handle)
1609 }
1610
1611 pub fn drop_column_family(&self, cf: ColumnFamilyHandle) -> Result<()> {
1620 self.ensure_writable()?;
1621 if cf.id() == DEFAULT_CF_ID {
1622 return Err(Error::invalid_argument(
1623 "cannot drop the default column family",
1624 ));
1625 }
1626 if cf.id() == META_CF_ID {
1627 return Err(Error::invalid_argument(
1628 "cannot drop the reserved metadata column family",
1629 ));
1630 }
1631 self.validate_cf_handle(&cf)?;
1632 let lo = cf_lower_bound(cf.id());
1633 let hi = cf_upper_bound(cf.id());
1634 let mut point_ops = BTreeMap::new();
1638 point_ops.insert(meta::name_key(cf.name()), None);
1639 let range_deletes = vec![(lo, hi)];
1640 self.engine
1641 .apply_grouped_batch(point_ops, range_deletes, Vec::new(), self.durability, false)
1642 .map(|_| ())
1643 .map_err(Error::from)?;
1644 self.cfs.remove(cf.name());
1645 Ok(())
1646 }
1647
1648 pub fn get_cf(&self, cf: &ColumnFamilyHandle, key: &[u8]) -> Result<Option<Vec<u8>>> {
1651 Ok(self.get_slice_cf(cf, key)?.map(DbSlice::into_vec))
1652 }
1653
1654 pub fn multi_get_cf(
1656 &self,
1657 cf: &ColumnFamilyHandle,
1658 keys: &[&[u8]],
1659 ) -> Result<Vec<Option<Vec<u8>>>> {
1660 self.validate_cf_handle(cf)?;
1661 let owned: Vec<Vec<u8>> = keys.iter().map(|k| prefix_key(cf.id(), k)).collect();
1662 let refs: Vec<&[u8]> = owned.iter().map(|k| k.as_slice()).collect();
1663 self.engine.multi_get_latest(&refs).map_err(Error::from)
1664 }
1665
1666 pub fn put_cf(&self, cf: &ColumnFamilyHandle, key: &[u8], value: &[u8]) -> Result<()> {
1668 self.ensure_writable()?;
1669 self.validate_cf_handle(cf)?;
1670 self.validate_write_kv_sizes(key, value)?;
1671 let mut batch = BTreeMap::new();
1672 batch.insert(prefix_key(cf.id(), key), Some(value.to_vec()));
1673 self.engine
1674 .apply_grouped_batch(batch, Vec::new(), Vec::new(), self.durability, false)
1675 .map(|_| ())
1676 .map_err(Error::from)
1677 }
1678
1679 pub fn delete_cf(&self, cf: &ColumnFamilyHandle, key: &[u8]) -> Result<()> {
1681 self.ensure_writable()?;
1682 self.validate_cf_handle(cf)?;
1683 self.validate_key_size(key)?;
1684 let mut batch = BTreeMap::new();
1685 batch.insert(prefix_key(cf.id(), key), None);
1686 self.engine
1687 .apply_grouped_batch(batch, Vec::new(), Vec::new(), self.durability, false)
1688 .map(|_| ())
1689 .map_err(Error::from)
1690 }
1691
1692 pub fn delete_range_cf(&self, cf: &ColumnFamilyHandle, start: &[u8], end: &[u8]) -> Result<()> {
1698 self.ensure_writable()?;
1699 if start >= end {
1700 return Ok(());
1701 }
1702 self.validate_cf_handle(cf)?;
1703 self.validate_key_size(start)?;
1704 self.validate_key_size(end)?;
1705 self.engine
1706 .apply_grouped_batch(
1707 BTreeMap::new(),
1708 vec![(prefix_key(cf.id(), start), prefix_key(cf.id(), end))],
1709 Vec::new(),
1710 self.durability,
1711 false,
1712 )
1713 .map(|_| ())
1714 .map_err(Error::from)
1715 }
1716
1717 pub fn merge_cf(&self, cf: &ColumnFamilyHandle, key: &[u8], operand: &[u8]) -> Result<()> {
1720 self.ensure_writable()?;
1721 self.validate_cf_handle(cf)?;
1722 self.validate_write_kv_sizes(key, operand)?;
1723 self.engine
1724 .apply_grouped_batch(
1725 BTreeMap::new(),
1726 Vec::new(),
1727 vec![(prefix_key(cf.id(), key), operand.to_vec())],
1728 self.durability,
1729 false,
1730 )
1731 .map(|_| ())
1732 .map_err(Error::from)
1733 }
1734
1735 pub fn scan_cf(
1743 &self,
1744 cf: &ColumnFamilyHandle,
1745 start: Option<&[u8]>,
1746 end: Option<&[u8]>,
1747 ) -> Result<Vec<(Vec<u8>, Vec<u8>)>> {
1748 self.validate_cf_handle(cf)?;
1749 let lo = match start {
1750 Some(s) => prefix_key(cf.id(), s),
1751 None => cf_lower_bound(cf.id()),
1752 };
1753 let hi = match end {
1754 Some(e) => prefix_key(cf.id(), e),
1755 None => cf_upper_bound(cf.id()),
1756 };
1757 let raw = collect_range(self.engine.new_iter_latest(), Some(&lo), Some(&hi))?;
1763 strip_cf_prefix_entries(raw)
1764 }
1765
1766 pub fn scan_page_cf(
1772 &self,
1773 cf: &ColumnFamilyHandle,
1774 start: Option<&[u8]>,
1775 end: Option<&[u8]>,
1776 limit: usize,
1777 ) -> Result<ScanPage> {
1778 self.validate_cf_handle(cf)?;
1779 let lo = match start {
1780 Some(s) => prefix_key(cf.id(), s),
1781 None => cf_lower_bound(cf.id()),
1782 };
1783 let hi = match end {
1784 Some(e) => prefix_key(cf.id(), e),
1785 None => cf_upper_bound(cf.id()),
1786 };
1787 collect_page(self.engine.new_iter_latest(), &lo, &hi, limit).and_then(strip_cf_prefix_page)
1788 }
1789
1790 pub fn iter_cf<'a>(&'a self, cf: &ColumnFamilyHandle) -> CfIter<'a> {
1793 let inner = self.raw_iter();
1794 if self.is_live_cf_handle(cf) {
1795 CfIter::new(inner, cf.id())
1796 } else {
1797 CfIter::invalid(inner, cf)
1798 }
1799 }
1800
1801 pub fn iter_tailing(&self) -> TailingIter {
1807 tailing::new_default(Arc::clone(&self.engine))
1808 }
1809
1810 pub fn iter_tailing_cf(&self, cf: &ColumnFamilyHandle) -> TailingIter {
1813 let engine = Arc::clone(&self.engine);
1814 if self.is_live_cf_handle(cf) {
1815 tailing::new_for_cf(engine, cf)
1816 } else {
1817 tailing::new_empty(engine, cf)
1818 }
1819 }
1820
1821 pub(crate) fn engine(&self) -> &RegolithEngine {
1822 &self.engine
1823 }
1824
1825 pub(crate) fn engine_arc(&self) -> Arc<RegolithEngine> {
1829 Arc::clone(&self.engine)
1830 }
1831
1832 pub(crate) fn durability(&self) -> engine::DurabilityMode {
1835 self.durability
1836 }
1837}
1838
1839pub struct CfIter<'a> {
1844 inner: Iter<'a>,
1845 cf_id: u32,
1846 upper_bound: Vec<u8>,
1847 valid_cf: bool,
1848 invalid_cf: Option<Box<str>>,
1855}
1856
1857impl<'a> CfIter<'a> {
1858 fn new(inner: Iter<'a>, cf_id: u32) -> Self {
1859 Self {
1860 invalid_cf: None,
1861 inner,
1862 cf_id,
1863 upper_bound: cf_upper_bound(cf_id),
1864 valid_cf: true,
1865 }
1866 }
1867
1868 fn invalid(inner: Iter<'a>, cf: &ColumnFamilyHandle) -> Self {
1869 Self {
1870 inner,
1871 cf_id: DEFAULT_CF_ID,
1872 upper_bound: cf_upper_bound(DEFAULT_CF_ID),
1873 valid_cf: false,
1874 invalid_cf: Some(
1875 format!(
1876 "column family handle '{}' with id {} is not live",
1877 cf.name(),
1878 cf.id()
1879 )
1880 .into_boxed_str(),
1881 ),
1882 }
1883 }
1884
1885 pub fn seek_to_first(&mut self) {
1887 if !self.valid_cf {
1888 return;
1889 }
1890 let lo = self.cf_id.to_be_bytes();
1891 self.inner.seek(&lo);
1892 }
1893
1894 pub fn seek_to_last(&mut self) {
1897 if !self.valid_cf {
1898 return;
1899 }
1900 self.inner.seek_to_last_before(&self.upper_bound);
1901 }
1902
1903 pub fn seek(&mut self, target: &[u8]) {
1905 if !self.valid_cf {
1906 return;
1907 }
1908 self.inner.seek(&prefix_key(self.cf_id, target));
1909 }
1910
1911 pub fn seek_for_prev(&mut self, target: &[u8]) {
1913 if !self.valid_cf {
1914 return;
1915 }
1916 self.inner.seek_for_prev(&prefix_key(self.cf_id, target));
1917 }
1918
1919 pub fn seek_prefix(&mut self, prefix: &[u8]) {
1924 if !self.valid_cf {
1925 return;
1926 }
1927 self.inner.seek_prefix(&prefix_key(self.cf_id, prefix));
1928 }
1929
1930 pub fn next(&mut self) {
1933 if !self.valid_cf {
1934 return;
1935 }
1936 self.inner.next();
1937 }
1938
1939 pub fn prev(&mut self) {
1942 if !self.valid_cf {
1943 return;
1944 }
1945 self.inner.prev();
1946 }
1947
1948 pub fn positioned(&self) -> bool {
1951 self.inner.positioned()
1952 }
1953
1954 pub fn valid(&self) -> bool {
1957 if !self.valid_cf {
1958 return false;
1959 }
1960 let Some(k) = self.inner.key() else {
1961 return false;
1962 };
1963 if k < self.cf_id.to_be_bytes().as_slice() {
1964 return false;
1965 }
1966 if k >= self.upper_bound.as_slice() {
1967 return false;
1968 }
1969 true
1970 }
1971
1972 pub fn key(&self) -> Option<&[u8]> {
1974 if !self.valid() {
1975 return None;
1976 }
1977 self.inner.key().and_then(|k| k.get(4..))
1978 }
1979
1980 pub fn value(&self) -> Option<&[u8]> {
1982 if !self.valid() {
1983 return None;
1984 }
1985 self.inner.value()
1986 }
1987
1988 pub fn value_slice(&self) -> Option<DbSlice> {
1991 if !self.valid() {
1992 return None;
1993 }
1994 self.inner.value_slice()
1995 }
1996
1997 pub fn status(&self) -> Result<()> {
2005 if let Some(reason) = &self.invalid_cf {
2006 return Err(Error::invalid_column_family(reason.to_string()));
2007 }
2008 self.inner.status()
2009 }
2010}
2011
2012pub struct OwnedSnapshotIter {
2017 inner: CfIter<'static>,
2018 _snapshot: Snapshot,
2019}
2020
2021impl OwnedSnapshotIter {
2022 fn new(snapshot: Snapshot) -> Self {
2023 let inner = Iter::<'static>::from_internal(snapshot.engine.new_iter_at(snapshot.seq))
2024 .with_stats(snapshot.engine.statistics_arc());
2025 Self {
2026 inner: CfIter::new(inner, DEFAULT_CF_ID),
2027 _snapshot: snapshot,
2028 }
2029 }
2030
2031 pub fn seek_to_first(&mut self) {
2033 self.inner.seek_to_first();
2034 }
2035
2036 pub fn seek_to_last(&mut self) {
2038 self.inner.seek_to_last();
2039 }
2040
2041 pub fn seek(&mut self, target: &[u8]) {
2043 self.inner.seek(target);
2044 }
2045
2046 pub fn seek_for_prev(&mut self, target: &[u8]) {
2048 self.inner.seek_for_prev(target);
2049 }
2050
2051 pub fn seek_prefix(&mut self, prefix: &[u8]) {
2053 self.inner.seek_prefix(prefix);
2054 }
2055
2056 pub fn next(&mut self) {
2058 self.inner.next();
2059 }
2060
2061 pub fn prev(&mut self) {
2063 self.inner.prev();
2064 }
2065
2066 pub fn positioned(&self) -> bool {
2069 self.inner.positioned()
2070 }
2071
2072 pub fn valid(&self) -> bool {
2074 self.inner.valid()
2075 }
2076
2077 pub fn key(&self) -> Option<&[u8]> {
2079 self.inner.key()
2080 }
2081
2082 pub fn value(&self) -> Option<&[u8]> {
2084 self.inner.value()
2085 }
2086
2087 pub fn value_slice(&self) -> Option<DbSlice> {
2090 self.inner.value_slice()
2091 }
2092
2093 pub fn status(&self) -> Result<()> {
2095 self.inner.status()
2096 }
2097}
2098
2099pub struct ScanStream {
2111 entries: Entries<OwnedSnapshotIter>,
2112 end: Option<Vec<u8>>,
2115 done: bool,
2116}
2117
2118impl ScanStream {
2119 pub fn status(&self) -> Result<()> {
2139 self.entries.status()
2140 }
2141}
2142
2143impl Iterator for ScanStream {
2144 type Item = (Vec<u8>, DbSlice);
2145
2146 fn next(&mut self) -> Option<Self::Item> {
2147 if self.done {
2148 return None;
2149 }
2150 let (key, value) = self.entries.next()?;
2151 if let Some(end) = &self.end
2152 && key.as_slice() >= end.as_slice()
2153 {
2154 self.done = true;
2157 return None;
2158 }
2159 Some((key, value))
2160 }
2161}
2162
2163impl std::fmt::Debug for ScanStream {
2164 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2165 f.debug_struct("ScanStream")
2166 .field("done", &self.done)
2167 .finish_non_exhaustive()
2168 }
2169}
2170
2171pub struct Entries<C> {
2185 cursor: C,
2186 started: bool,
2187 reverse: bool,
2188}
2189
2190impl<C> Entries<C> {
2191 fn new(cursor: C, reverse: bool) -> Self {
2192 Self {
2193 cursor,
2194 started: false,
2195 reverse,
2196 }
2197 }
2198
2199 pub fn into_cursor(self) -> C {
2201 self.cursor
2202 }
2203}
2204
2205macro_rules! impl_entries {
2209 ($cursor:ty $(, $lt:lifetime)?) => {
2210 impl$(<$lt>)? Iterator for Entries<$cursor> {
2211 type Item = (Vec<u8>, DbSlice);
2212
2213 fn next(&mut self) -> Option<Self::Item> {
2214 if self.started {
2215 if self.reverse {
2216 self.cursor.prev();
2217 } else {
2218 self.cursor.next();
2219 }
2220 } else {
2221 self.started = true;
2222 if !self.cursor.positioned() {
2227 if self.reverse {
2228 self.cursor.seek_to_last();
2229 } else {
2230 self.cursor.seek_to_first();
2231 }
2232 }
2233 }
2234 if !self.cursor.valid() {
2235 if let Err(e) = self.cursor.status() {
2242 tracing::error!(
2243 error = %e,
2244 "scan ended early: the iterator failed mid-range, \
2245 so the rows returned are a prefix and not the range"
2246 );
2247 }
2248 return None;
2249 }
2250 let key = self.cursor.key()?.to_vec();
2251 let value = self.cursor.value_slice()?;
2252 Some((key, value))
2253 }
2254 }
2255
2256 impl$(<$lt>)? Entries<$cursor> {
2257 pub fn status(&self) -> Result<()> {
2265 self.cursor.status()
2266 }
2267 }
2268
2269 impl$(<$lt>)? IntoIterator for $cursor {
2270 type Item = (Vec<u8>, DbSlice);
2271 type IntoIter = Entries<$cursor>;
2272
2273 fn into_iter(self) -> Self::IntoIter {
2274 Entries::new(self, false)
2275 }
2276 }
2277 };
2278}
2279
2280impl_entries!(OwnedSnapshotIter);
2281impl_entries!(CfIter<'a>, 'a);
2282
2283impl OwnedSnapshotIter {
2284 pub fn entries(self) -> Entries<Self> {
2286 Entries::new(self, false)
2287 }
2288
2289 pub fn entries_rev(self) -> Entries<Self> {
2291 Entries::new(self, true)
2292 }
2293}
2294
2295impl<'a> CfIter<'a> {
2296 pub fn entries(self) -> Entries<CfIter<'a>> {
2298 Entries::new(self, false)
2299 }
2300
2301 pub fn entries_rev(self) -> Entries<CfIter<'a>> {
2303 Entries::new(self, true)
2304 }
2305}
2306
2307pub struct Snapshot {
2309 engine: Arc<RegolithEngine>,
2310 cfs: Arc<CfRegistry>,
2311 seq: u64,
2312}
2313
2314impl Drop for Snapshot {
2315 fn drop(&mut self) {
2316 self.engine.release_snapshot(self.seq);
2322 }
2323}
2324
2325impl std::fmt::Debug for Snapshot {
2326 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2327 f.debug_struct("Snapshot")
2328 .field("seq", &self.seq)
2329 .finish_non_exhaustive()
2330 }
2331}
2332
2333impl Snapshot {
2334 fn clone_pin(&self) -> Self {
2335 self.engine.register_snapshot(self.seq);
2336 Self {
2337 engine: Arc::clone(&self.engine),
2338 cfs: Arc::clone(&self.cfs),
2339 seq: self.seq,
2340 }
2341 }
2342
2343 fn validate_cf_handle(&self, cf: &ColumnFamilyHandle) -> Result<()> {
2344 if self.cfs.is_live_handle(cf) {
2345 Ok(())
2346 } else {
2347 Err(invalid_cf_handle_error(cf))
2348 }
2349 }
2350
2351 fn is_live_cf_handle(&self, cf: &ColumnFamilyHandle) -> bool {
2352 self.cfs.is_live_handle(cf)
2353 }
2354
2355 pub fn sequence(&self) -> u64 {
2364 self.seq
2365 }
2366
2367 pub fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>> {
2369 Ok(self.get_slice(key)?.map(DbSlice::into_vec))
2370 }
2371
2372 pub fn get_slice(&self, key: &[u8]) -> Result<Option<DbSlice>> {
2375 self.lookup_slice(&LookupKey::new(DEFAULT_CF_ID, key, self.seq))
2376 }
2377
2378 pub fn get_slice_cf(&self, cf: &ColumnFamilyHandle, key: &[u8]) -> Result<Option<DbSlice>> {
2380 self.validate_cf_handle(cf)?;
2381 self.lookup_slice(&LookupKey::new(cf.id(), key, self.seq))
2382 }
2383
2384 fn lookup_slice(&self, lk: &LookupKey) -> Result<Option<DbSlice>> {
2385 self.engine
2386 .get_slice(lk)
2387 .map_err(|err| map_point_read_error(err, lk.prefixed_user_key()))
2388 }
2389
2390 pub fn has(&self, key: &[u8]) -> Result<bool> {
2393 Ok(self.get_size(key)?.is_some())
2394 }
2395
2396 pub fn has_cf(&self, cf: &ColumnFamilyHandle, key: &[u8]) -> Result<bool> {
2398 Ok(self.get_size_cf(cf, key)?.is_some())
2399 }
2400
2401 pub fn get_size(&self, key: &[u8]) -> Result<Option<usize>> {
2404 self.lookup_size(&LookupKey::new(DEFAULT_CF_ID, key, self.seq))
2405 }
2406
2407 pub fn get_size_cf(&self, cf: &ColumnFamilyHandle, key: &[u8]) -> Result<Option<usize>> {
2409 self.validate_cf_handle(cf)?;
2410 self.lookup_size(&LookupKey::new(cf.id(), key, self.seq))
2411 }
2412
2413 fn lookup_size(&self, lk: &LookupKey) -> Result<Option<usize>> {
2414 self.engine
2415 .get_size(lk)
2416 .map_err(|err| map_point_read_error(err, lk.prefixed_user_key()))
2417 }
2418
2419 pub fn multi_get(&self, keys: &[&[u8]]) -> Result<Vec<Option<Vec<u8>>>> {
2421 let owned: Vec<Vec<u8>> = keys.iter().map(|k| prefix_key(DEFAULT_CF_ID, k)).collect();
2422 let refs: Vec<&[u8]> = owned.iter().map(|k| k.as_slice()).collect();
2423 self.engine
2424 .multi_get_at(&refs, self.seq)
2425 .map_err(Error::from)
2426 }
2427
2428 pub fn scan(
2435 &self,
2436 start: Option<&[u8]>,
2437 end: Option<&[u8]>,
2438 ) -> Result<Vec<(Vec<u8>, Vec<u8>)>> {
2439 let lo = match start {
2440 Some(s) => prefix_key(DEFAULT_CF_ID, s),
2441 None => cf_lower_bound(DEFAULT_CF_ID),
2442 };
2443 let hi = match end {
2444 Some(e) => prefix_key(DEFAULT_CF_ID, e),
2445 None => cf_upper_bound(DEFAULT_CF_ID),
2446 };
2447 let raw = collect_range(self.engine.new_iter_at(self.seq), Some(&lo), Some(&hi))?;
2448 strip_cf_prefix_entries(raw)
2449 }
2450
2451 pub fn scan_page(
2457 &self,
2458 start: Option<&[u8]>,
2459 end: Option<&[u8]>,
2460 limit: usize,
2461 ) -> Result<ScanPage> {
2462 let lo = match start {
2463 Some(s) => prefix_key(DEFAULT_CF_ID, s),
2464 None => cf_lower_bound(DEFAULT_CF_ID),
2465 };
2466 let hi = match end {
2467 Some(e) => prefix_key(DEFAULT_CF_ID, e),
2468 None => cf_upper_bound(DEFAULT_CF_ID),
2469 };
2470 collect_page(self.engine.new_iter_at(self.seq), &lo, &hi, limit)
2471 .and_then(strip_cf_prefix_page)
2472 }
2473
2474 pub fn iter(&self) -> CfIter<'_> {
2477 CfIter::new(
2478 Iter::from_internal(self.engine.new_iter_at(self.seq))
2479 .with_stats(self.engine.statistics_arc()),
2480 DEFAULT_CF_ID,
2481 )
2482 }
2483
2484 pub fn scan_stream(&self, start: Option<&[u8]>, end: Option<&[u8]>) -> ScanStream {
2490 self.clone_pin().into_scan_stream(start, end)
2491 }
2492
2493 pub fn into_scan_stream(self, start: Option<&[u8]>, end: Option<&[u8]>) -> ScanStream {
2496 let end = end.map(<[u8]>::to_vec);
2497 let mut cursor = self.into_owned_iter();
2498 match start {
2499 Some(start) => cursor.seek(start),
2500 None => cursor.seek_to_first(),
2501 }
2502 ScanStream {
2503 entries: Entries::new(cursor, false),
2504 end,
2505 done: false,
2506 }
2507 }
2508
2509 pub fn owned_iter(&self) -> OwnedSnapshotIter {
2513 OwnedSnapshotIter::new(self.clone_pin())
2514 }
2515
2516 pub fn into_owned_iter(self) -> OwnedSnapshotIter {
2521 OwnedSnapshotIter::new(self)
2522 }
2523
2524 pub fn get_cf(&self, cf: &ColumnFamilyHandle, key: &[u8]) -> Result<Option<Vec<u8>>> {
2526 Ok(self.get_slice_cf(cf, key)?.map(DbSlice::into_vec))
2527 }
2528
2529 pub fn multi_get_cf(
2531 &self,
2532 cf: &ColumnFamilyHandle,
2533 keys: &[&[u8]],
2534 ) -> Result<Vec<Option<Vec<u8>>>> {
2535 self.validate_cf_handle(cf)?;
2536 let owned: Vec<Vec<u8>> = keys.iter().map(|k| prefix_key(cf.id(), k)).collect();
2537 let refs: Vec<&[u8]> = owned.iter().map(|k| k.as_slice()).collect();
2538 self.engine
2539 .multi_get_at(&refs, self.seq)
2540 .map_err(Error::from)
2541 }
2542
2543 pub fn scan_cf(
2551 &self,
2552 cf: &ColumnFamilyHandle,
2553 start: Option<&[u8]>,
2554 end: Option<&[u8]>,
2555 ) -> Result<Vec<(Vec<u8>, Vec<u8>)>> {
2556 self.validate_cf_handle(cf)?;
2557 let lo = match start {
2558 Some(s) => prefix_key(cf.id(), s),
2559 None => cf_lower_bound(cf.id()),
2560 };
2561 let hi = match end {
2562 Some(e) => prefix_key(cf.id(), e),
2563 None => cf_upper_bound(cf.id()),
2564 };
2565 let raw = collect_range(self.engine.new_iter_at(self.seq), Some(&lo), Some(&hi))?;
2566 strip_cf_prefix_entries(raw)
2567 }
2568
2569 pub fn scan_page_cf(
2576 &self,
2577 cf: &ColumnFamilyHandle,
2578 start: Option<&[u8]>,
2579 end: Option<&[u8]>,
2580 limit: usize,
2581 ) -> Result<ScanPage> {
2582 self.validate_cf_handle(cf)?;
2583 let lo = match start {
2584 Some(s) => prefix_key(cf.id(), s),
2585 None => cf_lower_bound(cf.id()),
2586 };
2587 let hi = match end {
2588 Some(e) => prefix_key(cf.id(), e),
2589 None => cf_upper_bound(cf.id()),
2590 };
2591 collect_page(self.engine.new_iter_at(self.seq), &lo, &hi, limit)
2592 .and_then(strip_cf_prefix_page)
2593 }
2594
2595 pub fn iter_cf<'a>(&'a self, cf: &ColumnFamilyHandle) -> CfIter<'a> {
2597 let inner = Iter::from_internal(self.engine.new_iter_at(self.seq))
2598 .with_stats(self.engine.statistics_arc());
2599 if self.is_live_cf_handle(cf) {
2600 CfIter::new(inner, cf.id())
2601 } else {
2602 CfIter::invalid(inner, cf)
2603 }
2604 }
2605}
2606
2607fn collect_range(
2611 mut iter: crate::engine::iterator::RegolithIterator,
2612 start: Option<&[u8]>,
2613 end: Option<&[u8]>,
2614) -> Result<Vec<(Vec<u8>, Vec<u8>)>> {
2615 match start {
2616 Some(s) => iter.seek(s),
2617 None => iter.seek_to_first(),
2618 }
2619 iter.status().map_err(Error::from)?;
2620
2621 let mut out = Vec::new();
2622 while iter.valid() {
2623 let (Some(k), Some(v)) = (iter.key(), iter.value()) else {
2624 break;
2625 };
2626 if let Some(e) = end
2627 && k >= e
2628 {
2629 break;
2630 }
2631 out.push((k.to_vec(), v.to_vec()));
2632 iter.next();
2633 }
2634 iter.status().map_err(Error::from)?;
2635 Ok(out)
2636}
2637
2638fn collect_page(
2639 mut iter: crate::engine::iterator::RegolithIterator,
2640 start: &[u8],
2641 end: &[u8],
2642 limit: usize,
2643) -> Result<ScanPage> {
2644 if limit == 0 {
2645 return Err(invalid_input_error(
2646 "scan page limit must be greater than zero",
2647 ));
2648 }
2649
2650 iter.seek(start);
2651 iter.status().map_err(Error::from)?;
2652
2653 let mut entries = Vec::new();
2654 let mut next_start = None;
2655 while iter.valid() {
2656 let (Some(k), Some(v)) = (iter.key(), iter.value()) else {
2657 break;
2658 };
2659 if k >= end {
2660 break;
2661 }
2662 if entries.len() == limit {
2663 next_start = Some(k.to_vec());
2664 break;
2665 }
2666 entries.push((k.to_vec(), v.to_vec()));
2667 iter.next();
2668 }
2669 iter.status().map_err(Error::from)?;
2670 Ok(ScanPage {
2671 entries,
2672 next_start,
2673 })
2674}
2675
2676fn strip_cf_prefix_page(page: ScanPage) -> Result<ScanPage> {
2677 let entries = strip_cf_prefix_entries(page.entries)?;
2678 let next_start = page
2679 .next_start
2680 .map(|k| strip_cf_prefix_key(&k))
2681 .transpose()?;
2682 Ok(ScanPage {
2683 entries,
2684 next_start,
2685 })
2686}
2687
2688#[derive(Debug)]
2690pub(crate) enum WriteBatchOp {
2691 Put { key: Vec<u8>, value: Vec<u8> },
2693 Delete { key: Vec<u8> },
2695 DeleteRange { start: Vec<u8>, end: Vec<u8> },
2697 Merge { key: Vec<u8>, operand: Vec<u8> },
2699}
2700
2701impl WriteBatchOp {
2702 pub(crate) fn buffered_bytes(&self) -> usize {
2706 match self {
2707 Self::Put { key, value } => key.len() + value.len(),
2708 Self::Delete { key } => key.len(),
2709 Self::DeleteRange { start, end } => start.len() + end.len(),
2710 Self::Merge { key, operand } => key.len() + operand.len(),
2711 }
2712 }
2713}
2714
2715#[derive(Debug, Default)]
2717pub struct WriteBatch {
2718 ops: Vec<WriteBatchOp>,
2719}
2720
2721impl WriteBatch {
2722 pub fn new() -> Self {
2724 Self::default()
2725 }
2726
2727 pub fn put(&mut self, key: &[u8], value: &[u8]) {
2729 self.put_owned(key, value.to_vec());
2730 }
2731
2732 pub fn put_owned(&mut self, key: &[u8], value: Vec<u8>) {
2739 self.ops.push(WriteBatchOp::Put {
2740 key: prefix_key(DEFAULT_CF_ID, key),
2741 value,
2742 });
2743 }
2744
2745 pub fn buffered_bytes(&self) -> usize {
2748 self.ops.iter().map(WriteBatchOp::buffered_bytes).sum()
2749 }
2750
2751 pub fn delete(&mut self, key: &[u8]) {
2753 self.ops.push(WriteBatchOp::Delete {
2754 key: prefix_key(DEFAULT_CF_ID, key),
2755 });
2756 }
2757
2758 pub fn delete_range(&mut self, start: &[u8], end: &[u8]) {
2766 if start >= end {
2767 return;
2768 }
2769 self.ops.push(WriteBatchOp::DeleteRange {
2770 start: prefix_key(DEFAULT_CF_ID, start),
2771 end: prefix_key(DEFAULT_CF_ID, end),
2772 });
2773 }
2774
2775 pub fn merge(&mut self, key: &[u8], operand: &[u8]) {
2782 self.ops.push(WriteBatchOp::Merge {
2783 key: prefix_key(DEFAULT_CF_ID, key),
2784 operand: operand.to_vec(),
2785 });
2786 }
2787
2788 pub fn put_cf(&mut self, cf: &ColumnFamilyHandle, key: &[u8], value: &[u8]) {
2790 self.ops.push(WriteBatchOp::Put {
2791 key: prefix_key(cf.id(), key),
2792 value: value.to_vec(),
2793 });
2794 }
2795
2796 pub fn delete_cf(&mut self, cf: &ColumnFamilyHandle, key: &[u8]) {
2798 self.ops.push(WriteBatchOp::Delete {
2799 key: prefix_key(cf.id(), key),
2800 });
2801 }
2802
2803 pub fn delete_range_cf(&mut self, cf: &ColumnFamilyHandle, start: &[u8], end: &[u8]) {
2805 if start >= end {
2806 return;
2807 }
2808 self.ops.push(WriteBatchOp::DeleteRange {
2809 start: prefix_key(cf.id(), start),
2810 end: prefix_key(cf.id(), end),
2811 });
2812 }
2813
2814 pub fn merge_cf(&mut self, cf: &ColumnFamilyHandle, key: &[u8], operand: &[u8]) {
2816 self.ops.push(WriteBatchOp::Merge {
2817 key: prefix_key(cf.id(), key),
2818 operand: operand.to_vec(),
2819 });
2820 }
2821
2822 pub(crate) fn insert_raw_put(&mut self, prefixed_key: Vec<u8>, value: Vec<u8>) {
2826 self.ops.push(WriteBatchOp::Put {
2827 key: prefixed_key,
2828 value,
2829 });
2830 }
2831
2832 pub(crate) fn insert_raw_delete(&mut self, prefixed_key: Vec<u8>) {
2834 self.ops.push(WriteBatchOp::Delete { key: prefixed_key });
2835 }
2836
2837 pub(crate) fn insert_raw_range_delete(
2839 &mut self,
2840 prefixed_start: Vec<u8>,
2841 prefixed_end: Vec<u8>,
2842 ) {
2843 self.ops.push(WriteBatchOp::DeleteRange {
2844 start: prefixed_start,
2845 end: prefixed_end,
2846 });
2847 }
2848
2849 pub(crate) fn insert_raw_merge(&mut self, prefixed_key: Vec<u8>, operand: Vec<u8>) {
2851 self.ops.push(WriteBatchOp::Merge {
2852 key: prefixed_key,
2853 operand,
2854 });
2855 }
2856
2857 pub fn len(&self) -> usize {
2863 self.ops
2864 .iter()
2865 .filter(|op| matches!(op, WriteBatchOp::Put { .. } | WriteBatchOp::Delete { .. }))
2866 .count()
2867 }
2868
2869 pub fn range_delete_count(&self) -> usize {
2871 self.ops
2872 .iter()
2873 .filter(|op| matches!(op, WriteBatchOp::DeleteRange { .. }))
2874 .count()
2875 }
2876
2877 pub fn merge_count(&self) -> usize {
2879 self.ops
2880 .iter()
2881 .filter(|op| matches!(op, WriteBatchOp::Merge { .. }))
2882 .count()
2883 }
2884
2885 pub fn is_empty(&self) -> bool {
2887 self.ops.is_empty()
2888 }
2889}
2890
2891#[cfg(test)]
2892mod tests {
2893 use super::*;
2894 use std::path::PathBuf;
2895 use tempfile::TempDir;
2896
2897 fn open_tmp() -> (Db, TempDir) {
2898 let dir = TempDir::new().unwrap();
2899 let db = Db::open(dir.path(), Options::default()).unwrap();
2900 (db, dir)
2901 }
2902
2903 fn first_wal_path(dir: &TempDir) -> PathBuf {
2904 let mut entries: Vec<_> = std::fs::read_dir(dir.path().join("wal"))
2905 .unwrap()
2906 .filter_map(|entry| entry.ok())
2907 .filter(|entry| entry.path().extension().and_then(|ext| ext.to_str()) == Some("log"))
2908 .collect();
2909 entries.sort_by_key(|entry| entry.path());
2910 entries.into_iter().next().unwrap().path()
2911 }
2912
2913 #[test]
2914 fn test_db_open_rejects_second_writer_on_same_directory() {
2915 let dir = TempDir::new().unwrap();
2916 let db = Db::open(dir.path(), Options::default()).unwrap();
2917
2918 let err = Db::open(dir.path(), Options::default()).unwrap_err();
2919 match err {
2920 Error::Io(io) => {
2921 assert_eq!(io.kind(), std::io::ErrorKind::AlreadyExists);
2922 assert!(io.to_string().contains("already locked"));
2923 }
2924 other => panic!("expected lock I/O error, got {other:?}"),
2925 }
2926
2927 db.put(b"k", b"v").unwrap();
2928 drop(db);
2929
2930 let reopened = Db::open(dir.path(), Options::default()).unwrap();
2931 assert_eq!(reopened.get(b"k").unwrap(), Some(b"v".to_vec()));
2932 }
2933
2934 #[test]
2935 fn test_db_open_rejects_invalid_options() {
2936 let dir = TempDir::new().unwrap();
2937 let err = Db::open(
2938 dir.path(),
2939 Options {
2940 write_buffer_size: 0,
2941 ..Options::default()
2942 },
2943 )
2944 .unwrap_err();
2945 match err {
2946 Error::InvalidArgument(message) => assert!(message.contains("write_buffer_size")),
2947 other => panic!("expected invalid argument, got {other:?}"),
2948 }
2949 }
2950
2951 #[test]
2952 fn test_open_read_only_replays_wal_without_mutating_files() {
2953 let dir = TempDir::new().unwrap();
2954 {
2955 let opts = Options {
2956 durability: DurabilityMode::Immediate,
2957 ..Options::default()
2958 };
2959 let db = Db::open(dir.path(), opts).unwrap();
2960 db.put(b"wal_only", b"value").unwrap();
2961 }
2962
2963 let manifest_len = std::fs::metadata(dir.path().join("MANIFEST"))
2964 .unwrap()
2965 .len();
2966 let mut wal_names_before: Vec<_> = std::fs::read_dir(dir.path().join("wal"))
2967 .unwrap()
2968 .map(|entry| entry.unwrap().file_name())
2969 .collect();
2970 wal_names_before.sort();
2971
2972 let ro = Db::open_read_only(dir.path(), Options::default()).unwrap();
2973 assert_eq!(ro.get(b"wal_only").unwrap(), Some(b"value".to_vec()));
2974
2975 let err = ro.put(b"blocked", b"write").unwrap_err();
2976 match err {
2977 Error::ReadOnly => {}
2978 other => panic!("expected read-only error, got {other:?}"),
2979 }
2980
2981 let writer_err = Db::open(dir.path(), Options::default()).unwrap_err();
2982 match writer_err {
2983 Error::Io(io) => assert_eq!(io.kind(), std::io::ErrorKind::AlreadyExists),
2984 other => panic!("expected lock conflict, got {other:?}"),
2985 }
2986 ro.close().unwrap();
2987 drop(ro);
2988
2989 assert_eq!(
2990 std::fs::metadata(dir.path().join("MANIFEST"))
2991 .unwrap()
2992 .len(),
2993 manifest_len
2994 );
2995 let mut wal_names_after: Vec<_> = std::fs::read_dir(dir.path().join("wal"))
2996 .unwrap()
2997 .map(|entry| entry.unwrap().file_name())
2998 .collect();
2999 wal_names_after.sort();
3000 assert_eq!(wal_names_after, wal_names_before);
3001 }
3002
3003 #[test]
3004 fn test_open_read_only_missing_db_errors() {
3005 let dir = TempDir::new().unwrap();
3006 let err = Db::open_read_only(dir.path(), Options::default()).unwrap_err();
3007 match err {
3008 Error::Io(io) => assert_eq!(io.kind(), std::io::ErrorKind::NotFound),
3009 other => panic!("expected missing DB error, got {other:?}"),
3010 }
3011 }
3012
3013 #[test]
3014 fn test_close_transitions_handle_to_closed_state() {
3015 let (db, _dir) = open_tmp();
3016 db.put(b"k", b"v").unwrap();
3017 let snap = db.snapshot();
3018
3019 db.close().unwrap();
3020 db.close().unwrap();
3021
3022 match db.get(b"k").unwrap_err() {
3023 Error::Closed => {}
3024 other => panic!("expected closed error from get, got {other:?}"),
3025 }
3026 match db.put(b"after", b"close").unwrap_err() {
3027 Error::Closed => {}
3028 other => panic!("expected closed error from put, got {other:?}"),
3029 }
3030 match db.write(WriteBatch::new()).unwrap_err() {
3031 Error::Closed => {}
3032 other => panic!("expected closed error from empty write, got {other:?}"),
3033 }
3034 match db.delete_range(b"z", b"a").unwrap_err() {
3035 Error::Closed => {}
3036 other => panic!("expected closed error from no-op range delete, got {other:?}"),
3037 }
3038 let default_cf = db.default_cf();
3039 match db.delete_range_cf(&default_cf, b"z", b"a").unwrap_err() {
3040 Error::Closed => {}
3041 other => panic!("expected closed error from no-op cf range delete, got {other:?}"),
3042 }
3043 match snap.get(b"k").unwrap_err() {
3044 Error::Closed => {}
3045 other => panic!("expected closed error from snapshot get, got {other:?}"),
3046 }
3047
3048 let mut iter = db.iter();
3049 iter.seek_to_first();
3050 match iter.status().unwrap_err() {
3051 Error::Closed => {}
3052 other => panic!("expected closed error from iterator, got {other:?}"),
3053 }
3054 }
3055
3056 #[test]
3057 fn test_failed_close_can_be_retried() {
3058 let dir = TempDir::new().unwrap();
3059 let db = Db::open(dir.path(), Options::default()).unwrap();
3060 db.put(b"k", b"v").unwrap();
3061
3062 let blocked_wal_path = dir.path().join("wal").join("wal_000002.log");
3063 std::fs::create_dir(&blocked_wal_path).unwrap();
3064
3065 match db.close().unwrap_err() {
3066 Error::Io(_) => {}
3067 other => panic!("expected close I/O error, got {other:?}"),
3068 }
3069
3070 assert_eq!(db.get(b"k").unwrap(), Some(b"v".to_vec()));
3071
3072 std::fs::remove_dir(&blocked_wal_path).unwrap();
3073 db.close().unwrap();
3074 db.close().unwrap();
3075
3076 let sst_count = std::fs::read_dir(dir.path().join("sst"))
3077 .unwrap()
3078 .filter_map(|entry| entry.ok())
3079 .filter(|entry| entry.path().extension().is_some_and(|ext| ext == "sst"))
3080 .count();
3081 assert!(sst_count > 0);
3082
3083 drop(db);
3084 let reopened = Db::open(dir.path(), Options::default()).unwrap();
3085 assert_eq!(reopened.get(b"k").unwrap(), Some(b"v".to_vec()));
3086 }
3087
3088 #[test]
3089 fn test_configured_key_value_size_limits_are_enforced() {
3090 let dir = TempDir::new().unwrap();
3091 let opts = Options {
3092 max_key_size: 8,
3093 max_value_size: 4,
3094 ..Options::default()
3095 };
3096 let db = Db::open(dir.path(), opts).unwrap();
3097
3098 db.put(b"abc", b"1234").unwrap();
3099 assert!(db.put(b"toolongky", b"1").is_err());
3100 assert!(db.put(b"a", b"12345").is_err());
3101 assert!(db.delete(b"toolongky").is_err());
3102 assert!(db.merge(b"toolongky", b"1").is_err());
3103 assert!(db.merge(b"a", b"12345").is_err());
3104
3105 let mut batch = WriteBatch::new();
3106 batch.put(b"ok", b"1");
3107 batch.put(b"toolong", b"2");
3108 db.write(batch).unwrap();
3109 assert_eq!(db.get(b"ok").unwrap(), Some(b"1".to_vec()));
3110
3111 let mut batch = WriteBatch::new();
3112 batch.put(b"ok2", b"1");
3113 batch.put(b"toolongky", b"2");
3114 assert!(db.write(batch).is_err());
3115 assert_eq!(db.get(b"ok2").unwrap(), None);
3116
3117 let cf = db.create_column_family("cf").unwrap();
3118 assert!(db.put_cf(&cf, b"toolongky", b"1").is_err());
3119 }
3120
3121 fn tiny_flush_opts() -> Options {
3123 Options {
3124 write_buffer_size: 4 * 1024,
3125 ..Options::default()
3126 }
3127 }
3128
3129 fn force_flush(db: &Db, tag: &str) {
3132 let payload = vec![0u8; 512];
3133 for i in 0..32 {
3134 let key = format!("__flush_{}_{:04}", tag, i);
3135 db.put(key.as_bytes(), &payload).unwrap();
3136 }
3137 }
3138
3139 fn force_flush_with_prefix(db: &Db, prefix: &str) {
3140 let payload = vec![0u8; 512];
3141 for i in 0..32 {
3142 let key = format!("{prefix}_{i:04}");
3143 db.put(key.as_bytes(), &payload).unwrap();
3144 }
3145 }
3146
3147 #[test]
3148 fn test_basic_crud() {
3149 let (db, _dir) = open_tmp();
3150
3151 db.put(b"key1", b"value1").unwrap();
3152 assert_eq!(db.get(b"key1").unwrap(), Some(b"value1".to_vec()));
3153
3154 db.put(b"key1", b"value2").unwrap();
3155 assert_eq!(db.get(b"key1").unwrap(), Some(b"value2".to_vec()));
3156
3157 db.delete(b"key1").unwrap();
3158 assert_eq!(db.get(b"key1").unwrap(), None);
3159
3160 assert_eq!(db.get(b"nonexistent").unwrap(), None);
3161 }
3162
3163 #[test]
3164 fn test_write_batch() {
3165 let (db, _dir) = open_tmp();
3166
3167 let mut batch = WriteBatch::new();
3168 batch.put(b"a", b"1");
3169 batch.put(b"b", b"2");
3170 batch.put(b"c", b"3");
3171 db.write(batch).unwrap();
3172
3173 assert_eq!(db.get(b"a").unwrap(), Some(b"1".to_vec()));
3174 assert_eq!(db.get(b"b").unwrap(), Some(b"2".to_vec()));
3175 assert_eq!(db.get(b"c").unwrap(), Some(b"3".to_vec()));
3176 }
3177
3178 #[test]
3179 fn test_write_batch_uses_single_wal_batch_record() {
3180 let (db, dir) = open_tmp();
3181
3182 let mut batch = WriteBatch::new();
3183 batch.put(b"a", b"1");
3184 batch.put(b"b", b"2");
3185 db.write(batch).unwrap();
3186
3187 let stamp = crate::engine::wal::WAL_STAMP_LEN;
3190 let wal = std::fs::read(first_wal_path(&dir)).unwrap();
3191 assert!(wal.len() >= stamp + 5);
3192 assert_eq!(&wal[0..4], b"REGO", "the log must carry its stamp");
3193 assert_eq!(
3194 wal[stamp + 4],
3195 0x05,
3196 "multi-op WriteBatch must use RECORD_BATCH"
3197 );
3198 }
3199
3200 #[test]
3201 fn test_snapshot_isolation() {
3202 let (db, _dir) = open_tmp();
3203
3204 db.put(b"key", b"v1").unwrap();
3205 let snap = db.snapshot();
3206
3207 db.put(b"key", b"v2").unwrap();
3208
3209 assert_eq!(snap.get(b"key").unwrap(), Some(b"v1".to_vec()));
3210 assert_eq!(db.get(b"key").unwrap(), Some(b"v2".to_vec()));
3211 }
3212
3213 #[test]
3214 fn test_scan() {
3215 let (db, _dir) = open_tmp();
3216
3217 db.put(b"a", b"1").unwrap();
3218 db.put(b"b", b"2").unwrap();
3219 db.put(b"c", b"3").unwrap();
3220 db.put(b"d", b"4").unwrap();
3221
3222 let results = db.scan(Some(b"b"), Some(b"d")).unwrap();
3223 assert_eq!(results.len(), 2);
3224 assert_eq!(results[0], (b"b".to_vec(), b"2".to_vec()));
3225 assert_eq!(results[1], (b"c".to_vec(), b"3".to_vec()));
3226 }
3227
3228 #[test]
3229 fn test_scan_page_limits_and_resumes() {
3230 let (db, _dir) = open_tmp();
3231
3232 for c in b'a'..=b'e' {
3233 db.put(&[c], &[c]).unwrap();
3234 }
3235
3236 let page = db.scan_page(Some(b"b"), Some(b"e"), 2).unwrap();
3237 assert_eq!(
3238 page,
3239 ScanPage {
3240 entries: vec![
3241 (b"b".to_vec(), b"b".to_vec()),
3242 (b"c".to_vec(), b"c".to_vec()),
3243 ],
3244 next_start: Some(b"d".to_vec()),
3245 }
3246 );
3247
3248 let next = db
3249 .scan_page(page.next_start.as_deref(), Some(b"e"), 2)
3250 .unwrap();
3251 assert_eq!(
3252 next,
3253 ScanPage {
3254 entries: vec![(b"d".to_vec(), b"d".to_vec())],
3255 next_start: None,
3256 }
3257 );
3258
3259 let exact_end = db.scan_page(Some(b"b"), Some(b"d"), 2).unwrap();
3260 assert_eq!(
3261 exact_end,
3262 ScanPage {
3263 entries: vec![
3264 (b"b".to_vec(), b"b".to_vec()),
3265 (b"c".to_vec(), b"c".to_vec()),
3266 ],
3267 next_start: None,
3268 }
3269 );
3270 }
3271
3272 #[test]
3273 fn test_scan_page_rejects_zero_limit() {
3274 let (db, _dir) = open_tmp();
3275
3276 let err = db.scan_page(None, None, 0).unwrap_err();
3277 match err {
3278 Error::InvalidArgument(message) => assert!(message.contains("greater than zero")),
3279 other => panic!("expected invalid argument error, got {other:?}"),
3280 }
3281 }
3282
3283 #[test]
3284 fn test_strip_cf_prefix_page_rejects_short_internal_key() {
3285 let page = ScanPage {
3286 entries: vec![(vec![0, 1, 2], b"value".to_vec())],
3287 next_start: None,
3288 };
3289
3290 match strip_cf_prefix_page(page).unwrap_err() {
3291 Error::Corruption(source) => assert_eq!(source.kind(), std::io::ErrorKind::InvalidData),
3292 other => panic!("expected corruption error, got {other:?}"),
3293 }
3294 }
3295
3296 #[test]
3297 fn test_snapshot_scan_page_is_stable() {
3298 let (db, _dir) = open_tmp();
3299
3300 db.put(b"a", b"1").unwrap();
3301 db.put(b"b", b"2").unwrap();
3302 let snap = db.snapshot();
3303
3304 db.put(b"a", b"new").unwrap();
3305 db.delete(b"b").unwrap();
3306 db.put(b"c", b"3").unwrap();
3307
3308 let first = snap.scan_page(None, None, 1).unwrap();
3309 assert_eq!(
3310 first,
3311 ScanPage {
3312 entries: vec![(b"a".to_vec(), b"1".to_vec())],
3313 next_start: Some(b"b".to_vec()),
3314 }
3315 );
3316
3317 let second = snap
3318 .scan_page(first.next_start.as_deref(), None, 2)
3319 .unwrap();
3320 assert_eq!(
3321 second,
3322 ScanPage {
3323 entries: vec![(b"b".to_vec(), b"2".to_vec())],
3324 next_start: None,
3325 }
3326 );
3327 }
3328
3329 #[test]
3330 fn test_drop_all() {
3331 let (db, _dir) = open_tmp();
3332
3333 db.put(b"key1", b"val1").unwrap();
3334 db.put(b"key2", b"val2").unwrap();
3335 db.drop_all().unwrap();
3336
3337 assert_eq!(db.get(b"key1").unwrap(), None);
3338 assert_eq!(db.get(b"key2").unwrap(), None);
3339
3340 db.put(b"key3", b"val3").unwrap();
3341 assert_eq!(db.get(b"key3").unwrap(), Some(b"val3".to_vec()));
3342 }
3343
3344 #[test]
3345 fn test_drop_all_reopen_ignores_leftover_old_wal() {
3346 let dir = TempDir::new().unwrap();
3347 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
3348
3349 db.put(b"flushed", b"old").unwrap();
3350 force_flush(&db, "drop-all-wal-floor");
3351 db.put(b"unflushed", b"old").unwrap();
3352 db.drop_all().unwrap();
3353 drop(db);
3354
3355 let stale_wal_path = dir.path().join("wal").join("wal_000001.log");
3356 let mut stale_wal = engine::wal::Wal::create(&stale_wal_path).unwrap();
3357 stale_wal
3358 .append_put(&prefix_key(DEFAULT_CF_ID, b"resurrect"), b"bad", 99)
3359 .unwrap();
3360 stale_wal.sync_data().unwrap();
3361
3362 let db = Db::open(dir.path(), Options::default()).unwrap();
3363 assert_eq!(db.get(b"flushed").unwrap(), None);
3364 assert_eq!(db.get(b"unflushed").unwrap(), None);
3365 assert_eq!(db.get(b"resurrect").unwrap(), None);
3366 assert!(db.scan(None, None).unwrap().is_empty());
3367 }
3368
3369 #[test]
3370 fn test_snapshot_isolation_across_flush() {
3371 let dir = TempDir::new().unwrap();
3372 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
3373
3374 db.put(b"key", b"v1").unwrap();
3375 let snap = db.snapshot();
3376
3377 db.put(b"key", b"v2").unwrap();
3378 force_flush(&db, "snap");
3379
3380 assert_eq!(snap.get(b"key").unwrap(), Some(b"v1".to_vec()));
3381 assert_eq!(db.get(b"key").unwrap(), Some(b"v2".to_vec()));
3382 }
3383
3384 #[test]
3385 fn test_delete_persists_across_flush() {
3386 let dir = TempDir::new().unwrap();
3387 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
3388
3389 db.put(b"key", b"v1").unwrap();
3390 force_flush(&db, "a");
3391
3392 db.delete(b"key").unwrap();
3393 force_flush(&db, "b");
3394
3395 assert_eq!(db.get(b"key").unwrap(), None);
3396 }
3397
3398 #[test]
3399 fn test_crash_recovery_without_close() {
3400 let dir = TempDir::new().unwrap();
3401
3402 {
3403 let db = Db::open(dir.path(), Options::default()).unwrap();
3404 db.put(b"a", b"1").unwrap();
3405 db.put(b"b", b"2").unwrap();
3406 db.delete(b"a").unwrap();
3407 db.put(b"c", b"3").unwrap();
3408 }
3410
3411 let db = Db::open(dir.path(), Options::default()).unwrap();
3412 assert_eq!(db.get(b"a").unwrap(), None);
3413 assert_eq!(db.get(b"b").unwrap(), Some(b"2".to_vec()));
3414 assert_eq!(db.get(b"c").unwrap(), Some(b"3".to_vec()));
3415 }
3416
3417 #[test]
3418 fn test_recovered_wal_survives_second_crash_before_flush() {
3419 let dir = TempDir::new().unwrap();
3420
3421 {
3422 let db = Db::open(dir.path(), Options::default()).unwrap();
3423 db.put(b"a", b"1").unwrap();
3424 db.put(b"b", b"2").unwrap();
3425 db.put(b"d", b"4").unwrap();
3426 db.delete(b"a").unwrap();
3427 db.delete_range(b"d", b"f").unwrap();
3428 }
3430
3431 {
3432 let db = Db::open(dir.path(), Options::default()).unwrap();
3433 assert_eq!(db.get(b"a").unwrap(), None);
3434 assert_eq!(db.get(b"b").unwrap(), Some(b"2".to_vec()));
3435 assert_eq!(db.get(b"d").unwrap(), None);
3436 }
3439
3440 let db = Db::open(dir.path(), Options::default()).unwrap();
3441 assert_eq!(db.get(b"a").unwrap(), None);
3442 assert_eq!(db.get(b"b").unwrap(), Some(b"2".to_vec()));
3443 assert_eq!(db.get(b"d").unwrap(), None);
3444 }
3445
3446 fn collect_iter(db: &Db) -> Vec<(Vec<u8>, Vec<u8>)> {
3449 let mut it = db.iter();
3450 it.seek_to_first();
3451 let mut out = Vec::new();
3452 while it.valid() {
3453 out.push((it.key().unwrap().to_vec(), it.value().unwrap().to_vec()));
3454 it.next();
3455 }
3456 it.status().unwrap();
3457 out
3458 }
3459
3460 #[test]
3461 fn test_iter_empty_db() {
3462 let (db, _dir) = open_tmp();
3463 let mut it = db.iter();
3464 it.seek_to_first();
3465 assert!(!it.valid());
3466 it.seek(b"anything");
3467 assert!(!it.valid());
3468 assert!(it.status().is_ok());
3469 }
3470
3471 #[test]
3472 fn test_iter_basic_forward() {
3473 let (db, _dir) = open_tmp();
3474 for i in 0..10 {
3475 let k = format!("k{:02}", i);
3476 let v = format!("v{}", i);
3477 db.put(k.as_bytes(), v.as_bytes()).unwrap();
3478 }
3479 let items = collect_iter(&db);
3480 assert_eq!(items.len(), 10);
3481 for (i, (k, v)) in items.iter().enumerate() {
3482 assert_eq!(k, format!("k{:02}", i).as_bytes());
3483 assert_eq!(v, format!("v{}", i).as_bytes());
3484 }
3485 }
3486
3487 #[test]
3488 fn test_owned_snapshot_iter_streams_from_snapshot() {
3489 let (db, _dir) = open_tmp();
3490 db.put(b"a", b"1").unwrap();
3491 db.put(b"b", b"2").unwrap();
3492 let snapshot = db.snapshot();
3493 db.put(b"c", b"3").unwrap();
3494
3495 let mut it = snapshot.owned_iter();
3496 assert_eq!(snapshot.get(b"b").unwrap(), Some(b"2".to_vec()));
3497
3498 it.seek_to_first();
3499 assert!(it.valid());
3500 assert_eq!(it.key(), Some(b"a".as_ref()));
3501 assert_eq!(it.value(), Some(b"1".as_ref()));
3502 it.next();
3503 assert_eq!(it.key(), Some(b"b".as_ref()));
3504 it.next();
3505 assert!(!it.valid());
3506
3507 it.seek_to_last();
3508 assert_eq!(it.key(), Some(b"b".as_ref()));
3509 it.prev();
3510 assert_eq!(it.key(), Some(b"a".as_ref()));
3511 it.status().unwrap();
3512 }
3513
3514 #[test]
3515 fn test_iter_seek_exact_and_between() {
3516 let (db, _dir) = open_tmp();
3517 db.put(b"a", b"1").unwrap();
3518 db.put(b"c", b"3").unwrap();
3519 db.put(b"e", b"5").unwrap();
3520
3521 let mut it = db.iter();
3522
3523 it.seek(b"a");
3524 assert!(it.valid());
3525 assert_eq!(it.key(), Some(b"a".as_ref()));
3526
3527 it.seek(b"b");
3528 assert_eq!(it.key(), Some(b"c".as_ref()));
3529
3530 it.seek(b"c");
3531 assert_eq!(it.key(), Some(b"c".as_ref()));
3532
3533 it.seek(b"f");
3534 assert!(!it.valid());
3535 }
3536
3537 #[test]
3538 fn test_iter_seek_for_prev() {
3539 let (db, _dir) = open_tmp();
3540 db.put(b"a", b"1").unwrap();
3541 db.put(b"c", b"3").unwrap();
3542 db.put(b"e", b"5").unwrap();
3543
3544 let mut it = db.iter();
3545
3546 it.seek_for_prev(b"e");
3547 assert_eq!(it.key(), Some(b"e".as_ref()));
3548
3549 it.seek_for_prev(b"d");
3550 assert_eq!(it.key(), Some(b"c".as_ref()));
3551
3552 it.seek_for_prev(b"a");
3553 assert_eq!(it.key(), Some(b"a".as_ref()));
3554
3555 it.seek_for_prev(b"0");
3556 assert!(!it.valid());
3557 }
3558
3559 #[test]
3560 fn test_iter_continues_after_seek() {
3561 let (db, _dir) = open_tmp();
3562 for c in b'a'..=b'j' {
3563 db.put(&[c], &[c]).unwrap();
3564 }
3565
3566 let mut it = db.iter();
3567 it.seek(b"d");
3568 let mut keys = Vec::new();
3569 while it.valid() {
3570 keys.push(it.key().unwrap().to_vec());
3571 it.next();
3572 }
3573 assert_eq!(
3574 keys,
3575 vec![
3576 b"d".to_vec(),
3577 b"e".to_vec(),
3578 b"f".to_vec(),
3579 b"g".to_vec(),
3580 b"h".to_vec(),
3581 b"i".to_vec(),
3582 b"j".to_vec(),
3583 ]
3584 );
3585 }
3586
3587 #[test]
3588 fn test_iter_across_memtable_and_l0() {
3589 let dir = TempDir::new().unwrap();
3590 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
3591
3592 for i in 0..10 {
3593 let k = format!("old{:02}", i);
3594 db.put(k.as_bytes(), b"old").unwrap();
3595 }
3596 force_flush(&db, "to-l0");
3597
3598 for i in 0..5 {
3599 let k = format!("new{:02}", i);
3600 db.put(k.as_bytes(), b"new").unwrap();
3601 }
3602
3603 let items = collect_iter(&db);
3604 let olds = items.iter().filter(|(k, _)| k.starts_with(b"old")).count();
3605 let news = items.iter().filter(|(k, _)| k.starts_with(b"new")).count();
3606 assert_eq!(olds, 10);
3607 assert_eq!(news, 5);
3608
3609 let sorted: Vec<_> = items.iter().map(|(k, _)| k.clone()).collect();
3610 let mut expected = sorted.clone();
3611 expected.sort();
3612 assert_eq!(sorted, expected);
3613 }
3614
3615 #[test]
3616 fn test_iter_tombstone_hides_older_level_entry() {
3617 let dir = TempDir::new().unwrap();
3618 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
3619
3620 db.put(b"kept", b"v1").unwrap();
3621 db.put(b"gone", b"v1").unwrap();
3622 force_flush(&db, "a");
3623
3624 db.delete(b"gone").unwrap();
3625
3626 let items = collect_iter(&db);
3627 let keys: Vec<_> = items.iter().map(|(k, _)| k.clone()).collect();
3628 assert!(keys.contains(&b"kept".to_vec()));
3629 assert!(!keys.contains(&b"gone".to_vec()));
3630 }
3631
3632 #[test]
3633 fn test_iter_latest_version_wins_across_levels() {
3634 let dir = TempDir::new().unwrap();
3635 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
3636
3637 db.put(b"k", b"v1").unwrap();
3638 force_flush(&db, "a");
3639 db.put(b"k", b"v2").unwrap();
3640
3641 let mut it = db.iter();
3642 it.seek(b"k");
3643 assert_eq!(it.key(), Some(b"k".as_ref()));
3644 assert_eq!(it.value(), Some(b"v2".as_ref()));
3645 }
3646
3647 #[test]
3648 fn test_iter_honors_snapshot_isolation() {
3649 let (db, _dir) = open_tmp();
3650 db.put(b"k", b"v1").unwrap();
3651 let snap = db.snapshot();
3652 db.put(b"k", b"v2").unwrap();
3653
3654 let mut it = snap.iter();
3655 it.seek(b"k");
3656 assert_eq!(it.value(), Some(b"v1".as_ref()));
3657 }
3658
3659 #[test]
3660 fn test_iter_snapshot_ignores_tombstone_newer_than_snap() {
3661 let (db, _dir) = open_tmp();
3662 db.put(b"k", b"v1").unwrap();
3663 let snap = db.snapshot();
3664 db.delete(b"k").unwrap();
3665
3666 let mut it = snap.iter();
3667 it.seek(b"k");
3668 assert_eq!(it.value(), Some(b"v1".as_ref()));
3669 }
3670
3671 #[test]
3672 fn test_iter_consistency_with_scan() {
3673 let (db, _dir) = open_tmp();
3674 for i in 0..100 {
3675 let k = format!("k{:03}", i);
3676 let v = format!("v{}", i);
3677 db.put(k.as_bytes(), v.as_bytes()).unwrap();
3678 }
3679
3680 let scan = db.scan(Some(b"k020"), Some(b"k050")).unwrap();
3681
3682 let mut it = db.iter();
3683 it.seek(b"k020");
3684 let mut from_iter = Vec::new();
3685 while it.valid() {
3686 let k = it.key().unwrap();
3687 if k >= b"k050".as_ref() {
3688 break;
3689 }
3690 from_iter.push((k.to_vec(), it.value().unwrap().to_vec()));
3691 it.next();
3692 }
3693
3694 assert_eq!(scan, from_iter);
3695 }
3696
3697 #[test]
3698 fn test_iter_large_scan_10k_keys_after_flush() {
3699 let dir = TempDir::new().unwrap();
3700 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
3701
3702 const N: usize = 10_000;
3703 for i in 0..N {
3704 let k = format!("key_{:06}", i);
3705 db.put(k.as_bytes(), b"v").unwrap();
3706 }
3707
3708 let mut it = db.iter();
3709 it.seek(b"key_");
3710 let mut count = 0;
3711 while it.valid() {
3712 let k = it.key().unwrap();
3713 if !k.starts_with(b"key_") {
3714 it.next();
3715 continue;
3716 }
3717 count += 1;
3718 it.next();
3719 }
3720 assert_eq!(count, N);
3721 }
3722
3723 fn all_versions_of(db: &Db, user_key: &[u8]) -> Vec<(u64, u8)> {
3729 let prefixed = prefix_key(DEFAULT_CF_ID, user_key);
3732 db.engine.all_persisted_versions_of(&prefixed).unwrap()
3733 }
3734
3735 #[test]
3736 fn test_gc_drops_old_versions_without_snapshot() {
3737 let dir = TempDir::new().unwrap();
3740 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
3741
3742 for v in 0..10 {
3743 db.put(b"k", format!("v{}", v).as_bytes()).unwrap();
3744 }
3745
3746 db.compact_range(None, None).unwrap();
3747
3748 let versions = all_versions_of(&db, b"k");
3749 assert_eq!(
3750 versions.len(),
3751 1,
3752 "expected a single surviving version, found {:?}",
3753 versions
3754 );
3755 assert_eq!(db.get(b"k").unwrap(), Some(b"v9".to_vec()));
3756 }
3757
3758 #[test]
3759 fn test_gc_preserves_versions_pinned_by_snapshot() {
3760 let dir = TempDir::new().unwrap();
3764 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
3765
3766 db.put(b"k", b"v1").unwrap();
3767 db.put(b"k", b"v2").unwrap();
3768 db.put(b"k", b"v3").unwrap();
3769 let snap = db.snapshot();
3770 for v in 4..10 {
3773 db.put(b"k", format!("v{}", v).as_bytes()).unwrap();
3774 }
3775
3776 db.compact_range(None, None).unwrap();
3777
3778 assert_eq!(snap.get(b"k").unwrap(), Some(b"v3".to_vec()));
3779 assert_eq!(db.get(b"k").unwrap(), Some(b"v9".to_vec()));
3780 }
3781
3782 #[test]
3783 fn test_gc_releases_pin_when_snapshot_drops() {
3784 let dir = TempDir::new().unwrap();
3788 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
3789
3790 for v in 0..5 {
3791 db.put(b"k", format!("v{}", v).as_bytes()).unwrap();
3792 }
3793
3794 {
3795 let _snap = db.snapshot();
3796 assert_eq!(db.engine.oldest_live_seq(), 5);
3797 }
3798 assert_eq!(db.engine.oldest_live_seq(), u64::MAX);
3800
3801 for v in 5..10 {
3802 db.put(b"k", format!("v{}", v).as_bytes()).unwrap();
3803 }
3804
3805 db.compact_range(None, None).unwrap();
3806
3807 let versions = all_versions_of(&db, b"k");
3808 assert_eq!(versions.len(), 1);
3809 assert_eq!(db.get(b"k").unwrap(), Some(b"v9".to_vec()));
3810 }
3811
3812 #[test]
3813 fn test_gc_with_multiple_live_snapshots_uses_oldest() {
3814 let dir = TempDir::new().unwrap();
3819 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
3820
3821 db.put(b"k", b"v1").unwrap();
3822 db.put(b"k", b"v2").unwrap();
3823 let old_snap = db.snapshot(); db.put(b"k", b"v3").unwrap();
3825 db.put(b"k", b"v4").unwrap();
3826 let new_snap = db.snapshot(); db.put(b"k", b"v5").unwrap();
3828 db.put(b"k", b"v6").unwrap();
3829
3830 db.compact_range(None, None).unwrap();
3831
3832 assert_eq!(old_snap.get(b"k").unwrap(), Some(b"v2".to_vec()));
3834 assert_eq!(new_snap.get(b"k").unwrap(), Some(b"v4".to_vec()));
3835 assert_eq!(db.get(b"k").unwrap(), Some(b"v6".to_vec()));
3836 }
3837
3838 #[test]
3839 fn test_gc_preserves_tombstone_hiding_older_entries() {
3840 let dir = TempDir::new().unwrap();
3844 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
3845
3846 for v in 0..5 {
3847 db.put(b"k", format!("v{}", v).as_bytes()).unwrap();
3848 }
3849 db.delete(b"k").unwrap();
3850
3851 db.compact_range(None, None).unwrap();
3852
3853 assert_eq!(db.get(b"k").unwrap(), None);
3854
3855 let versions = all_versions_of(&db, b"k");
3858 assert!(!versions.is_empty());
3859 let (_, vt) = *versions.iter().max_by_key(|(seq, _)| *seq).unwrap();
3861 const VALUE_TYPE_DELETION: u8 = 0;
3862 assert_eq!(vt, VALUE_TYPE_DELETION);
3863 }
3864
3865 #[test]
3866 fn test_range_tombstone_pruning_preserves_snapshot_visible_value() {
3867 let dir = TempDir::new().unwrap();
3868 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
3869
3870 db.put(b"k", b"old").unwrap();
3871 let snap = db.snapshot();
3872 db.delete_range(b"a", b"z").unwrap();
3873
3874 db.compact_range(None, None).unwrap();
3875
3876 assert_eq!(snap.get(b"k").unwrap(), Some(b"old".to_vec()));
3877 assert_eq!(db.get(b"k").unwrap(), None);
3878 }
3879
3880 #[test]
3881 fn test_gc_across_many_user_keys() {
3882 let dir = TempDir::new().unwrap();
3886 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
3887
3888 for i in 0..200 {
3889 for v in 0..3 {
3890 db.put(
3891 format!("k{:03}", i).as_bytes(),
3892 format!("v{}_{}", i, v).as_bytes(),
3893 )
3894 .unwrap();
3895 }
3896 }
3897
3898 db.compact_range(None, None).unwrap();
3899
3900 for i in 0..200 {
3901 let k = format!("k{:03}", i);
3902 let versions = all_versions_of(&db, k.as_bytes());
3903 assert_eq!(versions.len(), 1, "key {} survived with {:?}", k, versions);
3904 assert_eq!(
3905 db.get(k.as_bytes()).unwrap(),
3906 Some(format!("v{}_2", i).into_bytes())
3907 );
3908 }
3909 }
3910
3911 fn level_file_count(db: &Db, level: usize) -> usize {
3914 db.engine.level_file_count(level)
3915 }
3916
3917 fn total_file_count(db: &Db) -> usize {
3918 db.engine.total_file_count()
3919 }
3920
3921 #[test]
3922 fn test_compact_range_empty_db() {
3923 let (db, _dir) = open_tmp();
3924 db.compact_range(None, None).unwrap();
3926 assert_eq!(total_file_count(&db), 0);
3927 }
3928
3929 #[test]
3930 fn test_compact_range_full_preserves_reads() {
3931 let dir = TempDir::new().unwrap();
3932 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
3933
3934 for i in 0..500 {
3935 let k = format!("k{:04}", i);
3936 db.put(k.as_bytes(), format!("v{}", i).as_bytes()).unwrap();
3937 }
3938
3939 db.compact_range(None, None).unwrap();
3940
3941 for i in 0..500 {
3943 let k = format!("k{:04}", i);
3944 assert_eq!(
3945 db.get(k.as_bytes()).unwrap(),
3946 Some(format!("v{}", i).into_bytes())
3947 );
3948 }
3949 }
3950
3951 #[test]
3952 fn test_compact_range_flushes_active_memtable() {
3953 let (db, _dir) = open_tmp();
3957 for i in 0..10 {
3958 let k = format!("m{:02}", i);
3959 db.put(k.as_bytes(), b"v").unwrap();
3960 }
3961 assert!(!db.engine.active_memtable_is_empty());
3962
3963 db.compact_range(None, None).unwrap();
3964
3965 assert!(db.engine.active_memtable_is_empty());
3966 for i in 0..10 {
3968 let k = format!("m{:02}", i);
3969 assert_eq!(db.get(k.as_bytes()).unwrap(), Some(b"v".to_vec()));
3970 }
3971 }
3972
3973 #[test]
3974 fn test_compact_range_drains_l0() {
3975 let dir = TempDir::new().unwrap();
3978 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
3979
3980 for i in 0..200 {
3981 let k = format!("k{:04}", i);
3982 db.put(k.as_bytes(), b"v").unwrap();
3983 }
3984
3985 db.compact_range(None, None).unwrap();
3986
3987 assert_eq!(level_file_count(&db, 0), 0);
3988 assert!(total_file_count(&db) > 0);
3990 }
3991
3992 #[test]
3993 fn test_compact_range_bounded_preserves_all_data() {
3994 let dir = TempDir::new().unwrap();
3995 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
3996
3997 for i in 0..100 {
3999 db.put(format!("a{:03}", i).as_bytes(), b"a").unwrap();
4000 }
4001 for i in 0..100 {
4002 db.put(format!("m{:03}", i).as_bytes(), b"m").unwrap();
4003 }
4004 for i in 0..100 {
4005 db.put(format!("z{:03}", i).as_bytes(), b"z").unwrap();
4006 }
4007
4008 db.compact_range(Some(b"m"), Some(b"n")).unwrap();
4010
4011 for i in 0..100 {
4013 assert_eq!(
4014 db.get(format!("a{:03}", i).as_bytes()).unwrap(),
4015 Some(b"a".to_vec())
4016 );
4017 assert_eq!(
4018 db.get(format!("m{:03}", i).as_bytes()).unwrap(),
4019 Some(b"m".to_vec())
4020 );
4021 assert_eq!(
4022 db.get(format!("z{:03}", i).as_bytes()).unwrap(),
4023 Some(b"z".to_vec())
4024 );
4025 }
4026 }
4027
4028 #[test]
4029 fn test_compact_range_bounded_compacts_default_cf_files() {
4030 let dir = TempDir::new().unwrap();
4031 let opts = Options {
4032 write_buffer_size: 4 * 1024,
4033 l0_compaction_trigger: 1_000,
4034 ..Options::default()
4035 };
4036 let db = Db::open(dir.path(), opts).unwrap();
4037 let payload = vec![0u8; 512];
4038
4039 for i in 0..32 {
4040 db.put(format!("m{i:04}").as_bytes(), &payload).unwrap();
4041 }
4042 force_flush_with_prefix(&db, "m_flush");
4043
4044 let l0_before = level_file_count(&db, 0);
4045 assert!(l0_before > 0);
4046
4047 db.compact_range(Some(b"m"), Some(b"n")).unwrap();
4048
4049 assert_eq!(level_file_count(&db, 0), 0);
4050 assert!(total_file_count(&db) > 0);
4051 assert_eq!(db.get(b"m0000").unwrap(), Some(payload));
4052 }
4053
4054 #[test]
4055 fn test_compact_range_reclaims_space_after_overwrite() {
4056 let dir = TempDir::new().unwrap();
4061 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
4062
4063 for i in 0..200 {
4064 let k = format!("k{:03}", i);
4065 db.put(k.as_bytes(), b"v1").unwrap();
4066 }
4067 for i in 0..200 {
4068 let k = format!("k{:03}", i);
4069 db.put(k.as_bytes(), b"v2").unwrap();
4070 }
4071
4072 db.compact_range(None, None).unwrap();
4073
4074 for i in 0..200 {
4075 let k = format!("k{:03}", i);
4076 assert_eq!(db.get(k.as_bytes()).unwrap(), Some(b"v2".to_vec()));
4077 }
4078 }
4079
4080 #[test]
4081 fn test_compact_range_runs_alongside_background_compaction() {
4082 let dir = TempDir::new().unwrap();
4086 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
4087
4088 const N: usize = 2_000;
4089 for i in 0..N {
4090 let k = format!("key_{:05}", i);
4091 db.put(k.as_bytes(), b"v").unwrap();
4092 }
4093
4094 db.compact_range(None, None).unwrap();
4095
4096 for i in 0..N {
4098 let k = format!("key_{:05}", i);
4099 assert_eq!(db.get(k.as_bytes()).unwrap(), Some(b"v".to_vec()));
4100 }
4101 }
4102
4103 #[test]
4104 fn test_compact_range_iterator_still_correct() {
4105 let dir = TempDir::new().unwrap();
4107 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
4108
4109 for i in 0..300 {
4110 let k = format!("k{:04}", i);
4111 db.put(k.as_bytes(), b"v").unwrap();
4112 }
4113
4114 db.compact_range(None, None).unwrap();
4115
4116 let mut it = db.iter();
4117 it.seek_to_first();
4118 let mut count = 0;
4119 while it.valid() {
4120 if it.key().unwrap().starts_with(b"k") {
4121 count += 1;
4122 }
4123 it.next();
4124 }
4125 assert_eq!(count, 300);
4126 }
4127
4128 #[test]
4129 fn test_compact_range_tombstones_are_preserved() {
4130 let dir = TempDir::new().unwrap();
4134 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
4135
4136 for i in 0..50 {
4137 let k = format!("k{:02}", i);
4138 db.put(k.as_bytes(), b"v").unwrap();
4139 }
4140 for i in (0..50).step_by(2) {
4142 let k = format!("k{:02}", i);
4143 db.delete(k.as_bytes()).unwrap();
4144 }
4145
4146 db.compact_range(None, None).unwrap();
4147
4148 for i in 0..50 {
4149 let k = format!("k{:02}", i);
4150 let expected = if i % 2 == 0 {
4151 None
4152 } else {
4153 Some(b"v".to_vec())
4154 };
4155 assert_eq!(db.get(k.as_bytes()).unwrap(), expected);
4156 }
4157 }
4158
4159 #[test]
4160 fn test_compaction_range_tombstone_bounds_cover_point_keys_in_other_files() {
4161 let dir = TempDir::new().unwrap();
4162 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
4163
4164 db.put(b"b", b"old").unwrap();
4165 force_flush_with_prefix(&db, "__old_flush");
4166 db.compact_range(None, None).unwrap();
4167
4168 db.delete_range(b"a", b"z").unwrap();
4169 db.put(b"m", b"new").unwrap();
4170 force_flush_with_prefix(&db, "zz_new_flush");
4171 db.compact_range(None, None).unwrap();
4172
4173 assert_eq!(db.get(b"b").unwrap(), None);
4174 assert_eq!(db.get(b"m").unwrap(), Some(b"new".to_vec()));
4175 }
4176
4177 #[test]
4178 fn test_compaction_splits_range_tombstones_around_point_outputs() {
4179 let dir = TempDir::new().unwrap();
4180 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
4181
4182 db.put(b"b", b"old-left").unwrap();
4183 db.put(b"y", b"old-right").unwrap();
4184 db.compact_range(None, None).unwrap();
4185
4186 db.delete_range(b"a", b"z").unwrap();
4187 db.put(b"m", b"new").unwrap();
4188 db.compact_range(None, None).unwrap();
4189
4190 assert_eq!(db.get(b"b").unwrap(), None);
4191 assert_eq!(db.get(b"m").unwrap(), Some(b"new".to_vec()));
4192 assert_eq!(db.get(b"y").unwrap(), None);
4193
4194 let version = db.engine.current_version();
4195 let rt_only_files = version
4196 .levels
4197 .iter()
4198 .flatten()
4199 .filter(|file| file.meta.num_entries == 0)
4200 .count();
4201 assert!(
4202 rt_only_files >= 2,
4203 "range tombstone gaps should be emitted separately, got {rt_only_files}"
4204 );
4205 }
4206
4207 #[test]
4210 fn test_multi_get_empty_batch() {
4211 let (db, _dir) = open_tmp();
4212 db.put(b"x", b"y").unwrap();
4213 let results = db.multi_get(&[]).unwrap();
4214 assert!(results.is_empty());
4215 }
4216
4217 #[test]
4218 fn test_multi_get_all_hit() {
4219 let (db, _dir) = open_tmp();
4220 db.put(b"a", b"1").unwrap();
4221 db.put(b"b", b"2").unwrap();
4222 db.put(b"c", b"3").unwrap();
4223
4224 let keys: &[&[u8]] = &[b"a", b"b", b"c"];
4225 let results = db.multi_get(keys).unwrap();
4226 assert_eq!(
4227 results,
4228 vec![
4229 Some(b"1".to_vec()),
4230 Some(b"2".to_vec()),
4231 Some(b"3".to_vec())
4232 ]
4233 );
4234 }
4235
4236 #[test]
4237 fn test_multi_get_all_miss() {
4238 let (db, _dir) = open_tmp();
4239 db.put(b"a", b"1").unwrap();
4240
4241 let keys: &[&[u8]] = &[b"x", b"y", b"z"];
4242 let results = db.multi_get(keys).unwrap();
4243 assert_eq!(results, vec![None, None, None]);
4244 }
4245
4246 #[test]
4247 fn test_multi_get_mixed_hit_miss() {
4248 let (db, _dir) = open_tmp();
4249 db.put(b"a", b"1").unwrap();
4250 db.put(b"c", b"3").unwrap();
4251
4252 let keys: &[&[u8]] = &[b"a", b"b", b"c", b"d"];
4253 let results = db.multi_get(keys).unwrap();
4254 assert_eq!(
4255 results,
4256 vec![Some(b"1".to_vec()), None, Some(b"3".to_vec()), None]
4257 );
4258 }
4259
4260 #[test]
4261 fn test_multi_get_preserves_input_order() {
4262 let (db, _dir) = open_tmp();
4263 db.put(b"a", b"1").unwrap();
4264 db.put(b"b", b"2").unwrap();
4265 db.put(b"c", b"3").unwrap();
4266
4267 let keys: &[&[u8]] = &[b"c", b"a", b"b"];
4269 let results = db.multi_get(keys).unwrap();
4270 assert_eq!(
4271 results,
4272 vec![
4273 Some(b"3".to_vec()),
4274 Some(b"1".to_vec()),
4275 Some(b"2".to_vec())
4276 ]
4277 );
4278 }
4279
4280 #[test]
4281 fn test_multi_get_duplicates_in_input() {
4282 let (db, _dir) = open_tmp();
4283 db.put(b"a", b"1").unwrap();
4284 db.put(b"b", b"2").unwrap();
4285
4286 let keys: &[&[u8]] = &[b"a", b"b", b"a", b"missing", b"a"];
4287 let results = db.multi_get(keys).unwrap();
4288 assert_eq!(
4289 results,
4290 vec![
4291 Some(b"1".to_vec()),
4292 Some(b"2".to_vec()),
4293 Some(b"1".to_vec()),
4294 None,
4295 Some(b"1".to_vec()),
4296 ]
4297 );
4298 }
4299
4300 #[test]
4301 fn test_multi_get_honors_tombstones() {
4302 let (db, _dir) = open_tmp();
4303 db.put(b"a", b"1").unwrap();
4304 db.put(b"b", b"2").unwrap();
4305 db.put(b"c", b"3").unwrap();
4306 db.delete(b"b").unwrap();
4307
4308 let keys: &[&[u8]] = &[b"a", b"b", b"c"];
4309 let results = db.multi_get(keys).unwrap();
4310 assert_eq!(
4311 results,
4312 vec![Some(b"1".to_vec()), None, Some(b"3".to_vec())]
4313 );
4314 }
4315
4316 #[test]
4317 fn test_multi_get_tombstone_hides_older_level_entry() {
4318 let dir = TempDir::new().unwrap();
4319 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
4320
4321 db.put(b"keep", b"v").unwrap();
4322 db.put(b"gone", b"v").unwrap();
4323 force_flush(&db, "x");
4324 db.delete(b"gone").unwrap();
4325
4326 let keys: &[&[u8]] = &[b"keep", b"gone"];
4327 let results = db.multi_get(keys).unwrap();
4328 assert_eq!(results, vec![Some(b"v".to_vec()), None]);
4329 }
4330
4331 #[test]
4332 fn test_multi_get_spans_memtable_and_l0() {
4333 let dir = TempDir::new().unwrap();
4334 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
4335
4336 db.put(b"from_l0_1", b"v1").unwrap();
4337 db.put(b"from_l0_2", b"v2").unwrap();
4338 force_flush(&db, "x");
4339
4340 db.put(b"from_mem_1", b"v3").unwrap();
4341 db.put(b"from_mem_2", b"v4").unwrap();
4342
4343 let keys: &[&[u8]] = &[b"from_mem_1", b"from_l0_1", b"from_mem_2", b"from_l0_2"];
4344 let results = db.multi_get(keys).unwrap();
4345 assert_eq!(
4346 results,
4347 vec![
4348 Some(b"v3".to_vec()),
4349 Some(b"v1".to_vec()),
4350 Some(b"v4".to_vec()),
4351 Some(b"v2".to_vec())
4352 ]
4353 );
4354 }
4355
4356 #[test]
4357 fn test_multi_get_snapshot_isolation() {
4358 let (db, _dir) = open_tmp();
4359 db.put(b"a", b"a1").unwrap();
4360 db.put(b"b", b"b1").unwrap();
4361
4362 let snap = db.snapshot();
4363
4364 db.put(b"a", b"a2").unwrap();
4365 db.put(b"c", b"c1").unwrap();
4366 db.delete(b"b").unwrap();
4367
4368 let keys: &[&[u8]] = &[b"a", b"b", b"c"];
4369 let results = snap.multi_get(keys).unwrap();
4370 assert_eq!(
4371 results,
4372 vec![Some(b"a1".to_vec()), Some(b"b1".to_vec()), None],
4373 );
4374 }
4375
4376 #[test]
4377 fn test_multi_get_consistency_with_get() {
4378 let dir = TempDir::new().unwrap();
4381 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
4382
4383 for i in 0..500 {
4384 let k = format!("k{:04}", i);
4385 db.put(k.as_bytes(), format!("v{}", i).as_bytes()).unwrap();
4386 }
4387 for i in (0..500).step_by(7) {
4389 let k = format!("k{:04}", i);
4390 db.delete(k.as_bytes()).unwrap();
4391 }
4392
4393 let snap = db.snapshot();
4395
4396 let keys_owned: Vec<String> = (0..500)
4397 .step_by(3)
4398 .map(|i| format!("k{:04}", i))
4399 .chain(std::iter::once("missing_key".to_string()))
4400 .collect();
4401 let keys: Vec<&[u8]> = keys_owned.iter().map(|s| s.as_bytes()).collect();
4402
4403 let individual: Vec<_> = keys.iter().map(|k| snap.get(k).unwrap()).collect();
4404 let batched = snap.multi_get(&keys).unwrap();
4405
4406 assert_eq!(individual, batched);
4407 assert_eq!(individual.len(), keys.len());
4408 }
4409
4410 #[test]
4411 fn test_multi_get_large_batch_after_flush() {
4412 let dir = TempDir::new().unwrap();
4413 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
4414
4415 const N: usize = 2_000;
4416 for i in 0..N {
4417 let k = format!("key_{:05}", i);
4418 db.put(k.as_bytes(), b"v").unwrap();
4419 }
4420
4421 let keys_owned: Vec<String> = (0..N).map(|i| format!("key_{:05}", i)).collect();
4422 let keys: Vec<&[u8]> = keys_owned.iter().map(|s| s.as_bytes()).collect();
4423 let results = db.multi_get(&keys).unwrap();
4424 assert_eq!(results.len(), N);
4425 for r in &results {
4426 assert_eq!(r.as_deref(), Some(b"v".as_ref()));
4427 }
4428 }
4429
4430 #[test]
4431 fn test_multi_get_compacted_level_range_tombstones() {
4432 let dir = TempDir::new().unwrap();
4433 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
4434
4435 for i in 0..30 {
4436 let key = format!("k{:02}", i);
4437 let value = format!("v{:02}", i);
4438 db.put(key.as_bytes(), value.as_bytes()).unwrap();
4439 }
4440 force_flush(&db, "base");
4441 db.delete_range(b"k10", b"k20").unwrap();
4442 db.compact_range(None, None).unwrap();
4443
4444 let version = db.engine.current_version();
4445 assert!(
4446 version.levels.iter().skip(1).any(|level| !level.is_empty()),
4447 "test must exercise L1+ files"
4448 );
4449
4450 let keys_owned = ["k09", "k10", "k15", "k20", "k15", "k09", "missing", "k29"];
4451 let keys: Vec<&[u8]> = keys_owned.iter().map(|key| key.as_bytes()).collect();
4452 let individual: Vec<_> = keys.iter().map(|key| db.get(key).unwrap()).collect();
4453 let batched = db.multi_get(&keys).unwrap();
4454
4455 assert_eq!(batched, individual);
4456 assert_eq!(batched[0], Some(b"v09".to_vec()));
4457 assert_eq!(batched[1], None);
4458 assert_eq!(batched[2], None);
4459 assert_eq!(batched[3], Some(b"v20".to_vec()));
4460 assert_eq!(batched[4], None);
4461 assert_eq!(batched[5], Some(b"v09".to_vec()));
4462 assert_eq!(batched[6], None);
4463 assert_eq!(batched[7], Some(b"v29".to_vec()));
4464 }
4465
4466 fn collect_reverse(db: &Db) -> Vec<(Vec<u8>, Vec<u8>)> {
4469 let mut it = db.iter();
4470 it.seek_to_last();
4471 let mut out = Vec::new();
4472 while it.valid() {
4473 out.push((it.key().unwrap().to_vec(), it.value().unwrap().to_vec()));
4474 it.prev();
4475 }
4476 it.status().unwrap();
4477 out
4478 }
4479
4480 #[test]
4481 fn test_iter_seek_to_last_empty() {
4482 let (db, _dir) = open_tmp();
4483 let mut it = db.iter();
4484 it.seek_to_last();
4485 assert!(!it.valid());
4486 }
4487
4488 #[test]
4489 fn test_iter_reverse_walk_basic() {
4490 let (db, _dir) = open_tmp();
4491 for i in 0..10 {
4492 let k = format!("k{:02}", i);
4493 db.put(k.as_bytes(), b"v").unwrap();
4494 }
4495 let items = collect_reverse(&db);
4496 assert_eq!(items.len(), 10);
4497 for (i, (k, _)) in items.iter().enumerate() {
4498 assert_eq!(k, format!("k{:02}", 9 - i).as_bytes());
4499 }
4500 }
4501
4502 #[test]
4503 fn test_iter_prev_latest_version() {
4504 let (db, _dir) = open_tmp();
4505 db.put(b"a", b"a1").unwrap();
4506 db.put(b"b", b"b1").unwrap();
4507 db.put(b"b", b"b2").unwrap();
4508 db.put(b"c", b"c1").unwrap();
4509
4510 let mut it = db.iter();
4511 it.seek_to_last();
4512 assert_eq!(it.key(), Some(b"c".as_ref()));
4513 it.prev();
4514 assert_eq!(it.key(), Some(b"b".as_ref()));
4515 assert_eq!(it.value(), Some(b"b2".as_ref()));
4516 it.prev();
4517 assert_eq!(it.key(), Some(b"a".as_ref()));
4518 it.prev();
4519 assert!(!it.valid());
4520 }
4521
4522 #[test]
4523 fn test_iter_seek_for_prev_then_prev() {
4524 let (db, _dir) = open_tmp();
4525 db.put(b"a", b"1").unwrap();
4526 db.put(b"c", b"3").unwrap();
4527 db.put(b"e", b"5").unwrap();
4528 db.put(b"g", b"7").unwrap();
4529
4530 let mut it = db.iter();
4531 it.seek_for_prev(b"f");
4532 assert_eq!(it.key(), Some(b"e".as_ref()));
4533 it.prev();
4534 assert_eq!(it.key(), Some(b"c".as_ref()));
4535 it.prev();
4536 assert_eq!(it.key(), Some(b"a".as_ref()));
4537 it.prev();
4538 assert!(!it.valid());
4539 }
4540
4541 #[test]
4542 fn test_iter_reverse_across_flush_levels() {
4543 let dir = TempDir::new().unwrap();
4544 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
4545
4546 for i in 0..20 {
4547 let k = format!("k{:02}", i);
4548 db.put(k.as_bytes(), b"v").unwrap();
4549 }
4550 force_flush(&db, "a");
4551 for i in 20..30 {
4552 let k = format!("k{:02}", i);
4553 db.put(k.as_bytes(), b"v").unwrap();
4554 }
4555
4556 let items = collect_reverse(&db);
4557 let k_count = items.iter().filter(|(k, _)| k.starts_with(b"k")).count();
4558 assert_eq!(k_count, 30);
4559 let mut prev_k: Option<Vec<u8>> = None;
4560 for (k, _) in items.iter().filter(|(k, _)| k.starts_with(b"k")) {
4561 if let Some(p) = &prev_k {
4562 assert!(k < p, "not descending: {:?} after {:?}", k, p);
4563 }
4564 prev_k = Some(k.clone());
4565 }
4566 }
4567
4568 #[test]
4569 fn test_iter_reverse_hides_tombstoned_user_key() {
4570 let dir = TempDir::new().unwrap();
4571 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
4572
4573 db.put(b"keep", b"v").unwrap();
4574 db.put(b"gone", b"v").unwrap();
4575 force_flush(&db, "a");
4576 db.delete(b"gone").unwrap();
4577
4578 let items = collect_reverse(&db);
4579 let keys: Vec<_> = items.iter().map(|(k, _)| k.clone()).collect();
4580 assert!(keys.contains(&b"keep".to_vec()));
4581 assert!(!keys.contains(&b"gone".to_vec()));
4582 }
4583
4584 #[test]
4585 fn test_iter_reverse_honors_snapshot_isolation() {
4586 let (db, _dir) = open_tmp();
4587 db.put(b"k", b"v1").unwrap();
4588 let snap = db.snapshot();
4589 db.put(b"k", b"v2").unwrap();
4590
4591 let mut it = snap.iter();
4592 it.seek_to_last();
4593 assert_eq!(it.key(), Some(b"k".as_ref()));
4594 assert_eq!(it.value(), Some(b"v1".as_ref()));
4595 }
4596
4597 #[test]
4598 fn test_iter_direction_flip_forward_to_reverse() {
4599 let (db, _dir) = open_tmp();
4600 for c in b'a'..=b'e' {
4601 db.put(&[c], &[c]).unwrap();
4602 }
4603
4604 let mut it = db.iter();
4605 it.seek_to_first();
4606 assert_eq!(it.key(), Some(b"a".as_ref()));
4607 it.next();
4608 assert_eq!(it.key(), Some(b"b".as_ref()));
4609 it.next();
4610 assert_eq!(it.key(), Some(b"c".as_ref()));
4611
4612 it.prev();
4613 assert_eq!(it.key(), Some(b"b".as_ref()));
4614 it.prev();
4615 assert_eq!(it.key(), Some(b"a".as_ref()));
4616 it.prev();
4617 assert!(!it.valid());
4618 }
4619
4620 #[test]
4621 fn test_iter_direction_flip_reverse_to_forward() {
4622 let (db, _dir) = open_tmp();
4623 for c in b'a'..=b'e' {
4624 db.put(&[c], &[c]).unwrap();
4625 }
4626
4627 let mut it = db.iter();
4628 it.seek_to_last();
4629 assert_eq!(it.key(), Some(b"e".as_ref()));
4630 it.prev();
4631 assert_eq!(it.key(), Some(b"d".as_ref()));
4632 it.prev();
4633 assert_eq!(it.key(), Some(b"c".as_ref()));
4634
4635 it.next();
4636 assert_eq!(it.key(), Some(b"d".as_ref()));
4637 it.next();
4638 assert_eq!(it.key(), Some(b"e".as_ref()));
4639 it.next();
4640 assert!(!it.valid());
4641 }
4642
4643 #[test]
4644 fn test_iter_reverse_scan_10k_keys_after_flush() {
4645 let dir = TempDir::new().unwrap();
4646 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
4647
4648 const N: usize = 10_000;
4649 for i in 0..N {
4650 let k = format!("key_{:06}", i);
4651 db.put(k.as_bytes(), b"v").unwrap();
4652 }
4653
4654 let mut it = db.iter();
4655 it.seek_for_prev(b"key_~"); let mut count = 0;
4657 let mut prev: Option<Vec<u8>> = None;
4658 while it.valid() {
4659 let k = it.key().unwrap().to_vec();
4660 if !k.starts_with(b"key_") {
4661 it.prev();
4662 continue;
4663 }
4664 if let Some(p) = &prev {
4665 assert!(k < *p, "not descending: {:?} after {:?}", k, p);
4666 }
4667 prev = Some(k);
4668 count += 1;
4669 it.prev();
4670 }
4671 assert_eq!(count, N);
4672 assert!(it.status().is_ok());
4673 }
4674
4675 #[test]
4676 fn test_iter_reverse_seek_past_end_of_multi_block_sst() {
4677 let dir = TempDir::new().unwrap();
4687 let opts = Options {
4688 block_size: 128,
4689 write_buffer_size: 64 * 1024,
4690 ..Options::default()
4691 };
4692 {
4693 let db = Db::open(dir.path(), opts.clone()).unwrap();
4694 for i in 0..60u32 {
4695 let k = format!("k{:03}", i);
4696 db.put(k.as_bytes(), b"v").unwrap();
4697 }
4698 db.close().unwrap();
4699 }
4700
4701 let db = Db::open(dir.path(), opts).unwrap();
4702 let mut it = db.iter();
4703 it.seek_for_prev(b"~"); let mut seen = Vec::new();
4706 while it.valid() {
4707 seen.push(it.key().unwrap().to_vec());
4708 it.prev();
4709 }
4710 assert_eq!(seen.len(), 60);
4711 assert_eq!(seen.first().map(|k| k.as_slice()), Some(&b"k059"[..]));
4712 assert_eq!(seen.last().map(|k| k.as_slice()), Some(&b"k000"[..]));
4713 }
4714
4715 #[test]
4716 fn test_iter_seek_for_prev_on_tombstoned_key() {
4717 let dir = TempDir::new().unwrap();
4718 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
4719
4720 db.put(b"a", b"a1").unwrap();
4721 db.put(b"b", b"b1").unwrap();
4722 db.put(b"c", b"c1").unwrap();
4723 force_flush(&db, "x");
4724 db.delete(b"b").unwrap();
4725
4726 let mut it = db.iter();
4727 it.seek_for_prev(b"b");
4728 assert_eq!(it.key(), Some(b"a".as_ref()));
4731 }
4732
4733 #[test]
4734 fn test_iter_survives_drop_all() {
4735 let dir = TempDir::new().unwrap();
4740 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
4741
4742 for i in 0..20 {
4743 let k = format!("pin{:03}", i);
4744 db.put(k.as_bytes(), b"v").unwrap();
4745 }
4746 force_flush(&db, "pinned");
4747
4748 let mut it = db.iter();
4749 it.seek_to_first();
4750
4751 db.drop_all().unwrap();
4752
4753 let mut seen_pinned = 0;
4754 while it.valid() {
4755 if it.key().unwrap().starts_with(b"pin") {
4756 seen_pinned += 1;
4757 }
4758 it.next();
4759 }
4760 assert_eq!(seen_pinned, 20);
4761 assert!(it.status().is_ok());
4762 }
4763
4764 #[test]
4765 fn test_persistence() {
4766 let dir = TempDir::new().unwrap();
4767
4768 {
4769 let db = Db::open(dir.path(), Options::default()).unwrap();
4770 db.put(b"persist", b"data").unwrap();
4771 db.close().unwrap();
4772 }
4773
4774 {
4775 let db = Db::open(dir.path(), Options::default()).unwrap();
4776 assert_eq!(db.get(b"persist").unwrap(), Some(b"data".to_vec()));
4777 }
4778 }
4779
4780 #[test]
4783 fn test_delete_range_basic() {
4784 let (db, _dir) = open_tmp();
4785 for c in b'a'..=b'j' {
4786 db.put(&[c], &[c]).unwrap();
4787 }
4788 db.delete_range(b"c", b"g").unwrap();
4789
4790 assert_eq!(db.get(b"a").unwrap(), Some(b"a".to_vec()));
4791 assert_eq!(db.get(b"b").unwrap(), Some(b"b".to_vec()));
4792 assert_eq!(db.get(b"c").unwrap(), None);
4793 assert_eq!(db.get(b"d").unwrap(), None);
4794 assert_eq!(db.get(b"e").unwrap(), None);
4795 assert_eq!(db.get(b"f").unwrap(), None);
4796 assert_eq!(db.get(b"g").unwrap(), Some(b"g".to_vec())); assert_eq!(db.get(b"j").unwrap(), Some(b"j".to_vec()));
4798 }
4799
4800 #[test]
4801 fn test_delete_range_no_op_for_empty_or_inverted() {
4802 let (db, _dir) = open_tmp();
4803 db.put(b"a", b"1").unwrap();
4804 db.delete_range(b"z", b"a").unwrap();
4806 db.delete_range(b"a", b"a").unwrap();
4808 assert_eq!(db.get(b"a").unwrap(), Some(b"1".to_vec()));
4809 }
4810
4811 #[test]
4812 fn test_delete_range_then_put_inside_range() {
4813 let (db, _dir) = open_tmp();
4814 db.put(b"k", b"old").unwrap();
4815 db.delete_range(b"a", b"z").unwrap();
4816 db.put(b"k", b"new").unwrap();
4818 assert_eq!(db.get(b"k").unwrap(), Some(b"new".to_vec()));
4819 }
4820
4821 #[test]
4822 fn test_delete_range_put_then_range_delete_then_overwrite() {
4823 let (db, _dir) = open_tmp();
4824 db.put(b"k", b"v1").unwrap();
4825 db.delete_range(b"a", b"z").unwrap();
4826 assert_eq!(db.get(b"k").unwrap(), None);
4827 db.put(b"k", b"v2").unwrap();
4828 assert_eq!(db.get(b"k").unwrap(), Some(b"v2".to_vec()));
4829 }
4830
4831 #[test]
4832 fn test_delete_range_snapshot_isolation() {
4833 let (db, _dir) = open_tmp();
4834 db.put(b"k", b"v1").unwrap();
4835 let snap = db.snapshot();
4836 db.delete_range(b"a", b"z").unwrap();
4837 assert_eq!(db.get(b"k").unwrap(), None);
4838 assert_eq!(snap.get(b"k").unwrap(), Some(b"v1".to_vec()));
4840 }
4841
4842 #[test]
4843 fn test_delete_range_survives_flush() {
4844 let dir = TempDir::new().unwrap();
4845 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
4846 for i in 0..20 {
4847 db.put(format!("key_{:02}", i).as_bytes(), b"v").unwrap();
4848 }
4849 db.delete_range(b"key_05", b"key_15").unwrap();
4850 force_flush(&db, "rt");
4851 for i in 0..20 {
4852 let key = format!("key_{:02}", i);
4853 let got = db.get(key.as_bytes()).unwrap();
4854 if (5..15).contains(&i) {
4855 assert_eq!(got, None, "key {} should be deleted", key);
4856 } else {
4857 assert_eq!(got, Some(b"v".to_vec()), "key {} should survive", key);
4858 }
4859 }
4860 }
4861
4862 #[test]
4863 fn test_delete_range_survives_compaction() {
4864 let dir = TempDir::new().unwrap();
4865 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
4866 for i in 0..30 {
4867 db.put(format!("key_{:02}", i).as_bytes(), b"v").unwrap();
4868 }
4869 db.delete_range(b"key_10", b"key_20").unwrap();
4870 for tag in 0..6 {
4872 force_flush(&db, &format!("c{}", tag));
4873 }
4874 db.compact_range(None, None).unwrap();
4875
4876 for i in 0..30 {
4877 let key = format!("key_{:02}", i);
4878 let got = db.get(key.as_bytes()).unwrap();
4879 if (10..20).contains(&i) {
4880 assert_eq!(got, None, "key {} should be deleted post-compact", key);
4881 } else {
4882 assert_eq!(
4883 got,
4884 Some(b"v".to_vec()),
4885 "key {} should survive compact",
4886 key
4887 );
4888 }
4889 }
4890 }
4891
4892 #[test]
4893 fn test_delete_range_iterator_skips_deleted() {
4894 let (db, _dir) = open_tmp();
4895 for c in b'a'..=b'h' {
4896 db.put(&[c], &[c]).unwrap();
4897 }
4898 db.delete_range(b"c", b"f").unwrap();
4899
4900 let results = db.scan(None, None).unwrap();
4901 let keys: Vec<u8> = results.iter().map(|(k, _)| k[0]).collect();
4902 assert_eq!(keys, vec![b'a', b'b', b'f', b'g', b'h']);
4903 }
4904
4905 #[test]
4906 fn test_delete_range_reverse_iterator_skips_deleted() {
4907 let (db, _dir) = open_tmp();
4908 for c in b'a'..=b'h' {
4909 db.put(&[c], &[c]).unwrap();
4910 }
4911 db.delete_range(b"c", b"f").unwrap();
4912
4913 let mut iter = db.iter();
4914 iter.seek_to_last();
4915 let mut keys = Vec::new();
4916 while iter.valid() {
4917 keys.push(iter.key().unwrap()[0]);
4918 iter.prev();
4919 }
4920 assert_eq!(keys, vec![b'h', b'g', b'f', b'b', b'a']);
4921 }
4922
4923 #[test]
4924 fn test_delete_range_multi_get_honors_rt() {
4925 let (db, _dir) = open_tmp();
4926 for c in b'a'..=b'f' {
4927 db.put(&[c], &[c]).unwrap();
4928 }
4929 db.delete_range(b"b", b"e").unwrap();
4930
4931 let keys: Vec<&[u8]> = vec![b"a", b"b", b"c", b"d", b"e", b"f"];
4932 let got = db.multi_get(&keys).unwrap();
4933 assert_eq!(got[0], Some(b"a".to_vec()));
4934 assert_eq!(got[1], None);
4935 assert_eq!(got[2], None);
4936 assert_eq!(got[3], None);
4937 assert_eq!(got[4], Some(b"e".to_vec()));
4938 assert_eq!(got[5], Some(b"f".to_vec()));
4939 }
4940
4941 #[test]
4942 fn test_delete_range_crash_recovery() {
4943 let dir = TempDir::new().unwrap();
4944 {
4945 let db = Db::open(dir.path(), Options::default()).unwrap();
4946 for c in b'a'..=b'e' {
4947 db.put(&[c], &[c]).unwrap();
4948 }
4949 db.delete_range(b"b", b"d").unwrap();
4950 }
4952 let db = Db::open(dir.path(), Options::default()).unwrap();
4953 assert_eq!(db.get(b"a").unwrap(), Some(b"a".to_vec()));
4954 assert_eq!(db.get(b"b").unwrap(), None);
4955 assert_eq!(db.get(b"c").unwrap(), None);
4956 assert_eq!(db.get(b"d").unwrap(), Some(b"d".to_vec()));
4957 assert_eq!(db.get(b"e").unwrap(), Some(b"e".to_vec()));
4958 }
4959
4960 #[test]
4961 fn test_delete_range_in_write_batch() {
4962 let (db, _dir) = open_tmp();
4963 for c in b'a'..=b'f' {
4964 db.put(&[c], &[c]).unwrap();
4965 }
4966 let mut batch = WriteBatch::new();
4967 batch.put(b"x", b"x");
4968 batch.delete_range(b"b", b"e");
4969 batch.put(b"y", b"y");
4970 db.write(batch).unwrap();
4971
4972 assert_eq!(db.get(b"a").unwrap(), Some(b"a".to_vec()));
4973 assert_eq!(db.get(b"b").unwrap(), None);
4974 assert_eq!(db.get(b"c").unwrap(), None);
4975 assert_eq!(db.get(b"d").unwrap(), None);
4976 assert_eq!(db.get(b"e").unwrap(), Some(b"e".to_vec()));
4977 assert_eq!(db.get(b"x").unwrap(), Some(b"x".to_vec()));
4978 assert_eq!(db.get(b"y").unwrap(), Some(b"y".to_vec()));
4979 }
4980
4981 #[test]
4982 fn test_write_batch_delete_range_then_put_inside_range_keeps_put() {
4983 let (db, _dir) = open_tmp();
4984 db.put(b"k", b"old").unwrap();
4985
4986 let mut batch = WriteBatch::new();
4987 batch.delete_range(b"a", b"z");
4988 batch.put(b"k", b"new");
4989 db.write(batch).unwrap();
4990
4991 assert_eq!(db.get(b"k").unwrap(), Some(b"new".to_vec()));
4992 }
4993
4994 #[test]
4995 fn test_write_batch_put_then_delete_range_inside_range_deletes_put() {
4996 let (db, _dir) = open_tmp();
4997
4998 let mut batch = WriteBatch::new();
4999 batch.put(b"k", b"new");
5000 batch.delete_range(b"a", b"z");
5001 db.write(batch).unwrap();
5002
5003 assert_eq!(db.get(b"k").unwrap(), None);
5004 }
5005
5006 #[test]
5007 fn test_write_batch_order_survives_wal_replay() {
5008 let dir = TempDir::new().unwrap();
5009 {
5010 let db = Db::open(dir.path(), Options::default()).unwrap();
5011 db.put(b"k", b"old").unwrap();
5012
5013 let mut batch = WriteBatch::new();
5014 batch.delete_range(b"a", b"z");
5015 batch.put(b"k", b"new");
5016 db.write(batch).unwrap();
5017 }
5020
5021 let db = Db::open(dir.path(), Options::default()).unwrap();
5022 assert_eq!(db.get(b"k").unwrap(), Some(b"new".to_vec()));
5023 }
5024
5025 #[test]
5026 fn test_delete_range_overlapping_ranges() {
5027 let (db, _dir) = open_tmp();
5028 for c in b'a'..=b'j' {
5029 db.put(&[c], &[c]).unwrap();
5030 }
5031 db.delete_range(b"b", b"e").unwrap();
5032 db.delete_range(b"d", b"h").unwrap();
5033
5034 assert_eq!(db.get(b"a").unwrap(), Some(b"a".to_vec()));
5035 for c in b'b'..=b'g' {
5036 assert_eq!(db.get(&[c]).unwrap(), None, "key {} deleted", c as char);
5037 }
5038 assert_eq!(db.get(b"h").unwrap(), Some(b"h".to_vec()));
5039 }
5040
5041 fn compression_opts(codec: CompressionType) -> Options {
5044 Options {
5045 write_buffer_size: 4 * 1024,
5046 compression: codec,
5047 ..Options::default()
5048 }
5049 }
5050
5051 fn write_and_read_back(opts: Options) {
5052 let dir = TempDir::new().unwrap();
5053 let payload: Vec<u8> = (0..256).map(|i| (i % 31) as u8).collect();
5054 {
5055 let db = Db::open(dir.path(), opts.clone()).unwrap();
5056 for i in 0..200 {
5057 let key = format!("key_{:04}", i);
5058 db.put(key.as_bytes(), &payload).unwrap();
5059 }
5060 force_flush(&db, "comp");
5062 for i in 0..200 {
5063 let key = format!("key_{:04}", i);
5064 assert_eq!(
5065 db.get(key.as_bytes()).unwrap().as_deref(),
5066 Some(payload.as_slice()),
5067 "round-trip failed for {key}"
5068 );
5069 }
5070 db.close().unwrap();
5071 }
5072 let db = Db::open(dir.path(), opts).unwrap();
5075 for i in 0..200 {
5076 let key = format!("key_{:04}", i);
5077 assert_eq!(
5078 db.get(key.as_bytes()).unwrap().as_deref(),
5079 Some(payload.as_slice())
5080 );
5081 }
5082 }
5083
5084 #[test]
5085 fn test_compression_none_roundtrip() {
5086 write_and_read_back(compression_opts(CompressionType::None));
5087 }
5088
5089 #[test]
5090 fn test_compression_lz4_roundtrip() {
5091 write_and_read_back(compression_opts(CompressionType::Lz4));
5092 }
5093
5094 #[test]
5095 fn test_compression_snappy_roundtrip() {
5096 write_and_read_back(compression_opts(CompressionType::Snappy));
5097 }
5098
5099 #[test]
5100 fn test_compression_per_level_mixed_codecs() {
5101 let dir = TempDir::new().unwrap();
5105 let opts = Options {
5106 write_buffer_size: 4 * 1024,
5107 compression: CompressionType::Lz4,
5108 compression_per_level: Some(vec![
5109 CompressionType::Snappy, CompressionType::Lz4, CompressionType::None, ]),
5113 ..Options::default()
5114 };
5115 let payload: Vec<u8> = (0..256).map(|i| (i % 17) as u8).collect();
5116 {
5117 let db = Db::open(dir.path(), opts.clone()).unwrap();
5118 for i in 0..300 {
5119 let key = format!("k_{:04}", i);
5120 db.put(key.as_bytes(), &payload).unwrap();
5121 }
5122 force_flush(&db, "mix");
5123 db.compact_range(None, None).unwrap();
5125 for i in 0..300 {
5126 let key = format!("k_{:04}", i);
5127 assert_eq!(
5128 db.get(key.as_bytes()).unwrap().as_deref(),
5129 Some(payload.as_slice())
5130 );
5131 }
5132 db.close().unwrap();
5133 }
5134 let db = Db::open(dir.path(), opts).unwrap();
5137 for i in 0..300 {
5138 let key = format!("k_{:04}", i);
5139 assert_eq!(
5140 db.get(key.as_bytes()).unwrap().as_deref(),
5141 Some(payload.as_slice())
5142 );
5143 }
5144 }
5145
5146 #[test]
5147 fn test_compression_per_level_falls_back_to_default() {
5148 let dir = TempDir::new().unwrap();
5150 let opts = Options {
5151 write_buffer_size: 4 * 1024,
5152 compression: CompressionType::Snappy,
5153 compression_per_level: Some(vec![CompressionType::None]),
5154 ..Options::default()
5155 };
5156 let db = Db::open(dir.path(), opts).unwrap();
5157 for i in 0..50 {
5158 db.put(format!("k_{i:03}").as_bytes(), b"v").unwrap();
5159 }
5160 force_flush(&db, "fb");
5161 db.compact_range(None, None).unwrap();
5162 for i in 0..50 {
5163 assert_eq!(
5164 db.get(format!("k_{i:03}").as_bytes()).unwrap(),
5165 Some(b"v".to_vec())
5166 );
5167 }
5168 }
5169
5170 use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
5173
5174 struct DropOddKeysFilter {
5178 calls: AtomicUsize,
5179 }
5180
5181 impl CompactionFilter for DropOddKeysFilter {
5182 fn filter(&self, _level: usize, key: &[u8], _value: &[u8]) -> CompactionDecision {
5183 self.calls.fetch_add(1, AtomicOrdering::Relaxed);
5184 match key.last() {
5185 Some(b) if b.is_ascii_digit() && (b - b'0') % 2 == 1 => CompactionDecision::Remove,
5186 _ => CompactionDecision::Keep,
5187 }
5188 }
5189 fn name(&self) -> &'static str {
5190 "drop_odd_keys"
5191 }
5192 }
5193
5194 struct UppercaseValuesFilter;
5197
5198 impl CompactionFilter for UppercaseValuesFilter {
5199 fn filter(&self, _level: usize, _key: &[u8], value: &[u8]) -> CompactionDecision {
5200 let up: Vec<u8> = value.iter().map(|b| b.to_ascii_uppercase()).collect();
5201 if up == value {
5202 CompactionDecision::Keep
5203 } else {
5204 CompactionDecision::Change(up)
5205 }
5206 }
5207 fn name(&self) -> &'static str {
5208 "uppercase_values"
5209 }
5210 }
5211
5212 struct DropRangeTombstonesFilter;
5214
5215 impl CompactionFilter for DropRangeTombstonesFilter {
5216 fn filter(&self, _level: usize, _key: &[u8], _value: &[u8]) -> CompactionDecision {
5217 CompactionDecision::Keep
5218 }
5219 fn filter_range_delete(
5220 &self,
5221 _level: usize,
5222 _start: &[u8],
5223 _end: &[u8],
5224 ) -> CompactionDecision {
5225 CompactionDecision::Remove
5226 }
5227 fn name(&self) -> &'static str {
5228 "drop_range_tombstones"
5229 }
5230 }
5231
5232 #[test]
5233 fn test_compaction_filter_removes_matching_entries() {
5234 let dir = TempDir::new().unwrap();
5235 let filter = Arc::new(DropOddKeysFilter {
5236 calls: AtomicUsize::new(0),
5237 });
5238 let opts = Options {
5239 write_buffer_size: 4 * 1024,
5240 compaction_filter: Some(filter.clone()),
5241 ..Options::default()
5242 };
5243 let db = Db::open(dir.path(), opts).unwrap();
5244 let payload = vec![b'v'; 512];
5247 for _round in 0..4 {
5248 for i in 0..10 {
5249 db.put(format!("k{i}").as_bytes(), &payload).unwrap();
5250 }
5251 }
5252 db.compact_range(None, None).unwrap();
5253
5254 for i in 0..10 {
5256 let got = db.get(format!("k{i}").as_bytes()).unwrap();
5257 if i % 2 == 1 {
5258 assert_eq!(got, None, "k{i} should be filtered");
5259 } else {
5260 assert_eq!(got, Some(payload.clone()), "k{i} should survive");
5261 }
5262 }
5263 assert!(
5264 filter.calls.load(AtomicOrdering::Relaxed) > 0,
5265 "filter should have been invoked"
5266 );
5267 }
5268
5269 #[test]
5270 fn test_compaction_filter_rewrites_values() {
5271 let dir = TempDir::new().unwrap();
5272 let opts = Options {
5273 write_buffer_size: 4 * 1024,
5274 compaction_filter: Some(Arc::new(UppercaseValuesFilter)),
5275 ..Options::default()
5276 };
5277 let db = Db::open(dir.path(), opts).unwrap();
5278 for i in 0..20 {
5279 db.put(format!("k{i:02}").as_bytes(), b"hello world")
5280 .unwrap();
5281 }
5282 force_flush(&db, "filter");
5284 db.compact_range(None, None).unwrap();
5285
5286 for i in 0..20 {
5287 assert_eq!(
5288 db.get(format!("k{i:02}").as_bytes()).unwrap(),
5289 Some(b"HELLO WORLD".to_vec())
5290 );
5291 }
5292 }
5293
5294 #[test]
5295 fn test_compaction_filter_skipped_while_snapshot_alive() {
5296 let dir = TempDir::new().unwrap();
5297 let opts = Options {
5298 write_buffer_size: 4 * 1024,
5299 compaction_filter: Some(Arc::new(UppercaseValuesFilter)),
5300 ..Options::default()
5301 };
5302 let db = Db::open(dir.path(), opts).unwrap();
5303 for i in 0..20 {
5304 db.put(format!("k{i:02}").as_bytes(), b"hello").unwrap();
5305 }
5306 let snap = db.snapshot();
5308 force_flush(&db, "snap_filter");
5309 db.compact_range(None, None).unwrap();
5310
5311 for i in 0..20 {
5316 assert_eq!(
5317 snap.get(format!("k{i:02}").as_bytes()).unwrap(),
5318 Some(b"hello".to_vec())
5319 );
5320 assert_eq!(
5321 db.get(format!("k{i:02}").as_bytes()).unwrap(),
5322 Some(b"hello".to_vec())
5323 );
5324 }
5325 }
5326
5327 #[test]
5328 fn test_compaction_filter_drops_range_tombstones() {
5329 let dir = TempDir::new().unwrap();
5330 let opts = Options {
5331 write_buffer_size: 4 * 1024,
5332 compaction_filter: Some(Arc::new(DropRangeTombstonesFilter)),
5333 ..Options::default()
5334 };
5335 let db = Db::open(dir.path(), opts).unwrap();
5336 for c in b'a'..=b'f' {
5337 db.put(&[c], &[c]).unwrap();
5338 }
5339 db.delete_range(b"b", b"e").unwrap();
5340 for c in b'b'..=b'd' {
5343 assert_eq!(db.get(&[c]).unwrap(), None);
5344 }
5345 force_flush(&db, "drop_rt");
5346 db.compact_range(None, None).unwrap();
5347
5348 for c in b'a'..=b'f' {
5351 assert_eq!(
5352 db.get(&[c]).unwrap(),
5353 Some(vec![c]),
5354 "key {} restored",
5355 c as char
5356 );
5357 }
5358 }
5359
5360 fn prefix_opts() -> Options {
5361 Options {
5362 write_buffer_size: 4 * 1024,
5363 prefix_extractor: Some(std::sync::Arc::new(FixedLengthPrefix(10))),
5364 ..Options::default()
5365 }
5366 }
5367
5368 #[test]
5369 fn test_seek_prefix_basic() {
5370 let (db, _dir) = open_tmp();
5371 db.put(b"tenant_001:k1", b"1").unwrap();
5372 db.put(b"tenant_001:k2", b"2").unwrap();
5373 db.put(b"tenant_002:k1", b"3").unwrap();
5374 db.put(b"tenant_010:k1", b"4").unwrap();
5375
5376 let mut it = db.iter();
5377 it.seek_prefix(b"tenant_001");
5378 let mut got: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
5379 while it.valid() {
5380 got.push((it.key().unwrap().to_vec(), it.value().unwrap().to_vec()));
5381 it.next();
5382 }
5383 assert_eq!(
5384 got,
5385 vec![
5386 (b"tenant_001:k1".to_vec(), b"1".to_vec()),
5387 (b"tenant_001:k2".to_vec(), b"2".to_vec()),
5388 ]
5389 );
5390 }
5391
5392 #[test]
5393 fn test_seek_prefix_absent_returns_empty() {
5394 let dir = TempDir::new().unwrap();
5395 let db = Db::open(dir.path(), prefix_opts()).unwrap();
5396 for i in 0..200 {
5397 let key = format!("tenant_001:k{:04}", i);
5398 db.put(key.as_bytes(), b"v").unwrap();
5399 }
5400 force_flush(&db, "p");
5401
5402 let mut it = db.iter();
5403 it.seek_prefix(b"tenant_999");
5404 assert!(!it.valid(), "expected no keys under an absent prefix");
5405 }
5406
5407 #[test]
5408 fn test_seek_prefix_uses_extracted_bloom_probe_for_longer_prefix() {
5409 let stats = Arc::new(Statistics::new());
5410 let dir = TempDir::new().unwrap();
5411 let db = Db::open(
5412 dir.path(),
5413 Options {
5414 write_buffer_size: 4 * 1024,
5415 l0_compaction_trigger: 1_000,
5416 bloom_bits_per_key: 64,
5417 prefix_extractor: Some(Arc::new(FixedLengthPrefix(8))),
5418 statistics: Some(stats.clone()),
5419 ..Options::default()
5420 },
5421 )
5422 .unwrap();
5423
5424 db.put(b"aaaa:item", b"v").unwrap();
5425 db.put(b"zzzz:item", b"v").unwrap();
5426 force_flush(&db, "long_prefix_probe");
5427
5428 stats.reset();
5429 let mut it = db.iter();
5430 it.seek_prefix(b"bbbb:item");
5431 assert!(!it.valid(), "absent long prefix should return empty");
5432 it.status().unwrap();
5433 assert_eq!(
5434 stats.get_ticker(Ticker::BlockCacheMiss),
5435 0,
5436 "prefix bloom should skip the SSTable before loading a data block"
5437 );
5438 }
5439
5440 #[test]
5441 fn test_seek_prefix_across_flush_boundary() {
5442 let dir = TempDir::new().unwrap();
5443 let db = Db::open(dir.path(), prefix_opts()).unwrap();
5444
5445 db.put(b"tenant_001:a", b"1a").unwrap();
5447 db.put(b"tenant_002:a", b"2a").unwrap();
5448 force_flush(&db, "p1");
5449
5450 db.put(b"tenant_001:b", b"1b").unwrap();
5452 db.put(b"tenant_002:b", b"2b").unwrap();
5453
5454 let mut it = db.iter();
5455 it.seek_prefix(b"tenant_001");
5456 let mut keys: Vec<Vec<u8>> = Vec::new();
5457 while it.valid() {
5458 keys.push(it.key().unwrap().to_vec());
5459 it.next();
5460 }
5461 assert_eq!(
5462 keys,
5463 vec![b"tenant_001:a".to_vec(), b"tenant_001:b".to_vec()]
5464 );
5465 }
5466
5467 #[test]
5468 fn test_seek_prefix_after_compact_range() {
5469 let dir = TempDir::new().unwrap();
5470 let db = Db::open(dir.path(), prefix_opts()).unwrap();
5471
5472 for i in 0..50 {
5473 db.put(format!("tenant_001:{:04}", i).as_bytes(), b"v")
5474 .unwrap();
5475 db.put(format!("tenant_002:{:04}", i).as_bytes(), b"v")
5476 .unwrap();
5477 }
5478 force_flush(&db, "c1");
5479 db.compact_range(None, None).unwrap();
5480
5481 let mut it = db.iter();
5482 it.seek_prefix(b"tenant_002");
5483 let mut count = 0;
5484 while it.valid() {
5485 let k = it.key().unwrap();
5486 assert!(
5487 k.starts_with(b"tenant_002"),
5488 "got unexpected key {:?}",
5489 std::str::from_utf8(k).unwrap_or("<non-utf8>")
5490 );
5491 count += 1;
5492 it.next();
5493 }
5494 assert_eq!(count, 50);
5495 }
5496
5497 #[test]
5498 fn test_seek_prefix_mixed_with_without_extractor() {
5499 let dir = TempDir::new().unwrap();
5505 {
5506 let db = Db::open(
5507 dir.path(),
5508 Options {
5509 write_buffer_size: 4 * 1024,
5510 ..Options::default()
5511 },
5512 )
5513 .unwrap();
5514 db.put(b"tenant_001:old", b"old").unwrap();
5515 force_flush(&db, "a");
5516 }
5517
5518 let db = Db::open(dir.path(), prefix_opts()).unwrap();
5519 db.put(b"tenant_001:new", b"new").unwrap();
5520 db.put(b"tenant_002:new", b"new").unwrap();
5521 force_flush(&db, "b");
5522
5523 let mut it = db.iter();
5524 it.seek_prefix(b"tenant_001");
5525 let mut keys: Vec<Vec<u8>> = Vec::new();
5526 while it.valid() {
5527 keys.push(it.key().unwrap().to_vec());
5528 it.next();
5529 }
5530 assert_eq!(
5531 keys,
5532 vec![b"tenant_001:new".to_vec(), b"tenant_001:old".to_vec()]
5533 );
5534 }
5535
5536 #[test]
5537 fn test_compaction_filter_none_is_noop() {
5538 let (db, _dir) = open_tmp();
5539 for i in 0..10 {
5540 db.put(format!("k{i}").as_bytes(), b"v").unwrap();
5541 }
5542 db.compact_range(None, None).unwrap();
5543 for i in 0..10 {
5544 assert_eq!(
5545 db.get(format!("k{i}").as_bytes()).unwrap(),
5546 Some(b"v".to_vec())
5547 );
5548 }
5549 }
5550
5551 #[test]
5554 fn test_write_options_defaults_unchanged() {
5555 let (db, _dir) = open_tmp();
5558 db.put_opt(&WriteOptions::default(), b"a", b"1").unwrap();
5559 assert_eq!(db.get(b"a").unwrap(), Some(b"1".to_vec()));
5560 }
5561
5562 #[test]
5563 fn test_write_options_sync_override_persists_across_reopen() {
5564 let dir = TempDir::new().unwrap();
5569 let opts = Options {
5570 durability: DurabilityMode::Eventual,
5571 ..Options::default()
5572 };
5573 {
5574 let db = Db::open(dir.path(), opts.clone()).unwrap();
5575 db.put_opt(&WriteOptions::sync(), b"critical", b"payload")
5576 .unwrap();
5577 }
5580 let db = Db::open(dir.path(), opts).unwrap();
5581 assert_eq!(db.get(b"critical").unwrap(), Some(b"payload".to_vec()));
5582 }
5583
5584 #[test]
5585 fn test_write_options_disable_wal_loses_data_on_drop_without_flush() {
5586 let dir = TempDir::new().unwrap();
5590 let opts = Options::default();
5591 {
5592 let db = Db::open(dir.path(), opts.clone()).unwrap();
5593 db.put_opt(&WriteOptions::disable_wal(), b"ephemeral", b"ghost")
5594 .unwrap();
5595 }
5598 let db = Db::open(dir.path(), opts).unwrap();
5599 assert_eq!(db.get(b"ephemeral").unwrap(), None);
5600 }
5601
5602 #[test]
5603 fn test_write_options_disable_wal_visible_within_session() {
5604 let (db, _dir) = open_tmp();
5608 db.put_opt(&WriteOptions::disable_wal(), b"k", b"v")
5609 .unwrap();
5610 assert_eq!(db.get(b"k").unwrap(), Some(b"v".to_vec()));
5611 }
5612
5613 #[test]
5614 fn test_write_options_disable_wal_survives_clean_close() {
5615 let dir = TempDir::new().unwrap();
5620 let opts = Options::default();
5621 {
5622 let db = Db::open(dir.path(), opts.clone()).unwrap();
5623 db.put_opt(&WriteOptions::disable_wal(), b"bulk", b"loaded")
5624 .unwrap();
5625 db.close().unwrap();
5626 }
5627 let db = Db::open(dir.path(), opts).unwrap();
5628 assert_eq!(db.get(b"bulk").unwrap(), Some(b"loaded".to_vec()));
5629 }
5630
5631 #[test]
5632 fn test_close_flushes_range_tombstone_only_memtable() {
5633 let dir = TempDir::new().unwrap();
5637 let opts = Options::default();
5638
5639 {
5640 let db = Db::open(dir.path(), opts.clone()).unwrap();
5641 db.put(b"k", b"v").unwrap();
5642 db.close().unwrap();
5643 }
5644
5645 {
5646 let db = Db::open(dir.path(), opts.clone()).unwrap();
5647 assert_eq!(db.get(b"k").unwrap(), Some(b"v".to_vec()));
5648 db.delete_range_opt(&WriteOptions::disable_wal(), b"a", b"z")
5649 .unwrap();
5650 assert_eq!(db.get(b"k").unwrap(), None);
5651 db.close().unwrap();
5652 }
5653
5654 let db = Db::open(dir.path(), opts).unwrap();
5655 assert_eq!(db.get(b"k").unwrap(), None);
5656 }
5657
5658 #[test]
5659 fn test_write_options_batch_overrides() {
5660 let (db, _dir) = open_tmp();
5661 let mut batch = WriteBatch::new();
5662 batch.put(b"a", b"1");
5663 batch.put(b"b", b"2");
5664 batch.delete(b"ghost");
5665 db.write_opt(&WriteOptions::sync(), batch).unwrap();
5666 assert_eq!(db.get(b"a").unwrap(), Some(b"1".to_vec()));
5667 assert_eq!(db.get(b"b").unwrap(), Some(b"2".to_vec()));
5668 }
5669
5670 #[test]
5671 fn test_write_options_delete_and_delete_range_opts() {
5672 let (db, _dir) = open_tmp();
5673 for c in b'a'..=b'f' {
5674 db.put(&[c], &[c]).unwrap();
5675 }
5676 db.delete_opt(&WriteOptions::sync(), b"c").unwrap();
5677 db.delete_range_opt(&WriteOptions::sync(), b"d", b"f")
5678 .unwrap();
5679 assert_eq!(db.get(b"a").unwrap(), Some(b"a".to_vec()));
5680 assert_eq!(db.get(b"c").unwrap(), None);
5681 assert_eq!(db.get(b"d").unwrap(), None);
5682 assert_eq!(db.get(b"e").unwrap(), None);
5683 assert_eq!(db.get(b"f").unwrap(), Some(b"f".to_vec()));
5684 }
5685
5686 #[test]
5687 fn test_write_options_low_pri_and_no_slowdown_pass_through_when_not_stalling() {
5688 let (db, _dir) = open_tmp();
5691 let opts = WriteOptions {
5692 low_pri: true,
5693 no_slowdown: true,
5694 ..WriteOptions::default()
5695 };
5696 db.put_opt(&opts, b"k", b"v").unwrap();
5697 assert_eq!(db.get(b"k").unwrap(), Some(b"v".to_vec()));
5698 }
5699
5700 struct CounterMerge;
5707
5708 impl MergeOperator for CounterMerge {
5709 fn full_merge(
5710 &self,
5711 _key: &[u8],
5712 base: Option<&[u8]>,
5713 operands: &[&[u8]],
5714 ) -> Option<Vec<u8>> {
5715 let mut total: i64 = match base {
5716 Some(b) if b.len() == 8 => i64::from_be_bytes(b.try_into().unwrap()),
5717 Some(_) => return None,
5718 None => 0,
5719 };
5720 for op in operands {
5721 if op.len() != 8 {
5722 return None;
5723 }
5724 total = total.wrapping_add(i64::from_be_bytes((*op).try_into().unwrap()));
5725 }
5726 Some(total.to_be_bytes().to_vec())
5727 }
5728
5729 fn partial_merge(&self, _key: &[u8], left: &[u8], right: &[u8]) -> Option<Vec<u8>> {
5730 if left.len() != 8 || right.len() != 8 {
5731 return None;
5732 }
5733 let l = i64::from_be_bytes(left.try_into().unwrap());
5734 let r = i64::from_be_bytes(right.try_into().unwrap());
5735 Some(l.wrapping_add(r).to_be_bytes().to_vec())
5736 }
5737
5738 fn name(&self) -> &'static str {
5739 "CounterMerge"
5740 }
5741 }
5742
5743 struct AppendMerge;
5747
5748 impl MergeOperator for AppendMerge {
5749 fn full_merge(
5750 &self,
5751 _key: &[u8],
5752 base: Option<&[u8]>,
5753 operands: &[&[u8]],
5754 ) -> Option<Vec<u8>> {
5755 let mut out: Vec<u8> = base.map(|b| b.to_vec()).unwrap_or_default();
5756 for op in operands {
5757 out.extend_from_slice(op);
5758 }
5759 Some(out)
5760 }
5761
5762 fn name(&self) -> &'static str {
5763 "AppendMerge"
5764 }
5765 }
5766
5767 fn counter_opts() -> Options {
5768 Options {
5769 write_buffer_size: 4 * 1024,
5770 merge_operator: Some(Arc::new(CounterMerge)),
5771 ..Options::default()
5772 }
5773 }
5774
5775 fn encode_i64(n: i64) -> Vec<u8> {
5776 n.to_be_bytes().to_vec()
5777 }
5778
5779 #[test]
5780 fn test_merge_counter_basic_chain_of_one() {
5781 let dir = TempDir::new().unwrap();
5782 let db = Db::open(dir.path(), counter_opts()).unwrap();
5783 db.merge(b"counter", &encode_i64(5)).unwrap();
5784 assert_eq!(db.get(b"counter").unwrap(), Some(encode_i64(5)));
5785 }
5786
5787 #[test]
5788 fn test_merge_counter_chain_of_two() {
5789 let dir = TempDir::new().unwrap();
5790 let db = Db::open(dir.path(), counter_opts()).unwrap();
5791 db.put(b"counter", &encode_i64(10)).unwrap();
5792 db.merge(b"counter", &encode_i64(3)).unwrap();
5793 assert_eq!(db.get(b"counter").unwrap(), Some(encode_i64(13)));
5794 }
5795
5796 #[test]
5797 fn test_merge_counter_chain_of_ten() {
5798 let dir = TempDir::new().unwrap();
5799 let db = Db::open(dir.path(), counter_opts()).unwrap();
5800 db.put(b"counter", &encode_i64(100)).unwrap();
5801 for i in 1..=10 {
5802 db.merge(b"counter", &encode_i64(i)).unwrap();
5803 }
5804 assert_eq!(db.get(b"counter").unwrap(), Some(encode_i64(155)));
5806 }
5807
5808 #[test]
5809 fn test_merge_counter_chain_of_1000() {
5810 let dir = TempDir::new().unwrap();
5811 let db = Db::open(dir.path(), counter_opts()).unwrap();
5812 for _ in 0..1000 {
5813 db.merge(b"counter", &encode_i64(1)).unwrap();
5814 }
5815 assert_eq!(db.get(b"counter").unwrap(), Some(encode_i64(1000)));
5816 }
5817
5818 #[test]
5819 fn test_merge_without_base_defaults_to_none() {
5820 let dir = TempDir::new().unwrap();
5821 let db = Db::open(dir.path(), counter_opts()).unwrap();
5822 db.merge(b"counter", &encode_i64(7)).unwrap();
5824 db.merge(b"counter", &encode_i64(5)).unwrap();
5825 assert_eq!(db.get(b"counter").unwrap(), Some(encode_i64(12)));
5826 }
5827
5828 #[test]
5829 fn test_merge_failure_surfaces_key() {
5830 let dir = TempDir::new().unwrap();
5831 let db = Db::open(dir.path(), counter_opts()).unwrap();
5832 db.merge(b"counter", b"not an i64").unwrap();
5833
5834 let err = db.get(b"counter").unwrap_err();
5835 match err {
5836 Error::MergeFailed(key) => assert_eq!(key, b"counter".to_vec()),
5837 other => panic!("expected merge failure, got {other:?}"),
5838 }
5839 }
5840
5841 #[test]
5842 fn test_merge_snapshot_isolation() {
5843 let dir = TempDir::new().unwrap();
5844 let db = Db::open(dir.path(), counter_opts()).unwrap();
5845 db.put(b"counter", &encode_i64(10)).unwrap();
5846 let snap = db.snapshot();
5847 db.merge(b"counter", &encode_i64(5)).unwrap();
5848 assert_eq!(db.get(b"counter").unwrap(), Some(encode_i64(15)));
5850 assert_eq!(snap.get(b"counter").unwrap(), Some(encode_i64(10)));
5851 }
5852
5853 #[test]
5854 fn test_merge_survives_flush() {
5855 let dir = TempDir::new().unwrap();
5856 let db = Db::open(dir.path(), counter_opts()).unwrap();
5857 db.put(b"counter", &encode_i64(0)).unwrap();
5858 for i in 1..=20 {
5859 db.merge(b"counter", &encode_i64(i)).unwrap();
5860 }
5861 force_flush(&db, "merge");
5864 assert_eq!(db.get(b"counter").unwrap(), Some(encode_i64(210)));
5866 }
5867
5868 #[test]
5869 fn test_merge_survives_compaction_and_collapses() {
5870 let dir = TempDir::new().unwrap();
5871 let db = Db::open(dir.path(), counter_opts()).unwrap();
5872 db.put(b"counter", &encode_i64(0)).unwrap();
5873 for i in 1..=50 {
5874 db.merge(b"counter", &encode_i64(i)).unwrap();
5875 }
5876 for tag in 0..4 {
5877 force_flush(&db, &format!("c{tag}"));
5878 }
5879 db.compact_range(None, None).unwrap();
5880 assert_eq!(db.get(b"counter").unwrap(), Some(encode_i64(1275)));
5882 }
5883
5884 #[test]
5885 fn test_merge_tombstone_interaction() {
5886 let dir = TempDir::new().unwrap();
5887 let db = Db::open(dir.path(), counter_opts()).unwrap();
5888 db.put(b"k", &encode_i64(10)).unwrap();
5890 db.merge(b"k", &encode_i64(5)).unwrap();
5891 db.merge(b"k", &encode_i64(3)).unwrap();
5892 db.delete(b"k").unwrap();
5893 db.merge(b"k", &encode_i64(7)).unwrap();
5894 db.merge(b"k", &encode_i64(1)).unwrap();
5895 assert_eq!(db.get(b"k").unwrap(), Some(encode_i64(8)));
5898 }
5899
5900 #[test]
5901 fn test_merge_range_tombstone_interaction() {
5902 let dir = TempDir::new().unwrap();
5903 let db = Db::open(dir.path(), counter_opts()).unwrap();
5904 db.put(b"k", &encode_i64(10)).unwrap();
5905 db.merge(b"k", &encode_i64(5)).unwrap();
5906 db.delete_range(b"j", b"l").unwrap(); db.merge(b"k", &encode_i64(7)).unwrap();
5908 assert_eq!(db.get(b"k").unwrap(), Some(encode_i64(7)));
5910 }
5911
5912 #[test]
5913 fn test_merge_write_batch() {
5914 let dir = TempDir::new().unwrap();
5915 let db = Db::open(dir.path(), counter_opts()).unwrap();
5916 let mut batch = WriteBatch::new();
5917 batch.put(b"a", &encode_i64(1));
5918 batch.merge(b"a", &encode_i64(2));
5919 batch.merge(b"a", &encode_i64(3));
5920 batch.put(b"b", &encode_i64(100));
5921 db.write(batch).unwrap();
5922 assert_eq!(db.get(b"a").unwrap(), Some(encode_i64(6)));
5923 assert_eq!(db.get(b"b").unwrap(), Some(encode_i64(100)));
5924 }
5925
5926 #[test]
5927 fn test_merge_append_operator() {
5928 let dir = TempDir::new().unwrap();
5929 let opts = Options {
5930 merge_operator: Some(Arc::new(AppendMerge)),
5931 ..Options::default()
5932 };
5933 let db = Db::open(dir.path(), opts).unwrap();
5934 db.put(b"s", b"hello").unwrap();
5935 db.merge(b"s", b" ").unwrap();
5936 db.merge(b"s", b"world").unwrap();
5937 assert_eq!(db.get(b"s").unwrap(), Some(b"hello world".to_vec()));
5938 }
5939
5940 #[test]
5941 fn test_merge_iterator_sees_collapsed_value() {
5942 let dir = TempDir::new().unwrap();
5943 let db = Db::open(dir.path(), counter_opts()).unwrap();
5944 db.put(b"a", &encode_i64(0)).unwrap();
5945 db.merge(b"a", &encode_i64(5)).unwrap();
5946 db.put(b"b", &encode_i64(100)).unwrap();
5947 db.merge(b"b", &encode_i64(10)).unwrap();
5948 db.merge(b"b", &encode_i64(2)).unwrap();
5949
5950 let pairs = db.scan(None, None).unwrap();
5951 assert_eq!(
5952 pairs,
5953 vec![
5954 (b"a".to_vec(), encode_i64(5)),
5955 (b"b".to_vec(), encode_i64(112)),
5956 ]
5957 );
5958 }
5959
5960 #[test]
5961 fn test_merge_iterator_reverse() {
5962 let dir = TempDir::new().unwrap();
5963 let db = Db::open(dir.path(), counter_opts()).unwrap();
5964 db.put(b"a", &encode_i64(0)).unwrap();
5965 db.merge(b"a", &encode_i64(1)).unwrap();
5966 db.put(b"b", &encode_i64(0)).unwrap();
5967 db.merge(b"b", &encode_i64(2)).unwrap();
5968 db.merge(b"b", &encode_i64(3)).unwrap();
5969
5970 let mut iter = db.iter();
5971 iter.seek_to_last();
5972 let mut collected = Vec::new();
5973 while iter.valid() {
5974 collected.push((iter.key().unwrap().to_vec(), iter.value().unwrap().to_vec()));
5975 iter.prev();
5976 }
5977 assert_eq!(
5978 collected,
5979 vec![
5980 (b"b".to_vec(), encode_i64(5)),
5981 (b"a".to_vec(), encode_i64(1)),
5982 ]
5983 );
5984 }
5985
5986 #[test]
5987 fn test_merge_crash_recovery() {
5988 let dir = TempDir::new().unwrap();
5989 {
5990 let db = Db::open(dir.path(), counter_opts()).unwrap();
5991 db.put(b"counter", &encode_i64(0)).unwrap();
5992 db.merge(b"counter", &encode_i64(7)).unwrap();
5993 db.merge(b"counter", &encode_i64(3)).unwrap();
5994 }
5997 let db = Db::open(dir.path(), counter_opts()).unwrap();
5998 assert_eq!(db.get(b"counter").unwrap(), Some(encode_i64(10)));
5999 }
6000
6001 #[test]
6002 fn test_merge_operator_name_plumbs_through() {
6003 let opts = counter_opts();
6006 let dbg = format!("{opts:?}");
6007 assert!(dbg.contains("CounterMerge"));
6008 }
6009
6010 #[test]
6013 fn test_cf_default_exists_on_open() {
6014 let (db, _dir) = open_tmp();
6015 let default = db.default_cf();
6016 assert_eq!(default.name(), DEFAULT_CF_NAME);
6017 assert!(db.column_family(DEFAULT_CF_NAME).is_some());
6018 assert_eq!(db.list_column_families(), vec![DEFAULT_CF_NAME.to_string()]);
6019 }
6020
6021 #[test]
6022 fn test_cf_create_and_lookup() {
6023 let (db, _dir) = open_tmp();
6024 let users = db.create_column_family("users").unwrap();
6025 let orders = db.create_column_family("orders").unwrap();
6026 assert_ne!(users, orders);
6027 assert_eq!(db.column_family("users"), Some(users.clone()));
6028 assert_eq!(db.column_family("orders"), Some(orders.clone()));
6029 assert!(db.column_family("missing").is_none());
6030
6031 let mut names = db.list_column_families();
6032 names.sort();
6033 assert_eq!(names, vec!["default", "orders", "users"]);
6034 }
6035
6036 #[test]
6037 fn test_cf_create_is_idempotent() {
6038 let (db, _dir) = open_tmp();
6039 let a = db.create_column_family("x").unwrap();
6040 let b = db.create_column_family("x").unwrap();
6041 assert_eq!(a, b);
6042 }
6043
6044 #[test]
6045 fn test_cf_put_get_isolated_from_default() {
6046 let (db, _dir) = open_tmp();
6047 let users = db.create_column_family("users").unwrap();
6048 db.put(b"k", b"default_val").unwrap();
6049 db.put_cf(&users, b"k", b"users_val").unwrap();
6050 assert_eq!(db.get(b"k").unwrap(), Some(b"default_val".to_vec()));
6051 assert_eq!(
6052 db.get_cf(&users, b"k").unwrap(),
6053 Some(b"users_val".to_vec())
6054 );
6055 }
6056
6057 #[test]
6058 fn test_cf_writes_to_a_invisible_from_b() {
6059 let (db, _dir) = open_tmp();
6060 let a = db.create_column_family("a").unwrap();
6061 let b = db.create_column_family("b").unwrap();
6062 db.put_cf(&a, b"shared_key", b"alpha").unwrap();
6063 assert_eq!(
6064 db.get_cf(&a, b"shared_key").unwrap(),
6065 Some(b"alpha".to_vec())
6066 );
6067 assert_eq!(db.get_cf(&b, b"shared_key").unwrap(), None);
6068 }
6069
6070 #[test]
6071 fn test_cf_delete_cf() {
6072 let (db, _dir) = open_tmp();
6073 let cf = db.create_column_family("c").unwrap();
6074 db.put_cf(&cf, b"k", b"v").unwrap();
6075 db.delete_cf(&cf, b"k").unwrap();
6076 assert_eq!(db.get_cf(&cf, b"k").unwrap(), None);
6077 }
6078
6079 #[test]
6080 fn test_cf_scan_strips_prefix() {
6081 let (db, _dir) = open_tmp();
6082 let cf = db.create_column_family("s").unwrap();
6083 db.put_cf(&cf, b"a", b"1").unwrap();
6084 db.put_cf(&cf, b"b", b"2").unwrap();
6085 db.put_cf(&cf, b"c", b"3").unwrap();
6086 let pairs = db.scan_cf(&cf, None, None).unwrap();
6087 assert_eq!(
6088 pairs,
6089 vec![
6090 (b"a".to_vec(), b"1".to_vec()),
6091 (b"b".to_vec(), b"2".to_vec()),
6092 (b"c".to_vec(), b"3".to_vec()),
6093 ]
6094 );
6095 let pairs = db.scan_cf(&cf, Some(b"b"), Some(b"c")).unwrap();
6097 assert_eq!(pairs, vec![(b"b".to_vec(), b"2".to_vec())]);
6098 }
6099
6100 #[test]
6101 fn test_cf_iter_bounded_to_cf() {
6102 let (db, _dir) = open_tmp();
6103 let a = db.create_column_family("a").unwrap();
6104 let b = db.create_column_family("b").unwrap();
6105 db.put_cf(&a, b"a1", b"A1").unwrap();
6106 db.put_cf(&a, b"a2", b"A2").unwrap();
6107 db.put_cf(&b, b"b1", b"B1").unwrap();
6108 db.put(b"d1", b"D1").unwrap();
6109
6110 let mut iter = db.iter_cf(&a);
6111 iter.seek_to_first();
6112 let mut keys = Vec::new();
6113 while iter.valid() {
6114 keys.push(iter.key().unwrap().to_vec());
6115 iter.next();
6116 }
6117 assert_eq!(keys, vec![b"a1".to_vec(), b"a2".to_vec()]);
6118 }
6119
6120 #[test]
6121 fn test_cf_iter_reverse() {
6122 let (db, _dir) = open_tmp();
6123 let cf = db.create_column_family("rev").unwrap();
6124 db.put_cf(&cf, b"a", b"1").unwrap();
6125 db.put_cf(&cf, b"b", b"2").unwrap();
6126 db.put_cf(&cf, b"c", b"3").unwrap();
6127
6128 let mut iter = db.iter_cf(&cf);
6129 iter.seek_to_last();
6130 let mut keys = Vec::new();
6131 while iter.valid() {
6132 keys.push(iter.key().unwrap().to_vec());
6133 iter.prev();
6134 }
6135 assert_eq!(keys, vec![b"c".to_vec(), b"b".to_vec(), b"a".to_vec()]);
6136 }
6137
6138 #[test]
6139 fn test_cf_drop_removes_all_keys_in_cf() {
6140 let (db, _dir) = open_tmp();
6141 let cf = db.create_column_family("tmp").unwrap();
6142 db.put_cf(&cf, b"a", b"1").unwrap();
6143 db.put_cf(&cf, b"b", b"2").unwrap();
6144 db.put_cf(&cf, b"c", b"3").unwrap();
6145 db.put(b"default_key", b"default_val").unwrap();
6146
6147 db.drop_column_family(cf.clone()).unwrap();
6148
6149 assert!(db.column_family("tmp").is_none());
6151 assert_eq!(
6153 db.get(b"default_key").unwrap(),
6154 Some(b"default_val".to_vec())
6155 );
6156 let cf2 = db.create_column_family("tmp").unwrap();
6158 assert_eq!(db.get_cf(&cf2, b"a").unwrap(), None);
6159 }
6160
6161 #[test]
6162 fn test_cf_stale_handle_is_rejected_after_drop() {
6163 let (db, _dir) = open_tmp();
6164 let cf = db.create_column_family("tmp").unwrap();
6165 db.put_cf(&cf, b"k", b"v").unwrap();
6166 db.drop_column_family(cf.clone()).unwrap();
6167
6168 let err = db.get_cf(&cf, b"k").unwrap_err();
6169 match err {
6170 Error::InvalidColumnFamily(message) => assert!(message.contains("tmp")),
6171 other => panic!("expected invalid column family error, got {other:?}"),
6172 }
6173 assert!(db.multi_get_cf(&cf, &[b"k"]).is_err());
6174 assert!(db.put_cf(&cf, b"k", b"ghost").is_err());
6175 assert!(db.delete_cf(&cf, b"k").is_err());
6176 assert!(db.delete_range_cf(&cf, b"a", b"z").is_err());
6177 assert!(db.merge_cf(&cf, b"k", b"operand").is_err());
6178 assert!(db.scan_cf(&cf, None, None).is_err());
6179 assert_eq!(
6180 db.get_approximate_sizes_cf(&cf, &[Range::new(b"a", b"z")]),
6181 vec![0]
6182 );
6183 assert_eq!(
6184 db.get_approximate_memtable_stats_cf(&cf, Range::new(b"a", b"z")),
6185 MemTableStats::default()
6186 );
6187
6188 let mut iter = db.iter_cf(&cf);
6189 iter.seek_to_first();
6190 assert!(!iter.valid());
6191
6192 let mut tail = db.iter_tailing_cf(&cf);
6193 tail.seek_to_first();
6194 assert!(!tail.valid());
6195 }
6196
6197 #[test]
6198 fn test_cf_stale_handle_cannot_write_into_recreated_cf_name() {
6199 let (db, _dir) = open_tmp();
6200 let stale = db.create_column_family("tmp").unwrap();
6201 db.put_cf(&stale, b"k", b"old").unwrap();
6202 db.drop_column_family(stale.clone()).unwrap();
6203
6204 let live = db.create_column_family("tmp").unwrap();
6205 assert_ne!(stale, live);
6206 assert!(db.put_cf(&stale, b"k", b"ghost").is_err());
6207 db.put_cf(&live, b"k", b"new").unwrap();
6208
6209 assert!(db.get_cf(&stale, b"k").is_err());
6210 assert_eq!(db.get_cf(&live, b"k").unwrap(), Some(b"new".to_vec()));
6211 }
6212
6213 #[test]
6214 fn test_cf_write_batch_rejects_stale_handle_ops() {
6215 let (db, _dir) = open_tmp();
6216 let stale = db.create_column_family("tmp").unwrap();
6217 db.drop_column_family(stale.clone()).unwrap();
6218
6219 let mut batch = WriteBatch::new();
6220 batch.put_cf(&stale, b"k", b"ghost");
6221 let err = db.write(batch).unwrap_err();
6222 match err {
6223 Error::InvalidColumnFamily(message) => assert!(message.contains("column family id")),
6224 other => panic!("expected invalid column family error, got {other:?}"),
6225 }
6226
6227 let live = db.create_column_family("tmp").unwrap();
6228 assert_eq!(db.get_cf(&live, b"k").unwrap(), None);
6229 }
6230
6231 #[test]
6232 fn test_cf_ingest_rejects_stale_handle_entries() {
6233 let (db, dir) = open_tmp();
6234 let stale = db.create_column_family("tmp").unwrap();
6235 db.drop_column_family(stale.clone()).unwrap();
6236
6237 let path = dir.path().join("stale-cf.sst");
6238 let mut writer = SstFileWriter::create(&path, &Options::default()).unwrap();
6239 writer.put_cf(&stale, b"k", b"ghost").unwrap();
6240 writer.finish().unwrap();
6241
6242 assert!(
6243 db.ingest_external_files(&[path], IngestOptions::default())
6244 .is_err()
6245 );
6246 let live = db.create_column_family("tmp").unwrap();
6247 assert_eq!(db.get_cf(&live, b"k").unwrap(), None);
6248 }
6249
6250 #[test]
6251 fn test_cf_snapshot_rejects_stale_handle_after_drop() {
6252 let (db, _dir) = open_tmp();
6253 let cf = db.create_column_family("tmp").unwrap();
6254 db.put_cf(&cf, b"k", b"v").unwrap();
6255 let snap = db.snapshot();
6256 db.drop_column_family(cf.clone()).unwrap();
6257
6258 assert!(snap.get_cf(&cf, b"k").is_err());
6259 assert!(snap.multi_get_cf(&cf, &[b"k"]).is_err());
6260 assert!(snap.scan_cf(&cf, None, None).is_err());
6261 let mut iter = snap.iter_cf(&cf);
6262 iter.seek_to_first();
6263 assert!(!iter.valid());
6264 }
6265
6266 #[test]
6267 fn test_cf_cannot_drop_default() {
6268 let (db, _dir) = open_tmp();
6269 let default = db.default_cf();
6270 assert!(db.drop_column_family(default).is_err());
6271 }
6272
6273 #[test]
6274 fn test_cf_survives_reopen() {
6275 let dir = TempDir::new().unwrap();
6276 {
6277 let db = Db::open(dir.path(), Options::default()).unwrap();
6278 let cf = db.create_column_family("persistent").unwrap();
6279 db.put_cf(&cf, b"k", b"v").unwrap();
6280 db.close().unwrap();
6281 }
6282 let db = Db::open(dir.path(), Options::default()).unwrap();
6283 let cf = db
6284 .column_family("persistent")
6285 .expect("CF must survive reopen");
6286 assert_eq!(db.get_cf(&cf, b"k").unwrap(), Some(b"v".to_vec()));
6287 }
6288
6289 #[test]
6290 fn test_cf_dropped_cf_does_not_survive_reopen() {
6291 let dir = TempDir::new().unwrap();
6292 {
6293 let db = Db::open(dir.path(), Options::default()).unwrap();
6294 let cf = db.create_column_family("doomed").unwrap();
6295 db.put_cf(&cf, b"k", b"v").unwrap();
6296 db.drop_column_family(cf).unwrap();
6297 db.close().unwrap();
6298 }
6299 let db = Db::open(dir.path(), Options::default()).unwrap();
6300 assert!(db.column_family("doomed").is_none());
6301 }
6302
6303 #[test]
6304 fn test_cf_write_batch_cross_cf_atomic() {
6305 let (db, _dir) = open_tmp();
6306 let a = db.create_column_family("a").unwrap();
6307 let b = db.create_column_family("b").unwrap();
6308 let mut batch = WriteBatch::new();
6309 batch.put_cf(&a, b"k1", b"v_a1");
6310 batch.put_cf(&b, b"k1", b"v_b1");
6311 batch.put(b"k1", b"v_default");
6312 batch.delete_cf(&a, b"ghost");
6313 db.write(batch).unwrap();
6314
6315 assert_eq!(db.get_cf(&a, b"k1").unwrap(), Some(b"v_a1".to_vec()));
6316 assert_eq!(db.get_cf(&b, b"k1").unwrap(), Some(b"v_b1".to_vec()));
6317 assert_eq!(db.get(b"k1").unwrap(), Some(b"v_default".to_vec()));
6318 }
6319
6320 #[test]
6321 fn test_cf_write_batch_survives_crash_recovery() {
6322 let dir = TempDir::new().unwrap();
6323 {
6324 let db = Db::open(dir.path(), Options::default()).unwrap();
6325 let cf = db.create_column_family("txn").unwrap();
6326 let mut batch = WriteBatch::new();
6327 batch.put_cf(&cf, b"a", b"1");
6328 batch.put_cf(&cf, b"b", b"2");
6329 batch.put(b"default_k", b"default_v");
6330 db.write(batch).unwrap();
6331 }
6333 let db = Db::open(dir.path(), Options::default()).unwrap();
6334 let cf = db.column_family("txn").expect("CF must survive");
6335 assert_eq!(db.get_cf(&cf, b"a").unwrap(), Some(b"1".to_vec()));
6336 assert_eq!(db.get_cf(&cf, b"b").unwrap(), Some(b"2".to_vec()));
6337 assert_eq!(db.get(b"default_k").unwrap(), Some(b"default_v".to_vec()));
6338 }
6339
6340 #[test]
6341 fn test_cf_snapshot_isolation_per_cf() {
6342 let (db, _dir) = open_tmp();
6343 let a = db.create_column_family("a").unwrap();
6344 db.put_cf(&a, b"k", b"v0").unwrap();
6345 let snap = db.snapshot();
6346 db.put_cf(&a, b"k", b"v1").unwrap();
6347 assert_eq!(snap.get_cf(&a, b"k").unwrap(), Some(b"v0".to_vec()));
6348 assert_eq!(db.get_cf(&a, b"k").unwrap(), Some(b"v1".to_vec()));
6349 }
6350
6351 #[test]
6352 fn test_cf_scan_across_cfs_is_isolated() {
6353 let (db, _dir) = open_tmp();
6354 let a = db.create_column_family("a").unwrap();
6355 let b = db.create_column_family("b").unwrap();
6356 db.put_cf(&a, b"apple", b"A").unwrap();
6357 db.put_cf(&b, b"apple", b"B").unwrap();
6358 db.put(b"apple", b"D").unwrap();
6359
6360 assert_eq!(
6361 db.scan_cf(&a, None, None).unwrap(),
6362 vec![(b"apple".to_vec(), b"A".to_vec())]
6363 );
6364 assert_eq!(
6365 db.scan_cf(&b, None, None).unwrap(),
6366 vec![(b"apple".to_vec(), b"B".to_vec())]
6367 );
6368 assert_eq!(
6369 db.scan(None, None).unwrap(),
6370 vec![(b"apple".to_vec(), b"D".to_vec())]
6371 );
6372 }
6373
6374 #[test]
6375 fn test_cf_scan_page_is_scoped_and_resumable() {
6376 let (db, _dir) = open_tmp();
6377 let cf = db.create_column_family("paged").unwrap();
6378
6379 db.put(b"a", b"default").unwrap();
6380 db.put_cf(&cf, b"a", b"1").unwrap();
6381 db.put_cf(&cf, b"b", b"2").unwrap();
6382 db.put_cf(&cf, b"c", b"3").unwrap();
6383
6384 let first = db.scan_page_cf(&cf, None, None, 2).unwrap();
6385 assert_eq!(
6386 first,
6387 ScanPage {
6388 entries: vec![
6389 (b"a".to_vec(), b"1".to_vec()),
6390 (b"b".to_vec(), b"2".to_vec()),
6391 ],
6392 next_start: Some(b"c".to_vec()),
6393 }
6394 );
6395
6396 let second = db
6397 .scan_page_cf(&cf, first.next_start.as_deref(), None, 2)
6398 .unwrap();
6399 assert_eq!(
6400 second,
6401 ScanPage {
6402 entries: vec![(b"c".to_vec(), b"3".to_vec())],
6403 next_start: None,
6404 }
6405 );
6406 }
6407
6408 #[test]
6409 fn test_cf_multi_get_cf() {
6410 let (db, _dir) = open_tmp();
6411 let cf = db.create_column_family("mg").unwrap();
6412 db.put_cf(&cf, b"a", b"1").unwrap();
6413 db.put_cf(&cf, b"b", b"2").unwrap();
6414 let keys: Vec<&[u8]> = vec![b"a", b"missing", b"b"];
6415 let got = db.multi_get_cf(&cf, &keys).unwrap();
6416 assert_eq!(got, vec![Some(b"1".to_vec()), None, Some(b"2".to_vec())]);
6417 }
6418
6419 #[test]
6420 fn test_cf_delete_range_cf() {
6421 let (db, _dir) = open_tmp();
6422 let cf = db.create_column_family("r").unwrap();
6423 for c in b'a'..=b'f' {
6424 db.put_cf(&cf, &[c], &[c]).unwrap();
6425 }
6426 db.delete_range_cf(&cf, b"b", b"e").unwrap();
6427 assert_eq!(db.get_cf(&cf, b"a").unwrap(), Some(b"a".to_vec()));
6428 assert_eq!(db.get_cf(&cf, b"b").unwrap(), None);
6429 assert_eq!(db.get_cf(&cf, b"c").unwrap(), None);
6430 assert_eq!(db.get_cf(&cf, b"d").unwrap(), None);
6431 assert_eq!(db.get_cf(&cf, b"e").unwrap(), Some(b"e".to_vec()));
6432 assert_eq!(db.get_cf(&cf, b"f").unwrap(), Some(b"f".to_vec()));
6433 }
6434
6435 #[test]
6436 fn test_cf_create_empty_name_errors() {
6437 let (db, _dir) = open_tmp();
6438 assert!(db.create_column_family("").is_err());
6439 }
6440
6441 #[test]
6442 fn test_cf_many_cfs_all_isolated() {
6443 let (db, _dir) = open_tmp();
6444 let mut handles = Vec::new();
6445 for i in 0..10 {
6446 handles.push(db.create_column_family(&format!("cf{i}")).unwrap());
6447 }
6448 for (i, h) in handles.iter().enumerate() {
6449 db.put_cf(h, b"k", format!("v{i}").as_bytes()).unwrap();
6450 }
6451 for (i, h) in handles.iter().enumerate() {
6452 assert_eq!(
6453 db.get_cf(h, b"k").unwrap(),
6454 Some(format!("v{i}").into_bytes())
6455 );
6456 }
6457 }
6458
6459 #[test]
6462 fn test_approximate_sizes_empty_db() {
6463 let (db, _dir) = open_tmp();
6464 let sizes = db.get_approximate_sizes(&[Range::new(b"a", b"z")]);
6465 assert_eq!(sizes, vec![0]);
6466 }
6467
6468 #[test]
6469 fn test_approximate_sizes_empty_range_returns_zero() {
6470 let (db, _dir) = open_tmp();
6471 db.put(b"k", b"v").unwrap();
6472 assert_eq!(db.get_approximate_sizes(&[Range::new(b"z", b"a")]), vec![0]);
6474 assert_eq!(db.get_approximate_sizes(&[Range::new(b"k", b"k")]), vec![0]);
6475 }
6476
6477 #[test]
6478 fn test_approximate_memtable_stats_exact_for_memtable() {
6479 let (db, _dir) = open_tmp();
6480 for c in b'a'..=b'e' {
6481 db.put(&[c], b"v").unwrap();
6482 }
6483 let stats = db.get_approximate_memtable_stats(Range::new(b"b", b"e"));
6484 assert_eq!(stats.count, 3, "count must be exact");
6485 assert!(stats.size > 0);
6489 assert!(stats.size < 500);
6490 }
6491
6492 #[test]
6493 fn test_approximate_memtable_stats_empty_range() {
6494 let (db, _dir) = open_tmp();
6495 db.put(b"k", b"v").unwrap();
6496 let stats = db.get_approximate_memtable_stats(Range::new(b"m", b"n"));
6497 assert_eq!(stats, MemTableStats::default());
6498 }
6499
6500 #[test]
6501 fn test_approximate_memtable_stats_counts_every_version() {
6502 let (db, _dir) = open_tmp();
6503 db.put(b"k", b"v1").unwrap();
6504 db.put(b"k", b"v2").unwrap();
6505 db.put(b"k", b"v3").unwrap();
6506 let stats = db.get_approximate_memtable_stats(Range::new(b"k", b"l"));
6507 assert_eq!(stats.count, 3);
6509 }
6510
6511 #[test]
6512 fn test_approximate_sizes_after_flush_within_factor_of_2() {
6513 let dir = TempDir::new().unwrap();
6521 let opts = Options {
6522 write_buffer_size: 4 * 1024,
6523 compression: CompressionType::None,
6524 ..Options::default()
6525 };
6526 let db = Db::open(dir.path(), opts).unwrap();
6527 let payload: Vec<u8> = (0..256).map(|i| (i % 251) as u8).collect();
6528 for i in 0..100 {
6529 db.put(format!("k_{i:04}").as_bytes(), &payload).unwrap();
6530 }
6531 force_flush(&db, "sizes");
6532 let sizes = db.get_approximate_sizes(&[Range::new(b"k_0000", b"k_9999")]);
6533 assert!(sizes[0] > 0, "whole-range size must be > 0 after flush");
6534 let approx = sizes[0];
6540 assert!(approx > 10_000, "approx={approx} too small; expected > 10k");
6541 assert!(
6542 approx < 1_000_000,
6543 "approx={approx} absurdly large; expected < 1M"
6544 );
6545 }
6546
6547 #[test]
6548 fn test_approximate_sizes_multi_range_preserves_order() {
6549 let dir = TempDir::new().unwrap();
6550 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
6551 let payload = vec![0u8; 256];
6552 for i in 0..200 {
6553 db.put(format!("k_{i:04}").as_bytes(), &payload).unwrap();
6554 }
6555 force_flush(&db, "multi");
6556 let ranges = vec![
6557 Range::new(b"k_0000", b"k_0050"),
6558 Range::new(b"k_0050", b"k_0100"),
6559 Range::new(b"k_0100", b"k_0200"),
6560 ];
6561 let sizes = db.get_approximate_sizes(&ranges);
6562 assert_eq!(sizes.len(), 3);
6563 for (i, &s) in sizes.iter().enumerate() {
6565 assert!(s > 0, "range {i} size was 0");
6566 }
6567 }
6568
6569 #[test]
6570 fn test_approximate_sizes_cf_scoped() {
6571 let (db, _dir) = open_tmp();
6572 let cf = db.create_column_family("scoped").unwrap();
6573 for i in 0..20 {
6576 db.put(format!("k{i}").as_bytes(), b"v").unwrap();
6577 }
6578 let default_sizes = db.get_approximate_sizes(&[Range::new(b"a", b"z")]);
6579 let cf_sizes = db.get_approximate_sizes_cf(&cf, &[Range::new(b"a", b"z")]);
6580 assert_eq!(default_sizes[0], 0);
6586 assert_eq!(cf_sizes[0], 0);
6587
6588 let default_mt = db.get_approximate_memtable_stats(Range::new(b"a", b"z"));
6591 let cf_mt = db.get_approximate_memtable_stats_cf(&cf, Range::new(b"a", b"z"));
6592 assert_eq!(default_mt.count, 20);
6593 assert_eq!(cf_mt.count, 0);
6594 }
6595
6596 #[test]
6599 fn test_atomic_flush_multi_cf_batch_survives_crash() {
6600 let dir = TempDir::new().unwrap();
6604 {
6605 let db = Db::open(dir.path(), Options::default()).unwrap();
6606 let cf_a = db.create_column_family("a").unwrap();
6607 let cf_b = db.create_column_family("b").unwrap();
6608 let mut batch = WriteBatch::new();
6609 batch.put_cf(&cf_a, b"k1", b"a1");
6610 batch.put_cf(&cf_a, b"k2", b"a2");
6611 batch.put_cf(&cf_b, b"k1", b"b1");
6612 batch.put_cf(&cf_b, b"k2", b"b2");
6613 batch.put(b"default_k", b"default_v");
6614 db.write(batch).unwrap();
6615 }
6618 let db = Db::open(dir.path(), Options::default()).unwrap();
6619 let cf_a = db.column_family("a").expect("cf a survives reopen");
6620 let cf_b = db.column_family("b").expect("cf b survives reopen");
6621 assert_eq!(db.get_cf(&cf_a, b"k1").unwrap(), Some(b"a1".to_vec()));
6622 assert_eq!(db.get_cf(&cf_a, b"k2").unwrap(), Some(b"a2".to_vec()));
6623 assert_eq!(db.get_cf(&cf_b, b"k1").unwrap(), Some(b"b1".to_vec()));
6624 assert_eq!(db.get_cf(&cf_b, b"k2").unwrap(), Some(b"b2".to_vec()));
6625 assert_eq!(db.get(b"default_k").unwrap(), Some(b"default_v".to_vec()));
6626 }
6627
6628 #[test]
6629 fn test_atomic_flush_cross_cf_survives_rotate_and_flush() {
6630 let dir = TempDir::new().unwrap();
6635 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
6636 let cf_a = db.create_column_family("a").unwrap();
6637 let cf_b = db.create_column_family("b").unwrap();
6638
6639 for i in 0..16 {
6642 db.put_cf(&cf_a, format!("fill_a_{i:02}").as_bytes(), &[0u8; 256])
6643 .unwrap();
6644 db.put_cf(&cf_b, format!("fill_b_{i:02}").as_bytes(), &[0u8; 256])
6645 .unwrap();
6646 }
6647
6648 let mut batch = WriteBatch::new();
6649 batch.put_cf(&cf_a, b"pivot", b"A_PIVOT");
6650 batch.put_cf(&cf_b, b"pivot", b"B_PIVOT");
6651 db.write(batch).unwrap();
6652 force_flush(&db, "atomic");
6653
6654 assert_eq!(
6655 db.get_cf(&cf_a, b"pivot").unwrap(),
6656 Some(b"A_PIVOT".to_vec())
6657 );
6658 assert_eq!(
6659 db.get_cf(&cf_b, b"pivot").unwrap(),
6660 Some(b"B_PIVOT".to_vec())
6661 );
6662 }
6663
6664 #[test]
6665 fn test_atomic_flush_empty_cf_mixed_with_populated() {
6666 let dir = TempDir::new().unwrap();
6669 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
6670 let cf_populated = db.create_column_family("populated").unwrap();
6671 let cf_empty = db.create_column_family("empty").unwrap();
6672
6673 for i in 0..50 {
6674 db.put_cf(&cf_populated, format!("k{i:02}").as_bytes(), b"v")
6675 .unwrap();
6676 }
6677 force_flush(&db, "empty_mix");
6678
6679 for i in 0..50 {
6680 assert_eq!(
6681 db.get_cf(&cf_populated, format!("k{i:02}").as_bytes())
6682 .unwrap(),
6683 Some(b"v".to_vec())
6684 );
6685 }
6686 assert_eq!(db.get_cf(&cf_empty, b"anything").unwrap(), None);
6687
6688 db.put_cf(&cf_empty, b"new", b"fresh").unwrap();
6690 assert_eq!(
6691 db.get_cf(&cf_empty, b"new").unwrap(),
6692 Some(b"fresh".to_vec())
6693 );
6694 }
6695
6696 #[test]
6697 fn test_atomic_flush_option_accepted() {
6698 let dir = TempDir::new().unwrap();
6701 let opts = Options {
6702 atomic_flush: true,
6703 ..Options::default()
6704 };
6705 let db = Db::open(dir.path(), opts).unwrap();
6706 let cf = db.create_column_family("cf1").unwrap();
6707 db.put_cf(&cf, b"k", b"v").unwrap();
6708 assert_eq!(db.get_cf(&cf, b"k").unwrap(), Some(b"v".to_vec()));
6709 }
6710
6711 #[test]
6712 fn test_atomic_flush_close_with_pending_multi_cf_writes() {
6713 let dir = TempDir::new().unwrap();
6717 {
6718 let db = Db::open(dir.path(), Options::default()).unwrap();
6719 let cf_a = db.create_column_family("a").unwrap();
6720 let cf_b = db.create_column_family("b").unwrap();
6721 let mut batch = WriteBatch::new();
6722 batch.put_cf(&cf_a, b"k", b"a");
6723 batch.put_cf(&cf_b, b"k", b"b");
6724 db.write(batch).unwrap();
6725 db.close().unwrap();
6726 }
6727 let db = Db::open(dir.path(), Options::default()).unwrap();
6728 let cf_a = db.column_family("a").unwrap();
6729 let cf_b = db.column_family("b").unwrap();
6730 assert_eq!(db.get_cf(&cf_a, b"k").unwrap(), Some(b"a".to_vec()));
6731 assert_eq!(db.get_cf(&cf_b, b"k").unwrap(), Some(b"b".to_vec()));
6732 }
6733
6734 #[derive(Default)]
6739 struct CountingListener {
6740 flush_completed: AtomicUsize,
6741 compaction_begin: AtomicUsize,
6742 compaction_completed: AtomicUsize,
6743 table_file_created: AtomicUsize,
6744 table_file_deleted: AtomicUsize,
6745 external_file_ingested: AtomicUsize,
6746 background_error: AtomicUsize,
6747 last_flush_file_id: AtomicUsize,
6748 last_compaction_output_count: AtomicUsize,
6749 }
6750
6751 impl EventListener for CountingListener {
6752 fn on_flush_completed(&self, info: &FlushJobInfo) {
6753 self.flush_completed.fetch_add(1, AtomicOrdering::Relaxed);
6754 self.last_flush_file_id
6755 .store(info.file_id as usize, AtomicOrdering::Relaxed);
6756 }
6757 fn on_compaction_begin(&self, _info: &CompactionJobInfo) {
6758 self.compaction_begin.fetch_add(1, AtomicOrdering::Relaxed);
6759 }
6760 fn on_compaction_completed(&self, info: &CompactionJobInfo) {
6761 self.compaction_completed
6762 .fetch_add(1, AtomicOrdering::Relaxed);
6763 self.last_compaction_output_count
6764 .store(info.output_files.len(), AtomicOrdering::Relaxed);
6765 }
6766 fn on_table_file_created(&self, _info: &TableFileCreationInfo) {
6767 self.table_file_created
6768 .fetch_add(1, AtomicOrdering::Relaxed);
6769 }
6770 fn on_table_file_deleted(&self, _info: &TableFileDeletionInfo) {
6771 self.table_file_deleted
6772 .fetch_add(1, AtomicOrdering::Relaxed);
6773 }
6774 fn on_external_file_ingested(&self, _info: &ExternalFileIngestionInfo) {
6775 self.external_file_ingested
6776 .fetch_add(1, AtomicOrdering::Relaxed);
6777 }
6778 fn on_background_error(&self, _reason: BackgroundErrorReason, _err: &Error) {
6779 self.background_error.fetch_add(1, AtomicOrdering::Relaxed);
6780 }
6781 }
6782
6783 #[test]
6784 fn test_listener_fires_on_flush() {
6785 let listener = Arc::new(CountingListener::default());
6786 let dir = TempDir::new().unwrap();
6787 let opts = Options {
6788 write_buffer_size: 4 * 1024,
6789 listeners: vec![listener.clone() as Arc<dyn EventListener>],
6790 ..Options::default()
6791 };
6792 let db = Db::open(dir.path(), opts).unwrap();
6793 force_flush(&db, "listener");
6794
6795 assert!(
6796 listener.flush_completed.load(AtomicOrdering::Relaxed) >= 1,
6797 "flush callback should fire at least once"
6798 );
6799 assert!(
6800 listener.table_file_created.load(AtomicOrdering::Relaxed) >= 1,
6801 "table_file_created should fire for every flushed file"
6802 );
6803 assert_ne!(
6804 listener.last_flush_file_id.load(AtomicOrdering::Relaxed),
6805 0,
6806 "file id recorded"
6807 );
6808 }
6809
6810 #[test]
6811 fn test_listener_fires_on_compaction() {
6812 let listener = Arc::new(CountingListener::default());
6813 let dir = TempDir::new().unwrap();
6814 let opts = Options {
6815 write_buffer_size: 4 * 1024,
6816 listeners: vec![listener.clone() as Arc<dyn EventListener>],
6817 ..Options::default()
6818 };
6819 let db = Db::open(dir.path(), opts).unwrap();
6820 for i in 0..400 {
6824 db.put(format!("k_{i:04}").as_bytes(), b"v").unwrap();
6825 }
6826 force_flush(&db, "listener");
6827 db.compact_range(None, None).unwrap();
6828
6829 let begin = listener.compaction_begin.load(AtomicOrdering::Relaxed);
6830 let complete = listener.compaction_completed.load(AtomicOrdering::Relaxed);
6831 assert!(
6832 begin >= 1,
6833 "compaction_begin must fire at least once, got {begin}"
6834 );
6835 assert_eq!(
6836 begin, complete,
6837 "begin and completed must fire in matched pairs"
6838 );
6839 assert!(
6840 listener.table_file_created.load(AtomicOrdering::Relaxed) >= 2,
6841 "flush + compaction both produce files"
6842 );
6843 assert!(
6844 listener.table_file_deleted.load(AtomicOrdering::Relaxed) >= 1,
6845 "old L0 files must be unlinked after compaction"
6846 );
6847 }
6848
6849 #[test]
6850 fn test_listener_fires_on_ingest() {
6851 let listener = Arc::new(CountingListener::default());
6852 let dir = TempDir::new().unwrap();
6853 let opts = Options {
6854 listeners: vec![listener.clone() as Arc<dyn EventListener>],
6855 ..Options::default()
6856 };
6857 let db = Db::open(dir.path(), opts.clone()).unwrap();
6858
6859 let sst_path = dir.path().join("ingest.sst");
6860 {
6861 let mut w = SstFileWriter::create(&sst_path, &opts).unwrap();
6862 for i in 0..10 {
6863 w.put(format!("ik_{i:02}").as_bytes(), b"iv").unwrap();
6864 }
6865 w.finish().unwrap();
6866 }
6867 db.ingest_external_files(&[sst_path], IngestOptions::default())
6868 .unwrap();
6869
6870 assert_eq!(
6871 listener
6872 .external_file_ingested
6873 .load(AtomicOrdering::Relaxed),
6874 1,
6875 "external_file_ingested fires once per ingested file"
6876 );
6877 assert!(
6878 listener.table_file_created.load(AtomicOrdering::Relaxed) >= 1,
6879 "ingest re-emits the file and fires table_file_created"
6880 );
6881 }
6882
6883 #[test]
6884 fn test_listener_multiple_listeners_all_fire() {
6885 let a = Arc::new(CountingListener::default());
6886 let b = Arc::new(CountingListener::default());
6887 let dir = TempDir::new().unwrap();
6888 let opts = Options {
6889 write_buffer_size: 4 * 1024,
6890 listeners: vec![
6891 a.clone() as Arc<dyn EventListener>,
6892 b.clone() as Arc<dyn EventListener>,
6893 ],
6894 ..Options::default()
6895 };
6896 let db = Db::open(dir.path(), opts).unwrap();
6897 force_flush(&db, "multi");
6898
6899 assert!(a.flush_completed.load(AtomicOrdering::Relaxed) >= 1);
6900 assert!(b.flush_completed.load(AtomicOrdering::Relaxed) >= 1);
6901 }
6902
6903 #[test]
6904 fn test_listener_none_configured_is_noop() {
6905 let (db, _dir) = open_tmp();
6908 db.put(b"k", b"v").unwrap();
6909 force_flush(&db, "none");
6910 db.compact_range(None, None).unwrap();
6911 }
6912
6913 #[test]
6914 fn test_listener_compaction_job_info_contains_input_files() {
6915 struct CaptureListener {
6918 captured: Mutex<Option<CompactionJobInfo>>,
6919 }
6920 impl EventListener for CaptureListener {
6921 fn on_compaction_completed(&self, info: &CompactionJobInfo) {
6922 *self.captured.lock() = Some(info.clone());
6923 }
6924 }
6925 use crate::sync::Mutex;
6926
6927 let listener = Arc::new(CaptureListener {
6928 captured: Mutex::new(None),
6929 });
6930 let dir = TempDir::new().unwrap();
6931 let opts = Options {
6932 write_buffer_size: 4 * 1024,
6933 listeners: vec![listener.clone() as Arc<dyn EventListener>],
6934 ..Options::default()
6935 };
6936 let db = Db::open(dir.path(), opts).unwrap();
6937 for i in 0..400 {
6938 db.put(format!("k_{i:04}").as_bytes(), b"v").unwrap();
6939 }
6940 force_flush(&db, "capture");
6941 db.compact_range(None, None).unwrap();
6942
6943 let captured = listener.captured.lock().clone();
6944 let info = captured.expect("compaction_completed must have fired");
6945 assert!(
6946 !info.input_files_input_level.is_empty(),
6947 "at least one L0 input file was picked"
6948 );
6949 assert_eq!(info.output_level, info.input_level + 1);
6950 assert!(!info.output_files.is_empty(), "compaction produced outputs");
6951 }
6952
6953 fn stats_opts(stats: Arc<Statistics>) -> Options {
6956 Options {
6957 statistics: Some(stats),
6958 ..Options::default()
6959 }
6960 }
6961
6962 fn tiny_flush_stats_opts(stats: Arc<Statistics>) -> Options {
6963 Options {
6964 write_buffer_size: 4 * 1024,
6965 statistics: Some(stats),
6966 ..Options::default()
6967 }
6968 }
6969
6970 #[test]
6971 fn test_stats_keys_written_and_bytes_written() {
6972 let stats = Arc::new(Statistics::new());
6973 let dir = TempDir::new().unwrap();
6974 let db = Db::open(dir.path(), stats_opts(stats.clone())).unwrap();
6975 db.put(b"k1", b"value1").unwrap();
6976 db.put(b"k2", b"value2").unwrap();
6977 assert_eq!(stats.get_ticker(Ticker::KeysWritten), 2);
6978 assert_eq!(stats.get_ticker(Ticker::BytesWritten), 16);
6980 }
6981
6982 #[test]
6983 fn test_stats_keys_read_and_bytes_read() {
6984 let stats = Arc::new(Statistics::new());
6985 let dir = TempDir::new().unwrap();
6986 let db = Db::open(dir.path(), stats_opts(stats.clone())).unwrap();
6987 db.put(b"k", b"value").unwrap();
6988 db.get(b"k").unwrap();
6989 db.get(b"missing").unwrap();
6990 assert_eq!(stats.get_ticker(Ticker::KeysRead), 2);
6991 assert_eq!(stats.get_ticker(Ticker::BytesRead), 5);
6993 let get_hist = stats.get_histogram_snapshot(Histogram::DbGet);
6994 assert_eq!(get_hist.count, 2);
6995 }
6996
6997 #[test]
6998 fn test_stats_delete_counter() {
6999 let stats = Arc::new(Statistics::new());
7000 let dir = TempDir::new().unwrap();
7001 let db = Db::open(dir.path(), stats_opts(stats.clone())).unwrap();
7002 db.put(b"k", b"v").unwrap();
7003 db.delete(b"k").unwrap();
7004 assert_eq!(stats.get_ticker(Ticker::KeysDeleted), 1);
7005 }
7006
7007 #[test]
7008 fn test_stats_block_cache_hit_and_miss_populate() {
7009 let stats = Arc::new(Statistics::new());
7019 let dir = TempDir::new().unwrap();
7020 let db = Db::open(dir.path(), tiny_flush_stats_opts(stats.clone())).unwrap();
7021 for i in 0..200 {
7022 db.put(format!("k_{i:05}").as_bytes(), b"value").unwrap();
7023 }
7024 force_flush(&db, "cache");
7025 db.compact_range(None, None).unwrap();
7027 stats.reset();
7028 for _ in 0..2 {
7031 for i in 0..5 {
7032 db.get(format!("k_{i:05}").as_bytes()).unwrap();
7033 }
7034 }
7035 let hits = stats.get_ticker(Ticker::BlockCacheHit);
7036 let misses = stats.get_ticker(Ticker::BlockCacheMiss);
7037 let adds = stats.get_ticker(Ticker::BlockCacheAdd);
7038 assert!(misses > 0, "expected at least one block cache miss");
7039 assert!(hits > 0, "expected at least one block cache hit");
7040 assert!(adds <= misses, "adds={adds} misses={misses}");
7043 }
7044
7045 #[test]
7046 fn test_stats_bloom_filter_useful_increments_on_absent_key() {
7047 let stats = Arc::new(Statistics::new());
7054 let dir = TempDir::new().unwrap();
7055 let db = Db::open(dir.path(), tiny_flush_stats_opts(stats.clone())).unwrap();
7056 for i in 0..200 {
7057 let even = i * 2;
7058 db.put(format!("k_{even:05}").as_bytes(), b"v").unwrap();
7059 }
7060 force_flush(&db, "bloom");
7061 db.compact_range(None, None).unwrap();
7062 stats.reset();
7063 for i in 0..100 {
7067 let odd = i * 2 + 1;
7068 db.get(format!("k_{odd:05}").as_bytes()).unwrap();
7069 }
7070 let useful = stats.get_ticker(Ticker::BloomFilterUseful);
7071 assert!(
7072 useful > 0,
7073 "bloom filter should have ruled out at least one absent key"
7074 );
7075 }
7076
7077 #[test]
7078 fn test_stats_bloom_filter_full_positive_on_present_key() {
7079 let stats = Arc::new(Statistics::new());
7080 let dir = TempDir::new().unwrap();
7081 let db = Db::open(dir.path(), tiny_flush_stats_opts(stats.clone())).unwrap();
7082 for i in 0..100 {
7083 db.put(format!("k_{i:04}").as_bytes(), b"v").unwrap();
7084 }
7085 force_flush(&db, "bloom_pos");
7086 db.compact_range(None, None).unwrap();
7087 stats.reset();
7088 for i in 0..100 {
7089 db.get(format!("k_{i:04}").as_bytes()).unwrap();
7090 }
7091 let pos = stats.get_ticker(Ticker::BloomFilterFullPositive);
7092 assert!(
7093 pos > 0,
7094 "bloom filter should have returned 'maybe' and we found the key"
7095 );
7096 }
7097
7098 #[test]
7099 fn test_stats_flush_and_compaction_counters() {
7100 let stats = Arc::new(Statistics::new());
7101 let dir = TempDir::new().unwrap();
7102 let db = Db::open(dir.path(), tiny_flush_stats_opts(stats.clone())).unwrap();
7103 for i in 0..200 {
7104 db.put(format!("k_{i:04}").as_bytes(), b"v").unwrap();
7105 }
7106 force_flush(&db, "fcstats");
7107 db.compact_range(None, None).unwrap();
7108 assert!(stats.get_ticker(Ticker::FlushCount) >= 1);
7109 assert!(stats.get_ticker(Ticker::FlushBytesWritten) > 0);
7110 assert!(stats.get_ticker(Ticker::CompactionCount) >= 1);
7111 assert!(stats.get_ticker(Ticker::CompactionBytesRead) > 0);
7112 assert!(stats.get_ticker(Ticker::CompactionBytesWritten) > 0);
7113 assert!(stats.get_histogram_snapshot(Histogram::FlushTime).count > 0);
7114 assert!(
7115 stats
7116 .get_histogram_snapshot(Histogram::CompactionTime)
7117 .count
7118 > 0
7119 );
7120 }
7121
7122 #[test]
7123 fn test_stats_wal_counters() {
7124 let stats = Arc::new(Statistics::new());
7125 let dir = TempDir::new().unwrap();
7126 let opts = Options {
7127 statistics: Some(stats.clone()),
7128 durability: DurabilityMode::Immediate,
7129 ..Options::default()
7130 };
7131 let db = Db::open(dir.path(), opts).unwrap();
7132 db.put(b"k", b"v").unwrap();
7133 db.put(b"k2", b"v2").unwrap();
7134 assert!(stats.get_ticker(Ticker::WalBytesWritten) > 0);
7135 assert_eq!(stats.get_ticker(Ticker::WalSyncCount), 2);
7137 assert!(stats.get_histogram_snapshot(Histogram::WalWriteTime).count >= 2);
7138 }
7139
7140 #[test]
7141 fn test_stats_iter_seek_and_next_counters() {
7142 let stats = Arc::new(Statistics::new());
7143 let dir = TempDir::new().unwrap();
7144 let db = Db::open(dir.path(), stats_opts(stats.clone())).unwrap();
7145 db.put(b"a", b"1").unwrap();
7146 db.put(b"b", b"2").unwrap();
7147 db.put(b"c", b"3").unwrap();
7148 let mut it = db.iter();
7149 it.seek_to_first();
7150 while it.valid() {
7151 it.next();
7152 }
7153 assert!(stats.get_ticker(Ticker::IterSeekCount) >= 1);
7154 assert_eq!(stats.get_ticker(Ticker::IterNextCount), 2);
7157 }
7158
7159 #[test]
7160 fn test_stats_snapshot_register_release() {
7161 let stats = Arc::new(Statistics::new());
7162 let dir = TempDir::new().unwrap();
7163 let db = Db::open(dir.path(), stats_opts(stats.clone())).unwrap();
7164 {
7165 let _snap = db.snapshot();
7166 }
7167 assert_eq!(stats.get_ticker(Ticker::SnapshotsRegistered), 1);
7168 assert_eq!(stats.get_ticker(Ticker::SnapshotsReleased), 1);
7169 }
7170
7171 #[test]
7172 fn test_stats_reset_clears_everything() {
7173 let stats = Arc::new(Statistics::new());
7174 let dir = TempDir::new().unwrap();
7175 let db = Db::open(dir.path(), stats_opts(stats.clone())).unwrap();
7176 db.put(b"k", b"v").unwrap();
7177 assert!(stats.get_ticker(Ticker::KeysWritten) > 0);
7178 stats.reset();
7179 assert_eq!(stats.get_ticker(Ticker::KeysWritten), 0);
7180 assert_eq!(stats.get_ticker(Ticker::BytesWritten), 0);
7181 }
7182
7183 #[test]
7184 fn test_stats_none_configured_is_noop() {
7185 let (db, _dir) = open_tmp();
7188 db.put(b"k", b"v").unwrap();
7189 assert_eq!(db.get(b"k").unwrap(), Some(b"v".to_vec()));
7190 }
7191
7192 #[test]
7193 fn test_stats_dump_is_non_empty_and_contains_every_ticker() {
7194 let stats = Arc::new(Statistics::new());
7195 let dir = TempDir::new().unwrap();
7196 let db = Db::open(dir.path(), stats_opts(stats.clone())).unwrap();
7197 db.put(b"k", b"v").unwrap();
7198 let dump = stats.dump();
7199 for ticker_name in [
7200 "regolith.bytes_written",
7201 "regolith.keys_written",
7202 "regolith.bloom_filter_useful",
7203 "regolith.compaction_count",
7204 "regolith.flush_count",
7205 ] {
7206 assert!(dump.contains(ticker_name), "dump missing {ticker_name}");
7207 }
7208 }
7209
7210 #[test]
7213 fn test_property_unknown_name_returns_none() {
7214 let (db, _dir) = open_tmp();
7215 assert!(db.get_property("not.a.real.property").is_none());
7216 assert!(db.get_int_property("not.a.real.property").is_none());
7217 }
7218
7219 #[test]
7220 fn test_property_num_files_at_level() {
7221 let (db, _dir) = open_tmp();
7222 assert_eq!(db.get_int_property("regolith.num-files-at-level0"), Some(0));
7223 assert_eq!(db.get_int_property("regolith.num-files-at-level6"), Some(0));
7224 assert_eq!(
7226 db.get_int_property("regolith.num-files-at-level99"),
7227 Some(0)
7228 );
7229 }
7230
7231 #[test]
7232 fn test_property_level_counts_after_flush_and_compact() {
7233 let dir = TempDir::new().unwrap();
7234 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
7235 for i in 0..200 {
7236 db.put(format!("k_{i:04}").as_bytes(), b"v").unwrap();
7237 }
7238 force_flush(&db, "props");
7239 let l0_before = db.get_int_property("regolith.num-files-at-level0").unwrap();
7241 assert!(l0_before > 0 || db.get_int_property("regolith.num-files-at-level1").unwrap() > 0);
7242
7243 db.compact_range(None, None).unwrap();
7245 assert_eq!(
7246 db.get_int_property("regolith.num-files-at-level0"),
7247 Some(0),
7248 "L0 should be empty after compact_range"
7249 );
7250 }
7251
7252 #[test]
7253 fn test_property_total_sst_size_after_flush() {
7254 let dir = TempDir::new().unwrap();
7255 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
7256 assert_eq!(
7257 db.get_int_property("regolith.total-sst-files-size"),
7258 Some(0)
7259 );
7260 for i in 0..100 {
7261 db.put(format!("k_{i:04}").as_bytes(), b"v").unwrap();
7262 }
7263 force_flush(&db, "size");
7264 let size = db
7265 .get_int_property("regolith.total-sst-files-size")
7266 .unwrap();
7267 assert!(size > 0, "SST size should be > 0 after a flush");
7268 }
7269
7270 #[test]
7271 fn test_property_cur_size_active_mem_table() {
7272 let (db, _dir) = open_tmp();
7273 assert_eq!(
7274 db.get_int_property("regolith.cur-size-active-mem-table"),
7275 Some(0)
7276 );
7277 for i in 0..50 {
7278 db.put(format!("k_{i:03}").as_bytes(), b"value").unwrap();
7279 }
7280 let size = db
7281 .get_int_property("regolith.cur-size-active-mem-table")
7282 .unwrap();
7283 assert!(size > 0, "active memtable should have non-zero size");
7284 }
7285
7286 #[test]
7287 fn test_property_cur_size_all_mem_tables_aggregates() {
7288 let (db, _dir) = open_tmp();
7289 db.put(b"k", b"v").unwrap();
7290 let active = db
7291 .get_int_property("regolith.cur-size-active-mem-table")
7292 .unwrap();
7293 let all = db
7294 .get_int_property("regolith.cur-size-all-mem-tables")
7295 .unwrap();
7296 assert!(all >= active, "all mem tables must be >= active");
7297 }
7298
7299 #[test]
7300 fn test_property_estimate_num_keys() {
7301 let dir = TempDir::new().unwrap();
7302 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
7303 for i in 0..100 {
7304 db.put(format!("k_{i:04}").as_bytes(), b"v").unwrap();
7305 }
7306 force_flush(&db, "estimate");
7307 db.compact_range(None, None).unwrap();
7308 let estimate = db.get_int_property("regolith.estimate-num-keys").unwrap();
7309 assert!(estimate > 50, "estimate-num-keys={estimate} too low");
7313 }
7314
7315 #[test]
7316 fn test_property_num_snapshots_and_oldest_snapshot_time() {
7317 let (db, _dir) = open_tmp();
7318 assert_eq!(db.get_int_property("regolith.num-snapshots"), Some(0));
7319 assert!(
7320 db.get_int_property("regolith.oldest-snapshot-time")
7321 .is_none(),
7322 "oldest-snapshot-time should be None when no snapshots are live"
7323 );
7324 let _snap_a = db.snapshot();
7325 let _snap_b = db.snapshot();
7326 assert_eq!(db.get_int_property("regolith.num-snapshots"), Some(2));
7327 assert!(
7328 db.get_int_property("regolith.oldest-snapshot-time")
7329 .is_some()
7330 );
7331 }
7332
7333 #[test]
7334 fn test_property_background_errors_returns_zero() {
7335 let (db, _dir) = open_tmp();
7336 assert_eq!(db.get_int_property("regolith.background-errors"), Some(0));
7338 }
7339
7340 #[test]
7341 fn test_property_stats_string_includes_level_header_and_counters() {
7342 let stats = Arc::new(Statistics::new());
7343 let opts = Options {
7344 statistics: Some(stats),
7345 ..Options::default()
7346 };
7347 let dir = TempDir::new().unwrap();
7348 let db = Db::open(dir.path(), opts).unwrap();
7349 db.put(b"k", b"v").unwrap();
7350 let text = db.get_property("regolith.stats").unwrap();
7351 assert!(text.contains("== regolith engine stats =="));
7352 assert!(text.contains("Level Files Size(B)"));
7353 assert!(text.contains("regolith.keys_written"));
7354 }
7355
7356 #[test]
7357 fn test_property_stats_string_without_statistics_configured() {
7358 let (db, _dir) = open_tmp();
7359 let text = db.get_property("regolith.stats").unwrap();
7360 assert!(text.contains("== regolith engine stats =="));
7361 assert!(text.contains("(no Statistics object configured"));
7362 }
7363
7364 #[test]
7365 fn test_property_sstables_lists_files_after_flush() {
7366 let dir = TempDir::new().unwrap();
7367 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
7368 for i in 0..50 {
7369 db.put(format!("k_{i:03}").as_bytes(), b"v").unwrap();
7370 }
7371 force_flush(&db, "ssts");
7372 let text = db.get_property("regolith.sstables").unwrap();
7373 assert!(text.contains("Level FileID"));
7374 assert!(
7376 text.lines().any(|l| l.contains("\"k_")),
7377 "expected a file line to include a user key from the writes"
7378 );
7379 }
7380
7381 #[test]
7382 fn test_property_levelstats_format() {
7383 let dir = TempDir::new().unwrap();
7384 let db = Db::open(dir.path(), tiny_flush_opts()).unwrap();
7385 for i in 0..20 {
7386 db.put(format!("k_{i:03}").as_bytes(), b"v").unwrap();
7387 }
7388 force_flush(&db, "lvl");
7389 let text = db.get_property("regolith.levelstats").unwrap();
7390 assert!(text.starts_with("Level Files Size(B)"));
7391 for lvl in 0..7 {
7393 assert!(
7394 text.contains(&format!("{lvl:5}")),
7395 "level {lvl} should appear in levelstats"
7396 );
7397 }
7398 }
7399
7400 #[test]
7401 fn test_property_options_debug_dump() {
7402 let (db, _dir) = open_tmp();
7403 let text = db.get_property("regolith.options").unwrap();
7404 assert!(text.contains("OptionsSnapshot"));
7405 assert!(text.contains("default"));
7406 }
7407
7408 #[test]
7409 fn test_property_integer_forms_available_via_get_property() {
7410 let (db, _dir) = open_tmp();
7413 assert_eq!(
7414 db.get_property("regolith.num-files-at-level0").as_deref(),
7415 Some("0")
7416 );
7417 assert_eq!(
7418 db.get_property("regolith.num-snapshots").as_deref(),
7419 Some("0")
7420 );
7421 }
7422
7423 #[test]
7424 fn test_multi_worker_compaction_reads_are_correct() {
7425 let opts = Options {
7430 write_buffer_size: 4 * 1024,
7431 max_background_compactions: 4,
7432 l0_compaction_trigger: 2,
7433 target_file_size: 8 * 1024,
7434 ..Options::default()
7435 };
7436 let dir = TempDir::new().unwrap();
7437 let db = Db::open(dir.path(), opts).unwrap();
7438
7439 let mut expected = std::collections::BTreeMap::new();
7440 for i in 0..2048 {
7441 let k = format!("k{i:06}");
7442 let v = format!("v{i}");
7443 db.put(k.as_bytes(), v.as_bytes()).unwrap();
7444 expected.insert(k, v);
7445 }
7446 for i in 100..300 {
7448 let k = format!("k{i:06}");
7449 let v = format!("v{i}-new");
7450 db.put(k.as_bytes(), v.as_bytes()).unwrap();
7451 expected.insert(k, v);
7452 }
7453
7454 std::thread::sleep(std::time::Duration::from_millis(500));
7457 db.compact_range(None, None).unwrap();
7458
7459 for (k, v) in &expected {
7460 assert_eq!(
7461 db.get(k.as_bytes()).unwrap(),
7462 Some(v.as_bytes().to_vec()),
7463 "key {k} must read back its latest value"
7464 );
7465 }
7466 }
7467
7468 #[test]
7469 fn test_multi_worker_single_thread_matches_default() {
7470 let opts = Options {
7474 write_buffer_size: 4 * 1024,
7475 max_background_compactions: 1,
7476 ..Options::default()
7477 };
7478 let dir = TempDir::new().unwrap();
7479 let db = Db::open(dir.path(), opts).unwrap();
7480 for i in 0..500 {
7481 let k = format!("k{i:04}");
7482 db.put(k.as_bytes(), b"v").unwrap();
7483 }
7484 db.compact_range(None, None).unwrap();
7485 for i in 0..500 {
7486 let k = format!("k{i:04}");
7487 assert_eq!(db.get(k.as_bytes()).unwrap(), Some(b"v".to_vec()));
7488 }
7489 }
7490
7491 #[test]
7492 fn test_partitioned_index_reads_are_correct() {
7493 let opts = Options {
7496 write_buffer_size: 4 * 1024,
7497 partitioned_index: true,
7498 metadata_block_size: 128,
7499 ..Options::default()
7500 };
7501 let dir = TempDir::new().unwrap();
7502 let db = Db::open(dir.path(), opts).unwrap();
7503
7504 for i in 0..500 {
7505 let k = format!("k{i:04}");
7506 let v = format!("v{i}");
7507 db.put(k.as_bytes(), v.as_bytes()).unwrap();
7508 }
7509 db.compact_range(None, None).unwrap();
7510
7511 for i in 0..500 {
7512 let k = format!("k{i:04}");
7513 let v = format!("v{i}");
7514 assert_eq!(
7515 db.get(k.as_bytes()).unwrap(),
7516 Some(v.into_bytes()),
7517 "key {k} must read back correctly with partitioned index"
7518 );
7519 }
7520 }
7521
7522 #[test]
7523 fn test_partitioned_index_scan_matches_flat() {
7524 let write_and_scan = |partitioned: bool| -> Vec<(Vec<u8>, Vec<u8>)> {
7527 let dir = TempDir::new().unwrap();
7528 let opts = Options {
7529 write_buffer_size: 4 * 1024,
7530 partitioned_index: partitioned,
7531 metadata_block_size: 128,
7532 ..Options::default()
7533 };
7534 let db = Db::open(dir.path(), opts).unwrap();
7535 for i in 0..200 {
7536 let k = format!("k{i:04}");
7537 let v = format!("v{i}");
7538 db.put(k.as_bytes(), v.as_bytes()).unwrap();
7539 }
7540 db.compact_range(None, None).unwrap();
7541 db.scan(None, None).unwrap()
7542 };
7543 let flat = write_and_scan(false);
7544 let partitioned = write_and_scan(true);
7545 assert_eq!(flat, partitioned, "partitioned scan must match flat scan");
7546 }
7547
7548 #[test]
7549 fn test_partitioned_index_survives_reopen() {
7550 let dir = TempDir::new().unwrap();
7551 let opts = Options {
7552 write_buffer_size: 4 * 1024,
7553 partitioned_index: true,
7554 metadata_block_size: 128,
7555 ..Options::default()
7556 };
7557 {
7558 let db = Db::open(dir.path(), opts.clone()).unwrap();
7559 for i in 0..200 {
7560 let k = format!("k{i:04}");
7561 db.put(k.as_bytes(), b"v").unwrap();
7562 }
7563 db.compact_range(None, None).unwrap();
7564 }
7565 let db = Db::open(dir.path(), opts).unwrap();
7567 for i in 0..200 {
7568 let k = format!("k{i:04}");
7569 assert_eq!(db.get(k.as_bytes()).unwrap(), Some(b"v".to_vec()));
7570 }
7571 }
7572
7573 #[test]
7574 fn test_mixed_v1_v2_sstables_read_correctly() {
7575 let dir = TempDir::new().unwrap();
7579 {
7580 let opts = Options {
7581 write_buffer_size: 4 * 1024,
7582 partitioned_index: false,
7583 ..Options::default()
7584 };
7585 let db = Db::open(dir.path(), opts).unwrap();
7586 for i in 0..100 {
7587 let k = format!("k{i:04}");
7588 db.put(k.as_bytes(), b"v1").unwrap();
7589 }
7590 db.compact_range(None, None).unwrap();
7591 }
7592 {
7593 let opts = Options {
7594 write_buffer_size: 4 * 1024,
7595 partitioned_index: true,
7596 metadata_block_size: 128,
7597 ..Options::default()
7598 };
7599 let db = Db::open(dir.path(), opts).unwrap();
7600 for i in 100..200 {
7601 let k = format!("k{i:04}");
7602 db.put(k.as_bytes(), b"v2").unwrap();
7603 }
7604 }
7607 let opts = Options {
7608 partitioned_index: true,
7609 ..Options::default()
7610 };
7611 let db = Db::open(dir.path(), opts).unwrap();
7612 for i in 0..100 {
7613 let k = format!("k{i:04}");
7614 assert_eq!(db.get(k.as_bytes()).unwrap(), Some(b"v1".to_vec()));
7615 }
7616 for i in 100..200 {
7617 let k = format!("k{i:04}");
7618 assert_eq!(db.get(k.as_bytes()).unwrap(), Some(b"v2".to_vec()));
7619 }
7620 }
7621
7622 #[test]
7623 fn test_streaming_compaction_produces_correct_reads() {
7624 let opts = Options {
7628 write_buffer_size: 4 * 1024,
7629 max_subcompactions: 4,
7630 target_file_size: 8 * 1024,
7631 ..Options::default()
7632 };
7633 let dir = TempDir::new().unwrap();
7634 let db = Db::open(dir.path(), opts).unwrap();
7635
7636 let expected: Vec<(String, String)> = (0..2048)
7637 .map(|i| (format!("k{i:06}"), format!("v{i}")))
7638 .collect();
7639 for (k, v) in &expected {
7640 db.put(k.as_bytes(), v.as_bytes()).unwrap();
7641 }
7642 for i in 100..200 {
7644 let k = format!("k{i:06}");
7645 let v = format!("v{i}-new");
7646 db.put(k.as_bytes(), v.as_bytes()).unwrap();
7647 }
7648
7649 db.compact_range(None, None).unwrap();
7650
7651 for (k, v) in &expected {
7652 let i: usize = k[1..].parse().unwrap();
7653 let want = if (100..200).contains(&i) {
7654 format!("v{i}-new")
7655 } else {
7656 v.clone()
7657 };
7658 assert_eq!(
7659 db.get(k.as_bytes()).unwrap(),
7660 Some(want.into_bytes()),
7661 "key {k} must read back the latest value"
7662 );
7663 }
7664 }
7665
7666 #[test]
7667 fn test_streaming_compaction_handles_single_worker_option() {
7668 let opts = Options {
7672 write_buffer_size: 4 * 1024,
7673 max_subcompactions: 1,
7674 target_file_size: 8 * 1024,
7675 ..Options::default()
7676 };
7677 let dir = TempDir::new().unwrap();
7678 let db = Db::open(dir.path(), opts).unwrap();
7679 for i in 0..256 {
7680 let k = format!("k{i:04}");
7681 db.put(k.as_bytes(), b"v").unwrap();
7682 }
7683 db.compact_range(None, None).unwrap();
7684 for i in 0..256 {
7685 let k = format!("k{i:04}");
7686 assert_eq!(db.get(k.as_bytes()).unwrap(), Some(b"v".to_vec()));
7687 }
7688 }
7689
7690 #[test]
7691 fn test_streaming_compaction_accepts_subcompaction_option() {
7692 let opts = Options {
7696 write_buffer_size: 4 * 1024,
7697 max_subcompactions: 8,
7698 ..Options::default()
7699 };
7700 let dir = TempDir::new().unwrap();
7701 let db = Db::open(dir.path(), opts).unwrap();
7702 for i in 0..32 {
7703 let k = format!("k{i:02}");
7704 db.put(k.as_bytes(), b"v").unwrap();
7705 }
7706 db.compact_range(None, None).unwrap();
7707 for i in 0..32 {
7708 let k = format!("k{i:02}");
7709 assert_eq!(db.get(k.as_bytes()).unwrap(), Some(b"v".to_vec()));
7710 }
7711 }
7712
7713 #[test]
7714 fn test_perf_context_captures_db_get_and_put_activity() {
7715 let (db, _dir) = open_tmp();
7721
7722 PerfContext::set_level(PerfLevel::EnableTime);
7723 PerfContext::reset();
7724
7725 db.put(b"alpha", b"1").unwrap();
7726 db.put(b"beta", b"2").unwrap();
7727 db.put(b"gamma", b"3").unwrap();
7728
7729 let _ = db.get(b"alpha").unwrap();
7730 let _ = db.get(b"beta").unwrap();
7731
7732 let snap = PerfContext::capture();
7733 assert_eq!(snap.write_count, 3, "3 puts → write_count 3");
7734 assert_eq!(snap.get_count, 2, "2 gets → get_count 2");
7735 assert!(
7736 snap.write_wal_time_nanos > 0,
7737 "WAL phase should record non-zero time under EnableTime"
7738 );
7739 assert!(
7740 snap.write_memtable_time_nanos > 0,
7741 "memtable write phase should record non-zero time"
7742 );
7743 assert!(
7744 snap.get_from_memtable_time_nanos > 0,
7745 "memtable read phase should record non-zero time"
7746 );
7747
7748 PerfContext::set_level(PerfLevel::Disable);
7750 let before = snap;
7751 db.put(b"delta", b"4").unwrap();
7752 let _ = db.get(b"alpha").unwrap();
7753 let after = PerfContext::capture();
7754 assert_eq!(after, before, "Disable level must freeze counters");
7755 }
7756
7757 #[test]
7758 fn test_evict_compaction_data_from_page_cache_is_correctness_neutral() {
7759 let opts = Options {
7766 write_buffer_size: 4 * 1024,
7767 evict_compaction_data_from_page_cache: true,
7768 ..Options::default()
7769 };
7770 let dir = TempDir::new().unwrap();
7771 let db = Db::open(dir.path(), opts).unwrap();
7772
7773 for i in 0..128 {
7774 let k = format!("k{i:04}");
7775 let v = format!("v{i}");
7776 db.put(k.as_bytes(), v.as_bytes()).unwrap();
7777 }
7778 for i in 0..32 {
7780 let k = format!("k{i:04}");
7781 let v = format!("v{i}-new");
7782 db.put(k.as_bytes(), v.as_bytes()).unwrap();
7783 }
7784 db.compact_range(None, None).unwrap();
7785
7786 for i in 0..128 {
7787 let k = format!("k{i:04}");
7788 let expected = if i < 32 {
7789 format!("v{i}-new")
7790 } else {
7791 format!("v{i}")
7792 };
7793 assert_eq!(
7794 db.get(k.as_bytes()).unwrap(),
7795 Some(expected.into_bytes()),
7796 "key {k} must still read its latest value"
7797 );
7798 }
7799 }
7800
7801 #[test]
7802 fn test_universal_compaction_reads_are_correct_after_merge() {
7803 let opts = Options {
7807 write_buffer_size: 4 * 1024,
7808 compaction_style: CompactionStyle::Universal,
7809 ..Options::default()
7810 };
7811 let dir = TempDir::new().unwrap();
7812 let db = Db::open(dir.path(), opts).unwrap();
7813
7814 for i in 0..64 {
7815 let k = format!("k{i:04}");
7816 let v = format!("v{i}");
7817 db.put(k.as_bytes(), v.as_bytes()).unwrap();
7818 }
7819 for i in 0..16 {
7822 let k = format!("k{i:04}");
7823 let v = format!("v{i}-updated");
7824 db.put(k.as_bytes(), v.as_bytes()).unwrap();
7825 }
7826
7827 db.compact_range(None, None).unwrap();
7828 std::thread::sleep(std::time::Duration::from_millis(100));
7829
7830 for i in 0..64 {
7831 let k = format!("k{i:04}");
7832 let expected = if i < 16 {
7833 format!("v{i}-updated")
7834 } else {
7835 format!("v{i}")
7836 };
7837 assert_eq!(
7838 db.get(k.as_bytes()).unwrap(),
7839 Some(expected.into_bytes()),
7840 "key {k} must read back its latest value"
7841 );
7842 }
7843 }
7844
7845 #[test]
7846 fn test_universal_compaction_never_creates_l1_files() {
7847 let opts = Options {
7850 write_buffer_size: 4 * 1024,
7851 l0_compaction_trigger: 1,
7852 compaction_style: CompactionStyle::Universal,
7853 ..Options::default()
7854 };
7855 let dir = TempDir::new().unwrap();
7856 let db = Db::open(dir.path(), opts).unwrap();
7857 for i in 0..64 {
7858 let k = format!("k{i:04}");
7859 db.put(k.as_bytes(), &vec![0xCC; 256]).unwrap();
7860 }
7861 db.compact_range(None, None).unwrap();
7862 std::thread::sleep(std::time::Duration::from_millis(100));
7863
7864 let l1 = db.get_int_property("regolith.num-files-at-level1").unwrap();
7865 assert_eq!(l1, 0, "Universal must not produce L1 files, saw {l1}");
7866 let l0 = db.get_int_property("regolith.num-files-at-level0").unwrap();
7867 assert!(
7868 l0 >= 1,
7869 "Universal compaction should leave at least one L0 file"
7870 );
7871 }
7872
7873 #[test]
7874 fn test_universal_compaction_full_merge_drops_shadowed_versions() {
7875 let opts = Options {
7879 write_buffer_size: 4 * 1024,
7880 compaction_style: CompactionStyle::Universal,
7881 ..Options::default()
7882 };
7883 let dir = TempDir::new().unwrap();
7884 let db = Db::open(dir.path(), opts).unwrap();
7885 for i in 0..32 {
7886 let k = format!("k{i:04}");
7887 db.put(k.as_bytes(), &vec![0xAA; 512]).unwrap();
7888 }
7889 std::thread::sleep(std::time::Duration::from_millis(50));
7892 db.compact_range(None, None).unwrap();
7893 std::thread::sleep(std::time::Duration::from_millis(100));
7894
7895 let l0 = db.get_int_property("regolith.num-files-at-level0").unwrap();
7896 assert_eq!(
7897 l0, 1,
7898 "full universal compact_range should fold everything into one L0 file, saw {l0}"
7899 );
7900 }
7901
7902 #[test]
7903 fn test_fifo_compaction_bounds_total_size() {
7904 let opts = Options {
7908 write_buffer_size: 4 * 1024,
7909 compaction_style: CompactionStyle::Fifo,
7910 fifo_compaction_options: FifoCompactionOptions {
7911 max_table_files_size: 32 * 1024,
7912 },
7913 ..Options::default()
7914 };
7915 let dir = TempDir::new().unwrap();
7916 let db = Db::open(dir.path(), opts).unwrap();
7917
7918 let payload = vec![0xEEu8; 256];
7922 for i in 0..256 {
7923 let k = format!("k{i:06}");
7924 db.put(k.as_bytes(), &payload).unwrap();
7925 }
7926
7927 std::thread::sleep(std::time::Duration::from_millis(200));
7930
7931 db.compact_range(None, None).unwrap();
7935 std::thread::sleep(std::time::Duration::from_millis(100));
7936
7937 let total = db
7938 .get_int_property("regolith.total-sst-files-size")
7939 .unwrap_or(0);
7940 assert!(
7941 total <= 64 * 1024,
7942 "FIFO cap 32KB should keep total < 64KB slack, got {total}"
7943 );
7944 assert_eq!(
7947 db.get(b"k000255").unwrap(),
7948 Some(payload.clone()),
7949 "newest key must survive FIFO compaction"
7950 );
7951 }
7952
7953 #[test]
7954 fn test_fifo_compaction_keeps_at_least_one_file() {
7955 let opts = Options {
7959 write_buffer_size: 4 * 1024,
7960 compaction_style: CompactionStyle::Fifo,
7961 fifo_compaction_options: FifoCompactionOptions {
7962 max_table_files_size: 1, },
7964 ..Options::default()
7965 };
7966 let dir = TempDir::new().unwrap();
7967 let db = Db::open(dir.path(), opts).unwrap();
7968 for i in 0..32 {
7969 let k = format!("k{i:04}");
7970 db.put(k.as_bytes(), &vec![0xAA; 512]).unwrap();
7971 }
7972 std::thread::sleep(std::time::Duration::from_millis(200));
7973 db.compact_range(None, None).unwrap();
7974 std::thread::sleep(std::time::Duration::from_millis(100));
7975
7976 let l0 = db.get_int_property("regolith.num-files-at-level0").unwrap();
7977 assert!(
7978 l0 >= 1,
7979 "FIFO must keep at least one L0 file even when over the cap"
7980 );
7981 }
7982
7983 #[test]
7984 fn test_fifo_compaction_never_promotes_to_l1() {
7985 let opts = Options {
7989 write_buffer_size: 4 * 1024,
7990 l0_compaction_trigger: 2,
7991 compaction_style: CompactionStyle::Fifo,
7992 fifo_compaction_options: FifoCompactionOptions {
7993 max_table_files_size: 10 * 1024 * 1024,
7994 },
7995 ..Options::default()
7996 };
7997 let dir = TempDir::new().unwrap();
7998 let db = Db::open(dir.path(), opts).unwrap();
7999 for i in 0..64 {
8000 let k = format!("k{i:04}");
8001 db.put(k.as_bytes(), &vec![0xBB; 256]).unwrap();
8002 }
8003 std::thread::sleep(std::time::Duration::from_millis(200));
8004
8005 let l1 = db.get_int_property("regolith.num-files-at-level1").unwrap();
8006 assert_eq!(l1, 0, "FIFO must not produce L1 files, saw {l1}");
8007 }
8008
8009 #[test]
8010 fn test_tailing_iter_sees_writes_after_creation() {
8011 let (db, _dir) = open_tmp();
8015 for i in 0..5 {
8016 let k = format!("log/{i:04}");
8017 db.put(k.as_bytes(), format!("v{i}").as_bytes()).unwrap();
8018 }
8019
8020 let mut tail = db.iter_tailing();
8021 tail.seek_to_first();
8022
8023 let mut seen: Vec<String> = Vec::new();
8025 while tail.valid() {
8026 seen.push(String::from_utf8(tail.key().unwrap().to_vec()).unwrap());
8027 tail.next();
8028 }
8029 assert_eq!(seen.len(), 5, "first drain saw {seen:?}");
8030
8031 for i in 5..10 {
8033 let k = format!("log/{i:04}");
8034 db.put(k.as_bytes(), format!("v{i}").as_bytes()).unwrap();
8035 }
8036
8037 tail.next();
8040 while tail.valid() {
8041 seen.push(String::from_utf8(tail.key().unwrap().to_vec()).unwrap());
8042 tail.next();
8043 }
8044 assert_eq!(seen.len(), 10, "tail saw {seen:?}");
8045 for (i, k) in seen.iter().enumerate() {
8046 assert_eq!(k, &format!("log/{i:04}"));
8047 }
8048 }
8049
8050 #[test]
8051 fn test_tailing_iter_survives_flush_and_compaction() {
8052 let opts = Options {
8056 write_buffer_size: 4 * 1024,
8057 ..Options::default()
8058 };
8059 let dir = TempDir::new().unwrap();
8060 let db = Db::open(dir.path(), opts).unwrap();
8061
8062 for i in 0..16 {
8063 let k = format!("log/{i:04}");
8064 db.put(k.as_bytes(), &vec![0xAA; 256]).unwrap();
8065 }
8066
8067 let mut tail = db.iter_tailing();
8068 tail.seek_to_first();
8069 let mut seen: usize = 0;
8070 while tail.valid() {
8071 seen += 1;
8072 tail.next();
8073 }
8074 assert_eq!(seen, 16);
8075
8076 db.compact_range(None, None).unwrap();
8080 for i in 16..32 {
8081 let k = format!("log/{i:04}");
8082 db.put(k.as_bytes(), &vec![0xBB; 256]).unwrap();
8083 }
8084
8085 tail.refresh();
8086 let mut seen_after = 0;
8087 while tail.valid() {
8088 seen_after += 1;
8089 tail.next();
8090 }
8091 assert_eq!(
8092 seen_after, 16,
8093 "tail should pick up the 16 new entries after refresh"
8094 );
8095 }
8096
8097 #[test]
8098 fn test_tailing_iter_no_re_emission_after_explicit_refresh() {
8099 let (db, _dir) = open_tmp();
8100 for i in 0..3 {
8101 db.put(format!("k{i}").as_bytes(), b"v").unwrap();
8102 }
8103 let mut tail = db.iter_tailing();
8104 tail.seek_to_first();
8105 assert!(tail.valid());
8106 let first_key = tail.key().unwrap().to_vec();
8107 assert_eq!(first_key, b"k0");
8108 tail.next();
8109 assert_eq!(tail.key().unwrap(), b"k1");
8110
8111 tail.refresh();
8114 assert!(tail.valid());
8118 assert_eq!(tail.key().unwrap(), b"k2");
8119 tail.next();
8120 assert!(!tail.valid(), "no more keys after k2");
8121 }
8122
8123 #[test]
8124 fn test_tailing_iter_cf_scoping() {
8125 let (db, _dir) = open_tmp();
8128 let cf_logs = db.create_column_family("logs").unwrap();
8129 let cf_other = db.create_column_family("other").unwrap();
8130
8131 db.put_cf(&cf_logs, b"a", b"1").unwrap();
8132 db.put_cf(&cf_other, b"a", b"x").unwrap();
8133 db.put_cf(&cf_logs, b"b", b"2").unwrap();
8134
8135 let mut tail = db.iter_tailing_cf(&cf_logs);
8136 tail.seek_to_first();
8137 let mut seen = Vec::new();
8138 while tail.valid() {
8139 seen.push((tail.key().unwrap().to_vec(), tail.value().unwrap().to_vec()));
8140 tail.next();
8141 }
8142 assert_eq!(
8143 seen,
8144 vec![
8145 (b"a".to_vec(), b"1".to_vec()),
8146 (b"b".to_vec(), b"2".to_vec())
8147 ]
8148 );
8149
8150 db.put_cf(&cf_other, b"c", b"y").unwrap();
8153 tail.refresh();
8154 assert!(!tail.valid());
8155 }
8156
8157 #[test]
8158 fn test_block_cache_usage_property_reports_nonzero_after_reads() {
8159 let opts = Options {
8165 write_buffer_size: 4 * 1024,
8166 block_cache_size: 1024 * 1024,
8167 ..Options::default()
8168 };
8169 let dir = TempDir::new().unwrap();
8170 let db = Db::open(dir.path(), opts).unwrap();
8171
8172 let payload = vec![0xABu8; 256];
8173 for i in 0..200 {
8174 let k = format!("k{i:04}");
8175 db.put(k.as_bytes(), &payload).unwrap();
8176 }
8177 db.compact_range(None, None).unwrap();
8179
8180 for i in 0..50 {
8182 let k = format!("k{i:04}");
8183 let _ = db.get(k.as_bytes()).unwrap();
8184 }
8185
8186 let usage = db
8187 .get_int_property("regolith.block-cache-usage")
8188 .expect("property must exist");
8189 assert!(
8190 usage > 0,
8191 "expected block-cache-usage > 0 after reads, got {usage}"
8192 );
8193 let cap = db
8194 .get_int_property("regolith.block-cache-capacity")
8195 .expect("property must exist");
8196 assert!(
8197 cap >= 512 * 1024,
8198 "expected at least 512KB capacity, got {cap}"
8199 );
8200 assert!(usage <= cap, "usage {usage} must not exceed capacity {cap}");
8201 }
8202
8203 #[test]
8204 fn test_rate_limiter_throttles_compaction() {
8205 use std::sync::Arc;
8206 use std::time::{Duration, Instant};
8207
8208 let limiter = Arc::new(TokenBucketRateLimiter::new(
8216 100_000,
8217 Duration::from_millis(50),
8218 5_000,
8219 ));
8220 let opts = Options {
8221 write_buffer_size: 16 * 1024 * 1024,
8222 compression: CompressionType::None,
8223 rate_limiter: Some(limiter.clone() as Arc<dyn RateLimiter>),
8224 ..Options::default()
8225 };
8226
8227 let dir = TempDir::new().unwrap();
8228 let db = Db::open(dir.path(), opts).unwrap();
8229
8230 for i in 0..200 {
8233 let k = format!("key-{i:010}");
8234 let v = format!("value-for-key-{i:010}-payload-{}", i);
8237 db.put(k.as_bytes(), v.as_bytes()).unwrap();
8238 }
8239
8240 let start = Instant::now();
8241 db.compact_range(None, None).unwrap();
8242 let elapsed = start.elapsed();
8243
8244 assert!(
8251 elapsed >= Duration::from_millis(100),
8252 "compaction with 100KB/s limiter finished in {elapsed:?}, expected >= 100ms"
8253 );
8254
8255 assert!(
8257 limiter.get_total_bytes_through(Priority::Low) > 0,
8258 "limiter saw zero background bytes"
8259 );
8260 assert_eq!(limiter.get_total_bytes_through(Priority::High), 0);
8261 }
8262
8263 #[test]
8264 fn test_write_stall_slowdown_accumulates_micros() {
8265 use std::sync::Arc;
8266
8267 let stats = Arc::new(Statistics::new());
8268 let opts = Options {
8269 write_buffer_size: 4 * 1024,
8271 l0_compaction_trigger: 1000,
8273 level0_slowdown_writes_trigger: 2,
8275 level0_stop_writes_trigger: 10_000,
8276 max_write_buffer_number: 0,
8279 statistics: Some(stats.clone()),
8280 ..Options::default()
8281 };
8282 let dir = TempDir::new().unwrap();
8283 let db = Db::open(dir.path(), opts).unwrap();
8284
8285 let payload = vec![0xCDu8; 600];
8290 for i in 0..128 {
8291 let k = format!("k{i:04}");
8292 db.put(k.as_bytes(), &payload).unwrap();
8293 }
8294
8295 let stall = stats.get_ticker(Ticker::WriteStallMicros);
8296 assert!(
8297 stall > 0,
8298 "expected WriteStallMicros > 0 after crossing slowdown trigger, got {stall}"
8299 );
8300 }
8301
8302 #[test]
8303 fn test_write_stall_no_slowdown_returns_busy() {
8304 let opts = Options {
8305 write_buffer_size: 4 * 1024,
8306 l0_compaction_trigger: 1000,
8307 level0_slowdown_writes_trigger: 2,
8308 level0_stop_writes_trigger: 10_000,
8309 max_write_buffer_number: 0,
8310 ..Options::default()
8311 };
8312 let dir = TempDir::new().unwrap();
8313 let db = Db::open(dir.path(), opts).unwrap();
8314
8315 let payload = vec![0xEFu8; 600];
8317 for i in 0..64 {
8318 let k = format!("k{i:04}");
8319 db.put(k.as_bytes(), &payload).unwrap();
8320 }
8321
8322 let wo = WriteOptions {
8325 no_slowdown: true,
8326 ..WriteOptions::default()
8327 };
8328 let err = db.put_opt(&wo, b"extra", b"value").unwrap_err();
8329 assert!(
8330 matches!(err, Error::Busy(_)),
8331 "expected Error::Busy, got {err:?}"
8332 );
8333 }
8334
8335 #[test]
8336 fn test_write_stall_stop_unblocks_after_compaction() {
8337 use std::sync::Arc;
8338 use std::thread;
8339 use std::time::{Duration, Instant};
8340
8341 let opts = Options {
8343 write_buffer_size: 4 * 1024,
8344 l0_compaction_trigger: 1000,
8345 level0_slowdown_writes_trigger: 0,
8346 level0_stop_writes_trigger: 2,
8347 max_write_buffer_number: 0,
8348 ..Options::default()
8349 };
8350 let dir = TempDir::new().unwrap();
8351 let db = Arc::new(Db::open(dir.path(), opts).unwrap());
8352
8353 let payload = vec![0x12u8; 600];
8358 for i in 0..32 {
8359 let k = format!("fill{i:04}");
8360 db.put(k.as_bytes(), &payload).unwrap();
8361 if db
8362 .get_int_property("regolith.num-files-at-level0")
8363 .unwrap_or(0)
8364 >= 2
8365 {
8366 break;
8367 }
8368 }
8369 let l0 = db
8370 .get_int_property("regolith.num-files-at-level0")
8371 .unwrap_or(0);
8372 assert!(l0 >= 2, "precondition: need L0 >= 2, got {l0}");
8373
8374 let db_writer = db.clone();
8375 let blocked = thread::spawn(move || {
8376 let start = Instant::now();
8377 db_writer.put(b"stopkey", b"stopval").unwrap();
8378 start.elapsed()
8379 });
8380
8381 thread::sleep(Duration::from_millis(50));
8383 assert!(!blocked.is_finished(), "writer should be blocked on stall");
8384
8385 db.compact_range(None, None).unwrap();
8389
8390 let waited = blocked.join().unwrap();
8391 assert!(
8392 waited < Duration::from_secs(5),
8393 "blocked writer took too long to unblock: {waited:?}"
8394 );
8395
8396 assert_eq!(db.get(b"stopkey").unwrap(), Some(b"stopval".to_vec()));
8398 }
8399
8400 #[test]
8401 fn test_rate_limiter_unset_leaves_compaction_uncapped() {
8402 use std::time::Instant;
8407
8408 let opts = Options {
8409 write_buffer_size: 64 * 1024,
8410 ..Options::default()
8411 };
8412 let dir = TempDir::new().unwrap();
8413 let db = Db::open(dir.path(), opts).unwrap();
8414
8415 let payload = vec![0xABu8; 1024];
8416 for i in 0..256 {
8417 let k = format!("k{i:06}");
8418 db.put(k.as_bytes(), &payload).unwrap();
8419 }
8420
8421 let start = Instant::now();
8422 db.compact_range(None, None).unwrap();
8423 assert!(
8424 start.elapsed() < std::time::Duration::from_secs(5),
8425 "unthrottled compaction took unreasonably long: {:?}",
8426 start.elapsed()
8427 );
8428
8429 for i in 0..256 {
8431 let k = format!("k{i:06}");
8432 assert_eq!(db.get(k.as_bytes()).unwrap(), Some(payload.clone()));
8433 }
8434 }
8435}