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(
90        &self,
91        key: &[u8],
92        start: i64,
93        stop: i64,
94    ) -> KevyResult<usize> {
95        ensure_writable(self)?;
96        let mut g = self.wshard(key);
97        let removed = g.store.zrem_range_by_rank(key, start, stop).map_err(store_err)?;
98        if removed > 0 {
99            let s = format!("{start}");
100            let e = format!("{stop}");
101            commit_write(&mut g, &[b"ZREMRANGEBYRANK", key, s.as_bytes(), e.as_bytes()])?;
102        }
103        Ok(removed)
104    }
105
106    /// `ZREMRANGEBYSCORE key min max` — remove members with scores
107    /// in `[min, max]` (inclusive). Returns count removed.
108    pub fn zremrangebyscore(
109        &self,
110        key: &[u8],
111        min: f64,
112        max: f64,
113    ) -> KevyResult<usize> {
114        ensure_writable(self)?;
115        let mut g = self.wshard(key);
116        let removed = g
117            .store
118            .zrem_range_by_score(
119                key,
120                ScoreBound { value: min, exclusive: false },
121                ScoreBound { value: max, exclusive: false },
122            )
123            .map_err(store_err)?;
124        if removed > 0 {
125            let s = format!("{min}");
126            let e = format!("{max}");
127            commit_write(&mut g, &[b"ZREMRANGEBYSCORE", key, s.as_bytes(), e.as_bytes()])?;
128        }
129        Ok(removed)
130    }
131
132    /// `ZREVRANGEBYSCORE key max min` — members with scores in
133    /// `[min, max]` in DESCENDING score order. Inclusive bounds.
134    pub fn zrev_range_by_score(
135        &self,
136        key: &[u8],
137        max: f64,
138        min: f64,
139    ) -> KevyResult<Vec<(Vec<u8>, f64)>> {
140        self.wshard(key)
141            .store
142            .zrev_range_by_score(
143                key,
144                ScoreBound { value: min, exclusive: false },
145                ScoreBound { value: max, exclusive: false },
146            )
147            .map_err(store_err)
148    }
149
150    // ---- list extras -------------------------------------------------
151
152    /// `LSET key idx value` — set the element at `idx` (negative
153    /// indexes count from tail). Errors `NoSuchKey` / `OutOfRange`
154    /// matching Redis.
155    pub fn lset(&self, key: &[u8], idx: i64, value: &[u8]) -> KevyResult<()> {
156        ensure_writable(self)?;
157        let mut g = self.wshard(key);
158        g.store.lset(key, idx, value).map_err(store_err)?;
159        let i = format!("{idx}");
160        commit_write(&mut g, &[b"LSET", key, i.as_bytes(), value])?;
161        Ok(())
162    }
163
164    /// `LTRIM key start stop` — trim list to `[start, stop]`
165    /// inclusive (Redis-style negative indexing).
166    pub fn ltrim(&self, key: &[u8], start: i64, stop: i64) -> KevyResult<()> {
167        ensure_writable(self)?;
168        let mut g = self.wshard(key);
169        g.store.ltrim(key, start, stop).map_err(store_err)?;
170        let s = format!("{start}");
171        let e = format!("{stop}");
172        commit_write(&mut g, &[b"LTRIM", key, s.as_bytes(), e.as_bytes()])?;
173        Ok(())
174    }
175
176    // ---- keyspace extras --------------------------------------------
177
178    /// `RENAME src dst` — atomic rename. Returns `true` when the
179    /// rename happened. Errors when `src` doesn't exist (Redis would
180    /// reply `-ERR no such key`, here `Err(NoSuchKey)`).
181    pub fn rename(&self, src: &[u8], dst: &[u8]) -> KevyResult<bool> {
182        ensure_writable(self)?;
183        // Cross-shard rename is non-trivial; the single-shard
184        // embedded default lands src+dst on the same lock.
185        let mut g = self.wshard(src);
186        let outcome = g.store.rename(src, dst, false);
187        match outcome {
188            kevy_store::RenameOutcome::Renamed => {
189                commit_write(&mut g, &[b"RENAME", src, dst])?;
190                Ok(true)
191            }
192            kevy_store::RenameOutcome::NoSuchSrc => {
193                Err(KevyError::Store(kevy_store::StoreError::NoSuchKey))
194            }
195            kevy_store::RenameOutcome::DstExists => Ok(false),
196        }
197    }
198
199    /// `RENAMENX src dst` — rename only when `dst` doesn't exist.
200    /// Returns `true` when the rename happened.
201    pub fn renamenx(&self, src: &[u8], dst: &[u8]) -> KevyResult<bool> {
202        ensure_writable(self)?;
203        let mut g = self.wshard(src);
204        let outcome = g.store.rename(src, dst, true);
205        match outcome {
206            kevy_store::RenameOutcome::Renamed => {
207                commit_write(&mut g, &[b"RENAMENX", src, dst])?;
208                Ok(true)
209            }
210            kevy_store::RenameOutcome::DstExists => Ok(false),
211            kevy_store::RenameOutcome::NoSuchSrc => {
212                Err(KevyError::Store(kevy_store::StoreError::NoSuchKey))
213            }
214        }
215    }
216}