Skip to main content

trine_kv/
options.rs

1use std::path::{Path, PathBuf};
2
3use crate::{codec::CodecId, prefix::PrefixExtractor, runtime::RuntimeOptions};
4
5/// Storage location and host backend selected for a database.
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub enum StorageMode {
8    /// Keep all data in memory and discard it when the handle closes.
9    InMemory,
10    /// Store data in a native filesystem directory.
11    Persistent {
12        /// Native filesystem database directory.
13        path: PathBuf,
14    },
15    /// Store data through an explicit host-provided backend.
16    HostPersistent {
17        /// Host backend used for persistent storage.
18        backend: HostStorageBackend,
19    },
20}
21
22/// Host storage backend selected for non-native persistent modes.
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub enum HostStorageBackend {
25    /// Use a WASI preopened filesystem path.
26    Wasi {
27        /// WASI preopened filesystem path.
28        path: PathBuf,
29    },
30    /// Use browser storage.
31    Browser,
32}
33
34impl HostStorageBackend {
35    pub(crate) const fn as_str(&self) -> &'static str {
36        match self {
37            Self::Wasi { .. } => "WASI persistent storage backend",
38            Self::Browser => "browser persistent storage backend",
39        }
40    }
41}
42
43impl StorageMode {
44    pub(crate) fn persistent_path(&self) -> Option<&Path> {
45        match self {
46            Self::Persistent { path }
47            | Self::HostPersistent {
48                backend: HostStorageBackend::Wasi { path },
49            } => Some(path.as_path()),
50            Self::InMemory
51            | Self::HostPersistent {
52                backend: HostStorageBackend::Browser,
53            } => None,
54        }
55    }
56
57    pub(crate) const fn is_wasi_persistent(&self) -> bool {
58        matches!(
59            self,
60            Self::HostPersistent {
61                backend: HostStorageBackend::Wasi { .. }
62            }
63        )
64    }
65
66    pub(crate) const fn is_browser_persistent(&self) -> bool {
67        matches!(
68            self,
69            Self::HostPersistent {
70                backend: HostStorageBackend::Browser
71            }
72        )
73    }
74}
75
76impl Default for StorageMode {
77    fn default() -> Self {
78        Self::InMemory
79    }
80}
81
82/// Durability requested for committed writes.
83#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
84pub enum DurabilityMode {
85    /// Accept writes after buffering them in the storage backend.
86    #[default]
87    Buffered,
88    /// Flush buffered bytes to the backend.
89    Flush,
90    /// Sync file data without requiring metadata sync where the backend allows it.
91    SyncData,
92    /// Sync file data and metadata where the backend allows it.
93    SyncAll,
94}
95
96impl DurabilityMode {
97    pub(crate) const fn as_str(self) -> &'static str {
98        match self {
99            Self::Buffered => "buffered",
100            Self::Flush => "flush",
101            Self::SyncData => "sync-data",
102            Self::SyncAll => "sync-all",
103        }
104    }
105}
106
107/// Compression codec profile used for table blocks.
108#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
109pub enum CompressionProfile {
110    /// Store table blocks without compression.
111    None,
112    /// Use the v1 fast LZ4 block codec.
113    #[default]
114    Fast,
115}
116
117impl CompressionProfile {
118    #[must_use]
119    pub(crate) const fn codec_id(self) -> CodecId {
120        match self {
121            Self::None => CodecId::None,
122            Self::Fast => CodecId::FastLz4Block,
123        }
124    }
125}
126
127/// Point-read filter policy for table keys.
128#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129pub enum FilterPolicy {
130    /// Do not write table key filters.
131    Disabled,
132    /// Write Bloom filters with the requested bit budget per key.
133    Bloom {
134        /// Bloom-filter bit budget per key.
135        bits_per_key: u8,
136    },
137}
138
139impl Default for FilterPolicy {
140    fn default() -> Self {
141        Self::Bloom { bits_per_key: 10 }
142    }
143}
144
145/// Prefix-read filter policy for extracted key prefixes.
146#[derive(Debug, Clone, Copy, PartialEq, Eq)]
147pub enum PrefixFilterPolicy {
148    /// Do not write prefix filters.
149    Disabled,
150    /// Write Bloom filters with the requested bit budget per prefix.
151    Bloom {
152        /// Bloom-filter bit budget per extracted prefix.
153        bits_per_prefix: u8,
154    },
155}
156
157impl Default for PrefixFilterPolicy {
158    fn default() -> Self {
159        Self::Bloom {
160            bits_per_prefix: 10,
161        }
162    }
163}
164
165/// Search strategy used inside table indexes.
166#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
167pub enum IndexSearchPolicy {
168    /// Scan index entries linearly.
169    Linear,
170    /// Search index entries with binary search.
171    Binary,
172    /// Let Trine choose based on index size.
173    #[default]
174    Auto,
175}
176
177/// Startup policy when repairable temporary files are found.
178#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
179pub enum FailOnCorruptionPolicy {
180    /// Report the files as corruption and leave them untouched.
181    #[default]
182    FailClosed,
183    /// Delete safe temporary files and write a recovery report.
184    RepairSafeTemporaryFiles,
185}
186
187/// Options used when opening a database.
188///
189/// `DbOptions` controls where data is stored, whether missing storage is
190/// created, the default write durability, bucket defaults, runtime behavior,
191/// maintenance thresholds, and startup recovery policy. Path-like calls to
192/// [`crate::Db::open`] and [`crate::Db::open_sync`] are converted with
193/// [`DbOptions::new`], which selects persistent native filesystem storage.
194///
195/// # Examples
196///
197/// ```rust
198/// use trine_kv::{Db, DbOptions, DurabilityMode};
199///
200/// # fn main() -> trine_kv::Result<()> {
201/// let persistent = Db::open_sync(
202///     DbOptions::new("target/doc-example-options")
203///         .with_durability(DurabilityMode::SyncAll),
204/// )?;
205///
206/// let memory = Db::open_sync(DbOptions::memory())?;
207/// assert_eq!(persistent.options().read_only, false);
208/// assert_eq!(memory.options().read_only, false);
209/// # Ok(())
210/// # }
211/// ```
212#[derive(Debug, Clone, PartialEq, Eq)]
213pub struct DbOptions {
214    /// Storage location and host backend.
215    pub storage_mode: StorageMode,
216    /// Create the database directory and metadata when missing.
217    pub create_if_missing: bool,
218    /// Open without allowing writes or maintenance that mutates storage.
219    pub read_only: bool,
220    /// Options used when the built-in default bucket is first created.
221    pub default_bucket_options: BucketOptions,
222    /// Default durability used by write helpers that do not pass `WriteOptions`.
223    pub durability: DurabilityMode,
224    /// Active memtable target size before flush.
225    pub write_buffer_bytes: usize,
226    /// Maximum queued immutable memtables before writes apply backpressure.
227    pub max_immutable_memtables: usize,
228    /// Target size for newly written table files.
229    pub target_table_bytes: usize,
230    /// Per-level size multiplier used by compaction.
231    pub level_size_multiplier: usize,
232    /// Maximum L0 table count before compaction is requested.
233    pub max_l0_files: usize,
234    /// Approximate bytes reserved for cached table blocks.
235    pub block_cache_bytes: usize,
236    /// Number of background workers used by maintenance-capable runtimes.
237    pub background_worker_count: usize,
238    /// Runtime used for async, blocking, and background work.
239    pub runtime: RuntimeOptions,
240    /// Startup policy for safe temporary files left by interrupted writes.
241    pub fail_on_corruption: FailOnCorruptionPolicy,
242    /// Enable garbage collection of obsolete blob bytes.
243    pub blob_gc_enabled: bool,
244    /// Minimum discardable-byte ratio required before a blob file is collected.
245    pub blob_gc_discardable_ratio: BlobGcRatio,
246    /// Minimum blob file size considered for garbage collection.
247    pub blob_gc_min_file_bytes: u64,
248}
249
250impl DbOptions {
251    /// Default active memtable target size.
252    pub const DEFAULT_WRITE_BUFFER_BYTES: usize = 64 * 1024 * 1024;
253    /// Default target size for table files.
254    pub const DEFAULT_TARGET_TABLE_BYTES: usize = 64 * 1024 * 1024;
255    /// Default block-cache byte budget.
256    pub const DEFAULT_BLOCK_CACHE_BYTES: usize = 256 * 1024 * 1024;
257    /// Default minimum blob file size for garbage collection.
258    pub const DEFAULT_BLOB_GC_MIN_FILE_BYTES: u64 = 64 * 1024 * 1024;
259
260    /// Creates persistent database options for `path`.
261    ///
262    /// This is the path-first constructor used by `Db::open(path)` and
263    /// `Db::open_sync(path)`. It selects [`StorageMode::Persistent`], sets
264    /// safety-first default durability for confirmed writes, and leaves
265    /// `create_if_missing` enabled.
266    ///
267    /// # Parameters
268    ///
269    /// - `path`: native filesystem database directory.
270    #[must_use]
271    pub fn new(path: impl Into<PathBuf>) -> Self {
272        Self::persistent(path)
273    }
274
275    /// Creates in-memory database options.
276    ///
277    /// In-memory databases keep memtables, tables, metadata, and WAL state only
278    /// in process memory. Closing the handle drops all data. This is useful for
279    /// tests, temporary indexes, and caches that can be rebuilt.
280    #[must_use]
281    pub fn memory() -> Self {
282        Self::default()
283    }
284
285    /// Creates persistent native-filesystem options for `path`.
286    ///
287    /// This is equivalent to [`DbOptions::new`].
288    #[must_use]
289    pub fn persistent(path: impl Into<PathBuf>) -> Self {
290        Self {
291            storage_mode: StorageMode::Persistent { path: path.into() },
292            durability: DurabilityMode::SyncAll,
293            ..Self::default()
294        }
295    }
296
297    /// Creates persistent WASI host-filesystem options for `path`.
298    #[must_use]
299    pub fn wasi_persistent(path: impl Into<PathBuf>) -> Self {
300        Self {
301            storage_mode: StorageMode::HostPersistent {
302                backend: HostStorageBackend::Wasi { path: path.into() },
303            },
304            background_worker_count: 0,
305            durability: DurabilityMode::Flush,
306            runtime: RuntimeOptions::inline(),
307            ..Self::default()
308        }
309    }
310
311    /// Creates read-only WASI host-filesystem options for `path`.
312    #[must_use]
313    pub fn wasi_persistent_read_only(path: impl Into<PathBuf>) -> Self {
314        Self::wasi_persistent(path).read_only()
315    }
316
317    /// Creates browser persistent options.
318    #[must_use]
319    pub fn browser_persistent() -> Self {
320        Self {
321            storage_mode: StorageMode::HostPersistent {
322                backend: HostStorageBackend::Browser,
323            },
324            background_worker_count: 0,
325            durability: DurabilityMode::Flush,
326            runtime: RuntimeOptions::inline(),
327            ..Self::default()
328        }
329    }
330
331    /// Creates read-only browser persistent options.
332    #[must_use]
333    pub fn browser_persistent_read_only() -> Self {
334        Self::browser_persistent().read_only()
335    }
336
337    /// Creates read-only native-filesystem options for `path`.
338    #[must_use]
339    pub fn persistent_read_only(path: impl Into<PathBuf>) -> Self {
340        Self::persistent(path).read_only()
341    }
342
343    /// Sets the default durability for writes that do not pass `WriteOptions`.
344    ///
345    /// This affects helpers such as [`crate::Db::put_sync`] and
346    /// [`crate::Db::put`]. Calls that pass [`WriteOptions`] use their explicit
347    /// durability instead.
348    #[must_use]
349    pub const fn with_durability(mut self, durability: DurabilityMode) -> Self {
350        self.durability = durability;
351        self
352    }
353
354    /// Sets the options used by the built-in default bucket.
355    ///
356    /// Named buckets still use the options passed to `Db::bucket_with_options`.
357    #[must_use]
358    pub fn with_default_bucket_options(mut self, options: BucketOptions) -> Self {
359        self.default_bucket_options = options;
360        self
361    }
362
363    /// Marks these options read-only and disables creation of missing storage.
364    ///
365    /// Read-only handles can read existing persistent data, but reject writes,
366    /// flushes, compactions, and repairs that would mutate storage.
367    #[must_use]
368    pub const fn read_only(mut self) -> Self {
369        self.read_only = true;
370        self.create_if_missing = false;
371        self
372    }
373}
374
375impl Default for DbOptions {
376    fn default() -> Self {
377        Self {
378            storage_mode: StorageMode::InMemory,
379            create_if_missing: true,
380            read_only: false,
381            default_bucket_options: BucketOptions::default(),
382            durability: DurabilityMode::Buffered,
383            write_buffer_bytes: Self::DEFAULT_WRITE_BUFFER_BYTES,
384            max_immutable_memtables: 4,
385            target_table_bytes: Self::DEFAULT_TARGET_TABLE_BYTES,
386            level_size_multiplier: 10,
387            max_l0_files: 8,
388            block_cache_bytes: Self::DEFAULT_BLOCK_CACHE_BYTES,
389            background_worker_count: 1,
390            runtime: RuntimeOptions::default(),
391            fail_on_corruption: FailOnCorruptionPolicy::FailClosed,
392            blob_gc_enabled: true,
393            blob_gc_discardable_ratio: BlobGcRatio::HALF,
394            blob_gc_min_file_bytes: Self::DEFAULT_BLOB_GC_MIN_FILE_BYTES,
395        }
396    }
397}
398
399/// Ratio threshold used by blob garbage collection.
400#[derive(Debug, Clone, Copy, PartialEq, Eq)]
401pub struct BlobGcRatio {
402    millionths: u32,
403}
404
405impl BlobGcRatio {
406    /// A 50% discardable-byte threshold.
407    pub const HALF: Self = Self {
408        millionths: 500_000,
409    };
410    /// A 100% discardable-byte threshold.
411    pub const FULL: Self = Self {
412        millionths: 1_000_000,
413    };
414
415    /// Creates a ratio from millionths, where `1_000_000` means 100%.
416    #[must_use]
417    pub const fn from_millionths(millionths: u32) -> Self {
418        Self { millionths }
419    }
420
421    /// Returns this ratio in millionths.
422    #[must_use]
423    pub const fn millionths(self) -> u32 {
424        self.millionths
425    }
426
427    pub(crate) fn should_collect(self, discardable_bytes: u64, total_bytes: u64) -> bool {
428        if total_bytes == 0 {
429            return false;
430        }
431        u128::from(discardable_bytes).saturating_mul(1_000_000)
432            >= u128::from(total_bytes).saturating_mul(u128::from(self.millionths))
433    }
434}
435
436impl Default for BlobGcRatio {
437    fn default() -> Self {
438        Self::HALF
439    }
440}
441
442/// Policy for merging blob values back into table levels during compaction.
443#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
444pub enum BlobLevelMergePolicy {
445    /// Keep blob values in blob files during compaction.
446    Disabled,
447    /// Let Trine decide based on level and value size.
448    #[default]
449    Auto,
450    /// Always merge blob values back into table files when compacting.
451    Always,
452}
453
454/// Options fixed for a bucket when the bucket is created.
455#[derive(Debug, Clone, PartialEq, Eq)]
456pub struct BucketOptions {
457    /// Allow empty user keys.
458    pub allow_empty_keys: bool,
459    /// Compression profile used for table blocks.
460    pub compression: CompressionProfile,
461    /// Target uncompressed data-block size.
462    pub block_bytes: usize,
463    /// Point-read filter policy for table keys.
464    pub filter_policy: FilterPolicy,
465    /// Prefix extractor used by prefix filters.
466    pub prefix_extractor: PrefixExtractor,
467    /// Prefix-read filter policy.
468    pub prefix_filter_policy: PrefixFilterPolicy,
469    /// Search strategy used inside table indexes.
470    pub index_search_policy: IndexSearchPolicy,
471    /// Values at or above this size are stored in blob files.
472    pub blob_threshold_bytes: usize,
473    /// Policy for merging blob values during compaction.
474    pub blob_level_merge_policy: BlobLevelMergePolicy,
475}
476
477impl BucketOptions {
478    /// Default target data-block size.
479    pub const DEFAULT_BLOCK_BYTES: usize = 16 * 1024;
480    /// Default threshold for storing values in blob files.
481    pub const DEFAULT_BLOB_THRESHOLD_BYTES: usize = 1024 * 1024;
482
483    /// Sets the prefix extractor for this bucket.
484    #[must_use]
485    pub fn with_prefix_extractor(mut self, prefix_extractor: PrefixExtractor) -> Self {
486        self.prefix_extractor = prefix_extractor;
487        self
488    }
489
490    /// Sets the value-size threshold for blob storage.
491    #[must_use]
492    pub const fn with_blob_threshold_bytes(mut self, blob_threshold_bytes: usize) -> Self {
493        self.blob_threshold_bytes = blob_threshold_bytes;
494        self
495    }
496
497    /// Sets the blob-level merge policy.
498    #[must_use]
499    pub const fn with_blob_level_merge_policy(mut self, policy: BlobLevelMergePolicy) -> Self {
500        self.blob_level_merge_policy = policy;
501        self
502    }
503
504    /// Enables or disables blob-level merge with a boolean convenience flag.
505    #[must_use]
506    pub const fn with_blob_level_merge_enabled(mut self, enabled: bool) -> Self {
507        self.blob_level_merge_policy = if enabled {
508            BlobLevelMergePolicy::Always
509        } else {
510            BlobLevelMergePolicy::Disabled
511        };
512        self
513    }
514}
515
516impl Default for BucketOptions {
517    fn default() -> Self {
518        Self {
519            allow_empty_keys: true,
520            compression: CompressionProfile::Fast,
521            block_bytes: Self::DEFAULT_BLOCK_BYTES,
522            filter_policy: FilterPolicy::default(),
523            prefix_extractor: PrefixExtractor::default(),
524            prefix_filter_policy: PrefixFilterPolicy::default(),
525            index_search_policy: IndexSearchPolicy::Auto,
526            blob_threshold_bytes: Self::DEFAULT_BLOB_THRESHOLD_BYTES,
527            blob_level_merge_policy: BlobLevelMergePolicy::Auto,
528        }
529    }
530}
531
532/// Per-write options passed to commit operations.
533///
534/// `WriteOptions` lets a single write or batch request a durability level
535/// different from the database default. The options are evaluated when the
536/// write is accepted; changing a `WriteOptions` value later has no effect on an
537/// already committed write.
538///
539/// # Examples
540///
541/// ```rust
542/// use trine_kv::{Db, WriteOptions};
543///
544/// # fn main() -> trine_kv::Result<()> {
545/// let db = Db::open_sync(trine_kv::DbOptions::memory())?;
546/// let commit = db.put_with_options_sync(b"k", b"v", WriteOptions::sync_all())?;
547/// assert!(commit.sequence().get() > 0);
548/// # Ok(())
549/// # }
550/// ```
551#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
552pub struct WriteOptions {
553    /// Durability requested for this write.
554    pub durability: DurabilityMode,
555}
556
557impl WriteOptions {
558    /// Creates write options with an explicit durability mode.
559    ///
560    /// # Parameters
561    ///
562    /// - `durability`: durability requested for the write or batch.
563    #[must_use]
564    pub const fn new(durability: DurabilityMode) -> Self {
565        Self { durability }
566    }
567
568    /// Creates buffered write options.
569    ///
570    /// The write may return after bytes are accepted by the storage backend but
571    /// before a flush or sync is requested. This is the lowest-latency and
572    /// weakest durability mode.
573    #[must_use]
574    pub const fn buffered() -> Self {
575        Self::new(DurabilityMode::Buffered)
576    }
577
578    /// Creates flush write options.
579    ///
580    /// The write requests a backend flush for accepted WAL bytes. Exact meaning
581    /// depends on the selected backend capabilities.
582    #[must_use]
583    pub const fn flush() -> Self {
584        Self::new(DurabilityMode::Flush)
585    }
586
587    /// Creates data-sync write options.
588    ///
589    /// The write requests durable file data without requiring metadata sync
590    /// where the backend can distinguish those operations.
591    #[must_use]
592    pub const fn sync_data() -> Self {
593        Self::new(DurabilityMode::SyncData)
594    }
595
596    /// Creates full-sync write options.
597    ///
598    /// The write requests the strongest durability mode supported by the
599    /// backend for WAL data and required metadata.
600    #[must_use]
601    pub const fn sync_all() -> Self {
602        Self::new(DurabilityMode::SyncAll)
603    }
604}