Skip to main content

kevy_embedded/
ops_p2.rs

1//! Hash field reads, sorted-set range queries, list slice access, and
2//! the atomic single-call string helpers `getset` / `getdel`.
3//!
4//! Every method is a thin facade over the corresponding
5//! `kevy_store::Store` method, with `commit_write` AOF logging on the
6//! write paths.
7
8use crate::KevyResult;
9
10use kevy_store::ScoreBound;
11
12use crate::store::ensure_writable;
13use crate::store::{Store, commit_write, store_err};
14
15impl Store {
16    // ---- hash mass-getters --------------------------------------------
17
18    /// `HGETALL key` — every `(field, value)` pair in `key`'s hash, in
19    /// arbitrary order. Empty when `key` is absent. Errors on wrong type.
20    pub fn hgetall(&self, key: &[u8]) -> KevyResult<Vec<(Vec<u8>, Vec<u8>)>> {
21        let flat = self.wshard(key).store.hgetall(key).map_err(store_err)?;
22        // kevy-store returns [f0, v0, f1, v1, ...] — pair them up.
23        let mut out = Vec::with_capacity(flat.len() / 2);
24        let mut it = flat.into_iter();
25        while let (Some(f), Some(v)) = (it.next(), it.next()) {
26            out.push((f, v));
27        }
28        Ok(out)
29    }
30
31    /// `HEXISTS key field` — `true` when `field` is present.
32    pub fn hexists(&self, key: &[u8], field: &[u8]) -> KevyResult<bool> {
33        self.wshard(key).store.hexists(key, field).map_err(store_err)
34    }
35
36    /// `HLEN key` — number of fields; 0 when absent.
37    pub fn hlen(&self, key: &[u8]) -> KevyResult<usize> {
38        self.wshard(key).store.hlen(key).map_err(store_err)
39    }
40
41    /// `HKEYS key` — every field name in `key`'s hash.
42    pub fn hkeys(&self, key: &[u8]) -> KevyResult<Vec<Vec<u8>>> {
43        self.wshard(key).store.hkeys(key).map_err(store_err)
44    }
45
46    /// `HRANDFIELD key count` — random field names, distinct for a
47    /// positive count and with repeats allowed for a negative one.
48    ///
49    /// ```
50    /// let s = kevy_embedded::Store::open(kevy_embedded::Config::default())?;
51    /// s.hset(b"h", &[(b"f1", b"v1"), (b"f2", b"v2")])?;
52    /// assert_eq!(s.hrandfield(b"h", 5)?.len(), 2);
53    /// assert_eq!(s.hrandfield(b"h", -5)?.len(), 5);
54    /// # Ok::<(), kevy_embedded::KevyError>(())
55    /// ```
56    pub fn hrandfield(&self, key: &[u8], count: i64) -> KevyResult<Vec<Vec<u8>>> {
57        self.wshard(key).store.hrandfield(key, count).map_err(store_err)
58    }
59
60    /// `HRANDFIELD key count WITHVALUES` — [`Self::hrandfield`] with each
61    /// field paired with its value.
62    ///
63    /// ```
64    /// let s = kevy_embedded::Store::open(kevy_embedded::Config::default())?;
65    /// s.hset(b"h", &[(b"f", b"v")])?;
66    /// assert_eq!(s.hrandfield_with_values(b"h", 1)?, vec![(b"f".to_vec(), b"v".to_vec())]);
67    /// # Ok::<(), kevy_embedded::KevyError>(())
68    /// ```
69    pub fn hrandfield_with_values(
70        &self,
71        key: &[u8],
72        count: i64,
73    ) -> KevyResult<kevy_store::FieldValuePairs> {
74        self.wshard(key).store.hrandfield_with_values(key, count).map_err(store_err)
75    }
76
77    /// `HVALS key` — every value in `key`'s hash.
78    pub fn hvals(&self, key: &[u8]) -> KevyResult<Vec<Vec<u8>>> {
79        self.wshard(key).store.hvals(key).map_err(store_err)
80    }
81
82    /// `HMGET key field [field ...]` — read multiple fields in one
83    /// call. `None` per requested field that is absent.
84    pub fn hmget(&self, key: &[u8], fields: &[&[u8]]) -> KevyResult<Vec<Option<Vec<u8>>>> {
85        self.wshard(key).store.hmget(key, fields).map_err(store_err)
86    }
87
88    /// `HINCRBY key field delta` — atomic integer increment of a hash
89    /// field. Returns the post-increment value.
90    pub fn hincrby(&self, key: &[u8], field: &[u8], delta: i64) -> KevyResult<i64> {
91        ensure_writable(self)?;
92        let mut g = self.wshard(key);
93        let new_val = g.store.hincrby(key, field, delta).map_err(store_err)?;
94        let delta_str = format!("{delta}");
95        commit_write(&mut g, &[b"HINCRBY", key, field, delta_str.as_bytes()])?;
96        Ok(new_val)
97    }
98
99    // ---- zset mass-readers + atomic incr -----------------------------
100
101    /// `ZRANGE key start stop WITHSCORES` — members in ascending score
102    /// order between rank `start..=stop` (Redis-style inclusive
103    /// indexing; negatives count from the tail). Returns `(member,
104    /// score)` pairs.
105    pub fn zrange(&self, key: &[u8], start: i64, stop: i64) -> KevyResult<Vec<(Vec<u8>, f64)>> {
106        self.wshard(key).store.zrange(key, start, stop).map_err(store_err)
107    }
108
109    /// `ZREVRANGE key start stop WITHSCORES` — `zrange` with the order
110    /// reversed (highest score first). The `start..=stop` indexing is
111    /// against the reversed list, matching Redis semantics.
112    pub fn zrevrange(&self, key: &[u8], start: i64, stop: i64) -> KevyResult<Vec<(Vec<u8>, f64)>> {
113        self.wshard(key).store.zrevrange(key, start, stop).map_err(store_err)
114    }
115
116    /// `ZRANGEBYSCORE` — score-range read. `min` / `max` are
117    /// inclusive; pass `f64::NEG_INFINITY` / `f64::INFINITY` for open
118    /// bounds. Returns `(member, score)` pairs in ascending score
119    /// order. Exclusive bounds are `ZRANGEBYSCORE (` syntax in Redis;
120    /// expose via the dedicated [`Self::zrange_by_score_excl`].
121    pub fn zrange_by_score(
122        &self,
123        key: &[u8],
124        min: f64,
125        max: f64,
126    ) -> KevyResult<Vec<(Vec<u8>, f64)>> {
127        self.wshard(key)
128            .store
129            .zrange_by_score(key, ScoreBound::inclusive(min), ScoreBound::inclusive(max))
130            .map_err(store_err)
131    }
132
133    /// Same as [`Self::zrange_by_score`] but with explicit
134    /// inclusive/exclusive control on each bound (`(min` / `(max` in
135    /// Redis syntax).
136    pub fn zrange_by_score_excl(
137        &self,
138        key: &[u8],
139        min: ScoreBound,
140        max: ScoreBound,
141    ) -> KevyResult<Vec<(Vec<u8>, f64)>> {
142        self.wshard(key).store.zrange_by_score(key, min, max).map_err(store_err)
143    }
144
145    /// `ZRANGEBYSCORE key min max LIMIT offset count` — score-range
146    /// read with pagination (closes the embedded LIMIT gap —
147    /// the server parser always had it).
148    pub fn zrange_by_score_limit(
149        &self,
150        key: &[u8],
151        min: f64,
152        max: f64,
153        offset: usize,
154        count: usize,
155    ) -> KevyResult<Vec<(Vec<u8>, f64)>> {
156        let all = self.zrange_by_score(key, min, max)?;
157        Ok(all.into_iter().skip(offset).take(count).collect())
158    }
159
160    /// `ZREVRANGEBYSCORE key max min LIMIT offset count` — descending
161    /// score-range read with pagination.
162    pub fn zrevrange_by_score_limit(
163        &self,
164        key: &[u8],
165        max: f64,
166        min: f64,
167        offset: usize,
168        count: usize,
169    ) -> KevyResult<Vec<(Vec<u8>, f64)>> {
170        let mut all = self.zrange_by_score(key, min, max)?;
171        all.reverse();
172        Ok(all.into_iter().skip(offset).take(count).collect())
173    }
174
175    /// `zpopmin_below` — pop up to `count` lowest members with
176    /// score strictly `< below` (delayed-job "pop what's due").
177    /// AOF logs the effect (`ZREM` of the popped members).
178    pub fn zpopmin_below(
179        &self,
180        key: &[u8],
181        below: f64,
182        count: usize,
183    ) -> KevyResult<Vec<(Vec<u8>, f64)>> {
184        ensure_writable(self)?;
185        let mut g = self.wshard(key);
186        let items = g.store.zpopmin_below(key, below, count).map_err(store_err)?;
187        if !items.is_empty() {
188            let mut argv: Vec<&[u8]> = Vec::with_capacity(2 + items.len());
189            argv.push(b"ZREM");
190            argv.push(key);
191            argv.extend(items.iter().map(|(m, _)| m.as_slice()));
192            commit_write(&mut g, &argv)?;
193        }
194        Ok(items)
195    }
196
197    /// `ZINCRBY key delta member` — atomic float increment of a member's
198    /// score. Returns the post-increment score.
199    pub fn zincrby(&self, key: &[u8], delta: f64, member: &[u8]) -> KevyResult<f64> {
200        ensure_writable(self)?;
201        let mut g = self.wshard(key);
202        let new_score = g.store.zincrby(key, delta, member).map_err(store_err)?;
203        let delta_str = format!("{delta}");
204        commit_write(&mut g, &[b"ZINCRBY", key, delta_str.as_bytes(), member])?;
205        Ok(new_score)
206    }
207
208    // ---- list slice + index ops --------------------------------------
209
210    /// `LRANGE key start stop` — list slice. Negative indices count
211    /// from the tail. Empty when absent.
212    pub fn lrange(&self, key: &[u8], start: i64, stop: i64) -> KevyResult<Vec<Vec<u8>>> {
213        self.wshard(key).store.lrange(key, start, stop).map_err(store_err)
214    }
215
216    /// `LINDEX key idx` — element at index `idx`; `None` out of range.
217    pub fn lindex(&self, key: &[u8], idx: i64) -> KevyResult<Option<Vec<u8>>> {
218        self.wshard(key).store.lindex(key, idx).map_err(store_err)
219    }
220
221    /// `LREM key count value` — remove up to `|count|` occurrences of
222    /// `value`. `count > 0` from head, `count < 0` from tail,
223    /// `count == 0` all. Returns the count actually removed.
224    pub fn lrem(&self, key: &[u8], count: i64, value: &[u8]) -> KevyResult<usize> {
225        ensure_writable(self)?;
226        let mut g = self.wshard(key);
227        let removed = g.store.lrem(key, count, value).map_err(store_err)?;
228        if removed > 0 {
229            let count_str = format!("{count}");
230            commit_write(&mut g, &[b"LREM", key, count_str.as_bytes(), value])?;
231        }
232        Ok(removed)
233    }
234
235    // ---- string single-call atomic patterns --------------------------
236
237    /// `GETSET key new` — set `key` to `new`, return the previous
238    /// value (or `None` when `key` was absent).
239    pub fn getset(&self, key: &[u8], new: &[u8]) -> KevyResult<Option<Vec<u8>>> {
240        ensure_writable(self)?;
241        let mut g = self.wshard(key);
242        let prev = g.store.getset(key, new.to_vec()).map_err(store_err)?;
243        commit_write(&mut g, &[b"SET", key, new])?;
244        Ok(prev)
245    }
246
247    /// `GETDEL key` — delete `key`, return the previous value
248    /// (`None` when `key` was absent).
249    pub fn getdel(&self, key: &[u8]) -> KevyResult<Option<Vec<u8>>> {
250        ensure_writable(self)?;
251        let mut g = self.wshard(key);
252        let prev = g.store.getdel(key).map_err(store_err)?;
253        if prev.is_some() {
254            commit_write(&mut g, &[b"DEL", key])?;
255        }
256        Ok(prev)
257    }
258}