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}