feoxdb/core/store/
ttl.rs

1use bytes::Bytes;
2use std::sync::atomic::Ordering;
3use std::sync::Arc;
4use std::time::{SystemTime, UNIX_EPOCH};
5
6use crate::core::ttl_sweep::TtlConfig;
7use crate::error::{FeoxError, Result};
8
9use super::FeoxStore;
10
11impl FeoxStore {
12    /// Insert or update a key-value pair with TTL (Time-To-Live).
13    ///
14    /// # Arguments
15    ///
16    /// * `key` - The key to insert
17    /// * `value` - The value to store
18    /// * `ttl_seconds` - Time-to-live in seconds
19    ///
20    /// # Returns
21    ///
22    /// Returns `Ok(())` if successful.
23    ///
24    /// # Example
25    ///
26    /// ```rust
27    /// # use feoxdb::FeoxStore;
28    /// # fn main() -> feoxdb::Result<()> {
29    /// # let store = FeoxStore::builder().enable_ttl(true).build()?;
30    /// // Key expires after 60 seconds
31    /// store.insert_with_ttl(b"session:123", b"data", 60)?;
32    /// # Ok(())
33    /// # }
34    /// ```
35    ///
36    /// # Performance
37    ///
38    /// * Memory mode: ~800ns
39    /// * Persistent mode: ~1µs (buffered write)
40    pub fn insert_with_ttl(&self, key: &[u8], value: &[u8], ttl_seconds: u64) -> Result<bool> {
41        if !self.enable_ttl {
42            return Err(FeoxError::TtlNotEnabled);
43        }
44        self.insert_with_ttl_and_timestamp(key, value, ttl_seconds, None)
45    }
46
47    /// Insert or update a key-value pair with TTL and explicit timestamp.
48    ///
49    /// # Arguments
50    ///
51    /// * `key` - The key to insert
52    /// * `value` - The value to store
53    /// * `ttl_seconds` - Time-to-live in seconds
54    /// * `timestamp` - Optional timestamp for conflict resolution. If `None`, uses current time.
55    ///
56    /// # Returns
57    ///
58    /// Returns `Ok(())` if successful.
59    pub fn insert_with_ttl_and_timestamp(
60        &self,
61        key: &[u8],
62        value: &[u8],
63        ttl_seconds: u64,
64        timestamp: Option<u64>,
65    ) -> Result<bool> {
66        if !self.enable_ttl {
67            return Err(FeoxError::TtlNotEnabled);
68        }
69        let timestamp = match timestamp {
70            Some(0) | None => self.get_timestamp(),
71            Some(ts) => ts,
72        };
73
74        // Calculate expiry timestamp
75        let ttl_expiry = if ttl_seconds > 0 {
76            timestamp + (ttl_seconds * 1_000_000_000) // Convert seconds to nanoseconds
77        } else {
78            0
79        };
80
81        self.insert_with_timestamp_and_ttl_internal(key, value, Some(timestamp), ttl_expiry)
82    }
83
84    /// Insert or update a key-value pair with TTL using zero-copy Bytes.
85    ///
86    /// This method avoids copying the value data by directly using the Bytes type,
87    /// which provides reference-counted zero-copy semantics.
88    ///
89    /// # Arguments
90    ///
91    /// * `key` - The key to insert
92    /// * `value` - The value to store as Bytes
93    /// * `ttl_seconds` - Time-to-live in seconds
94    ///
95    /// # Returns
96    ///
97    /// Returns `Ok(())` if successful.
98    ///
99    /// # Example
100    ///
101    /// ```rust
102    /// # use feoxdb::FeoxStore;
103    /// # use bytes::Bytes;
104    /// # fn main() -> feoxdb::Result<()> {
105    /// # let store = FeoxStore::builder().enable_ttl(true).build()?;
106    /// let data = Bytes::from_static(b"session_data");
107    /// // Key expires after 60 seconds
108    /// store.insert_bytes_with_ttl(b"session:123", data, 60)?;
109    /// # Ok(())
110    /// # }
111    /// ```
112    ///
113    /// # Performance
114    ///
115    /// * Memory mode: ~800ns (avoids value copy)
116    /// * Persistent mode: ~1µs (buffered write, avoids value copy)
117    pub fn insert_bytes_with_ttl(
118        &self,
119        key: &[u8],
120        value: Bytes,
121        ttl_seconds: u64,
122    ) -> Result<bool> {
123        if !self.enable_ttl {
124            return Err(FeoxError::TtlNotEnabled);
125        }
126        self.insert_bytes_with_ttl_and_timestamp(key, value, ttl_seconds, None)
127    }
128
129    /// Insert or update a key-value pair with TTL and explicit timestamp using zero-copy Bytes.
130    ///
131    /// # Arguments
132    ///
133    /// * `key` - The key to insert
134    /// * `value` - The value to store as Bytes
135    /// * `ttl_seconds` - Time-to-live in seconds
136    /// * `timestamp` - Optional timestamp for conflict resolution. If `None`, uses current time.
137    ///
138    /// # Returns
139    ///
140    /// Returns `Ok(())` if successful.
141    pub fn insert_bytes_with_ttl_and_timestamp(
142        &self,
143        key: &[u8],
144        value: Bytes,
145        ttl_seconds: u64,
146        timestamp: Option<u64>,
147    ) -> Result<bool> {
148        if !self.enable_ttl {
149            return Err(FeoxError::TtlNotEnabled);
150        }
151        self.insert_bytes_with_timestamp_and_ttl_internal(key, value, timestamp, ttl_seconds)
152    }
153
154    /// Get the remaining TTL (Time-To-Live) for a key in seconds.
155    ///
156    /// # Arguments
157    ///
158    /// * `key` - The key to check
159    ///
160    /// # Returns
161    ///
162    /// Returns `Some(seconds)` if the key has TTL set, `None` if no TTL or key not found.
163    ///
164    /// # Example
165    ///
166    /// ```rust
167    /// # use feoxdb::FeoxStore;
168    /// # fn main() -> feoxdb::Result<()> {
169    /// # let store = FeoxStore::builder().enable_ttl(true).build()?;
170    /// store.insert_with_ttl(b"session", b"data", 3600)?;
171    ///
172    /// // Check remaining TTL
173    /// if let Ok(Some(ttl)) = store.get_ttl(b"session") {
174    ///     println!("Session expires in {} seconds", ttl);
175    /// }
176    /// # Ok(())
177    /// # }
178    /// ```
179    pub fn get_ttl(&self, key: &[u8]) -> Result<Option<u64>> {
180        if !self.enable_ttl {
181            return Err(FeoxError::TtlNotEnabled);
182        }
183        self.validate_key(key)?;
184
185        let record = self
186            .hash_table
187            .read(key, |_, v| v.clone())
188            .ok_or(FeoxError::KeyNotFound)?;
189        let ttl_expiry = record.ttl_expiry.load(Ordering::Acquire);
190
191        if ttl_expiry == 0 {
192            return Ok(None); // No TTL set
193        }
194
195        let now = self.get_timestamp();
196        if now >= ttl_expiry {
197            return Ok(Some(0)); // Already expired
198        }
199
200        // Return remaining seconds
201        Ok(Some((ttl_expiry - now) / 1_000_000_000))
202    }
203
204    /// Update the TTL for an existing key.
205    ///
206    /// # Arguments
207    ///
208    /// * `key` - The key to update
209    /// * `ttl_seconds` - New TTL in seconds (0 to remove TTL)
210    ///
211    /// # Returns
212    ///
213    /// Returns `Ok(())` if successful.
214    ///
215    /// # Errors
216    ///
217    /// * `KeyNotFound` - Key does not exist
218    ///
219    /// # Example
220    ///
221    /// ```rust
222    /// # use feoxdb::FeoxStore;
223    /// # fn main() -> feoxdb::Result<()> {
224    /// # let store = FeoxStore::builder().enable_ttl(true).build()?;
225    /// # store.insert(b"key", b"value")?;
226    /// // Extend TTL to 1 hour
227    /// store.update_ttl(b"key", 3600)?;
228    /// # Ok(())
229    /// # }
230    /// ```
231    pub fn update_ttl(&self, key: &[u8], ttl_seconds: u64) -> Result<()> {
232        if !self.enable_ttl {
233            return Err(FeoxError::TtlNotEnabled);
234        }
235        self.validate_key(key)?;
236
237        let record = self
238            .hash_table
239            .read(key, |_, v| v.clone())
240            .ok_or(FeoxError::KeyNotFound)?;
241
242        let new_expiry = if ttl_seconds > 0 {
243            self.get_timestamp() + (ttl_seconds * 1_000_000_000)
244        } else {
245            0
246        };
247
248        record.ttl_expiry.store(new_expiry, Ordering::Release);
249        Ok(())
250    }
251
252    /// Remove TTL from a key, making it persistent.
253    ///
254    /// # Arguments
255    ///
256    /// * `key` - The key to persist
257    ///
258    /// # Returns
259    ///
260    /// Returns `Ok(())` if successful.
261    ///
262    /// # Errors
263    ///
264    /// * `KeyNotFound` - Key does not exist
265    ///
266    /// # Example
267    ///
268    /// ```rust
269    /// # use feoxdb::FeoxStore;
270    /// # fn main() -> feoxdb::Result<()> {
271    /// # let store = FeoxStore::builder().enable_ttl(true).build()?;
272    /// # store.insert_with_ttl(b"temp", b"data", 60)?;
273    /// // Remove TTL, make permanent
274    /// store.persist(b"temp")?;
275    /// # Ok(())
276    /// # }
277    /// ```
278    pub fn persist(&self, key: &[u8]) -> Result<()> {
279        if !self.enable_ttl {
280            return Err(FeoxError::TtlNotEnabled);
281        }
282        self.update_ttl(key, 0)
283    }
284
285    /// Start the TTL sweeper if configured
286    /// This must be called with an `Arc<Self>` after construction
287    pub fn start_ttl_sweeper(self: &Arc<Self>, config: Option<TtlConfig>) {
288        // Only start TTL sweeper if TTL is enabled
289        if !self.enable_ttl {
290            return;
291        }
292
293        let ttl_config = config.unwrap_or_else(|| {
294            if self.memory_only {
295                TtlConfig::default_memory()
296            } else {
297                TtlConfig::default_persistent()
298            }
299        });
300
301        if ttl_config.enabled {
302            let weak_store = Arc::downgrade(self);
303            let mut sweeper = crate::core::ttl_sweep::TtlSweeper::new(weak_store, ttl_config);
304            sweeper.start();
305
306            // Store the sweeper
307            *self.ttl_sweeper.write() = Some(sweeper);
308        }
309    }
310
311    /// Get current timestamp (public for TTL cleaner)
312    pub fn get_timestamp_pub(&self) -> u64 {
313        SystemTime::now()
314            .duration_since(UNIX_EPOCH)
315            .unwrap()
316            .as_nanos() as u64
317    }
318}