Skip to main content

feoxdb/core/store/
builder.rs

1use std::time::Duration;
2
3use crate::constants::*;
4use crate::core::ttl_sweep::TtlConfig;
5use crate::error::Result;
6
7use super::FeoxStore;
8
9/// Configuration options for FeoxStore.
10///
11/// Use `StoreBuilder` for a more ergonomic way to configure the store.
12pub struct StoreConfig {
13    pub hash_bits: u32,
14    pub memory_only: bool,
15    pub enable_caching: bool,
16    pub device_path: Option<String>,
17    pub file_size: Option<u64>,
18    pub max_memory: Option<usize>,
19    pub enable_ttl: bool,
20    pub ttl_config: Option<TtlConfig>,
21}
22
23/// Builder for creating FeoxStore with custom configuration.
24///
25/// Provides a fluent interface for configuring store parameters.
26///
27/// # Example
28///
29/// ```rust
30/// use feoxdb::FeoxStore;
31///
32/// # fn main() -> feoxdb::Result<()> {
33/// let store = FeoxStore::builder()
34///     .max_memory(1_000_000_000)
35///     .hash_bits(20)
36///     .enable_ttl(true)
37///     .build()?;
38/// # Ok(())
39/// # }
40/// ```
41pub struct StoreBuilder {
42    hash_bits: u32,
43    device_path: Option<String>,
44    file_size: Option<u64>,
45    max_memory: Option<usize>,
46    enable_caching: Option<bool>,
47    enable_ttl: bool,
48    ttl_config: Option<TtlConfig>,
49    allow_ambiguous_legacy_recovery: bool,
50}
51
52impl StoreBuilder {
53    pub fn new() -> Self {
54        Self {
55            hash_bits: DEFAULT_HASH_BITS,
56            device_path: None,
57            file_size: None,
58            max_memory: Some(DEFAULT_MAX_MEMORY),
59            enable_caching: None, // Disable caching for memory-only mode
60            enable_ttl: false,
61            ttl_config: None,
62            allow_ambiguous_legacy_recovery: false,
63        }
64    }
65
66    /// Set the device path for persistent storage.
67    ///
68    /// When set, data will be persisted to disk asynchronously.
69    /// If not set, the store operates in memory-only mode.
70    pub fn device_path(mut self, path: impl Into<String>) -> Self {
71        self.device_path = Some(path.into());
72        self
73    }
74
75    /// Allow recovery past a released v1/v2 deletion marker.
76    ///
77    /// Those formats did not store the deleted extent length, so a continuation
78    /// block can be indistinguishable from a record head. This opt-in preserves
79    /// their historical skip-one behavior for trusted stores or migration.
80    pub fn allow_ambiguous_legacy_recovery(mut self, allow: bool) -> Self {
81        self.allow_ambiguous_legacy_recovery = allow;
82        self
83    }
84
85    /// Set the initial file size for new persistent stores (in bytes).
86    ///
87    /// When creating a new persistent store file, it will be pre-allocated
88    /// to this size for better performance. If not set, defaults to 1GB.
89    /// This option is ignored for existing files.
90    ///
91    /// # Example
92    ///
93    /// ```no_run
94    /// use feoxdb::FeoxStore;
95    ///
96    /// # fn main() -> feoxdb::Result<()> {
97    /// let store = FeoxStore::builder()
98    ///     .device_path("/path/to/data.feox")
99    ///     .file_size(10 * 1024 * 1024 * 1024)  // 10GB
100    ///     .build()?;
101    /// # Ok(())
102    /// # }
103    /// ```
104    pub fn file_size(mut self, size: u64) -> Self {
105        self.file_size = Some(size);
106        self
107    }
108
109    /// Set the maximum memory limit (in bytes).
110    ///
111    /// The store will start evicting entries when this limit is approached.
112    /// Default: 1GB
113    pub fn max_memory(mut self, limit: usize) -> Self {
114        self.max_memory = Some(limit);
115        self
116    }
117
118    /// Remove memory limit.
119    ///
120    /// Use with caution as the store can grow unbounded.
121    pub fn no_memory_limit(mut self) -> Self {
122        self.max_memory = None;
123        self
124    }
125
126    /// Set number of hash bits (determines hash table size).
127    ///
128    /// More bits = larger hash table = better performance for large datasets.
129    /// Default: 18 (256K buckets)
130    pub fn hash_bits(mut self, bits: u32) -> Self {
131        self.hash_bits = bits;
132        self
133    }
134
135    /// Enable or disable caching.
136    ///
137    /// When enabled, frequently accessed values are kept in memory
138    /// even after being written to disk. Uses CLOCK eviction algorithm.
139    pub fn enable_caching(mut self, enable: bool) -> Self {
140        self.enable_caching = Some(enable);
141        self
142    }
143
144    /// Enable or disable TTL (Time-To-Live) functionality.
145    ///
146    /// When disabled (default), TTL operations will return errors and no background cleaner runs.
147    /// When enabled, keys can have expiry times and a background cleaner removes expired keys.
148    /// Default: false (disabled for optimal performance)
149    pub fn enable_ttl(mut self, enable: bool) -> Self {
150        self.enable_ttl = enable;
151        if enable {
152            let mut config = self.ttl_config.unwrap_or_default();
153            config.enabled = true;
154            self.ttl_config = Some(config);
155        }
156        self
157    }
158
159    /// Enable or disable TTL sweeper.
160    ///
161    /// When enabled, a background thread periodically removes expired keys.
162    /// Note: This method is deprecated in favor of enable_ttl().
163    pub fn enable_ttl_cleaner(mut self, enable: bool) -> Self {
164        let mut config = self.ttl_config.unwrap_or_default();
165        config.enabled = enable;
166        self.ttl_config = Some(config);
167        self.enable_ttl = enable; // Also enable TTL when cleaner is enabled
168        self
169    }
170
171    /// Configure TTL sweeper with custom parameters.
172    ///
173    /// # Arguments
174    ///
175    /// * `sample_size` - Keys to check per batch
176    /// * `threshold` - Continue if >threshold expired (0.0-1.0)
177    /// * `max_time_ms` - Max milliseconds per cleaning run
178    /// * `interval_ms` - Sleep between runs
179    pub fn ttl_sweeper_config(
180        mut self,
181        sample_size: usize,
182        threshold: f32,
183        max_time_ms: u64,
184        interval_ms: u64,
185    ) -> Self {
186        self.ttl_config = Some(TtlConfig {
187            sample_size,
188            expiry_threshold: threshold,
189            max_iterations: 16,
190            max_time_per_run: Duration::from_millis(max_time_ms),
191            sleep_interval: Duration::from_millis(interval_ms),
192            enabled: true,
193        });
194        self
195    }
196
197    /// Build the FeoxStore
198    pub fn build(self) -> Result<FeoxStore> {
199        let memory_only = self.device_path.is_none();
200        let enable_caching = self.enable_caching.unwrap_or(!memory_only);
201
202        let config = StoreConfig {
203            hash_bits: self.hash_bits,
204            memory_only,
205            enable_caching,
206            device_path: self.device_path,
207            file_size: self.file_size,
208            max_memory: self.max_memory,
209            enable_ttl: self.enable_ttl,
210            ttl_config: self.ttl_config,
211        };
212
213        FeoxStore::with_config_and_legacy_recovery(config, self.allow_ambiguous_legacy_recovery)
214    }
215}
216
217impl Default for StoreBuilder {
218    fn default() -> Self {
219        Self::new()
220    }
221}