Skip to main content

kevy_embedded/
ops_bonus.rs

1//! String `SET` variants (`SETNX`, SET with a TTL, `APPEND`, `STRLEN`), hash
2//! conditional set (`HSETNX`), decrement helpers (`DECR`, `DECRBY`,
3//! `INCRBYFLOAT`), and the seconds-precision TTL accessor
4//! (`ttl_secs`).
5
6use std::time::Duration;
7
8use crate::{KevyError, KevyResult};
9
10use crate::store::ensure_writable;
11use crate::store::{Store, commit_write, store_err};
12use crate::store_glue::commit_deadline;
13
14impl Store {
15    // ---- string SET variants ----------------------------------------
16
17    /// `SETNX key value` — set only if the key does not exist.
18    /// Returns `true` when the SET succeeded; `false` when it was
19    /// vetoed by an existing value.
20    pub fn setnx(&self, key: &[u8], value: &[u8]) -> KevyResult<bool> {
21        ensure_writable(self)?;
22        let mut g = self.wshard(key);
23        let ok = g.store.set(key, value.to_vec(), None, kevy_store::SetCondition::IfAbsent);
24        if ok {
25            commit_write(&mut g, &[b"SET", key, value, b"NX"])?;
26        }
27        Ok(ok)
28    }
29
30    /// `SET key value PX ms` — overwrites + sets TTL. The AOF records an
31    /// **absolute** `PEXPIREAT` deadline (not the relative `ttl`) so the key
32    /// expires at the same wall-clock instant after a restart — a relative
33    /// `PEXPIRE` would be re-anchored to replay-time, resetting the TTL to a
34    /// fresh full duration on every restart (seen as a production
35    /// incident: cache keys never expired across restarts).
36    /// The value and its TTL change under one lock, so no other thread sees
37    /// the value without its TTL. The AOF gets the server's shape: `SET key
38    /// value PX ms` carries the TTL in the value's own frame, so a crash
39    /// before the second frame still leaves an expiring key, and
40    /// `PEXPIREAT` then pins the absolute deadline.
41    pub fn set_with_ttl(&self, key: &[u8], value: &[u8], ttl: Duration) -> KevyResult<bool> {
42        ensure_writable(self)?;
43        let mut g = self.wshard(key);
44        let ok = g.store.set(key, value.to_vec(), Some(ttl), kevy_store::SetCondition::Always);
45        let ms = ttl.as_millis().min(u128::from(u64::MAX)).to_string();
46        commit_write(&mut g, &[b"SET", key, value, b"PX", ms.as_bytes()])?;
47        commit_deadline(&mut g, key)?;
48        Ok(ok)
49    }
50
51    /// `INCRBYFLOAT key delta` — atomic float increment of a string
52    /// value. Returns the post-increment value parsed as f64.
53    pub fn incrbyfloat(&self, key: &[u8], delta: f64) -> KevyResult<f64> {
54        ensure_writable(self)?;
55        let mut g = self.wshard(key);
56        let new_bytes = g.store.incr_by_float(key, delta).map_err(store_err)?;
57        let delta_str = format!("{delta}");
58        commit_write(&mut g, &[b"INCRBYFLOAT", key, delta_str.as_bytes()])?;
59        std::str::from_utf8(&new_bytes)
60            .ok()
61            .and_then(|s| s.parse::<f64>().ok())
62            .ok_or_else(|| KevyError::Protocol("incrbyfloat result not parseable".into()))
63    }
64
65    /// `DECR key` — atomic decrement by 1.
66    pub fn decr(&self, key: &[u8]) -> KevyResult<i64> {
67        self.incr_by(key, -1)
68    }
69
70    /// `DECRBY key delta` — atomic decrement by `delta`.
71    pub fn decrby(&self, key: &[u8], delta: i64) -> KevyResult<i64> {
72        self.incr_by(key, delta.checked_neg().unwrap_or(i64::MIN.saturating_add(1)))
73    }
74
75    /// `STRLEN key` — length of the string value at `key`; 0 if
76    /// absent. Errors on wrong type.
77    pub fn strlen(&self, key: &[u8]) -> KevyResult<usize> {
78        self.wshard(key).store.strlen(key).map_err(store_err)
79    }
80
81    /// `APPEND key data` — append `data` to the string at `key`.
82    /// Creates the key if absent. Returns the new total length.
83    pub fn append(&self, key: &[u8], data: &[u8]) -> KevyResult<usize> {
84        ensure_writable(self)?;
85        let mut g = self.wshard(key);
86        let new_len = g.store.append(key, data).map_err(store_err)?;
87        commit_write(&mut g, &[b"APPEND", key, data])?;
88        Ok(new_len)
89    }
90
91    // ---- hash conditional set ---------------------------------------
92
93    /// `HSETNX key field value` — set the hash field only if it
94    /// does not already exist. Returns `true` when set; `false`
95    /// when the field existed.
96    pub fn hsetnx(&self, key: &[u8], field: &[u8], value: &[u8]) -> KevyResult<bool> {
97        ensure_writable(self)?;
98        let mut g = self.wshard(key);
99        let ok = g.store.hsetnx(key, field, value).map_err(store_err)?;
100        if ok {
101            commit_write(&mut g, &[b"HSETNX", key, field, value])?;
102        }
103        Ok(ok)
104    }
105
106    // ---- TTL units --------------------------------------------------
107
108    /// `TTL key` — TTL in **seconds** (truncated from ms). `-1`
109    /// when the key has no TTL; `-2` when absent. Matches Redis
110    /// wire semantics for the integer reply.
111    pub fn ttl_secs(&self, key: &[u8]) -> i64 {
112        let ms = self.ttl_ms(key);
113        if ms <= 0 { ms } else { ms / 1000 }
114    }
115}