Skip to main content

kevy_embedded/
ops_more.rs

1//! Set membership and pop, sorted-set ranking and pop, list `lset` /
2//! `ltrim`, and key rename: `sismember`, `spop`, `srandmember`,
3//! `zrank`, `zcount`, `zpopmin`, `zremrangebyrank`,
4//! `zremrangebyscore`, `zrev_range_by_score`, `lset`, `ltrim`,
5//! `rename`, `renamenx`.
6//!
7//! Every method wraps a corresponding `kevy_store::Store` method.
8
9use crate::{KevyError, KevyResult};
10
11use kevy_store::ScoreBound;
12
13use crate::store::ensure_writable;
14use crate::store::{Store, commit_write, store_err};
15
16impl Store {
17    // ---- set extras --------------------------------------------------
18
19    /// `SISMEMBER key member` — `true` when `member` is in the set.
20    pub fn sismember(&self, key: &[u8], member: &[u8]) -> KevyResult<bool> {
21        self.wshard(key).store.sismember(key, member).map_err(store_err)
22    }
23
24    /// `SPOP key count` — atomically remove + return up to `count`
25    /// random members.
26    ///
27    /// AOF form: logged as `SREM key <popped…>` (the members actually
28    /// removed), not `SPOP key count` — a random pick replayed against
29    /// a store whose internal layout differs (replica applying frames
30    /// onto snapshot-loaded state) would remove *different* members.
31    /// Redis propagates SPOP the same way.
32    pub fn spop(&self, key: &[u8], count: usize) -> KevyResult<Vec<Vec<u8>>> {
33        ensure_writable(self)?;
34        let mut g = self.wshard(key);
35        let popped = g.store.spop(key, count).map_err(store_err)?;
36        if !popped.is_empty() {
37            let mut argv: Vec<&[u8]> = Vec::with_capacity(2 + popped.len());
38            argv.push(b"SREM");
39            argv.push(key);
40            argv.extend(popped.iter().map(Vec::as_slice));
41            commit_write(&mut g, &argv)?;
42        }
43        Ok(popped)
44    }
45
46    /// `SRANDMEMBER key count` — return up to `count` random members
47    /// without removing them.
48    pub fn srandmember(&self, key: &[u8], count: usize) -> KevyResult<Vec<Vec<u8>>> {
49        self.wshard(key).store.srandmember(key, count).map_err(store_err)
50    }
51
52    // ---- sorted set extras ------------------------------------------
53
54    /// `ZRANK key member` — rank (0-based, ascending) of `member`;
55    /// `None` if not present.
56    pub fn zrank(&self, key: &[u8], member: &[u8]) -> KevyResult<Option<usize>> {
57        self.wshard(key).store.zrank(key, member).map_err(store_err)
58    }
59
60    /// `ZCOUNT key min max` — count members whose score falls in
61    /// `[min, max]` (inclusive). Pass `±INFINITY` for open bounds.
62    pub fn zcount(&self, key: &[u8], min: f64, max: f64) -> KevyResult<usize> {
63        self.wshard(key)
64            .store
65            .zcount(
66                key,
67                ScoreBound { value: min, exclusive: false },
68                ScoreBound { value: max, exclusive: false },
69            )
70            .map_err(store_err)
71    }
72
73    /// `ZPOPMIN key count` — atomically remove + return up to `count`
74    /// members with the lowest scores. Pairs are `(member, score)`.
75    pub fn zpopmin(&self, key: &[u8], count: usize) -> KevyResult<Vec<(Vec<u8>, f64)>> {
76        ensure_writable(self)?;
77        let mut g = self.wshard(key);
78        let popped = g.store.zpopmin(key, count).map_err(store_err)?;
79        if !popped.is_empty() {
80            let count_str = format!("{count}");
81            commit_write(&mut g, &[b"ZPOPMIN", key, count_str.as_bytes()])?;
82        }
83        Ok(popped)
84    }
85
86    /// `ZREMRANGEBYRANK key start stop` — remove members in
87    /// `[start, stop]` rank range (inclusive, Redis-style negative
88    /// indexing). Returns count removed.
89    pub fn zremrangebyrank(&self, key: &[u8], start: i64, stop: i64) -> KevyResult<usize> {
90        ensure_writable(self)?;
91        let mut g = self.wshard(key);
92        let removed = g.store.zrem_range_by_rank(key, start, stop).map_err(store_err)?;
93        if removed > 0 {
94            let s = format!("{start}");
95            let e = format!("{stop}");
96            commit_write(&mut g, &[b"ZREMRANGEBYRANK", key, s.as_bytes(), e.as_bytes()])?;
97        }
98        Ok(removed)
99    }
100
101    /// `ZREMRANGEBYSCORE key min max` — remove members with scores
102    /// in `[min, max]` (inclusive). Returns count removed.
103    pub fn zremrangebyscore(&self, key: &[u8], min: f64, max: f64) -> KevyResult<usize> {
104        ensure_writable(self)?;
105        let mut g = self.wshard(key);
106        let removed = g
107            .store
108            .zrem_range_by_score(
109                key,
110                ScoreBound { value: min, exclusive: false },
111                ScoreBound { value: max, exclusive: false },
112            )
113            .map_err(store_err)?;
114        if removed > 0 {
115            let s = format!("{min}");
116            let e = format!("{max}");
117            commit_write(&mut g, &[b"ZREMRANGEBYSCORE", key, s.as_bytes(), e.as_bytes()])?;
118        }
119        Ok(removed)
120    }
121
122    /// `ZREVRANGEBYSCORE key max min` — members with scores in
123    /// `[min, max]` in DESCENDING score order. Inclusive bounds.
124    pub fn zrev_range_by_score(
125        &self,
126        key: &[u8],
127        max: f64,
128        min: f64,
129    ) -> KevyResult<Vec<(Vec<u8>, f64)>> {
130        self.wshard(key)
131            .store
132            .zrev_range_by_score(
133                key,
134                ScoreBound { value: min, exclusive: false },
135                ScoreBound { value: max, exclusive: false },
136            )
137            .map_err(store_err)
138    }
139
140    // ---- list extras -------------------------------------------------
141
142    /// `LSET key idx value` — set the element at `idx` (negative
143    /// indexes count from tail). Errors `NoSuchKey` / `OutOfRange`
144    /// matching Redis.
145    pub fn lset(&self, key: &[u8], idx: i64, value: &[u8]) -> KevyResult<()> {
146        ensure_writable(self)?;
147        let mut g = self.wshard(key);
148        g.store.lset(key, idx, value).map_err(store_err)?;
149        let i = format!("{idx}");
150        commit_write(&mut g, &[b"LSET", key, i.as_bytes(), value])?;
151        Ok(())
152    }
153
154    /// `LTRIM key start stop` — trim list to `[start, stop]`
155    /// inclusive (Redis-style negative indexing).
156    pub fn ltrim(&self, key: &[u8], start: i64, stop: i64) -> KevyResult<()> {
157        ensure_writable(self)?;
158        let mut g = self.wshard(key);
159        g.store.ltrim(key, start, stop).map_err(store_err)?;
160        let s = format!("{start}");
161        let e = format!("{stop}");
162        commit_write(&mut g, &[b"LTRIM", key, s.as_bytes(), e.as_bytes()])?;
163        Ok(())
164    }
165
166    // ---- keyspace extras --------------------------------------------
167
168    /// `RENAME src dst` — atomic rename. Returns `true` when the
169    /// rename happened. Errors when `src` doesn't exist (Redis would
170    /// reply `-ERR no such key`, here `Err(NoSuchKey)`).
171    pub fn rename(&self, src: &[u8], dst: &[u8]) -> KevyResult<bool> {
172        ensure_writable(self)?;
173        // Cross-shard rename is non-trivial; the single-shard
174        // embedded default lands src+dst on the same lock.
175        let mut g = self.wshard(src);
176        let outcome = g.store.rename(src, dst, false);
177        match outcome {
178            kevy_store::RenameOutcome::Renamed => {
179                commit_write(&mut g, &[b"RENAME", src, dst])?;
180                Ok(true)
181            }
182            kevy_store::RenameOutcome::NoSuchSrc => {
183                Err(KevyError::Store(kevy_store::StoreError::NoSuchKey))
184            }
185            kevy_store::RenameOutcome::DstExists => Ok(false),
186        }
187    }
188
189    /// `RENAMENX src dst` — rename only when `dst` doesn't exist.
190    /// Returns `true` when the rename happened.
191    pub fn renamenx(&self, src: &[u8], dst: &[u8]) -> KevyResult<bool> {
192        ensure_writable(self)?;
193        let mut g = self.wshard(src);
194        let outcome = g.store.rename(src, dst, true);
195        match outcome {
196            kevy_store::RenameOutcome::Renamed => {
197                commit_write(&mut g, &[b"RENAMENX", src, dst])?;
198                Ok(true)
199            }
200            kevy_store::RenameOutcome::DstExists => Ok(false),
201            kevy_store::RenameOutcome::NoSuchSrc => {
202                Err(KevyError::Store(kevy_store::StoreError::NoSuchKey))
203            }
204        }
205    }
206}