Coverage Report

Created: 2026-01-04 13:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/runner/work/feoxdb/feoxdb/src/core/store/ttl.rs
Line
Count
Source
1
use bytes::Bytes;
2
use std::sync::atomic::Ordering;
3
use std::sync::Arc;
4
use std::time::{SystemTime, UNIX_EPOCH};
5
6
use crate::core::ttl_sweep::TtlConfig;
7
use crate::error::{FeoxError, Result};
8
9
use super::FeoxStore;
10
11
impl 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
10
    pub fn insert_with_ttl(&self, key: &[u8], value: &[u8], ttl_seconds: u64) -> Result<bool> {
41
10
        if !self.enable_ttl {
42
2
            return Err(FeoxError::TtlNotEnabled);
43
8
        }
44
8
        self.insert_with_ttl_and_timestamp(key, value, ttl_seconds, None)
45
10
    }
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
9
    pub fn insert_with_ttl_and_timestamp(
60
9
        &self,
61
9
        key: &[u8],
62
9
        value: &[u8],
63
9
        ttl_seconds: u64,
64
9
        timestamp: Option<u64>,
65
9
    ) -> Result<bool> {
66
9
        if !self.enable_ttl {
67
1
            return Err(FeoxError::TtlNotEnabled);
68
8
        }
69
8
        let timestamp = match timestamp {
70
8
            Some(0) | None => self.get_timestamp(),
71
0
            Some(ts) => ts,
72
        };
73
74
        // Calculate expiry timestamp
75
8
        let ttl_expiry = if ttl_seconds > 0 {
76
7
            timestamp + (ttl_seconds * 1_000_000_000) // Convert seconds to nanoseconds
77
        } else {
78
1
            0
79
        };
80
81
8
        self.insert_with_timestamp_and_ttl_internal(key, value, Some(timestamp), ttl_expiry)
82
9
    }
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
3
    pub fn insert_bytes_with_ttl(
118
3
        &self,
119
3
        key: &[u8],
120
3
        value: Bytes,
121
3
        ttl_seconds: u64,
122
3
    ) -> Result<bool> {
123
3
        if !self.enable_ttl {
124
1
            return Err(FeoxError::TtlNotEnabled);
125
2
        }
126
2
        self.insert_bytes_with_ttl_and_timestamp(key, value, ttl_seconds, None)
127
3
    }
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
5
    pub fn insert_bytes_with_ttl_and_timestamp(
142
5
        &self,
143
5
        key: &[u8],
144
5
        value: Bytes,
145
5
        ttl_seconds: u64,
146
5
        timestamp: Option<u64>,
147
5
    ) -> Result<bool> {
148
5
        if !self.enable_ttl {
149
0
            return Err(FeoxError::TtlNotEnabled);
150
5
        }
151
5
        self.insert_bytes_with_timestamp_and_ttl_internal(key, value, timestamp, ttl_seconds)
152
5
    }
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
10
    pub fn get_ttl(&self, key: &[u8]) -> Result<Option<u64>> {
180
10
        if !self.enable_ttl {
181
1
            return Err(FeoxError::TtlNotEnabled);
182
9
        }
183
9
        self.validate_key(key)
?0
;
184
185
9
        let record = self
186
9
            .hash_table
187
9
            .read(key, |_, v| v.clone())
188
9
            .ok_or(FeoxError::KeyNotFound)
?0
;
189
9
        let ttl_expiry = record.ttl_expiry.load(Ordering::Acquire);
190
191
9
        if ttl_expiry == 0 {
192
4
            return Ok(None); // No TTL set
193
5
        }
194
195
5
        let now = self.get_timestamp();
196
5
        if now >= ttl_expiry {
197
0
            return Ok(Some(0)); // Already expired
198
5
        }
199
200
        // Return remaining seconds
201
5
        Ok(Some((ttl_expiry - now) / 1_000_000_000))
202
10
    }
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
4
    pub fn update_ttl(&self, key: &[u8], ttl_seconds: u64) -> Result<()> {
232
4
        if !self.enable_ttl {
233
1
            return Err(FeoxError::TtlNotEnabled);
234
3
        }
235
3
        self.validate_key(key)
?0
;
236
237
3
        let record = self
238
3
            .hash_table
239
3
            .read(key, |_, v| v.clone())
240
3
            .ok_or(FeoxError::KeyNotFound)
?0
;
241
242
3
        let new_expiry = if ttl_seconds > 0 {
243
2
            self.get_timestamp() + (ttl_seconds * 1_000_000_000)
244
        } else {
245
1
            0
246
        };
247
248
3
        record.ttl_expiry.store(new_expiry, Ordering::Release);
249
3
        Ok(())
250
4
    }
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
2
    pub fn persist(&self, key: &[u8]) -> Result<()> {
279
2
        if !self.enable_ttl {
280
1
            return Err(FeoxError::TtlNotEnabled);
281
1
        }
282
1
        self.update_ttl(key, 0)
283
2
    }
284
285
    /// Start the TTL sweeper if configured
286
    /// This must be called with an `Arc<Self>` after construction
287
0
    pub fn start_ttl_sweeper(self: &Arc<Self>, config: Option<TtlConfig>) {
288
        // Only start TTL sweeper if TTL is enabled
289
0
        if !self.enable_ttl {
290
0
            return;
291
0
        }
292
293
0
        let ttl_config = config.unwrap_or_else(|| {
294
0
            if self.memory_only {
295
0
                TtlConfig::default_memory()
296
            } else {
297
0
                TtlConfig::default_persistent()
298
            }
299
0
        });
300
301
0
        if ttl_config.enabled {
302
0
            let weak_store = Arc::downgrade(self);
303
0
            let mut sweeper = crate::core::ttl_sweep::TtlSweeper::new(weak_store, ttl_config);
304
0
            sweeper.start();
305
0
306
0
            // Store the sweeper
307
0
            *self.ttl_sweeper.write() = Some(sweeper);
308
0
        }
309
0
    }
310
311
    /// Get current timestamp (public for TTL cleaner)
312
4.34k
    pub fn get_timestamp_pub(&self) -> u64 {
313
4.34k
        SystemTime::now()
314
4.34k
            .duration_since(UNIX_EPOCH)
315
4.34k
            .unwrap()
316
4.34k
            .as_nanos() as u64
317
4.34k
    }
318
}