Skip to main content

kevy_embedded/
ops_bonus.rs

1//! String `SET` variants (`SETNX`, `APPEND`, `STRLEN`), hash
2//! conditional set (`HSETNX`), decrement helpers (`DECR`, `DECRBY`,
3//! `INCRBYFLOAT`), and the seconds-precision TTL accessor
4//! (`ttl_secs`).
5
6use crate::{KevyError, KevyResult};
7
8use crate::store::ensure_writable;
9use crate::store::{Store, commit_write, store_err};
10
11impl Store {
12    // ---- string SET variants ----------------------------------------
13
14    /// `SETNX key value` — set only if the key does not exist.
15    /// Returns `true` when the SET succeeded; `false` when it was
16    /// vetoed by an existing value.
17    pub fn setnx(&self, key: &[u8], value: &[u8]) -> KevyResult<bool> {
18        ensure_writable(self)?;
19        let mut g = self.wshard(key);
20        let ok = g.store.set(key, value.to_vec(), None, /*nx=*/ true, /*xx=*/ false);
21        if ok {
22            commit_write(&mut g, &[b"SET", key, value, b"NX"])?;
23        }
24        Ok(ok)
25    }
26
27    /// `INCRBYFLOAT key delta` — atomic float increment of a string
28    /// value. Returns the post-increment value parsed as f64.
29    pub fn incrbyfloat(&self, key: &[u8], delta: f64) -> KevyResult<f64> {
30        ensure_writable(self)?;
31        let mut g = self.wshard(key);
32        let new_bytes = g.store.incr_by_float(key, delta).map_err(store_err)?;
33        let delta_str = format!("{delta}");
34        commit_write(&mut g, &[b"INCRBYFLOAT", key, delta_str.as_bytes()])?;
35        std::str::from_utf8(&new_bytes)
36            .ok()
37            .and_then(|s| s.parse::<f64>().ok())
38            .ok_or_else(|| KevyError::Protocol("incrbyfloat result not parseable".into()))
39    }
40
41    /// `DECR key` — atomic decrement by 1.
42    pub fn decr(&self, key: &[u8]) -> KevyResult<i64> {
43        self.incr_by(key, -1)
44    }
45
46    /// `DECRBY key delta` — atomic decrement by `delta`.
47    pub fn decrby(&self, key: &[u8], delta: i64) -> KevyResult<i64> {
48        self.incr_by(key, delta.checked_neg().unwrap_or(i64::MIN.saturating_add(1)))
49    }
50
51    /// `STRLEN key` — length of the string value at `key`; 0 if
52    /// absent. Errors on wrong type.
53    pub fn strlen(&self, key: &[u8]) -> KevyResult<usize> {
54        self.wshard(key).store.strlen(key).map_err(store_err)
55    }
56
57    /// `APPEND key data` — append `data` to the string at `key`.
58    /// Creates the key if absent. Returns the new total length.
59    pub fn append(&self, key: &[u8], data: &[u8]) -> KevyResult<usize> {
60        ensure_writable(self)?;
61        let mut g = self.wshard(key);
62        let new_len = g.store.append(key, data).map_err(store_err)?;
63        commit_write(&mut g, &[b"APPEND", key, data])?;
64        Ok(new_len)
65    }
66
67    // ---- hash conditional set ---------------------------------------
68
69    /// `HSETNX key field value` — set the hash field only if it
70    /// does not already exist. Returns `true` when set; `false`
71    /// when the field existed.
72    pub fn hsetnx(&self, key: &[u8], field: &[u8], value: &[u8]) -> KevyResult<bool> {
73        ensure_writable(self)?;
74        let mut g = self.wshard(key);
75        let ok = g.store.hsetnx(key, field, value).map_err(store_err)?;
76        if ok {
77            commit_write(&mut g, &[b"HSETNX", key, field, value])?;
78        }
79        Ok(ok)
80    }
81
82    // ---- TTL units --------------------------------------------------
83
84    /// `TTL key` — TTL in **seconds** (truncated from ms). `-1`
85    /// when the key has no TTL; `-2` when absent. Matches Redis
86    /// wire semantics for the integer reply.
87    pub fn ttl_secs(&self, key: &[u8]) -> i64 {
88        let ms = self.ttl_ms(key);
89        if ms <= 0 { ms } else { ms / 1000 }
90    }
91}