Skip to main content

yo_kv/
lists.rs

1//! The list commands.
2//!
3//! One method per Redis command on [`Keyspace`], the same arrangement the set
4//! and hash commands use and for the same reason: a key belongs to the database
5//! and not to a type, so `LPUSH` against a string has to be able to see that it
6//! is a string. The list itself, and the choice between the two representations
7//! it can be in, is [`crate::list`]. This file is what the wire and the embedded
8//! API both call.
9//!
10//! # Indexes
11//!
12//! Every index a client sends is signed and counts from the back when it is
13//! negative, and every range is inclusive at both ends. Both of those are Redis
14//! rules that no structure below here should have to know about, so they are
15//! turned into an offset and a count in `at` and `window` and the list sees
16//! nothing but `usize`.
17//!
18//! # An empty list is not a list
19//!
20//! The key goes when the last element does, whether that was `LPOP`, `LREM` or
21//! `LTRIM`. Redis has the same rule for every collection, and it is the reason
22//! `EXISTS` answers zero after a list is emptied rather than one.
23
24use yo_common::{Code, Error, Result};
25
26use crate::keyspace::Keyspace;
27use crate::list::{Element, List};
28use crate::strings;
29use crate::value::{self, Kind};
30
31/// What `LSET` and `LINSERT` say about a key that is not there.
32///
33/// Redis's words, because they go on the wire verbatim.
34const NO_KEY: &str = "no such key";
35
36/// What `LSET` says about an index past the end.
37const OUT_OF_RANGE: &str = "index out of range";
38
39/// What `LPOS` says about a rank of zero.
40///
41/// Read off a running 8.8 rather than written from memory, because the older
42/// wording of this message is still all over the internet and clients match on
43/// the text. Zero is the one rank with no reading: 1 is the first match from
44/// the front, -1 the first from the back, and 0 would have to mean neither.
45const ZERO_RANK: &str = "RANK can't be zero: use 1 to start from the first match, 2 from the second ... or use negative to start from the end of the list";
46
47/// Which end a command works from.
48///
49/// `LPUSH` and `RPUSH` are one method, and so are `LPOP` and `RPOP`, because
50/// the difference between each pair is this and nothing else. `LMOVE` needs two
51/// of them and would need two flags whichever way this was written.
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub enum End {
54    /// The head, which is where `LPUSH` puts things and `LPOP` takes them from.
55    Left,
56    /// The tail.
57    Right,
58}
59
60impl End {
61    /// Whether this is the head.
62    #[inline]
63    #[must_use]
64    pub const fn is_left(self) -> bool {
65        matches!(self, End::Left)
66    }
67}
68
69impl Keyspace {
70    /// `LPUSH key element [element ...]` and `RPUSH`. Answers the new length.
71    ///
72    /// The elements arrive as an iterator rather than a slice, the same as
73    /// `SADD`, because the wire layer has them as positions in the connection's
74    /// read buffer and collecting them into a slice would be an allocation on a
75    /// shard thread.
76    ///
77    /// They go in one at a time, so `LPUSH k a b c` leaves the list holding
78    /// `c b a`. That reads like a bug and is not: each element in turn is put
79    /// at the head, and the last one sent ends up in front.
80    pub fn push<'v>(
81        &mut self,
82        key: &[u8],
83        end: End,
84        values: impl Iterator<Item = &'v [u8]> + Clone,
85    ) -> Result<usize> {
86        for v in values.clone() {
87            strings::check_len(key, v.len())?;
88        }
89        let at = match self.list_slot(key)? {
90            Some(at) => at,
91            None => {
92                // `LPUSH k` with no elements does not make a key. The wire
93                // parser rejects it on arity before it gets here, but the
94                // embedded API has no parser in front of it and an empty list
95                // left behind would be a key that exists and holds nothing.
96                if values.clone().next().is_none() {
97                    return Ok(0);
98                }
99                self.new_list(key)
100            }
101        };
102        let limits = self.list_limits;
103        let list = self
104            .lists
105            .get_mut(at)
106            .expect("the record points at its body");
107        for v in values {
108            if end.is_left() {
109                list.push_front(v, &limits);
110            } else {
111                list.push_back(v, &limits);
112            }
113        }
114        Ok(list.len())
115    }
116
117    /// `LPUSHX key element [element ...]` and `RPUSHX`.
118    ///
119    /// The same as [`Keyspace::push`] except that a key which is not there
120    /// stays not there, and the answer is zero.
121    pub fn pushx<'v>(
122        &mut self,
123        key: &[u8],
124        end: End,
125        values: impl Iterator<Item = &'v [u8]> + Clone,
126    ) -> Result<usize> {
127        if self.list_slot(key)?.is_none() {
128            return Ok(0);
129        }
130        self.push(key, end, values)
131    }
132
133    /// `LPOP key` and `RPOP key`, one element.
134    ///
135    /// This allocates, because the element it answers with is the element it
136    /// just took out of the structure that was holding it, the same bind
137    /// `SPOP` is in. [`Keyspace::pop_into`] is the version the wire uses, which
138    /// copies straight into the reply buffer instead.
139    pub fn pop(&mut self, key: &[u8], end: End) -> Result<Option<Vec<u8>>> {
140        let Some(at) = self.list_slot(key)? else {
141            return Ok(None);
142        };
143        let limits = self.list_limits;
144        let list = self
145            .lists
146            .get_mut(at)
147            .expect("the record points at its body");
148        let got = if end.is_left() {
149            list.pop_front(&limits)
150        } else {
151            list.pop_back(&limits)
152        };
153        if list.is_empty() {
154            self.drop_key(key);
155        }
156        Ok(got)
157    }
158
159    /// `LPOP key count` and `RPOP key count`, straight into the reply.
160    ///
161    /// Answers how many were taken. `f` is called with each element in the
162    /// order the reply wants them, which for `LPOP` with a count is head first
163    /// and for `RPOP` is tail first, and it is called before the element is
164    /// dropped so nothing has to be copied to a `Vec` on the way (Y18).
165    ///
166    /// A count larger than the list takes the whole list, and the key goes with
167    /// it.
168    pub fn pop_into<F>(&mut self, key: &[u8], end: End, count: usize, mut f: F) -> Result<usize>
169    where
170        F: FnMut(Element<'_>),
171    {
172        let Some(at) = self.list_slot(key)? else {
173            return Ok(0);
174        };
175        let limits = self.list_limits;
176        let list = self
177            .lists
178            .get_mut(at)
179            .expect("the record points at its body");
180        let take = count.min(list.len());
181        for _ in 0..take {
182            // The element is handed over while it is still in the list and
183            // dropped right after, which is why this is a loop of read then
184            // drop rather than a loop of `pop_front`. A `pop_front` would build
185            // a `Vec` per element for no reason other than to hand it back.
186            let e = if end.is_left() {
187                list.front()
188            } else {
189                list.back()
190            };
191            f(e.expect("a list shorter than it says it is"));
192            if end.is_left() {
193                list.drop_front(&limits);
194            } else {
195                list.drop_back(&limits);
196            }
197        }
198        if list.is_empty() {
199            self.drop_key(key);
200        }
201        Ok(take)
202    }
203
204    /// `LLEN key`. Zero for a key that is not there.
205    pub fn llen(&mut self, key: &[u8]) -> Result<usize> {
206        Ok(match self.list_slot(key)? {
207            Some(at) => self.list_at(at).len(),
208            None => 0,
209        })
210    }
211
212    /// `LINDEX key index`, counting from the back when the index is negative.
213    pub fn lindex(&mut self, key: &[u8], index: i64) -> Result<Option<Element<'_>>> {
214        let Some(slot) = self.list_slot(key)? else {
215            return Ok(None);
216        };
217        let list = self.list_at(slot);
218        Ok(at(index, list.len()).and_then(|i| list.get(i)))
219    }
220
221    /// `LRANGE key start stop`, both ends inclusive and both able to be
222    /// negative.
223    ///
224    /// The answer borrows the database for as long as it is alive, so the
225    /// caller walks it straight into the reply rather than collecting it (Y18).
226    /// A key that is not there is an empty range and not a nil, which is what
227    /// Redis replies and is the one place a list differs from a set.
228    pub fn lrange(
229        &mut self,
230        key: &[u8],
231        start: i64,
232        stop: i64,
233    ) -> Result<impl Iterator<Item = Element<'_>>> {
234        let slot = self.list_slot(key)?;
235        let list = slot.map(|at| self.list_at(at));
236        let (from, count) = match list {
237            Some(l) => window(start, stop, l.len()),
238            None => (0, 0),
239        };
240        Ok(list
241            .into_iter()
242            .flat_map(move |l| l.range(from, count))
243            .take(count))
244    }
245
246    /// `LSET key index element`.
247    ///
248    /// Two errors and no boolean, because both of them are errors on the wire:
249    /// `no such key` for a missing key and `index out of range` for an index
250    /// the list does not reach. A list is never empty, so those really are the
251    /// only two ways to miss.
252    pub fn lset(&mut self, key: &[u8], index: i64, value: &[u8]) -> Result<()> {
253        strings::check_len(key, value.len())?;
254        let Some(slot) = self.list_slot(key)? else {
255            return Err(Error::new(Code::Invalid, NO_KEY));
256        };
257        let limits = self.list_limits;
258        let list = self
259            .lists
260            .get_mut(slot)
261            .expect("the record points at its body");
262        let Some(i) = at(index, list.len()) else {
263            return Err(Error::new(Code::Invalid, OUT_OF_RANGE));
264        };
265        if !list.set(i, value, &limits) {
266            return Err(Error::new(Code::Invalid, OUT_OF_RANGE));
267        }
268        Ok(())
269    }
270
271    /// `LINSERT key BEFORE|AFTER pivot element`.
272    ///
273    /// The new length, or `-1` when the pivot is not in the list, or `0` when
274    /// the key is not there. Three answers in one signed number is Redis's
275    /// choice and it is a bad one, but it is on the wire and cannot be changed.
276    pub fn linsert(&mut self, key: &[u8], before: bool, pivot: &[u8], value: &[u8]) -> Result<i64> {
277        strings::check_len(key, value.len())?;
278        let Some(slot) = self.list_slot(key)? else {
279            return Ok(0);
280        };
281        let limits = self.list_limits;
282        let list = self
283            .lists
284            .get_mut(slot)
285            .expect("the record points at its body");
286        Ok(match list.insert_at_pivot(pivot, value, before, &limits) {
287            Some(len) => len as i64,
288            None => -1,
289        })
290    }
291
292    /// `LREM key count element`. Answers how many went.
293    ///
294    /// A positive count removes that many from the front, a negative one that
295    /// many from the back, and zero removes all of them. The key goes if the
296    /// list ends up empty.
297    pub fn lrem(&mut self, key: &[u8], count: i64, value: &[u8]) -> Result<usize> {
298        let Some(slot) = self.list_slot(key)? else {
299            return Ok(0);
300        };
301        let limits = self.list_limits;
302        let list = self
303            .lists
304            .get_mut(slot)
305            .expect("the record points at its body");
306        let gone = list.remove(count, value, &limits);
307        if list.is_empty() {
308            self.drop_key(key);
309        }
310        Ok(gone)
311    }
312
313    /// `LTRIM key start stop`, keeping the window and throwing the rest away.
314    ///
315    /// A window that selects nothing deletes the key, which is what an empty
316    /// range means here: `LTRIM k 1 0` is the documented way to empty a list.
317    pub fn ltrim(&mut self, key: &[u8], start: i64, stop: i64) -> Result<()> {
318        let Some(slot) = self.list_slot(key)? else {
319            return Ok(());
320        };
321        let limits = self.list_limits;
322        let list = self
323            .lists
324            .get_mut(slot)
325            .expect("the record points at its body");
326        let (from, count) = window(start, stop, list.len());
327        list.trim(from, count, &limits);
328        if list.is_empty() {
329            self.drop_key(key);
330        }
331        Ok(())
332    }
333
334    /// `LPOS key element [RANK rank] [COUNT count] [MAXLEN len]`.
335    ///
336    /// The positions land in `out`, which the caller supplies and which is
337    /// cleared first, because this runs on a shard thread and a shard thread
338    /// that allocates aborts. `count` of zero means every match and `maxlen` of
339    /// zero means no limit on how far to look, both of which are Redis's
340    /// spellings for no limit.
341    ///
342    /// # Errors
343    ///
344    /// A rank of zero, which has no reading. Everything else about a missing
345    /// key or a missing element is an empty answer rather than an error.
346    pub fn lpos(
347        &mut self,
348        key: &[u8],
349        value: &[u8],
350        rank: i64,
351        count: usize,
352        maxlen: usize,
353        out: &mut Vec<usize>,
354    ) -> Result<()> {
355        out.clear();
356        if rank == 0 {
357            return Err(Error::new(Code::Invalid, ZERO_RANK));
358        }
359        self.lpos_into(key, value, rank, count, maxlen, |at| out.push(at))?;
360        Ok(())
361    }
362
363    /// `LPOS`, with each position handed over as it is found.
364    ///
365    /// This is what the wire calls. The positions go straight into the reply
366    /// buffer as they are discovered, so a `LPOS key x COUNT 0` over a list
367    /// with ten thousand matches never builds a list of ten thousand numbers
368    /// anywhere (Y18). Answers how many there were.
369    ///
370    /// # Errors
371    ///
372    /// A rank of zero, and `WRONGTYPE` for a key that is not a list.
373    pub fn lpos_into<F>(
374        &mut self,
375        key: &[u8],
376        value: &[u8],
377        rank: i64,
378        count: usize,
379        maxlen: usize,
380        mut found: F,
381    ) -> Result<usize>
382    where
383        F: FnMut(usize),
384    {
385        if rank == 0 {
386            return Err(Error::new(Code::Invalid, ZERO_RANK));
387        }
388        let Some(slot) = self.list_slot(key)? else {
389            return Ok(0);
390        };
391        Ok(self
392            .list_at(slot)
393            .positions(value, rank, count, maxlen, &mut found))
394    }
395
396    /// `LMOVE src dst LEFT|RIGHT LEFT|RIGHT`, and `RPOPLPUSH` under it.
397    ///
398    /// Answers the element that moved, or nothing when the source is empty or
399    /// missing. The destination is made if it is not there, and the source key
400    /// goes if that was its last element.
401    ///
402    /// `src` and `dst` being the same key is not a special case to work around,
403    /// it is `LMOVE k k LEFT RIGHT`, which is the documented way to rotate a
404    /// list and is what a round robin scheduler is built out of. It falls out
405    /// of taking the element before deciding where to put it.
406    ///
407    /// This is the one list command that has to copy an element, for the reason
408    /// `SPOP` gives: the value it answers with no longer has a structure to
409    /// borrow from. Moving the bytes from one list to the other without the
410    /// copy would need both bodies borrowed at once, and the destination may be
411    /// the source.
412    ///
413    /// The copy is not an allocation, though, which is the difference between
414    /// this and the first version of it. The element goes into the database's
415    /// one scratch buffer and the answer borrows that, so a queue that runs
416    /// `RPOPLPUSH` in a loop does no allocator work at all after the first
417    /// call, where before it did a malloc and a free per element. The answer
418    /// borrows the database until the caller is done with it, which is what
419    /// both callers want anyway: they write it to the reply and drop it.
420    pub fn lmove(&mut self, src: &[u8], dst: &[u8], from: End, to: End) -> Result<Option<&[u8]>> {
421        // The destination's type is checked before anything is taken, so that
422        // `LMOVE list string LEFT LEFT` is a `WRONGTYPE` with the source
423        // untouched rather than an element that has gone nowhere.
424        self.list_slot(dst)?;
425        // Taken out of the database and put back at the end of every path, so
426        // that `pop_into` and `push` can have `&mut self` while the bytes are
427        // in hand. The buffer is empty for the duration and nothing else looks
428        // at it, so a path that returns early leaves it exactly as it found it.
429        let mut buf = std::mem::take(&mut self.scratch);
430        buf.clear();
431        let took = self.pop_into(src, from, 1, |e| e.write_to(&mut buf));
432        let moved = match took {
433            Ok(n) => n,
434            Err(e) => {
435                self.scratch = buf;
436                return Err(e);
437            }
438        };
439        if moved == 0 {
440            self.scratch = buf;
441            return Ok(None);
442        }
443        let pushed = self.push(dst, to, std::iter::once(buf.as_slice()));
444        self.scratch = buf;
445        pushed?;
446        Ok(Some(&self.scratch))
447    }
448
449    /// The slot `key`'s list is in, or `None` if there is no such key.
450    #[inline]
451    fn list_slot(&mut self, key: &[u8]) -> Result<Option<u32>> {
452        self.live_slot(key, Kind::List)
453    }
454
455    /// The body in a slot the record pointed at.
456    ///
457    /// Panicking here means a record outlived its body, the same bug
458    /// [`Keyspace::set_at`] is watching for.
459    #[inline]
460    fn list_at(&self, at: u32) -> &List {
461        self.lists.get(at).expect("the record points at its body")
462    }
463
464    /// Make an empty list under `key` and answer which slot it went in.
465    ///
466    /// No hint, unlike a set. A list starts packed whatever is going into it,
467    /// because the band it belongs in is decided by bytes rather than by count
468    /// and the first push finds that out for free.
469    fn new_list(&mut self, key: &[u8]) -> u32 {
470        // The body and, every so often, the slab that holds it. See
471        // `yo_alloc::first_touch` for why this is the one allocation a command
472        // is allowed to make.
473        let at = yo_alloc::first_touch(|| self.lists.insert(List::new()));
474        let len = value::slot_record_len(false);
475        self.write_rec(key, len, |out| {
476            value::write_slot_record(out, Kind::List, at, None);
477        });
478        self.bodies += 1;
479        at
480    }
481}
482
483/// Turn a signed index into an offset from the front, or nothing if it misses.
484///
485/// A negative index counts from the back, so -1 is the last element. An index
486/// that is still negative after that, or that reaches past the end, is a miss,
487/// and the two are the same answer because `LINDEX` replies nil to both.
488#[inline]
489fn at(index: i64, len: usize) -> Option<usize> {
490    let i = if index < 0 { len as i64 + index } else { index };
491    (i >= 0 && (i as usize) < len).then_some(i as usize)
492}
493
494/// Turn an inclusive `start` and `stop` into an offset and a count.
495///
496/// Every out of range case clamps rather than erroring, which is Redis's rule
497/// for `LRANGE` and `LTRIM` both: a start before the front is the front, a stop
498/// past the end is the end, and a start after the stop is nothing at all.
499#[inline]
500fn window(start: i64, stop: i64, len: usize) -> (usize, usize) {
501    let n = len as i64;
502    let from = if start < 0 { (n + start).max(0) } else { start };
503    let to = if stop < 0 { n + stop } else { stop.min(n - 1) };
504    if from > to || from >= n || to < 0 {
505        return (0, 0);
506    }
507    (from as usize, (to - from + 1) as usize)
508}
509
510#[cfg(test)]
511mod tests {
512    use super::*;
513    use crate::Clock;
514    use crate::list::Encoding;
515    use yo_common::Code;
516
517    fn db() -> Keyspace {
518        Keyspace::with_clock(Clock::fixed(1_000))
519    }
520
521    fn rpush(d: &mut Keyspace, key: &[u8], values: &[&[u8]]) -> usize {
522        d.push(key, End::Right, values.iter().copied())
523            .expect("a list")
524    }
525
526    fn all(d: &mut Keyspace, key: &[u8]) -> Vec<String> {
527        d.lrange(key, 0, -1)
528            .expect("a list")
529            .map(|e| String::from_utf8(e.to_vec()).expect("utf8 in these tests"))
530            .collect()
531    }
532
533    #[test]
534    fn pushing_to_a_key_that_is_not_there_makes_it() {
535        let mut d = db();
536        assert_eq!(rpush(&mut d, b"l", &[b"a", b"b", b"c"]), 3);
537        assert_eq!(all(&mut d, b"l"), ["a", "b", "c"]);
538        assert_eq!(d.llen(b"l").expect("a list"), 3);
539    }
540
541    #[test]
542    fn lpush_puts_the_last_element_in_front() {
543        let mut d = db();
544        d.push(b"l", End::Left, [b"a".as_slice(), b"b", b"c"].into_iter())
545            .expect("a list");
546        assert_eq!(all(&mut d, b"l"), ["c", "b", "a"]);
547    }
548
549    #[test]
550    fn pushing_nothing_does_not_make_a_key() {
551        let mut d = db();
552        let none: [&[u8]; 0] = [];
553        assert_eq!(d.push(b"l", End::Left, none.into_iter()).expect("ok"), 0);
554        assert_eq!(d.kind_of(b"l"), None);
555    }
556
557    #[test]
558    fn pushx_only_pushes_to_a_list_that_is_there() {
559        let mut d = db();
560        assert_eq!(
561            d.pushx(b"l", End::Right, [b"a".as_slice()].into_iter())
562                .expect("ok"),
563            0
564        );
565        assert_eq!(d.kind_of(b"l"), None);
566        rpush(&mut d, b"l", &[b"a"]);
567        assert_eq!(
568            d.pushx(b"l", End::Right, [b"b".as_slice()].into_iter())
569                .expect("ok"),
570            2
571        );
572    }
573
574    #[test]
575    fn popping_the_last_element_takes_the_key_with_it() {
576        let mut d = db();
577        rpush(&mut d, b"l", &[b"only"]);
578        assert_eq!(
579            d.pop(b"l", End::Left).expect("ok").as_deref(),
580            Some(&b"only"[..])
581        );
582        assert_eq!(d.kind_of(b"l"), None);
583        assert_eq!(d.pop(b"l", End::Left).expect("ok"), None);
584    }
585
586    #[test]
587    fn a_count_pop_takes_from_the_end_it_was_asked_for() {
588        let mut d = db();
589        rpush(&mut d, b"l", &[b"a", b"b", b"c", b"d"]);
590        let mut got = Vec::new();
591        d.pop_into(b"l", End::Right, 2, |e| got.push(e.to_vec()))
592            .expect("a list");
593        assert_eq!(got, [b"d".to_vec(), b"c".to_vec()]);
594        assert_eq!(all(&mut d, b"l"), ["a", "b"]);
595    }
596
597    #[test]
598    fn a_count_pop_larger_than_the_list_empties_it() {
599        let mut d = db();
600        rpush(&mut d, b"l", &[b"a", b"b"]);
601        let mut n = 0;
602        assert_eq!(
603            d.pop_into(b"l", End::Left, 99, |_| n += 1).expect("a list"),
604            2
605        );
606        assert_eq!(n, 2);
607        assert_eq!(d.kind_of(b"l"), None);
608    }
609
610    #[test]
611    fn lrange_clamps_at_both_ends() {
612        let mut d = db();
613        rpush(&mut d, b"l", &[b"a", b"b", b"c"]);
614        assert_eq!(all(&mut d, b"l"), ["a", "b", "c"]);
615        let got: Vec<_> = d
616            .lrange(b"l", -100, 100)
617            .expect("a list")
618            .map(|e| e.to_vec())
619            .collect();
620        assert_eq!(got.len(), 3);
621        assert_eq!(d.lrange(b"l", 2, 1).expect("a list").count(), 0);
622        assert_eq!(d.lrange(b"l", 5, 9).expect("a list").count(), 0);
623        assert_eq!(d.lrange(b"nope", 0, -1).expect("no key").count(), 0);
624    }
625
626    #[test]
627    fn lindex_counts_from_the_back_when_it_is_negative() {
628        let mut d = db();
629        rpush(&mut d, b"l", &[b"a", b"b", b"c"]);
630        assert_eq!(
631            d.lindex(b"l", 0).expect("ok").map(|e| e.to_vec()),
632            Some(b"a".to_vec())
633        );
634        assert_eq!(
635            d.lindex(b"l", -1).expect("ok").map(|e| e.to_vec()),
636            Some(b"c".to_vec())
637        );
638        assert!(d.lindex(b"l", 3).expect("ok").is_none());
639        assert!(d.lindex(b"l", -4).expect("ok").is_none());
640        assert!(d.lindex(b"nope", 0).expect("ok").is_none());
641    }
642
643    #[test]
644    fn lset_says_which_of_the_two_ways_it_missed() {
645        let mut d = db();
646        let e = d.lset(b"nope", 0, b"x").expect_err("no key");
647        assert_eq!(e.message(), NO_KEY);
648        rpush(&mut d, b"l", &[b"a", b"b"]);
649        d.lset(b"l", -1, b"z").expect("in range");
650        assert_eq!(all(&mut d, b"l"), ["a", "z"]);
651        let e = d.lset(b"l", 9, b"x").expect_err("out of range");
652        assert_eq!(e.message(), OUT_OF_RANGE);
653    }
654
655    #[test]
656    fn linsert_has_three_answers() {
657        let mut d = db();
658        assert_eq!(d.linsert(b"nope", true, b"a", b"x").expect("ok"), 0);
659        rpush(&mut d, b"l", &[b"a", b"c"]);
660        assert_eq!(d.linsert(b"l", true, b"c", b"b").expect("ok"), 3);
661        assert_eq!(all(&mut d, b"l"), ["a", "b", "c"]);
662        assert_eq!(d.linsert(b"l", false, b"zz", b"x").expect("ok"), -1);
663    }
664
665    #[test]
666    fn lrem_counts_from_the_end_the_sign_says() {
667        let mut d = db();
668        rpush(&mut d, b"l", &[b"a", b"x", b"b", b"x", b"c", b"x"]);
669        assert_eq!(d.lrem(b"l", 1, b"x").expect("ok"), 1);
670        assert_eq!(all(&mut d, b"l"), ["a", "b", "x", "c", "x"]);
671        assert_eq!(d.lrem(b"l", -1, b"x").expect("ok"), 1);
672        assert_eq!(all(&mut d, b"l"), ["a", "b", "x", "c"]);
673        assert_eq!(d.lrem(b"l", 0, b"x").expect("ok"), 1);
674        assert_eq!(all(&mut d, b"l"), ["a", "b", "c"]);
675    }
676
677    #[test]
678    fn removing_everything_takes_the_key() {
679        let mut d = db();
680        rpush(&mut d, b"l", &[b"x", b"x"]);
681        assert_eq!(d.lrem(b"l", 0, b"x").expect("ok"), 2);
682        assert_eq!(d.kind_of(b"l"), None);
683    }
684
685    #[test]
686    fn ltrim_keeps_the_window() {
687        let mut d = db();
688        rpush(&mut d, b"l", &[b"a", b"b", b"c", b"d", b"e"]);
689        d.ltrim(b"l", 1, -2).expect("ok");
690        assert_eq!(all(&mut d, b"l"), ["b", "c", "d"]);
691    }
692
693    #[test]
694    fn an_empty_window_deletes_the_key() {
695        let mut d = db();
696        rpush(&mut d, b"l", &[b"a", b"b"]);
697        d.ltrim(b"l", 1, 0).expect("ok");
698        assert_eq!(d.kind_of(b"l"), None);
699        d.ltrim(b"nope", 0, -1).expect("no key is not an error");
700    }
701
702    #[test]
703    fn lpos_answers_where_and_how_many() {
704        let mut d = db();
705        rpush(&mut d, b"l", &[b"a", b"b", b"c", b"b", b"b"]);
706        let mut out = Vec::new();
707        d.lpos(b"l", b"b", 1, 0, 0, &mut out).expect("ok");
708        assert_eq!(out, [1, 3, 4]);
709        d.lpos(b"l", b"b", -1, 2, 0, &mut out).expect("ok");
710        assert_eq!(out, [4, 3]);
711        d.lpos(b"l", b"b", 1, 0, 2, &mut out).expect("ok");
712        assert_eq!(out, [1]);
713        d.lpos(b"l", b"zz", 1, 0, 0, &mut out).expect("ok");
714        assert!(out.is_empty());
715        d.lpos(b"nope", b"b", 1, 0, 0, &mut out).expect("ok");
716        assert!(out.is_empty());
717    }
718
719    #[test]
720    fn a_rank_of_zero_is_an_error() {
721        let mut d = db();
722        rpush(&mut d, b"l", &[b"a"]);
723        let e = d
724            .lpos(b"l", b"a", 0, 0, 0, &mut Vec::new())
725            .expect_err("zero");
726        assert_eq!(e.message(), ZERO_RANK);
727    }
728
729    #[test]
730    fn lmove_between_two_keys_makes_the_second_one() {
731        let mut d = db();
732        rpush(&mut d, b"src", &[b"a", b"b"]);
733        let got = d.lmove(b"src", b"dst", End::Right, End::Left).expect("ok");
734        assert_eq!(got, Some(&b"b"[..]));
735        assert_eq!(all(&mut d, b"src"), ["a"]);
736        assert_eq!(all(&mut d, b"dst"), ["b"]);
737    }
738
739    #[test]
740    fn lmove_onto_itself_rotates() {
741        let mut d = db();
742        rpush(&mut d, b"l", &[b"a", b"b", b"c"]);
743        d.lmove(b"l", b"l", End::Right, End::Left).expect("ok");
744        assert_eq!(all(&mut d, b"l"), ["c", "a", "b"]);
745        d.lmove(b"l", b"l", End::Left, End::Right).expect("ok");
746        assert_eq!(all(&mut d, b"l"), ["a", "b", "c"]);
747    }
748
749    #[test]
750    fn lmove_from_a_key_that_is_not_there_does_nothing() {
751        let mut d = db();
752        assert_eq!(
753            d.lmove(b"nope", b"dst", End::Left, End::Left).expect("ok"),
754            None
755        );
756        assert_eq!(d.kind_of(b"dst"), None);
757    }
758
759    /// `RPOPLPUSH` in a loop is what a work queue is, so the element that moves
760    /// must not cost a malloc and a free every time round. The first call is
761    /// allowed to grow the scratch buffer and everything after it is not.
762    #[test]
763    fn lmove_stops_allocating_once_its_buffer_is_grown() {
764        let mut d = db();
765        rpush(&mut d, b"q", &[b"a", b"b", b"c"]);
766        // Warm up. This one may grow the scratch buffer, and on a fresh
767        // database it also makes the destination.
768        d.lmove(b"q", b"q", End::Right, End::Left).expect("ok");
769        let (_, allocs) = crate::tally::counted(|| {
770            for _ in 0..100 {
771                d.lmove(b"q", b"q", End::Right, End::Left).expect("ok");
772            }
773        });
774        assert_eq!(allocs, 0, "lmove allocated {allocs} times in a hundred");
775        assert_eq!(all(&mut d, b"q"), ["b", "c", "a"]);
776    }
777
778    /// `LREM` used to build a `Vec` to hold the indices it was about to remove,
779    /// and the count it is given is one in almost every use of it, so that was
780    /// an allocation to hold a single `usize`.
781    #[test]
782    fn lrem_does_not_allocate_to_remove_a_handful() {
783        let mut d = db();
784        // Built up front, so the loop below is a hundred `LREM` calls and
785        // nothing else. Pushing inside it would make the key over and over,
786        // and making a key is an allocation this is not asking about.
787        let many: Vec<&[u8]> = (0..100).map(|_| b"gone".as_slice()).collect();
788        rpush(&mut d, b"l", &many);
789        rpush(&mut d, b"l", &[b"keep"]);
790        let (_, allocs) = crate::tally::counted(|| {
791            for _ in 0..100 {
792                assert_eq!(d.lrem(b"l", 1, b"gone").expect("a list"), 1);
793            }
794        });
795        assert_eq!(allocs, 0, "lrem allocated {allocs} times in a hundred");
796        assert_eq!(all(&mut d, b"l"), ["keep"]);
797    }
798
799    /// And it still answers when the hits do not fit on the stack.
800    #[test]
801    fn lrem_with_more_hits_than_fit_inline_still_removes_all_of_them() {
802        let mut d = db();
803        let many: Vec<&[u8]> = (0..40).map(|_| b"x".as_slice()).collect();
804        rpush(&mut d, b"l", &many);
805        rpush(&mut d, b"l", &[b"keep"]);
806        assert_eq!(d.lrem(b"l", 0, b"x").expect("a list"), 40);
807        assert_eq!(all(&mut d, b"l"), ["keep"]);
808    }
809
810    #[test]
811    fn lmove_checks_the_destination_before_taking_anything() {
812        let mut d = db();
813        rpush(&mut d, b"src", &[b"a"]);
814        d.set_plain(b"dst", b"a string").expect("room");
815        let e = d
816            .lmove(b"src", b"dst", End::Left, End::Left)
817            .expect_err("the destination is a string");
818        assert_eq!(e.code(), Code::WrongType);
819        assert_eq!(all(&mut d, b"src"), ["a"]);
820    }
821
822    #[test]
823    fn every_command_says_wrongtype_against_a_string() {
824        let mut d = db();
825        d.set_plain(b"s", b"a string").expect("room");
826        assert_eq!(
827            d.push(b"s", End::Left, [b"x".as_slice()].into_iter())
828                .expect_err("a string")
829                .code(),
830            Code::WrongType
831        );
832        assert_eq!(d.llen(b"s").expect_err("a string").code(), Code::WrongType);
833        assert_eq!(
834            d.pop(b"s", End::Left).expect_err("a string").code(),
835            Code::WrongType
836        );
837        assert_eq!(
838            d.lset(b"s", 0, b"x").expect_err("a string").code(),
839            Code::WrongType
840        );
841        assert_eq!(
842            d.ltrim(b"s", 0, -1).expect_err("a string").code(),
843            Code::WrongType
844        );
845    }
846
847    #[test]
848    fn a_list_is_a_list_to_the_rest_of_the_keyspace() {
849        let mut d = db();
850        rpush(&mut d, b"l", &[b"a"]);
851        assert_eq!(d.kind_of(b"l").map(|k| k.name()), Some("list"));
852        assert_eq!(d.encoding_name(b"l"), Some(Encoding::Listpack.name()));
853        assert!(d.exists(b"l"));
854        assert!(d.drop_key(b"l"));
855        assert_eq!(d.kind_of(b"l"), None);
856    }
857
858    #[test]
859    fn a_list_can_be_given_a_deadline_and_reaped() {
860        let mut d = Keyspace::with_clock(Clock::fixed(1_000));
861        rpush(&mut d, b"l", &[b"a", b"b"]);
862        assert!(d.set_expiry(b"l", Some(1_500)));
863        assert_eq!(all(&mut d, b"l"), ["a", "b"]);
864        d.clock_mut().advance(1_000);
865        assert_eq!(d.llen(b"l").expect("gone"), 0);
866        assert_eq!(d.kind_of(b"l"), None);
867    }
868
869    #[test]
870    fn a_big_list_is_chunked_and_still_answers_the_same() {
871        let mut d = db();
872        let value = vec![b'x'; 400];
873        for i in 0..40 {
874            let mut v = value.clone();
875            v.extend_from_slice(format!("{i}").as_bytes());
876            d.push(b"l", End::Right, [v.as_slice()].into_iter())
877                .expect("a list");
878        }
879        assert_eq!(d.encoding_name(b"l"), Some(Encoding::Quicklist.name()));
880        assert_eq!(d.llen(b"l").expect("a list"), 40);
881        let last = d.lindex(b"l", -1).expect("ok").expect("in range").to_vec();
882        assert!(last.ends_with(b"39"));
883        d.ltrim(b"l", 0, 0).expect("ok");
884        assert_eq!(d.llen(b"l").expect("a list"), 1);
885        assert_eq!(d.encoding_name(b"l"), Some(Encoding::Listpack.name()));
886    }
887}