Skip to main content

kevy_store/
zset_range.rs

1//! `Store` sorted-set range / pop / range-removal commands
2//! (`ZRANGE` / `ZRANGEBYSCORE` / `ZCOUNT` / `ZPOPMIN` / `ZREMRANGEBY*`).
3//! Split out of `zset.rs` to keep it under the 500-LOC house cap; the
4//! write-path core (`ZADD` / `ZREM` / `ZINCRBY`) stays there.
5
6#[cfg(not(feature = "std"))]
7use crate::nostd_prelude::*;
8use crate::util::range_bounds;
9use crate::value::{ScoreBound, Value};
10use crate::{Store, StoreError};
11
12impl Store {
13    /// `ZRANGE key start stop` by rank.
14    pub fn zrange(
15        &mut self,
16        key: &[u8],
17        start: i64,
18        stop: i64,
19    ) -> Result<Vec<(Vec<u8>, f64)>, StoreError> {
20        match self.live_entry(key) {
21            None => Ok(Vec::new()),
22            Some(e) => match &e.value {
23                Value::ZSet(z) => Ok(match range_bounds(start, stop, z.len()) {
24                    None => Vec::new(),
25                    // O(log N) seek to the start rank, then walk M items —
26                    // no skip-walk from the front.
27                    Some((s, end)) => z
28                        .ordered_from(s)
29                        .take(end - s + 1)
30                        .map(|(m, sc)| (m.to_vec(), sc))
31                        .collect(),
32                }),
33                Value::SegZSet(z) => Ok(match range_bounds(start, stop, z.len()) {
34                    None => Vec::new(),
35                    Some((s, end)) => z
36                        .ordered_from(s)
37                        .take(end - s + 1)
38                        .map(|(m, sc)| (m.to_vec(), sc))
39                        .collect(),
40                }),
41                Value::SmallZSetInline(z) => {
42                    let mut entries: Vec<(Vec<u8>, f64)> =
43                        z.iter().map(|(m, sc)| (m.to_vec(), sc)).collect();
44                    entries.sort_by(|a, b| a.1.total_cmp(&b.1).then_with(|| a.0.cmp(&b.0)));
45                    Ok(match range_bounds(start, stop, entries.len()) {
46                        None => Vec::new(),
47                        Some((s, end)) => entries.into_iter().skip(s).take(end - s + 1).collect(),
48                    })
49                }
50                _ => Err(StoreError::WrongType),
51            },
52        }
53    }
54
55    /// `ZRANGEBYSCORE`.
56    pub fn zrange_by_score(
57        &mut self,
58        key: &[u8],
59        min: ScoreBound,
60        max: ScoreBound,
61    ) -> Result<Vec<(Vec<u8>, f64)>, StoreError> {
62        match self.live_entry(key) {
63            None => Ok(Vec::new()),
64            Some(e) => match &e.value {
65                Value::ZSet(z) => {
66                    // Two O(log N) rank descents bracket the score range,
67                    // then only the M matches are walked — no scan+filter.
68                    let lo = z.score_start_rank(&min);
69                    let hi = z.score_end_rank(&max);
70                    Ok(z.ordered_from(lo)
71                        .take(hi.saturating_sub(lo))
72                        .map(|(m, sc)| (m.to_vec(), sc))
73                        .collect())
74                }
75                Value::SegZSet(z) => {
76                    let lo = z.score_start_rank(&min);
77                    let hi = z.score_end_rank(&max);
78                    Ok(z.ordered_from(lo)
79                        .take(hi.saturating_sub(lo))
80                        .map(|(m, sc)| (m.to_vec(), sc))
81                        .collect())
82                }
83                Value::SmallZSetInline(z) => {
84                    let mut entries: Vec<(Vec<u8>, f64)> = z
85                        .iter()
86                        .filter(|(_, sc)| min.ge_ok(*sc) && max.le_ok(*sc))
87                        .map(|(m, sc)| (m.to_vec(), sc))
88                        .collect();
89                    entries.sort_by(|a, b| a.1.total_cmp(&b.1).then_with(|| a.0.cmp(&b.0)));
90                    Ok(entries)
91                }
92                _ => Err(StoreError::WrongType),
93            },
94        }
95    }
96
97    /// `ZCOUNT`.
98    pub fn zcount(
99        &mut self,
100        key: &[u8],
101        min: ScoreBound,
102        max: ScoreBound,
103    ) -> Result<usize, StoreError> {
104        match self.live_entry(key) {
105            None => Ok(0),
106            Some(e) => match &e.value {
107                // Two rank descents — O(log N), no iteration at all.
108                Value::ZSet(z) => {
109                    Ok(z.score_end_rank(&max).saturating_sub(z.score_start_rank(&min)))
110                }
111                Value::SegZSet(z) => {
112                    Ok(z.score_end_rank(&max).saturating_sub(z.score_start_rank(&min)))
113                }
114                Value::SmallZSetInline(z) => {
115                    Ok(z.iter().filter(|(_, sc)| min.ge_ok(*sc) && max.le_ok(*sc)).count())
116                }
117                _ => Err(StoreError::WrongType),
118            },
119        }
120    }
121
122    /// `ZPOPMIN key [count]` — pop and return the `count` lowest-scored
123    /// members (ascending by `(score, member)`). Returns `(member,
124    /// score)` pairs in pop order; empty when the key is absent / empty.
125    pub fn zpopmin(&mut self, key: &[u8], count: usize) -> Result<Vec<(Vec<u8>, f64)>, StoreError> {
126        if count == 0 {
127            // Validate type up-front so ZPOPMIN k 0 against a wrong-type
128            // key still reports WRONGTYPE (Redis behaviour).
129            if let Some(e) = self.live_entry(key) {
130                match &e.value {
131                    Value::ZSet(_) | Value::SegZSet(_) | Value::SmallZSetInline(_) => {}
132                    _ => return Err(StoreError::WrongType),
133                }
134            }
135            return Ok(Vec::new());
136        }
137        // Snapshot the lowest `count` members first (immutable borrow),
138        // then remove them via the shared zrem path (which handles the
139        // encoding, weight accounting, and empty-key cleanup uniformly).
140        let to_pop: Vec<(Vec<u8>, f64)> = match self.live_entry(key) {
141            None => return Ok(Vec::new()),
142            Some(e) => match &e.value {
143                Value::ZSet(z) => z.ordered().take(count).map(|(m, sc)| (m.to_vec(), sc)).collect(),
144                Value::SegZSet(z) => {
145                    z.ordered().take(count).map(|(m, sc)| (m.to_vec(), sc)).collect()
146                }
147                Value::SmallZSetInline(z) => {
148                    let mut entries: Vec<(Vec<u8>, f64)> =
149                        z.iter().map(|(m, sc)| (m.to_vec(), sc)).collect();
150                    entries.sort_by(|a, b| a.1.total_cmp(&b.1).then_with(|| a.0.cmp(&b.0)));
151                    entries.into_iter().take(count).collect()
152                }
153                _ => return Err(StoreError::WrongType),
154            },
155        };
156        if to_pop.is_empty() {
157            return Ok(to_pop);
158        }
159        let borrowed: Vec<&[u8]> = to_pop.iter().map(|(m, _)| m.as_slice()).collect();
160        self.zrem(key, &borrowed)?;
161        Ok(to_pop)
162    }
163
164    /// `zpopmin_below` — pop up to `count` lowest-scored members
165    /// whose score is `< below` (strictly). The delayed-job primitive:
166    /// "pop everything that is due" in one atomic call (score = due
167    /// time, `below` = now). Absent key = empty; wrong type errors.
168    pub fn zpopmin_below(
169        &mut self,
170        key: &[u8],
171        below: f64,
172        count: usize,
173    ) -> Result<Vec<(Vec<u8>, f64)>, StoreError> {
174        if count == 0 {
175            if let Some(e) = self.live_entry(key) {
176                match &e.value {
177                    Value::ZSet(_) | Value::SegZSet(_) | Value::SmallZSetInline(_) => {}
178                    _ => return Err(StoreError::WrongType),
179                }
180            }
181            return Ok(Vec::new());
182        }
183        let to_pop: Vec<(Vec<u8>, f64)> = match self.live_entry(key) {
184            None => return Ok(Vec::new()),
185            Some(e) => match &e.value {
186                Value::ZSet(z) => z
187                    .ordered()
188                    .take_while(|(_, sc)| *sc < below)
189                    .take(count)
190                    .map(|(m, sc)| (m.to_vec(), sc))
191                    .collect(),
192                Value::SegZSet(z) => z
193                    .ordered()
194                    .take_while(|(_, sc)| *sc < below)
195                    .take(count)
196                    .map(|(m, sc)| (m.to_vec(), sc))
197                    .collect(),
198                Value::SmallZSetInline(z) => {
199                    let mut entries: Vec<(Vec<u8>, f64)> =
200                        z.iter().map(|(m, sc)| (m.to_vec(), sc)).collect();
201                    entries.sort_by(|a, b| a.1.total_cmp(&b.1).then_with(|| a.0.cmp(&b.0)));
202                    entries.into_iter().take_while(|(_, sc)| *sc < below).take(count).collect()
203                }
204                _ => return Err(StoreError::WrongType),
205            },
206        };
207        if to_pop.is_empty() {
208            return Ok(to_pop);
209        }
210        let borrowed: Vec<&[u8]> = to_pop.iter().map(|(m, _)| m.as_slice()).collect();
211        self.zrem(key, &borrowed)?;
212        Ok(to_pop)
213    }
214
215    /// `ZREMRANGEBYRANK key start stop` — remove members in the rank
216    /// range `[start, stop]` (inclusive, negative indices count from
217    /// the tail). Returns the number of members removed.
218    pub fn zrem_range_by_rank(
219        &mut self,
220        key: &[u8],
221        start: i64,
222        stop: i64,
223    ) -> Result<usize, StoreError> {
224        let to_remove: Vec<Vec<u8>> = match self.live_entry(key) {
225            None => return Ok(0),
226            Some(e) => match &e.value {
227                Value::ZSet(z) => match crate::util::range_bounds(start, stop, z.len()) {
228                    None => return Ok(0),
229                    // Seek to the start rank (O(log N)), collect the M hits.
230                    Some((s, end)) => {
231                        z.ordered_from(s).take(end - s + 1).map(|(m, _)| m.to_vec()).collect()
232                    }
233                },
234                Value::SegZSet(z) => match crate::util::range_bounds(start, stop, z.len()) {
235                    None => return Ok(0),
236                    Some((s, end)) => {
237                        z.ordered_from(s).take(end - s + 1).map(|(m, _)| m.to_vec()).collect()
238                    }
239                },
240                Value::SmallZSetInline(z) => {
241                    let mut entries: Vec<(Vec<u8>, f64)> =
242                        z.iter().map(|(m, sc)| (m.to_vec(), sc)).collect();
243                    entries.sort_by(|a, b| a.1.total_cmp(&b.1).then_with(|| a.0.cmp(&b.0)));
244                    match crate::util::range_bounds(start, stop, entries.len()) {
245                        None => return Ok(0),
246                        Some((s, end)) => {
247                            entries.into_iter().skip(s).take(end - s + 1).map(|(m, _)| m).collect()
248                        }
249                    }
250                }
251                _ => return Err(StoreError::WrongType),
252            },
253        };
254        if to_remove.is_empty() {
255            return Ok(0);
256        }
257        let borrowed: Vec<&[u8]> = to_remove.iter().map(Vec::as_slice).collect();
258        self.zrem(key, &borrowed)
259    }
260
261    /// `ZREMRANGEBYSCORE key min max` — remove every member whose score
262    /// satisfies `min ≤ score ≤ max` (with `(` for exclusive bounds via
263    /// `ScoreBound`). Returns the number removed.
264    pub fn zrem_range_by_score(
265        &mut self,
266        key: &[u8],
267        min: ScoreBound,
268        max: ScoreBound,
269    ) -> Result<usize, StoreError> {
270        // Reuse zrange_by_score's bound logic to materialise the hit set
271        // — keeps inline / heap parity in one place.
272        let hits = self.zrange_by_score(key, min, max)?;
273        if hits.is_empty() {
274            // Still need to honour wrong-type errors that zrange_by_score
275            // already surfaced; here Ok([]) means empty match, not type
276            // mismatch, so it's safe to early-return.
277            return Ok(0);
278        }
279        let borrowed: Vec<&[u8]> = hits.iter().map(|(m, _)| m.as_slice()).collect();
280        self.zrem(key, &borrowed)
281    }
282
283    /// `ZREVRANGEBYSCORE` — `zrange_by_score` reversed. Bounds are
284    /// passed in the `(min, max)` order already (the caller is
285    /// responsible for swapping the user-facing `max first, min second`
286    /// at the dispatch layer).
287    pub fn zrev_range_by_score(
288        &mut self,
289        key: &[u8],
290        min: ScoreBound,
291        max: ScoreBound,
292    ) -> Result<Vec<(Vec<u8>, f64)>, StoreError> {
293        let mut v = self.zrange_by_score(key, min, max)?;
294        v.reverse();
295        Ok(v)
296    }
297}
298
299impl Store {
300    /// `ZREVRANGE key start stop` — the rank window counted from the
301    /// high end.
302    ///
303    /// One implementation, called by both the server's dispatch and the
304    /// embedded facade. Each had written its own before this existed,
305    /// and both had written the same bug: a positive start was clamped
306    /// up to the last rank, so `ZREVRANGE z 5 10` on a three-member set
307    /// answered one member where Redis answers none. They agreed with
308    /// each other, which is why the wire-vs-facade differential passed
309    /// it and the three-way against a real valkey did not.
310    ///
311    /// `range_bounds` has the rule right — floor a negative start at
312    /// zero, cap only the end, and call the window empty when the start
313    /// is past the last index — so the reversed window is computed from
314    /// it rather than beside it.
315    pub fn zrevrange(
316        &mut self,
317        key: &[u8],
318        start: i64,
319        stop: i64,
320    ) -> Result<Vec<(Vec<u8>, f64)>, StoreError> {
321        let n = self.zcard(key)?;
322        let Some((s, e)) = range_bounds(start, stop, n) else {
323            return Ok(Vec::new());
324        };
325        // Rank r from the top is rank n-1-r from the bottom, so the
326        // reversed window [s, e] is the ascending window [n-1-e, n-1-s].
327        let (asc_lo, asc_hi) = (n - 1 - e, n - 1 - s);
328        let mut out = self.zrange(key, asc_lo as i64, asc_hi as i64)?;
329        out.reverse();
330        Ok(out)
331    }
332}