Coverage Report

Created: 2026-07-11 22:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/runner/work/feoxdb/feoxdb/src/core/store/atomic.rs
Line
Count
Source
1
use bytes::Bytes;
2
use std::sync::atomic::Ordering;
3
use std::sync::Arc;
4
5
use crate::constants::Operation;
6
use crate::core::record::Record;
7
use crate::error::{FeoxError, Result};
8
9
use super::FeoxStore;
10
11
impl FeoxStore {
12
    /// Atomically increment a numeric counter.
13
    ///
14
    /// The value must be stored as an 8-byte little-endian i64. If the key doesn't exist,
15
    /// it will be created with the given delta value. If it exists, the value will be
16
    /// incremented atomically.
17
    ///
18
    /// # Value Format
19
    ///
20
    /// The value MUST be exactly 8 bytes representing a little-endian i64.
21
    /// Use `i64::to_le_bytes()` to create the initial value:
22
    /// ```rust,ignore
23
    /// let zero: i64 = 0;
24
    /// store.insert(b"counter", &zero.to_le_bytes())?;
25
    /// ```
26
    ///
27
    /// # Arguments
28
    ///
29
    /// * `key` - The key of the counter
30
    /// * `delta` - The amount to increment by (can be negative for decrement)
31
    /// * `timestamp` - Optional timestamp for conflict resolution
32
    ///
33
    /// # Returns
34
    ///
35
    /// Returns the new value after incrementing.
36
    ///
37
    /// # Errors
38
    ///
39
    /// * `InvalidOperation` - Existing value is not exactly 8 bytes (not a valid i64)
40
    /// * `OlderTimestamp` - Timestamp is not newer than existing record
41
    ///
42
    /// # Example
43
    ///
44
    /// ```rust
45
    /// # use feoxdb::FeoxStore;
46
    /// # fn main() -> feoxdb::Result<()> {
47
    /// # let store = FeoxStore::new(None)?;
48
    /// // Initialize counter with proper binary format
49
    /// let initial: i64 = 0;
50
    /// store.insert(b"visits", &initial.to_le_bytes())?;
51
    ///
52
    /// // Increment atomically
53
    /// let val = store.atomic_increment(b"visits", 1)?;
54
    /// assert_eq!(val, 1);
55
    ///
56
    /// // Increment by 5
57
    /// let val = store.atomic_increment(b"visits", 5)?;
58
    /// assert_eq!(val, 6);
59
    ///
60
    /// // Decrement by 2
61
    /// let val = store.atomic_increment(b"visits", -2)?;
62
    /// assert_eq!(val, 4);
63
    ///
64
    /// // Or create new counter directly (starts at delta value)
65
    /// let downloads = store.atomic_increment(b"downloads", 100)?;
66
    /// assert_eq!(downloads, 100);
67
    /// # Ok(())
68
    /// # }
69
    /// ```
70
1.11k
    pub fn atomic_increment(&self, key: &[u8], delta: i64) -> Result<i64> {
71
1.11k
        self.atomic_increment_with_timestamp_and_ttl(key, delta, None, 0)
72
1.11k
    }
73
74
    /// Atomically increment/decrement with explicit timestamp.
75
    ///
76
    /// This is the advanced version that allows manual timestamp control.
77
    /// Most users should use `atomic_increment()` instead.
78
    ///
79
    /// # Arguments
80
    ///
81
    /// * `key` - The key to increment/decrement
82
    /// * `delta` - Amount to add (negative to decrement)
83
    /// * `timestamp` - Optional timestamp. If `None`, uses current time.
84
    ///
85
    /// # Errors
86
    ///
87
    /// * `OlderTimestamp` - Timestamp is not newer than existing record
88
0
    pub fn atomic_increment_with_timestamp(
89
0
        &self,
90
0
        key: &[u8],
91
0
        delta: i64,
92
0
        timestamp: Option<u64>,
93
0
    ) -> Result<i64> {
94
0
        self.atomic_increment_with_timestamp_and_ttl(key, delta, timestamp, 0)
95
0
    }
96
97
    /// Atomically increment/decrement with TTL support.
98
    ///
99
    /// # Arguments
100
    ///
101
    /// * `key` - The key to increment/decrement
102
    /// * `delta` - Amount to add (negative to decrement)
103
    /// * `ttl_seconds` - Time-to-live in seconds (0 for no expiry)
104
    ///
105
    /// # Errors
106
    ///
107
    /// * `InvalidOperation` - Value is not a valid i64
108
0
    pub fn atomic_increment_with_ttl(
109
0
        &self,
110
0
        key: &[u8],
111
0
        delta: i64,
112
0
        ttl_seconds: u64,
113
0
    ) -> Result<i64> {
114
0
        self.atomic_increment_with_timestamp_and_ttl(key, delta, None, ttl_seconds)
115
0
    }
116
117
    /// Atomically increment/decrement with explicit timestamp and TTL.
118
    ///
119
    /// # Arguments
120
    ///
121
    /// * `key` - The key to increment/decrement
122
    /// * `delta` - Amount to add (negative to decrement)
123
    /// * `timestamp` - Optional timestamp. If `None`, uses current time.
124
    /// * `ttl_seconds` - Time-to-live in seconds (0 for no expiry)
125
    ///
126
    /// # Errors
127
    ///
128
    /// * `OlderTimestamp` - Timestamp is not newer than existing record
129
1.11k
    pub fn atomic_increment_with_timestamp_and_ttl(
130
1.11k
        &self,
131
1.11k
        key: &[u8],
132
1.11k
        delta: i64,
133
1.11k
        timestamp: Option<u64>,
134
1.11k
        ttl_seconds: u64,
135
1.11k
    ) -> Result<i64> {
136
1.11k
        self.validate_key(key)
?0
;
137
138
1.11k
        let key_vec = key.to_vec();
139
140
1.11k
        let 
result1.10k
= match self.hash_table.entry(key_vec.clone()) {
141
1.10k
            scc::hash_map::Entry::Occupied(mut entry) => {
142
1.10k
                let old_record = entry.get();
143
144
                // Get timestamp inside the critical section to ensure it's always newer
145
1.10k
                let timestamp = match timestamp {
146
1.10k
                    Some(0) | None => self.get_timestamp(),
147
0
                    Some(ts) => ts,
148
                };
149
150
                // Check if timestamp is valid
151
1.10k
                if timestamp < old_record.timestamp {
152
0
                    return Err(FeoxError::OlderTimestamp);
153
1.10k
                }
154
155
                // Load value from memory or disk
156
1.10k
                let value = if let Some(
val1.10k
) = old_record.get_value() {
157
1.10k
                    val.to_vec()
158
1
                } else if let Some(
value0
) = self
159
1
                    .cache
160
1
                    .as_ref()
161
1
                    .and_then(|cache| cache.get_for_record(key, old_record))
162
                {
163
0
                    value.to_vec()
164
                } else {
165
1
                    let value = self.load_value_from_disk(old_record)
?0
;
166
1
                    if let Some(ref cache) = self.cache {
167
1
                        cache.insert_for_record(
168
1
                            key_vec.clone(),
169
1
                            Bytes::from(value.clone()),
170
1
                            Arc::clone(old_record),
171
1
                        );
172
1
                    
}0
173
1
                    value
174
                };
175
176
1.10k
                let 
current_val1.10k
= if value.len() == 8 {
177
1.10k
                    let bytes = value
178
1.10k
                        .get(..8)
179
1.10k
                        .and_then(|slice| slice.try_into().ok())
180
1.10k
                        .ok_or(FeoxError::InvalidNumericValue)
?0
;
181
1.10k
                    i64::from_le_bytes(bytes)
182
                } else {
183
1
                    return Err(FeoxError::InvalidOperation);
184
                };
185
186
1.10k
                let new_val = current_val.saturating_add(delta);
187
1.10k
                let new_value = new_val.to_le_bytes().to_vec();
188
189
                // Create new record with TTL if specified
190
1.10k
                let new_record = if ttl_seconds > 0 {
191
0
                    let ttl_expiry = timestamp + (ttl_seconds * 1_000_000_000); // Convert to nanoseconds
192
0
                    Arc::new(Record::new_with_timestamp_ttl(
193
0
                        old_record.key.clone(),
194
0
                        new_value,
195
0
                        timestamp,
196
0
                        ttl_expiry,
197
                    ))
198
                } else {
199
1.10k
                    Arc::new(Record::new(old_record.key.clone(), new_value, timestamp))
200
                };
201
202
1.10k
                let old_value_len = old_record.value_len;
203
1.10k
                let old_size = old_record.calculate_size();
204
1.10k
                let new_size = self.calculate_record_size(old_record.key.len(), 8);
205
1.10k
                let old_record_arc = Arc::clone(old_record);
206
207
                // Atomically update the entry
208
1.10k
                old_record_arc.refcount.store(0, Ordering::Release);
209
1.10k
                entry.insert(Arc::clone(&new_record));
210
211
                // Update skip list as well
212
1.10k
                self.tree.insert(key_vec.clone(), Arc::clone(&new_record));
213
214
                // Update memory usage
215
1.10k
                if new_size > old_size {
216
0
                    self.stats
217
0
                        .memory_usage
218
0
                        .fetch_add(new_size - old_size, Ordering::AcqRel);
219
1.10k
                } else {
220
1.10k
                    self.stats
221
1.10k
                        .memory_usage
222
1.10k
                        .fetch_sub(old_size - new_size, Ordering::AcqRel);
223
1.10k
                }
224
225
                // Only do cache and persistence operations if not in memory-only mode
226
1.10k
                if !self.memory_only {
227
101
                    if self.enable_caching {
228
101
                        if let Some(ref cache) = self.cache {
229
101
                            cache.remove(&key_vec);
230
101
                        
}0
231
0
                    }
232
233
101
                    if let Some(ref wb) = self.write_buffer {
234
101
                        wb.add_write(Operation::Update, Arc::clone(&new_record), old_value_len)
?0
;
235
101
                        wb.add_write(Operation::Delete, old_record_arc, old_value_len)
?0
;
236
0
                    }
237
1.00k
                }
238
239
1.10k
                Ok(new_val)
240
            }
241
2
            scc::hash_map::Entry::Vacant(entry) => {
242
                // Key doesn't exist, create it with initial value
243
                // Get timestamp inside the critical section
244
2
                let timestamp = match timestamp {
245
2
                    Some(0) | None => self.get_timestamp(),
246
0
                    Some(ts) => ts,
247
                };
248
249
2
                let initial_val = delta;
250
2
                let value = initial_val.to_le_bytes().to_vec();
251
252
                // Create new record with TTL if specified
253
2
                let new_record = if ttl_seconds > 0 {
254
0
                    let ttl_expiry = timestamp + (ttl_seconds * 1_000_000_000); // Convert to nanoseconds
255
0
                    Arc::new(Record::new_with_timestamp_ttl(
256
0
                        key_vec.clone(),
257
0
                        value,
258
0
                        timestamp,
259
0
                        ttl_expiry,
260
                    ))
261
                } else {
262
2
                    Arc::new(Record::new(key_vec.clone(), value, timestamp))
263
                };
264
265
2
                let _ = entry.insert_entry(Arc::clone(&new_record));
266
267
                // Update skip list
268
2
                self.tree.insert(key_vec.clone(), Arc::clone(&new_record));
269
270
                // Update statistics
271
2
                self.stats.record_count.fetch_add(1, Ordering::AcqRel);
272
2
                let record_size = self.calculate_record_size(key.len(), 8);
273
2
                self.stats
274
2
                    .memory_usage
275
2
                    .fetch_add(record_size, Ordering::AcqRel);
276
277
                // Handle persistence if needed
278
2
                if !self.memory_only {
279
1
                    if let Some(ref wb) = self.write_buffer {
280
1
                        wb.add_write(Operation::Insert, Arc::clone(&new_record), 0)
?0
;
281
0
                    }
282
1
                }
283
284
2
                Ok(initial_val)
285
            }
286
        };
287
288
1.10k
        result
289
1.11k
    }
290
291
    /// Insert a key only when it does not already exist.
292
    ///
293
    /// The existence check and insertion happen under the hash-table entry guard, so
294
    /// concurrent callers cannot both create the same key.
295
    ///
296
    /// # Returns
297
    ///
298
    /// Returns `Ok(true)` when this call inserted the key and `Ok(false)` when another
299
    /// value already exists.
300
    ///
301
    /// # Example
302
    ///
303
    /// ```rust
304
    /// # use feoxdb::FeoxStore;
305
    /// # fn main() -> feoxdb::Result<()> {
306
    /// let store = FeoxStore::new(None)?;
307
    /// assert!(store.insert_if_absent(b"job:1", b"first")?);
308
    /// assert!(!store.insert_if_absent(b"job:1", b"second")?);
309
    /// assert_eq!(store.get(b"job:1")?, b"first");
310
    /// # Ok(())
311
    /// # }
312
    /// ```
313
20
    pub fn insert_if_absent(&self, key: &[u8], value: &[u8]) -> Result<bool> {
314
20
        let start = std::time::Instant::now();
315
20
        self.validate_key_value(key, value)
?0
;
316
20
        let key_vec = key.to_vec();
317
318
20
        match self.hash_table.entry(key_vec.clone()) {
319
17
            scc::hash_map::Entry::Occupied(_) => Ok(false),
320
3
            scc::hash_map::Entry::Vacant(entry) => {
321
3
                let record_size = self.calculate_record_size(key.len(), value.len());
322
3
                if !self.check_memory_limit(record_size) {
323
0
                    return Err(FeoxError::OutOfMemory);
324
3
                }
325
326
3
                let record = Arc::new(Record::new(
327
3
                    key_vec.clone(),
328
3
                    value.to_vec(),
329
3
                    self.get_timestamp(),
330
                ));
331
3
                let _entry = entry.insert_entry(Arc::clone(&record));
332
333
3
                self.tree.insert(key_vec, Arc::clone(&record));
334
3
                self.stats.record_count.fetch_add(1, Ordering::AcqRel);
335
3
                self.stats
336
3
                    .memory_usage
337
3
                    .fetch_add(record_size, Ordering::AcqRel);
338
3
                self.stats
339
3
                    .record_insert(start.elapsed().as_nanos() as u64, false);
340
341
3
                if !self.memory_only {
342
1
                    if let Some(ref write_buffer) = self.write_buffer {
343
1
                        write_buffer.add_write(Operation::Insert, record, 0)
?0
;
344
0
                    }
345
2
                }
346
347
3
                Ok(true)
348
            }
349
        }
350
20
    }
351
352
    /// Atomically compare and swap a value.
353
    ///
354
    /// Compares the current value of a key with an expected value, and if they match,
355
    /// atomically replaces it with a new value. This operation is atomic within the
356
    /// HashMap shard, preventing race conditions.
357
    ///
358
    /// # Arguments
359
    ///
360
    /// * `key` - The key to check and potentially update
361
    /// * `expected` - The expected current value
362
    /// * `new_value` - The new value to set if comparison succeeds
363
    ///
364
    /// # Returns
365
    ///
366
    /// Returns `Ok(true)` if the swap succeeded (current value matched expected).
367
    /// Returns `Ok(false)` if the current value didn't match or key doesn't exist.
368
    ///
369
    /// # Errors
370
    ///
371
    /// * `InvalidKeySize` - Key is invalid
372
    /// * `InvalidValueSize` - New value is too large
373
    /// * `OutOfMemory` - Memory limit exceeded
374
    /// * `IoError` - Failed to read value from disk
375
    ///
376
    /// # Example
377
    ///
378
    /// ```rust
379
    /// # use feoxdb::FeoxStore;
380
    /// # fn main() -> feoxdb::Result<()> {
381
    /// # let store = FeoxStore::new(None)?;
382
    /// store.insert(b"config", b"v1")?;
383
    ///
384
    /// // Successful CAS - value matches
385
    /// let swapped = store.compare_and_swap(b"config", b"v1", b"v2")?;
386
    /// assert_eq!(swapped, true);
387
    ///
388
    /// // Failed CAS - value doesn't match
389
    /// let swapped = store.compare_and_swap(b"config", b"v1", b"v3")?;
390
    /// assert_eq!(swapped, false); // Value is now "v2", not "v1"
391
    ///
392
    /// // CAS on non-existent key
393
    /// let swapped = store.compare_and_swap(b"missing", b"any", b"new")?;
394
    /// assert_eq!(swapped, false);
395
    /// # Ok(())
396
    /// # }
397
    /// ```
398
1.74k
    pub fn compare_and_swap(&self, key: &[u8], expected: &[u8], new_value: &[u8]) -> Result<bool> {
399
1.74k
        self.compare_and_swap_with_timestamp_and_ttl(key, expected, new_value, None, 0)
400
1.74k
    }
401
402
    /// Compare and swap with explicit timestamp.
403
    ///
404
    /// This is the advanced version that allows manual timestamp control for
405
    /// conflict resolution. Most users should use `compare_and_swap()` instead.
406
    ///
407
    /// # Arguments
408
    ///
409
    /// * `key` - The key to check and potentially update
410
    /// * `expected` - The expected current value
411
    /// * `new_value` - The new value to set if comparison succeeds
412
    /// * `timestamp` - Optional timestamp. If `None`, uses current time.
413
    ///
414
    /// # Errors
415
    ///
416
    /// * `OlderTimestamp` - Timestamp is not newer than existing record
417
1
    pub fn compare_and_swap_with_timestamp(
418
1
        &self,
419
1
        key: &[u8],
420
1
        expected: &[u8],
421
1
        new_value: &[u8],
422
1
        timestamp: Option<u64>,
423
1
    ) -> Result<bool> {
424
1
        self.compare_and_swap_with_timestamp_and_ttl(key, expected, new_value, timestamp, 0)
425
1
    }
426
427
    /// Compare and swap with TTL support.
428
    ///
429
    /// # Arguments
430
    ///
431
    /// * `key` - The key to check and potentially update
432
    /// * `expected` - The expected current value
433
    /// * `new_value` - The new value to set if comparison succeeds
434
    /// * `ttl_seconds` - Time-to-live in seconds (0 for no expiry)
435
    ///
436
    /// # Errors
437
    ///
438
    /// * `InvalidKeySize` - Key is invalid
439
    /// * `InvalidValueSize` - New value is too large
440
0
    pub fn compare_and_swap_with_ttl(
441
0
        &self,
442
0
        key: &[u8],
443
0
        expected: &[u8],
444
0
        new_value: &[u8],
445
0
        ttl_seconds: u64,
446
0
    ) -> Result<bool> {
447
0
        self.compare_and_swap_with_timestamp_and_ttl(key, expected, new_value, None, ttl_seconds)
448
0
    }
449
450
    /// Compare and swap with explicit timestamp and TTL.
451
    ///
452
    /// # Arguments
453
    ///
454
    /// * `key` - The key to check and potentially update
455
    /// * `expected` - The expected current value
456
    /// * `new_value` - The new value to set if comparison succeeds
457
    /// * `timestamp` - Optional timestamp. If `None`, uses current time.
458
    /// * `ttl_seconds` - Time-to-live in seconds (0 for no expiry)
459
    ///
460
    /// # Errors
461
    ///
462
    /// * `OlderTimestamp` - Timestamp is not newer than existing record
463
1.74k
    pub fn compare_and_swap_with_timestamp_and_ttl(
464
1.74k
        &self,
465
1.74k
        key: &[u8],
466
1.74k
        expected: &[u8],
467
1.74k
        new_value: &[u8],
468
1.74k
        timestamp: Option<u64>,
469
1.74k
        ttl_seconds: u64,
470
1.74k
    ) -> Result<bool> {
471
1.74k
        let start = std::time::Instant::now();
472
1.74k
        self.validate_key_value(key, new_value)
?0
;
473
1.74k
        let key_vec = key.to_vec();
474
475
        // Phase 1: Check value and save record reference for version tracking
476
1.33k
        let initial_record = {
477
1.74k
            let 
entry1.74k
= match self.hash_table.read(&key_vec, |_, v|
v1.74k
.
clone1.74k
()) {
478
1.74k
                Some(e) => e,
479
1
                None => return Ok(false), // Key doesn't exist
480
            };
481
482
1.74k
            let record_arc = entry;
483
484
            // Check if value matches expected
485
1.74k
            let value_matches = if let Some(
val1.74k
) = record_arc.get_value() {
486
                // Fast path: value in memory
487
1.74k
                val.as_ref() == expected
488
2
            } else if let Some(
value1
) = self
489
2
                .cache
490
2
                .as_ref()
491
2
                .and_then(|cache| cache.get_for_record(key, &record_arc))
492
            {
493
1
                value.as_ref() == expected
494
            } else {
495
1
                let disk_value = self.load_value_from_disk(&record_arc)
?0
;
496
1
                if let Some(ref cache) = self.cache {
497
1
                    cache.insert_for_record(
498
1
                        key_vec.clone(),
499
1
                        Bytes::from(disk_value.clone()),
500
1
                        Arc::clone(&record_arc),
501
1
                    );
502
1
                
}0
503
1
                disk_value == expected
504
            };
505
506
1.74k
            if !value_matches {
507
417
                return Ok(false); // Value doesn't match expected
508
1.33k
            }
509
510
            // Return the Arc pointer itself as our version identifier
511
            // NOTE: We can't use the stored timestamp for verification here because
512
            // SystemTime::now() resolution is 1us, which is too coarse for CAS operations.
513
1.33k
            record_arc
514
        };
515
516
        // Phase 2: Acquire write lock and verify record hasn't changed
517
1.33k
        match self.hash_table.entry(key_vec.clone()) {
518
1.33k
            scc::hash_map::Entry::Occupied(mut entry) => {
519
1.33k
                let old_record = entry.get();
520
521
                // Check if the record is still the same one we read earlier
522
1.33k
                if !Arc::ptr_eq(old_record, &initial_record) {
523
                    // Record was modified between our check and acquiring lock
524
221
                    return Ok(false);
525
1.11k
                }
526
527
1.11k
                let timestamp = match timestamp {
528
1.10k
                    Some(0) | None => self.get_timestamp(),
529
1
                    Some(ts) => ts,
530
                };
531
532
1.11k
                if timestamp < old_record.timestamp {
533
1
                    return Err(FeoxError::OlderTimestamp);
534
1.10k
                }
535
536
1.10k
                let old_size = old_record.calculate_size();
537
1.10k
                let new_size = self.calculate_record_size(key.len(), new_value.len());
538
1.10k
                let old_value_len = old_record.value_len;
539
1.10k
                let old_record_arc = Arc::clone(old_record);
540
541
                // Pre-check memory limit
542
1.10k
                if new_size > old_size && 
!8
self8
.
check_memory_limit8
(new_size - old_size) {
543
0
                    return Err(FeoxError::OutOfMemory);
544
1.10k
                }
545
546
                // Create new record with TTL if specified
547
1.10k
                let new_record = if ttl_seconds > 0 {
548
0
                    let ttl_expiry = timestamp + (ttl_seconds * 1_000_000_000); // Convert to nanoseconds
549
0
                    Arc::new(Record::new_with_timestamp_ttl(
550
0
                        key.to_vec(),
551
0
                        new_value.to_vec(),
552
0
                        timestamp,
553
0
                        ttl_expiry,
554
                    ))
555
                } else {
556
1.10k
                    Arc::new(Record::new(key.to_vec(), new_value.to_vec(), timestamp))
557
                };
558
559
1.10k
                old_record_arc.refcount.store(0, Ordering::Release);
560
1.10k
                entry.insert(Arc::clone(&new_record));
561
562
1.10k
                self.tree.insert(key_vec.clone(), Arc::clone(&new_record));
563
564
1.10k
                if new_size > old_size {
565
8
                    self.stats
566
8
                        .memory_usage
567
8
                        .fetch_add(new_size - old_size, Ordering::AcqRel);
568
1.10k
                } else {
569
1.10k
                    self.stats
570
1.10k
                        .memory_usage
571
1.10k
                        .fetch_sub(old_size - new_size, Ordering::AcqRel);
572
1.10k
                }
573
574
1.10k
                self.stats
575
1.10k
                    .record_insert(start.elapsed().as_nanos() as u64, true);
576
577
1.10k
                if !self.memory_only {
578
3
                    if self.enable_caching {
579
3
                        if let Some(ref cache) = self.cache {
580
3
                            cache.remove(&key_vec);
581
3
                        
}0
582
0
                    }
583
584
3
                    if let Some(ref wb) = self.write_buffer {
585
3
                        wb.add_write(Operation::Update, new_record, old_value_len)
?0
;
586
3
                        wb.add_write(Operation::Delete, old_record_arc, old_value_len)
?0
;
587
0
                    }
588
1.10k
                }
589
590
1.10k
                Ok(true)
591
            }
592
0
            scc::hash_map::Entry::Vacant(_) => Ok(false),
593
        }
594
1.74k
    }
595
}