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            commit_write(&mut g, &kevy_verbs::aof::spop_effect(key, &popped))?;
38        }
39        Ok(popped)
40    }
41
42    /// `SRANDMEMBER key count` — return up to `count` random members
43    /// without removing them.
44    pub fn srandmember(&self, key: &[u8], count: usize) -> KevyResult<Vec<Vec<u8>>> {
45        self.wshard(key).store.srandmember(key, count).map_err(store_err)
46    }
47
48    // ---- sorted set extras ------------------------------------------
49
50    /// `ZRANK key member` — rank (0-based, ascending) of `member`;
51    /// `None` if not present.
52    pub fn zrank(&self, key: &[u8], member: &[u8]) -> KevyResult<Option<usize>> {
53        self.wshard(key).store.zrank(key, member).map_err(store_err)
54    }
55
56    /// `ZCOUNT key min max` — count members whose score falls in
57    /// `[min, max]` (inclusive). Pass `±INFINITY` for open bounds.
58    pub fn zcount(&self, key: &[u8], min: f64, max: f64) -> KevyResult<usize> {
59        self.wshard(key)
60            .store
61            .zcount(key, ScoreBound::inclusive(min), ScoreBound::inclusive(max))
62            .map_err(store_err)
63    }
64
65    /// `ZPOPMIN key count` — atomically remove + return up to `count`
66    /// members with the lowest scores. Pairs are `(member, score)`.
67    pub fn zpopmin(&self, key: &[u8], count: usize) -> KevyResult<Vec<(Vec<u8>, f64)>> {
68        ensure_writable(self)?;
69        let mut g = self.wshard(key);
70        let popped = g.store.zpopmin(key, count).map_err(store_err)?;
71        if !popped.is_empty() {
72            let count_str = format!("{count}");
73            commit_write(&mut g, &[b"ZPOPMIN", key, count_str.as_bytes()])?;
74        }
75        Ok(popped)
76    }
77
78    /// `ZREMRANGEBYRANK key start stop` — remove members in
79    /// `[start, stop]` rank range (inclusive, Redis-style negative
80    /// indexing). Returns count removed.
81    pub fn zremrangebyrank(&self, key: &[u8], start: i64, stop: i64) -> KevyResult<usize> {
82        ensure_writable(self)?;
83        let mut g = self.wshard(key);
84        let removed = g.store.zrem_range_by_rank(key, start, stop).map_err(store_err)?;
85        if removed > 0 {
86            let s = format!("{start}");
87            let e = format!("{stop}");
88            commit_write(&mut g, &[b"ZREMRANGEBYRANK", key, s.as_bytes(), e.as_bytes()])?;
89        }
90        Ok(removed)
91    }
92
93    /// `ZREMRANGEBYSCORE key min max` — remove members with scores
94    /// in `[min, max]` (inclusive). Returns count removed.
95    pub fn zremrangebyscore(&self, key: &[u8], min: f64, max: f64) -> KevyResult<usize> {
96        ensure_writable(self)?;
97        let mut g = self.wshard(key);
98        let removed = g
99            .store
100            .zrem_range_by_score(key, ScoreBound::inclusive(min), ScoreBound::inclusive(max))
101            .map_err(store_err)?;
102        if removed > 0 {
103            let s = format!("{min}");
104            let e = format!("{max}");
105            commit_write(&mut g, &[b"ZREMRANGEBYSCORE", key, s.as_bytes(), e.as_bytes()])?;
106        }
107        Ok(removed)
108    }
109
110    /// `ZREVRANGEBYSCORE key max min` — members with scores in
111    /// `[min, max]` in DESCENDING score order. Inclusive bounds.
112    pub fn zrev_range_by_score(
113        &self,
114        key: &[u8],
115        max: f64,
116        min: f64,
117    ) -> KevyResult<Vec<(Vec<u8>, f64)>> {
118        self.wshard(key)
119            .store
120            .zrev_range_by_score(key, ScoreBound::inclusive(min), ScoreBound::inclusive(max))
121            .map_err(store_err)
122    }
123
124    // ---- list extras -------------------------------------------------
125
126    /// `LSET key idx value` — set the element at `idx` (negative
127    /// indexes count from tail). Errors `NoSuchKey` / `OutOfRange`
128    /// matching Redis.
129    pub fn lset(&self, key: &[u8], idx: i64, value: &[u8]) -> KevyResult<()> {
130        ensure_writable(self)?;
131        let mut g = self.wshard(key);
132        g.store.lset(key, idx, value).map_err(store_err)?;
133        let i = format!("{idx}");
134        commit_write(&mut g, &[b"LSET", key, i.as_bytes(), value])?;
135        Ok(())
136    }
137
138    /// `LTRIM key start stop` — trim list to `[start, stop]`
139    /// inclusive (Redis-style negative indexing).
140    pub fn ltrim(&self, key: &[u8], start: i64, stop: i64) -> KevyResult<()> {
141        ensure_writable(self)?;
142        let mut g = self.wshard(key);
143        g.store.ltrim(key, start, stop).map_err(store_err)?;
144        let s = format!("{start}");
145        let e = format!("{stop}");
146        commit_write(&mut g, &[b"LTRIM", key, s.as_bytes(), e.as_bytes()])?;
147        Ok(())
148    }
149
150    // ---- keyspace extras --------------------------------------------
151
152    /// `RENAME src dst` — atomic rename. Returns `true` when the
153    /// rename happened. Errors when `src` doesn't exist (Redis would
154    /// reply `-ERR no such key`, here `Err(NoSuchKey)`).
155    pub fn rename(&self, src: &[u8], dst: &[u8]) -> KevyResult<bool> {
156        ensure_writable(self)?;
157        // Cross-shard rename is non-trivial; the single-shard
158        // embedded default lands src+dst on the same lock.
159        let mut g = self.wshard(src);
160        let outcome = g.store.rename(src, dst);
161        match outcome {
162            kevy_store::RenameOutcome::Renamed => {
163                commit_write(&mut g, &[b"RENAME", src, dst])?;
164                Ok(true)
165            }
166            kevy_store::RenameOutcome::NoSuchSrc => {
167                Err(KevyError::Store(kevy_store::StoreError::NoSuchKey))
168            }
169            kevy_store::RenameOutcome::DstExists => Ok(false),
170        }
171    }
172
173    /// `RENAMENX src dst` — rename only when `dst` doesn't exist.
174    /// Returns `true` when the rename happened.
175    pub fn renamenx(&self, src: &[u8], dst: &[u8]) -> KevyResult<bool> {
176        ensure_writable(self)?;
177        let mut g = self.wshard(src);
178        let outcome = g.store.rename_nx(src, dst);
179        match outcome {
180            kevy_store::RenameOutcome::Renamed => {
181                commit_write(&mut g, &[b"RENAMENX", src, dst])?;
182                Ok(true)
183            }
184            kevy_store::RenameOutcome::DstExists => Ok(false),
185            kevy_store::RenameOutcome::NoSuchSrc => {
186                Err(KevyError::Store(kevy_store::StoreError::NoSuchKey))
187            }
188        }
189    }
190}