Skip to main content

yo_kv/
list.rs

1//! A list, in whichever of the two representations currently fits it.
2//!
3//! A list is one packed blob while it is small and a ring of [`Chunk`]s once it
4//! is not, which is the same two bands Redis has and the same two names
5//! `OBJECT ENCODING` answers, `listpack` and `quicklist`.
6//!
7//! ```text
8//!   under eight kilobytes            everything else
9//! +--------------------------+   +------------------------------------+
10//! | one listpack             |-->| chunk | chunk | ... | chunk        |
11//! | walked from either end   |   | head cursor ...... tail cursor     |
12//! +--------------------------+   +------------------------------------+
13//! ```
14//!
15//! # Why a chunk and not a listpack per node
16//!
17//! Redis's quicklist node is a listpack, and taking the first element out of a
18//! listpack moves every byte behind it left. On a list that is being used as a
19//! queue, which is what a list is for, that memmove is on the hot path of every
20//! single `LPOP`. [`Chunk`] holds the same entries in the same encoding with a
21//! cursor at each end, so a pop is a cursor step and a push at the other end
22//! does not touch it. That is `04` section 6's independent head and tail, and it
23//! is the answer to the row aki lost.
24//!
25//! # The band boundary is Redis's, and it goes both ways
26//!
27//! `list-max-listpack-size` defaults to `-2`, which means eight kilobytes rather
28//! than a count, so a list of a thousand short strings is still one blob and a
29//! list of two hundred long ones is not. That was read off `t_list.c` in the
30//! 7.4.5 tarball rather than assumed, and so was the part that surprised: a list
31//! converts **back** when it shrinks, which no other collection here does. Redis
32//! only converts back once the list is under **half** the limit, so that a
33//! workload sitting exactly on the boundary does not rebuild itself on every
34//! other command, and `List::shrunk` is that rule.
35//!
36//! # Elements
37//!
38//! An element comes back as a [`Member`](crate::set::Member), which is
39//! [`listpack::Entry`](crate::listpack::Entry) under another name, so a value
40//! stored as an integer is handed over as one and formatted once, into the reply
41//! buffer, at the moment the reply is built. That is Y18 again.
42
43use std::cell::RefCell;
44use std::collections::VecDeque;
45
46use yo_common::Small;
47
48use crate::chunk::{CHUNK_BYTES, Chunk};
49use crate::frozen::{self, Broken};
50use crate::listpack::{Entry, Listpack};
51
52/// How many `LREM` hits fit without the allocator.
53///
54/// `LREM key 1 value` and `LREM key -1 value` are what this command is for, and
55/// a count past a handful is somebody clearing every copy out of a long list,
56/// where one allocation is not what it is paying for.
57const HITS: usize = 8;
58
59/// One packed blob, which is Redis's `LIST_QUICKLIST` of a single listpack.
60const FORM_PACKED: u8 = 1;
61/// The ring, written chunk by chunk.
62const FORM_CHUNKS: u8 = 2;
63
64/// A list element: bytes as they lie, or an integer not yet formatted.
65pub type Element<'a> = Entry<'a>;
66
67/// Where a list changes representation.
68///
69/// One number, because Redis has one: `list-max-listpack-size`. A negative value
70/// there is a size in kilobytes and a positive one is a count of elements, and
71/// both arrive here already turned into the two fields the bands actually ask
72/// about.
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub struct Limits {
75    /// The most bytes a packed list holds before it becomes chunks.
76    pub max_packed_bytes: usize,
77    /// The most elements a packed list holds, or none for no limit.
78    ///
79    /// The default configuration has no count limit at all, because `-2` is a
80    /// size. A server configured with a positive `list-max-listpack-size` has
81    /// one and no size limit, which is why these are two fields and not an enum
82    /// of one or the other: the halving rule for shrinking applies to whichever
83    /// is set and the code below should not have to know which that was.
84    pub max_packed_entries: Option<usize>,
85}
86
87impl Default for Limits {
88    /// What a server with no configuration file uses.
89    ///
90    /// `list-max-listpack-size -2`, which is eight kilobytes and no count.
91    fn default() -> Limits {
92        Limits {
93            max_packed_bytes: CHUNK_BYTES,
94            max_packed_entries: None,
95        }
96    }
97}
98
99impl Limits {
100    /// The limits a `list-max-listpack-size` of `fill` describes.
101    ///
102    /// This is `quicklistNodeLimit`. A positive fill is a count and the size is
103    /// left at the safety limit, a negative one is an index into Redis's five
104    /// sizes, and zero means one element per node, which is a setting nobody
105    /// uses and which still has to mean something.
106    #[must_use]
107    pub fn of(fill: i32) -> Limits {
108        if fill >= 0 {
109            return Limits {
110                max_packed_bytes: CHUNK_BYTES,
111                max_packed_entries: Some((fill as usize).max(1)),
112            };
113        }
114        // Redis's `optimization_level`, which is 4 KiB, 8, 16, 32 and 64.
115        const SIZES: [usize; 5] = [4096, 8192, 16384, 32768, 65536];
116        let at = ((-fill) as usize - 1).min(SIZES.len() - 1);
117        Limits {
118            max_packed_bytes: SIZES[at],
119            max_packed_entries: None,
120        }
121    }
122
123    /// Whether a packed list of these dimensions is past what the band holds.
124    #[must_use]
125    fn exceeded(&self, bytes: usize, entries: usize) -> bool {
126        match self.max_packed_entries {
127            Some(cap) => bytes > CHUNK_BYTES || entries > cap,
128            None => bytes > self.max_packed_bytes,
129        }
130    }
131
132    /// The same question with both limits halved, which is the shrinking rule.
133    #[must_use]
134    fn exceeded_halved(&self, bytes: usize, entries: usize) -> bool {
135        match self.max_packed_entries {
136            Some(cap) => bytes > CHUNK_BYTES / 2 || entries > cap / 2,
137            None => bytes > self.max_packed_bytes / 2,
138        }
139    }
140}
141
142/// What `OBJECT ENCODING` calls a list.
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144pub enum Encoding {
145    /// One packed blob.
146    Listpack,
147    /// A ring of chunks.
148    Quicklist,
149}
150
151impl Encoding {
152    /// The string `OBJECT ENCODING` returns.
153    #[must_use]
154    pub const fn name(self) -> &'static str {
155        match self {
156            Encoding::Listpack => "listpack",
157            Encoding::Quicklist => "quicklist",
158        }
159    }
160}
161
162/// Which representation the elements are in.
163#[derive(Debug, Clone, PartialEq, Eq)]
164enum Body {
165    Packed(Listpack),
166    Chunks(Deque),
167}
168
169/// A list of elements, in order, reachable from both ends.
170#[derive(Debug, Clone, PartialEq, Eq)]
171pub struct List {
172    body: Body,
173}
174
175impl Default for List {
176    fn default() -> List {
177        List::new()
178    }
179}
180
181impl List {
182    /// An empty list, in the band every list starts in.
183    #[must_use]
184    pub fn new() -> List {
185        List {
186            body: Body::Packed(Listpack::new()),
187        }
188    }
189
190    /// How many elements it holds.
191    #[must_use]
192    #[inline]
193    pub fn len(&self) -> usize {
194        match &self.body {
195            Body::Packed(lp) => lp.len(),
196            Body::Chunks(d) => d.len(),
197        }
198    }
199
200    /// Whether it holds nothing.
201    ///
202    /// A list that reaches zero is deleted by the keyspace, the same as a set
203    /// that does, so this is a question about the moment between the last pop
204    /// and that delete rather than a state a client can observe.
205    #[must_use]
206    #[inline]
207    pub fn is_empty(&self) -> bool {
208        self.len() == 0
209    }
210
211    /// Write this list out as the bytes it comes back from.
212    ///
213    /// What a demotion turns the body into. The packed band goes out as the
214    /// listpack bytes it already is, and the ring goes out chunk by chunk, each
215    /// one its element count and its live bytes.
216    ///
217    /// Chunk by chunk and not element by element, because a chunk's bytes are
218    /// already in the encoding [`Chunk::adopt`] takes, so a list of a million
219    /// elements is a couple of thousand copies out and the same number back
220    /// rather than two million encodes. The ring also comes back with the same
221    /// chunk boundaries it left with, which keeps `MEMORY USAGE` and the walk
222    /// cost of an index the same on both sides of a trip to the device.
223    ///
224    /// The dead space at either end of a chunk is not written. A chunk that had
225    /// room to push into comes back full, and pushing into it again allocates a
226    /// new chunk where the old one would have grown in place. That is a list
227    /// which was quiet long enough to be demoted paying one allocation on the
228    /// write that wakes it, and it is worth the bytes it saves on the device.
229    pub fn freeze(&self, out: &mut Vec<u8>) {
230        match &self.body {
231            Body::Packed(lp) => {
232                out.push(FORM_PACKED);
233                out.extend_from_slice(lp.as_bytes());
234            }
235            Body::Chunks(d) => {
236                out.push(FORM_CHUNKS);
237                frozen::put_uint(out, d.chunks.len() as u64);
238                for c in &d.chunks {
239                    frozen::put_uint(out, c.len() as u64);
240                    frozen::put_bytes(out, c.entries());
241                }
242            }
243        }
244    }
245
246    /// Read a list back out of what [`List::freeze`] wrote.
247    ///
248    /// # Errors
249    ///
250    /// [`Broken`] for bytes that are not the shape freeze wrote, which is a read
251    /// that came back torn rather than anything a caller did.
252    pub fn thaw(bytes: &[u8]) -> Result<List, Broken> {
253        let mut cut = frozen::Cut::new(bytes);
254        match cut.byte()? {
255            FORM_PACKED => Ok(List {
256                body: Body::Packed(Listpack::from_bytes(cut.rest()).map_err(|_| Broken::Body)?),
257            }),
258            FORM_CHUNKS => {
259                let n = usize::try_from(cut.uint()?).map_err(|_| Broken::Short)?;
260                // A chunk is at least a count and a length, so two bytes, and a
261                // number larger than what is left is not worth an allocation.
262                if n > cut.rest().len() {
263                    return Err(Broken::Body);
264                }
265                let mut d = Deque::new();
266                d.chunks.reserve(n);
267                for _ in 0..n {
268                    let count = usize::try_from(cut.uint()?).map_err(|_| Broken::Short)?;
269                    let entries = cut.bytes()?;
270                    if count > entries.len() {
271                        // An entry is at least one byte, so this cannot be a
272                        // chunk anything wrote.
273                        return Err(Broken::Body);
274                    }
275                    d.len += count;
276                    d.chunks.push_back(Chunk::adopt(entries, count));
277                }
278                Ok(List {
279                    body: Body::Chunks(d),
280                })
281            }
282            _ => Err(Broken::Form),
283        }
284    }
285
286    /// What `OBJECT ENCODING` says about it.
287    #[must_use]
288    pub const fn encoding(&self) -> Encoding {
289        match &self.body {
290            Body::Packed(_) => Encoding::Listpack,
291            Body::Chunks(_) => Encoding::Quicklist,
292        }
293    }
294
295    /// What it costs, not counting anything a caller is holding.
296    #[must_use]
297    pub fn memory_bytes(&self) -> usize {
298        match &self.body {
299            Body::Packed(lp) => lp.byte_len(),
300            Body::Chunks(d) => d.memory_bytes(),
301        }
302    }
303
304    /// The element at `index` from the front.
305    #[must_use]
306    pub fn get(&self, index: usize) -> Option<Element<'_>> {
307        match &self.body {
308            Body::Packed(lp) => lp.get(index),
309            Body::Chunks(d) => d.get(index),
310        }
311    }
312
313    /// The first element.
314    #[must_use]
315    pub fn front(&self) -> Option<Element<'_>> {
316        match &self.body {
317            Body::Packed(lp) => lp.get(0),
318            Body::Chunks(d) => d.front(),
319        }
320    }
321
322    /// The last element.
323    ///
324    /// Reads the back length rather than walking, in both bands, which is what
325    /// makes `RPOP` on a long list cost the same as `LPOP` on one.
326    #[must_use]
327    pub fn back(&self) -> Option<Element<'_>> {
328        match &self.body {
329            Body::Packed(lp) => lp.get_back(0),
330            Body::Chunks(d) => d.back(),
331        }
332    }
333
334    /// A forward walk over every element.
335    pub fn iter(&self) -> impl Iterator<Item = Element<'_>> {
336        // Two shapes with one type, because a caller that only wants the
337        // elements should not have to know which band it is standing on.
338        let (packed, chunks) = match &self.body {
339            Body::Packed(lp) => (Some(lp.iter()), None),
340            Body::Chunks(d) => (None, Some(d.iter())),
341        };
342        packed
343            .into_iter()
344            .flatten()
345            .chain(chunks.into_iter().flatten())
346    }
347
348    /// The same walk the other way.
349    ///
350    /// Both bands keep a length behind every element, so this costs what the
351    /// forward walk costs. `LPOS` with a negative rank is what wants it.
352    pub fn iter_back(&self) -> impl Iterator<Item = Element<'_>> {
353        let (packed, chunks) = match &self.body {
354            Body::Packed(lp) => (Some(lp.iter_back()), None),
355            Body::Chunks(d) => (None, Some(d.iter_back())),
356        };
357        packed
358            .into_iter()
359            .flatten()
360            .chain(chunks.into_iter().flatten())
361    }
362
363    /// `count` elements starting at `start`, which is `LRANGE`.
364    ///
365    /// Both ends are already normalised by the caller, because the wire's start
366    /// and stop can be negative, can be the wrong way round and can hang off
367    /// either end, and every one of those turns into an empty reply rather than
368    /// into an error.
369    ///
370    /// A window in the middle does not walk to its start. The packed band skips
371    /// entries because that is all a hundred and twenty eight of them costs, and
372    /// the chunked band steps over whole chunks and only decodes the ones it is
373    /// going to hand back. `LRANGE mylist 500000 500099` on a million element
374    /// list reads a hundred elements and not five hundred thousand.
375    pub fn range(&self, start: usize, count: usize) -> impl Iterator<Item = Element<'_>> {
376        let (packed, chunks) = match &self.body {
377            Body::Packed(lp) => (Some(lp.iter_from(start).take(count)), None),
378            Body::Chunks(d) => (None, Some(d.range(start, count))),
379        };
380        packed
381            .into_iter()
382            .flatten()
383            .chain(chunks.into_iter().flatten())
384    }
385
386    /// Put `value` at the front.
387    pub fn push_front(&mut self, value: &[u8], limits: &Limits) {
388        self.grow_by(value, limits);
389        match &mut self.body {
390            Body::Packed(lp) => lp.insert(0, value),
391            Body::Chunks(d) => d.push_front(value),
392        }
393    }
394
395    /// Put `value` at the back.
396    pub fn push_back(&mut self, value: &[u8], limits: &Limits) {
397        self.grow_by(value, limits);
398        match &mut self.body {
399            Body::Packed(lp) => lp.push(value),
400            Body::Chunks(d) => d.push_back(value),
401        }
402    }
403
404    /// Put `value` in at `index`, pushing what was there along, which is the
405    /// half of `LINSERT` that already knows where the pivot was.
406    ///
407    /// An index equal to the length appends. Anything past that is nothing.
408    pub fn insert(&mut self, index: usize, value: &[u8], limits: &Limits) -> bool {
409        if index > self.len() {
410            return false;
411        }
412        self.grow_by(value, limits);
413        match &mut self.body {
414            Body::Packed(lp) => {
415                lp.insert(index, value);
416                true
417            }
418            Body::Chunks(d) => d.insert_at(index, value),
419        }
420    }
421
422    /// Put `value` next to the first `pivot` in the list, which is `LINSERT`.
423    ///
424    /// Gives back the new length, or nothing when the pivot is not there, which
425    /// is the difference between the reply being a length and being `-1`.
426    pub fn insert_at_pivot(
427        &mut self,
428        pivot: &[u8],
429        value: &[u8],
430        before: bool,
431        limits: &Limits,
432    ) -> Option<usize> {
433        let at = self.find(pivot)?;
434        let at = if before { at } else { at + 1 };
435        self.insert(at, value, limits).then(|| self.len())
436    }
437
438    /// Put `value` where the element at `index` is, which is `LSET`.
439    pub fn set(&mut self, index: usize, value: &[u8], limits: &Limits) -> bool {
440        if index >= self.len() {
441            return false;
442        }
443        // What the blob would weigh after the swap, which is not what it weighs
444        // now plus the new element: the old one is going away. Getting this
445        // wrong would promote a list that still fits and then keep it promoted,
446        // because a list only converts back under half the limit.
447        if let Body::Packed(lp) = &self.body {
448            let old = lp.get(index).map_or(0, |e| e.byte_len());
449            let after = lp.byte_len() + crate::listpack::entry_len(value) - old;
450            self.grow_to(after, self.len(), limits);
451        }
452        match &mut self.body {
453            Body::Packed(lp) => lp.replace(index, value),
454            Body::Chunks(d) => d.replace_at(index, value),
455        }
456    }
457
458    /// Where the first `value` is, front to back.
459    ///
460    /// This is what `LINSERT` spends its time in, and on a long list it is
461    /// essentially all of it: the insert itself is a couple of hundred
462    /// nanoseconds and the pivot search in front of it is however long the list
463    /// is. So it goes to the band rather than through the element walk, and the
464    /// band reads entry headers instead of decoding elements.
465    #[must_use]
466    pub fn find(&self, value: &[u8]) -> Option<usize> {
467        let as_int = yo_common::num::parse_i64(value);
468        match &self.body {
469            Body::Packed(lp) => lp.find_parsed(value, as_int, 1),
470            Body::Chunks(d) => d.find(value, as_int),
471        }
472    }
473
474    /// Where `value` is, as many times as asked, which is `LPOS`.
475    ///
476    /// `rank` is which match to start at and which way to look: 1 is the first
477    /// from the front, -1 the first from the back, 2 the second from the front.
478    /// `count` is how many to give back with 0 meaning all of them, and `maxlen`
479    /// is how many elements may be compared before giving up, with 0 meaning no
480    /// limit. The indexes handed back are always from the front, whichever way
481    /// the walk went, because that is what the client can use.
482    ///
483    /// Each answer is handed to `found` as it is discovered, and the number of
484    /// them comes back, because this runs on a shard thread and a shard thread
485    /// that allocates aborts. The wire writes each position straight into the
486    /// reply buffer and never holds a list of them at all.
487    ///
488    /// `found` is a `dyn` call rather than a generic, so that the two walks
489    /// below stay one body. Monomorphising this over the sink would double a
490    /// function whose whole cost is the comparison inside it.
491    ///
492    /// Like [`List::find`] this goes to the band rather than through the element
493    /// walk, and for the same reason: an `LPOS` that is not answered by the
494    /// first few elements reads the list, and reading the list one decoded
495    /// [`Element`] at a time costs about three times what reading it as entry
496    /// headers does. The walk carries the `MAXLEN` budget itself rather than
497    /// counting elements out here, because counting them out here means the
498    /// budget is only checked between calls into the band, which on a ring is
499    /// once a chunk.
500    pub fn positions(
501        &self,
502        value: &[u8],
503        rank: i64,
504        count: usize,
505        maxlen: usize,
506        found: &mut dyn FnMut(usize),
507    ) -> usize {
508        if rank == 0 {
509            return 0;
510        }
511        let as_int = yo_common::num::parse_i64(value);
512        let len = self.len();
513        let mut skip = rank.unsigned_abs() as usize - 1;
514        let mut hits = 0usize;
515        {
516            // What to do with a match, wherever the walk found it. A rank past
517            // the first drops matches on the floor until it has dropped enough,
518            // which is why this cannot be the walk's own counter.
519            let mut take = |at: usize| -> bool {
520                if skip > 0 {
521                    skip -= 1;
522                    return true;
523                }
524                found(at);
525                hits += 1;
526                count == 0 || hits < count
527            };
528            if rank > 0 {
529                match &self.body {
530                    Body::Packed(lp) => lp.find_each(value, as_int, maxlen, &mut take),
531                    Body::Chunks(d) => d.find_each(value, as_int, maxlen, &mut take),
532                };
533            } else {
534                // The backward walk counts from the last element and the client
535                // wants indexes from the first, so they are turned round here
536                // and nowhere below.
537                let mut back = |at: usize| take(len - at - 1);
538                match &self.body {
539                    Body::Packed(lp) => lp.find_each_back(value, as_int, maxlen, &mut back),
540                    Body::Chunks(d) => d.find_each_back(value, as_int, maxlen, &mut back),
541                };
542            }
543        }
544        hits
545    }
546
547    /// Take out up to `count` elements equal to `value`, which is `LREM`.
548    ///
549    /// A positive count works from the front, a negative one from the back, and
550    /// zero means every one of them. Gives back how many went.
551    pub fn remove(&mut self, count: i64, value: &[u8], limits: &Limits) -> usize {
552        let as_int = yo_common::num::parse_i64(value);
553        let want = if count == 0 {
554            usize::MAX
555        } else {
556            count.unsigned_abs() as usize
557        };
558        // Collected first and removed after, because removing during the walk
559        // moves the elements the walk has not reached yet. Highest index first
560        // so that the ones still to go do not move either.
561        //
562        // On the stack up to `HITS`, because the count `LREM` is given is one
563        // or two in almost every use of it and a `Vec` for one `usize` is a
564        // malloc and a free on a command path. `LREM key 0 value` on a list
565        // holding many copies spills, which is the right answer for it.
566        let mut hits: Small<usize, HITS> = Small::new();
567        if count >= 0 {
568            match &self.body {
569                Body::Packed(lp) => lp.find_each(value, as_int, 0, &mut |at| {
570                    hits.push(at);
571                    hits.len() < want
572                }),
573                Body::Chunks(d) => d.find_each(value, as_int, 0, &mut |at| {
574                    hits.push(at);
575                    hits.len() < want
576                }),
577            };
578            hits.reverse();
579        } else {
580            let len = self.len();
581            match &self.body {
582                Body::Packed(lp) => lp.find_each_back(value, as_int, 0, &mut |at| {
583                    hits.push(len - at - 1);
584                    hits.len() < want
585                }),
586                Body::Chunks(d) => d.find_each_back(value, as_int, 0, &mut |at| {
587                    hits.push(len - at - 1);
588                    hits.len() < want
589                }),
590            };
591        }
592        for at in &hits {
593            self.remove_at(*at);
594        }
595        self.shrunk(limits);
596        hits.len()
597    }
598
599    /// Take out the element at `index`.
600    ///
601    /// The band is left alone, because a caller taking several out in a row
602    /// would otherwise convert between them. Everything public that removes
603    /// finishes with [`List::shrunk`].
604    fn remove_at(&mut self, index: usize) -> bool {
605        match &mut self.body {
606            Body::Packed(lp) => lp.delete(index, 1),
607            Body::Chunks(d) => d.remove_at(index),
608        }
609    }
610
611    /// Keep `count` elements starting at `start` and drop the rest, which is
612    /// `LTRIM`.
613    ///
614    /// Both ends are normalised by the caller, the same as [`List::range`], and
615    /// a count of zero empties the list, which on the wire deletes the key.
616    pub fn trim(&mut self, start: usize, count: usize, limits: &Limits) {
617        let len = self.len();
618        let start = start.min(len);
619        let keep = count.min(len - start);
620        match &mut self.body {
621            Body::Packed(lp) => {
622                lp.delete(start + keep, len - start - keep);
623                lp.delete(0, start);
624            }
625            Body::Chunks(d) => d.trim(start, keep),
626        }
627        self.shrunk(limits);
628    }
629
630    /// Drop the first element, and say whether there was one.
631    ///
632    /// The read and the removal are separate so that `LPOP` on the wire can
633    /// write the element straight into the reply buffer and then drop it,
634    /// which is the same split [`crate::set::Set::drop_at`] exists for.
635    pub fn drop_front(&mut self, limits: &Limits) -> bool {
636        let gone = match &mut self.body {
637            Body::Packed(lp) => lp.delete(0, 1),
638            Body::Chunks(d) => d.drop_front(),
639        };
640        self.shrunk(limits);
641        gone
642    }
643
644    /// Drop the last element, and say whether there was one.
645    pub fn drop_back(&mut self, limits: &Limits) -> bool {
646        let gone = match &mut self.body {
647            Body::Packed(lp) => {
648                let last = lp.len().checked_sub(1);
649                last.is_some_and(|at| lp.delete(at, 1))
650            }
651            Body::Chunks(d) => d.drop_back(),
652        };
653        self.shrunk(limits);
654        gone
655    }
656
657    /// Take the first element out and hand it back.
658    ///
659    /// The embedded API's `LPOP`, where the caller wants the bytes and has
660    /// nowhere to put them.
661    pub fn pop_front(&mut self, limits: &Limits) -> Option<Vec<u8>> {
662        let out = self.front()?.to_vec();
663        self.drop_front(limits);
664        Some(out)
665    }
666
667    /// Take the last element out and hand it back.
668    pub fn pop_back(&mut self, limits: &Limits) -> Option<Vec<u8>> {
669        let out = self.back()?.to_vec();
670        self.drop_back(limits);
671        Some(out)
672    }
673
674    /// Go back to one blob if the list has shrunk far enough to deserve it.
675    ///
676    /// Called by everything here that removes elements. Redis converts back only
677    /// below half the limit, so that a list sitting on the boundary does not
678    /// rebuild itself on every other command, and it only converts a quicklist
679    /// that is down to one node. Both of those are here.
680    fn shrunk(&mut self, limits: &Limits) {
681        let Body::Chunks(d) = &self.body else {
682            return;
683        };
684        if d.chunks.len() != 1 {
685            return;
686        }
687        let only = &d.chunks[0];
688        if limits.exceeded_halved(only.live_bytes(), only.len()) {
689            return;
690        }
691        let mut lp = Listpack::new();
692        for e in only.iter() {
693            match e {
694                Entry::Int(n) => {
695                    let mut digits = Vec::new();
696                    Entry::Int(n).write_to(&mut digits);
697                    lp.push(&digits);
698                }
699                Entry::Str(s) => lp.push(s),
700            }
701        }
702        self.body = Body::Packed(lp);
703    }
704
705    /// Promote out of the packed band if one more `value` would not fit in it.
706    ///
707    /// Asked before the write rather than after, because a listpack that has
708    /// already been grown past the limit and is then converted has done the
709    /// work twice.
710    fn grow_by(&mut self, value: &[u8], limits: &Limits) {
711        let Body::Packed(lp) = &self.body else {
712            return;
713        };
714        let after = lp.byte_len() + crate::listpack::entry_len(value);
715        self.grow_to(after, lp.len() + 1, limits);
716    }
717
718    /// Promote out of the packed band if a list of this size does not fit it.
719    fn grow_to(&mut self, bytes: usize, entries: usize, limits: &Limits) {
720        if !matches!(self.body, Body::Packed(_)) || !limits.exceeded(bytes, entries) {
721            return;
722        }
723        let Body::Packed(lp) = std::mem::replace(&mut self.body, Body::Chunks(Deque::new())) else {
724            unreachable!("just matched a packed body");
725        };
726        let Body::Chunks(d) = &mut self.body else {
727            unreachable!("just put a chunked body there");
728        };
729        d.adopt(&lp);
730    }
731}
732
733/// A chunk of its own holding nothing but `value`, at the end asked for.
734///
735/// An element too big for an ordinary chunk gets one sized to it, which is what
736/// Redis calls a plain node. Without this a value over eight kilobytes would be
737/// refused by a chunk that had just been made for it and the list would count an
738/// element it does not hold.
739fn lone(value: &[u8], front: bool) -> Chunk {
740    if crate::listpack::entry_len(value) > CHUNK_BYTES {
741        return Chunk::plain(value);
742    }
743    let mut c = if front {
744        Chunk::for_front()
745    } else {
746        Chunk::for_back()
747    };
748    let put = if front {
749        c.push_front(value)
750    } else {
751        c.push_back(value)
752    };
753    debug_assert!(put, "an empty chunk refused the only element in it");
754    c
755}
756
757/// A ring of chunks, with the list's length kept beside it.
758///
759/// The length is carried rather than summed because `LLEN` is a command and
760/// summing a thousand chunk counts to answer it would be a walk of the whole
761/// list to say how long it is.
762#[derive(Debug, Clone)]
763struct Deque {
764    chunks: VecDeque<Chunk>,
765    len: usize,
766    /// Where each chunk starts, so that finding an index is a binary search
767    /// over the ring rather than a walk along it. See [`Deque::locate`].
768    ///
769    /// Behind a cell because it is filled in by reads, and the reads that want
770    /// it take `&self`. Nothing outside this thread can see it: a shard owns
771    /// its keyspace and `yo-shard` has a test that the type system says so.
772    starts: RefCell<VecDeque<i64>>,
773}
774
775/// Two rings are the same when they hold the same elements in the same chunks.
776///
777/// Written out rather than derived because the start index is a cache, and a
778/// list that has been read is not a different list from one that has not.
779impl PartialEq for Deque {
780    fn eq(&self, other: &Deque) -> bool {
781        self.len == other.len && self.chunks == other.chunks
782    }
783}
784
785impl Eq for Deque {}
786
787impl Deque {
788    /// An empty ring.
789    fn new() -> Deque {
790        Deque {
791            chunks: VecDeque::new(),
792            len: 0,
793            starts: RefCell::new(VecDeque::new()),
794        }
795    }
796
797    /// How many elements are in the whole ring.
798    #[inline]
799    const fn len(&self) -> usize {
800        self.len
801    }
802
803    /// Take the entries of a listpack as this ring's first chunk.
804    fn adopt(&mut self, lp: &Listpack) {
805        self.len = lp.len();
806        self.chunks.push_back(Chunk::adopt(lp.entries(), lp.len()));
807        self.tail_added();
808    }
809
810    /// What the whole ring costs.
811    ///
812    /// A chunk counts its own header, because it is sitting in the ring's own
813    /// allocation, so what is left to add is the slots the ring is holding empty
814    /// for the chunks it does not have yet. The start index goes in as well,
815    /// because it is eight bytes a chunk that the list would not otherwise be
816    /// holding, and a structure that hides part of itself from `MEMORY USAGE`
817    /// is worse than one that costs a little more.
818    fn memory_bytes(&self) -> usize {
819        let spare = self.chunks.capacity() - self.chunks.len();
820        self.chunks.iter().map(Chunk::memory_bytes).sum::<usize>()
821            + spare * size_of::<Chunk>()
822            + self.starts.borrow().capacity() * size_of::<i64>()
823    }
824
825    /// The first element.
826    fn front(&self) -> Option<Element<'_>> {
827        self.chunks.front()?.front()
828    }
829
830    /// The last element.
831    fn back(&self) -> Option<Element<'_>> {
832        self.chunks.back()?.back()
833    }
834
835    /// The element at `index`, chunks first and elements second.
836    fn get(&self, index: usize) -> Option<Element<'_>> {
837        let (i, within) = self.locate(index)?;
838        self.chunks[i].get(within)
839    }
840
841    /// A forward walk over every chunk in turn.
842    fn iter(&self) -> impl Iterator<Item = Element<'_>> {
843        self.chunks.iter().flat_map(Chunk::iter)
844    }
845
846    /// Where the first `value` is, counting from the front of the ring.
847    ///
848    /// Chunk by chunk, with a running base, rather than element by element. A
849    /// chunk that does not hold the value costs one call and one walk of its own
850    /// bytes, and the ring never builds an element for anything it is only
851    /// stepping over.
852    fn find(&self, value: &[u8], as_int: Option<i64>) -> Option<usize> {
853        let mut base = 0usize;
854        for c in &self.chunks {
855            if let Some(at) = c.find(value, as_int) {
856                return Some(base + at);
857            }
858            base += c.len();
859        }
860        None
861    }
862
863    /// Every place `value` is, front to back, with indexes from the front.
864    ///
865    /// One `limit` is spent across the whole ring rather than per chunk, which
866    /// is what makes `LPOS`'s `MAXLEN` mean the same thing here as it does on a
867    /// list small enough to still be one blob.
868    fn find_each(
869        &self,
870        value: &[u8],
871        as_int: Option<i64>,
872        limit: usize,
873        hit: &mut dyn FnMut(usize) -> bool,
874    ) -> usize {
875        let mut base = 0usize;
876        let mut looked = 0usize;
877        for c in &self.chunks {
878            if limit != 0 && looked >= limit {
879                break;
880            }
881            let at = base;
882            let mut on = true;
883            looked += c.find_each(value, as_int, limit.saturating_sub(looked), &mut |i| {
884                on = hit(at + i);
885                on
886            });
887            base += c.len();
888            if !on {
889                break;
890            }
891        }
892        looked
893    }
894
895    /// The same from the back, with indexes counted from the last element.
896    fn find_each_back(
897        &self,
898        value: &[u8],
899        as_int: Option<i64>,
900        limit: usize,
901        hit: &mut dyn FnMut(usize) -> bool,
902    ) -> usize {
903        let mut base = 0usize;
904        let mut looked = 0usize;
905        for c in self.chunks.iter().rev() {
906            if limit != 0 && looked >= limit {
907                break;
908            }
909            let at = base;
910            let mut on = true;
911            looked += c.find_each_back(value, as_int, limit.saturating_sub(looked), &mut |i| {
912                on = hit(at + i);
913                on
914            });
915            base += c.len();
916            if !on {
917                break;
918            }
919        }
920        looked
921    }
922
923    /// `count` elements from `start`, without walking to `start`.
924    ///
925    /// The chunks before the one holding `start` are stepped over as chunks, so
926    /// the only elements this decodes are the ones inside the chunk it lands in
927    /// and the ones it is going to return. A `skip` on the element walk decodes
928    /// every entry it passes, which turned a hundred element window in the
929    /// middle of a million element list into two milliseconds of reading
930    /// listpack headers nobody asked for.
931    fn range(&self, start: usize, count: usize) -> impl Iterator<Item = Element<'_>> {
932        let (chunk, within) = self.locate(start).unwrap_or((self.chunks.len(), 0));
933        let first = self.chunks.get(chunk).map(|c| c.iter_from(within));
934        first
935            .into_iter()
936            .flatten()
937            .chain(self.chunks.iter().skip(chunk + 1).flat_map(Chunk::iter))
938            .take(count)
939    }
940
941    /// The same walk the other way, chunks in reverse and each one backward.
942    fn iter_back(&self) -> impl Iterator<Item = Element<'_>> {
943        self.chunks.iter().rev().flat_map(Chunk::iter_back)
944    }
945
946    /// Which chunk holds the element at `index`, and where in that chunk.
947    ///
948    /// This used to walk the ring from whichever end was closer, which is fine
949    /// for a queue and terrible for anything that reads the middle: a million
950    /// element list is a few thousand chunks, and a `LINDEX` halfway along it
951    /// stepped over half of them to get there. That was two and a half
952    /// microseconds against a hundred and thirty nanoseconds for the same call
953    /// near an end.
954    ///
955    /// Now the ring carries where each chunk starts and the lookup is a binary
956    /// search. `08` section 6 puts it as chunk count arithmetic plus one chunk
957    /// walk, and the arithmetic is this.
958    ///
959    /// The starts are in their own coordinate system, whose origin is wherever
960    /// the head chunk happened to be when the index was last built. What a
961    /// lookup uses is the difference between two entries and never an entry on
962    /// its own, so the origin can be anything, and that is what makes work at
963    /// the front free: pushing an element on to the head chunk moves that
964    /// chunk's start back by one and leaves every other entry correct, where an
965    /// index of real positions would have had to add one to all of them.
966    ///
967    /// Only the first `starts.len()` chunks are described. A mutation in the
968    /// middle of the ring cuts the index back to the chunk it touched and
969    /// nothing more, so the mutation itself never walks, and the next lookup
970    /// that needs the rest pays for it once.
971    fn locate(&self, index: usize) -> Option<(usize, usize)> {
972        if index >= self.len {
973            return None;
974        }
975        // The two end chunks are answered by a comparison each, before any of
976        // the above. A list is a queue and the position a client asks for is
977        // usually near an end, and a binary search over a few thousand entries
978        // is eleven scattered loads to say what one subtraction already knew.
979        // Without this the index made `LINDEX mylist 3` half again as slow as
980        // the walk it replaced.
981        let head = self.chunks.front()?.len();
982        if index < head {
983            return Some((0, index));
984        }
985        let last = self.chunks.len() - 1;
986        let before_tail = self.len - self.chunks[last].len();
987        if index >= before_tail {
988            return Some((last, index - before_tail));
989        }
990        let mut starts = self.starts.borrow_mut();
991        if starts.is_empty() {
992            starts.push_back(0);
993        }
994        // Carry the index on from where the last lookup or the last mutation
995        // left it, and only as far as this index needs. A read near the front
996        // of a ring that was just cut does not describe the whole ring to
997        // answer.
998        let want = starts[0] + index as i64;
999        loop {
1000            let last = starts.len() - 1;
1001            let end = starts[last] + self.chunks[last].len() as i64;
1002            if end > want || starts.len() == self.chunks.len() {
1003                break;
1004            }
1005            starts.push_back(end);
1006        }
1007        // The last chunk that starts at or before the wanted position. An empty
1008        // chunk starts where the next one does, and this lands on the later of
1009        // the two, which is the one holding the element.
1010        let at = starts.partition_point(|&s| s <= want) - 1;
1011        Some((at, (want - starts[at]) as usize))
1012    }
1013
1014    /// Forget where every chunk from `from` onward starts.
1015    ///
1016    /// Cheap on purpose. Every mutation in the middle of the ring calls this
1017    /// and none of them rebuild anything, because the next lookup will.
1018    #[inline]
1019    fn cut(&mut self, from: usize) {
1020        let keep = from.min(self.chunks.len());
1021        let starts = self.starts.get_mut();
1022        if starts.len() > keep {
1023            starts.truncate(keep);
1024        }
1025    }
1026
1027    /// The head chunk's first element moved `by` places later.
1028    ///
1029    /// Negative for a push, positive for a pop. One subtraction, whatever the
1030    /// ring is holding, which is the whole point of the floating origin.
1031    #[inline]
1032    fn head_moved(&mut self, by: i64) {
1033        if let Some(first) = self.starts.get_mut().front_mut() {
1034            *first += by;
1035        }
1036    }
1037
1038    /// A chunk holding `len` elements went on the front of the ring.
1039    #[inline]
1040    fn head_added(&mut self, len: usize) {
1041        let starts = self.starts.get_mut();
1042        if let Some(&first) = starts.front() {
1043            starts.push_front(first - len as i64);
1044        }
1045    }
1046
1047    /// The head chunk left the ring, with everything that was in it.
1048    #[inline]
1049    fn head_dropped(&mut self) {
1050        self.starts.get_mut().pop_front();
1051    }
1052
1053    /// A chunk went on the back of the ring.
1054    ///
1055    /// Described only if everything before it already is, which is the case
1056    /// that matters: a list being filled with `RPUSH` grows a chunk at a time
1057    /// and never invalidates anything, so the index is complete by the time
1058    /// anybody reads the middle of it.
1059    #[inline]
1060    fn tail_added(&mut self) {
1061        let n = self.chunks.len();
1062        let before = if n >= 2 { self.chunks[n - 2].len() } else { 0 };
1063        let starts = self.starts.get_mut();
1064        if n == 1 && starts.is_empty() {
1065            starts.push_back(0);
1066        } else if starts.len() + 1 == n {
1067            let last = starts[n - 2];
1068            starts.push_back(last + before as i64);
1069        }
1070    }
1071
1072    /// Put `value` at the front, in the head chunk or in a new one.
1073    fn push_front(&mut self, value: &[u8]) {
1074        if let Some(head) = self.chunks.front_mut()
1075            && head.push_front(value)
1076        {
1077            self.len += 1;
1078            self.head_moved(-1);
1079            return;
1080        }
1081        // The chunk that was the head stops being an end, so it gives back the
1082        // room it was keeping. One that is empty goes instead, because a chunk
1083        // holding nothing is one every walk from that end has to step over.
1084        if self.chunks.front().is_some_and(Chunk::is_empty) {
1085            self.chunks.pop_front();
1086            self.head_dropped();
1087        } else if let Some(head) = self.chunks.front_mut() {
1088            head.seal();
1089        }
1090        self.chunks.push_front(lone(value, true));
1091        self.len += 1;
1092        self.head_added(1);
1093    }
1094
1095    /// Put `value` at the back, in the tail chunk or in a new one.
1096    fn push_back(&mut self, value: &[u8]) {
1097        if let Some(tail) = self.chunks.back_mut()
1098            && tail.push_back(value)
1099        {
1100            self.len += 1;
1101            return;
1102        }
1103        if self.chunks.back().is_some_and(Chunk::is_empty) {
1104            self.chunks.pop_back();
1105            self.cut(self.chunks.len());
1106        } else if let Some(tail) = self.chunks.back_mut() {
1107            tail.seal();
1108        }
1109        self.chunks.push_back(lone(value, false));
1110        self.len += 1;
1111        self.tail_added();
1112    }
1113
1114    /// Put `value` in at `index`, splitting a chunk if it will not take it.
1115    ///
1116    /// A chunk that refuses is split at the insertion point, which leaves two
1117    /// chunks with room between them for what would not fit. That is Redis's
1118    /// `_quicklistSplitNode` and the reason is the same: the alternative is
1119    /// pushing the rest of the list along one chunk at a time.
1120    fn insert_at(&mut self, index: usize, value: &[u8]) -> bool {
1121        if index > self.len {
1122            return false;
1123        }
1124        if index == 0 {
1125            self.push_front(value);
1126            return true;
1127        }
1128        if index == self.len {
1129            self.push_back(value);
1130            return true;
1131        }
1132        let Some((i, within)) = self.locate(index) else {
1133            return false;
1134        };
1135        if self.chunks[i].insert_at(within, value) {
1136            self.len += 1;
1137            // Chunk `i` still starts where it did. Everything after it moved.
1138            self.cut(i + 1);
1139            return true;
1140        }
1141        let mut rest = self.chunks[i].split_off(within);
1142        // Both halves have room now unless the element needs a chunk of its own.
1143        let put = self.chunks[i].push_back(value) || rest.push_front(value);
1144        self.chunks.insert(i + 1, rest);
1145        if !put {
1146            self.chunks.insert(i + 1, lone(value, false));
1147        }
1148        self.len += 1;
1149        self.cut(i + 1);
1150        true
1151    }
1152
1153    /// Take the element at `index` out.
1154    fn remove_at(&mut self, index: usize) -> bool {
1155        let Some((i, within)) = self.locate(index) else {
1156            return false;
1157        };
1158        if !self.chunks[i].remove_at(within) {
1159            return false;
1160        }
1161        self.len -= 1;
1162        if self.chunks[i].is_empty() && self.chunks.len() > 1 {
1163            self.chunks.remove(i);
1164            // The chunk that takes its place starts where the empty one did,
1165            // so `i` is still right, but keeping it would be an argument and
1166            // cutting it is a memory write.
1167            self.cut(i);
1168        } else {
1169            self.cut(i + 1);
1170        }
1171        true
1172    }
1173
1174    /// Put `value` where the element at `index` is.
1175    ///
1176    /// A replacement that does not fit is the same split an insert does, with
1177    /// the element being replaced dropped off the front of the second half.
1178    fn replace_at(&mut self, index: usize, value: &[u8]) -> bool {
1179        let Some((i, within)) = self.locate(index) else {
1180            return false;
1181        };
1182        if self.chunks[i].replace_at(within, value) {
1183            // One element out and one in, so no chunk moved and the index is
1184            // still true. This is the common case and it costs nothing.
1185            return true;
1186        }
1187        let mut rest = self.chunks[i].split_off(within);
1188        rest.drop_front();
1189        let put = self.chunks[i].push_back(value) || rest.push_front(value);
1190        if !rest.is_empty() {
1191            self.chunks.insert(i + 1, rest);
1192        }
1193        if !put {
1194            self.chunks.insert(i + 1, lone(value, false));
1195        }
1196        if self.chunks[i].is_empty() && self.chunks.len() > 1 {
1197            self.chunks.remove(i);
1198        }
1199        self.cut(i);
1200        true
1201    }
1202
1203    /// Keep `keep` elements from `start` and drop everything else.
1204    ///
1205    /// Whole chunks at either end go without their bytes being touched, and the
1206    /// two chunks the range ends inside move a cursor. A trim of a million
1207    /// element list down to ten is the walk over the chunk list and two walks
1208    /// inside a chunk.
1209    fn trim(&mut self, start: usize, keep: usize) {
1210        let mut front = start;
1211        while front > 0 {
1212            let Some(held) = self.chunks.front().map(Chunk::len) else {
1213                break;
1214            };
1215            if held <= front && self.chunks.len() > 1 {
1216                front -= held;
1217                self.len -= held;
1218                self.chunks.pop_front();
1219                self.head_dropped();
1220            } else {
1221                let took = self.chunks[0].drop_front_n(front);
1222                self.len -= took;
1223                front -= took;
1224                self.head_moved(took as i64);
1225                if took == 0 {
1226                    break;
1227                }
1228            }
1229        }
1230        let mut back = self.len - keep.min(self.len);
1231        while back > 0 {
1232            let Some(held) = self.chunks.back().map(Chunk::len) else {
1233                break;
1234            };
1235            if held <= back && self.chunks.len() > 1 {
1236                back -= held;
1237                self.len -= held;
1238                self.chunks.pop_back();
1239                self.cut(self.chunks.len());
1240            } else {
1241                let last = self.chunks.len() - 1;
1242                let took = self.chunks[last].drop_back_n(back);
1243                self.len -= took;
1244                back -= took;
1245                if took == 0 {
1246                    break;
1247                }
1248            }
1249        }
1250    }
1251
1252    /// Drop the first element, dropping the chunk with it if it was the last.
1253    fn drop_front(&mut self) -> bool {
1254        let Some(head) = self.chunks.front_mut() else {
1255            return false;
1256        };
1257        if !head.drop_front() {
1258            return false;
1259        }
1260        let gone = head.is_empty();
1261        self.len -= 1;
1262        self.head_moved(1);
1263        if gone && self.chunks.len() > 1 {
1264            self.chunks.pop_front();
1265            self.head_dropped();
1266        }
1267        true
1268    }
1269
1270    /// Drop the last element, dropping the chunk with it if it was the last.
1271    fn drop_back(&mut self) -> bool {
1272        let Some(tail) = self.chunks.back_mut() else {
1273            return false;
1274        };
1275        if !tail.drop_back() {
1276            return false;
1277        }
1278        let gone = tail.is_empty();
1279        self.len -= 1;
1280        if gone && self.chunks.len() > 1 {
1281            self.chunks.pop_back();
1282            self.cut(self.chunks.len());
1283        }
1284        true
1285    }
1286
1287    /// Every start the index claims to know, checked against a walk.
1288    ///
1289    /// The index is maintained by hand at nine call sites and a wrong entry
1290    /// would hand back the wrong element without anything else noticing, so
1291    /// the tests that mutate a ring call this rather than trusting the
1292    /// argument that the call sites are right.
1293    #[cfg(test)]
1294    fn index_is_true(&self) {
1295        let starts = self.starts.borrow();
1296        assert!(
1297            starts.len() <= self.chunks.len(),
1298            "the index describes {} chunks and the ring holds {}",
1299            starts.len(),
1300            self.chunks.len()
1301        );
1302        let Some(&base) = starts.front() else {
1303            return;
1304        };
1305        let mut real = 0usize;
1306        for (i, &s) in starts.iter().enumerate() {
1307            assert_eq!(
1308                s - base,
1309                real as i64,
1310                "chunk {i} is indexed at {} and starts at {real}",
1311                s - base
1312            );
1313            real += self.chunks[i].len();
1314        }
1315    }
1316}
1317
1318#[cfg(test)]
1319mod tests {
1320    use super::*;
1321
1322    fn all(l: &List) -> Vec<Vec<u8>> {
1323        l.iter().map(|e| e.to_vec()).collect()
1324    }
1325
1326    /// The same list in both bands, so a test can run its case over each.
1327    ///
1328    /// The elements differ in length between the two, because that is the only
1329    /// thing that decides which band a list of a given length is in, so every
1330    /// test over this compares against the list it was handed rather than
1331    /// against a literal.
1332    fn both_bands(n: usize) -> [List; 2] {
1333        let limits = Limits::default();
1334        let mut packed = List::new();
1335        let mut chunks = List::new();
1336        for i in 0..n {
1337            packed.push_back(format!("e{i}").as_bytes(), &limits);
1338            chunks.push_back(format!("e{i}:{}", "p".repeat(400)).as_bytes(), &limits);
1339        }
1340        assert_eq!(packed.encoding(), Encoding::Listpack);
1341        assert_eq!(chunks.encoding(), Encoding::Quicklist);
1342        [packed, chunks]
1343    }
1344
1345    /// A list of `n` elements, each long enough that `n` of them do not fit the
1346    /// packed band, so the test is standing on the chunked one.
1347    fn chunked(n: usize) -> List {
1348        let mut l = List::new();
1349        let limits = Limits::default();
1350        for i in 0..n {
1351            l.push_back(format!("value:{i:0>60}").as_bytes(), &limits);
1352        }
1353        assert_eq!(l.encoding(), Encoding::Quicklist, "{n} did not promote");
1354        l
1355    }
1356
1357    #[test]
1358    fn a_new_list_is_empty_and_packed() {
1359        let l = List::new();
1360        assert!(l.is_empty());
1361        assert_eq!(l.len(), 0);
1362        assert_eq!(l.encoding(), Encoding::Listpack);
1363        assert!(l.front().is_none());
1364        assert!(l.back().is_none());
1365        assert!(l.get(0).is_none());
1366    }
1367
1368    #[test]
1369    fn pushing_at_both_ends_puts_the_elements_in_order() {
1370        let mut l = List::new();
1371        let limits = Limits::default();
1372        l.push_back(b"b", &limits);
1373        l.push_back(b"c", &limits);
1374        l.push_front(b"a", &limits);
1375        assert_eq!(all(&l), vec![b"a".to_vec(), b"b".to_vec(), b"c".to_vec()]);
1376        assert_eq!(l.front().unwrap().to_vec(), b"a");
1377        assert_eq!(l.back().unwrap().to_vec(), b"c");
1378        assert_eq!(l.get(1).unwrap().to_vec(), b"b");
1379        assert_eq!(l.len(), 3);
1380    }
1381
1382    #[test]
1383    fn popping_takes_from_the_end_it_says() {
1384        let mut l = List::new();
1385        let limits = Limits::default();
1386        for m in [b"a", b"b", b"c"] {
1387            l.push_back(m, &limits);
1388        }
1389        assert_eq!(l.pop_front(&limits).unwrap(), b"a");
1390        assert_eq!(l.pop_back(&limits).unwrap(), b"c");
1391        assert_eq!(all(&l), vec![b"b".to_vec()]);
1392        assert_eq!(l.pop_front(&limits).unwrap(), b"b");
1393        assert!(l.pop_front(&limits).is_none());
1394        assert!(l.pop_back(&limits).is_none());
1395        assert!(l.is_empty());
1396    }
1397
1398    /// The band boundary is a size and not a count at the default setting, so a
1399    /// thousand short elements are still one blob.
1400    #[test]
1401    fn a_thousand_short_elements_stay_packed() {
1402        let mut l = List::new();
1403        let limits = Limits::default();
1404        for i in 0..1000 {
1405            l.push_back(i.to_string().as_bytes(), &limits);
1406        }
1407        assert_eq!(l.encoding(), Encoding::Listpack);
1408        assert_eq!(l.len(), 1000);
1409    }
1410
1411    #[test]
1412    fn enough_bytes_promotes_and_keeps_every_element() {
1413        let l = chunked(300);
1414        assert_eq!(l.len(), 300);
1415        for i in 0..300 {
1416            assert_eq!(
1417                l.get(i).unwrap().to_vec(),
1418                format!("value:{i:0>60}").into_bytes(),
1419                "element {i} after promotion"
1420            );
1421        }
1422    }
1423
1424    #[test]
1425    fn a_chunked_list_pushes_and_pops_at_both_ends() {
1426        let mut l = chunked(300);
1427        let limits = Limits::default();
1428        l.push_front(b"first", &limits);
1429        l.push_back(b"last", &limits);
1430        assert_eq!(l.len(), 302);
1431        assert_eq!(l.front().unwrap().to_vec(), b"first");
1432        assert_eq!(l.back().unwrap().to_vec(), b"last");
1433        assert_eq!(l.pop_front(&limits).unwrap(), b"first");
1434        assert_eq!(l.pop_back(&limits).unwrap(), b"last");
1435        assert_eq!(l.len(), 300);
1436        assert_eq!(
1437            l.front().unwrap().to_vec(),
1438            format!("value:{:0>60}", 0).into_bytes()
1439        );
1440    }
1441
1442    /// A queue: everything in at one end, everything out at the other, which is
1443    /// the shape that empties chunks from the front and makes new ones at the
1444    /// back at the same time.
1445    #[test]
1446    fn a_queue_drains_in_the_order_it_filled() {
1447        let mut l = List::new();
1448        let limits = Limits::default();
1449        for i in 0..5000 {
1450            l.push_back(format!("job:{i:0>40}").as_bytes(), &limits);
1451        }
1452        for i in 0..5000 {
1453            assert_eq!(
1454                l.pop_front(&limits).unwrap(),
1455                format!("job:{i:0>40}").into_bytes(),
1456                "job {i} came back in the wrong place"
1457            );
1458        }
1459        assert!(l.is_empty());
1460    }
1461
1462    /// A stack: in and out at the same end, which is the shape that leaves a
1463    /// chunk half empty and pushes into it again.
1464    #[test]
1465    fn a_stack_comes_back_in_reverse() {
1466        let mut l = List::new();
1467        let limits = Limits::default();
1468        for i in 0..2000 {
1469            l.push_front(format!("frame:{i:0>40}").as_bytes(), &limits);
1470        }
1471        for i in (0..2000).rev() {
1472            assert_eq!(
1473                l.pop_front(&limits).unwrap(),
1474                format!("frame:{i:0>40}").into_bytes()
1475            );
1476        }
1477        assert!(l.is_empty());
1478    }
1479
1480    #[test]
1481    fn indexing_agrees_with_the_walk_from_both_ends() {
1482        let l = chunked(1000);
1483        let walked = all(&l);
1484        for (i, want) in walked.iter().enumerate() {
1485            assert_eq!(&l.get(i).unwrap().to_vec(), want, "at {i}");
1486        }
1487        assert!(l.get(walked.len()).is_none());
1488    }
1489
1490    /// Redis converts a list back to a listpack when it shrinks under half the
1491    /// limit, and only then, so a list at the boundary does not flap.
1492    #[test]
1493    fn a_list_that_shrinks_far_enough_goes_back_to_one_blob() {
1494        let mut l = chunked(300);
1495        let limits = Limits::default();
1496        while l.len() > 200 {
1497            l.drop_back(&limits);
1498        }
1499        assert_eq!(
1500            l.encoding(),
1501            Encoding::Quicklist,
1502            "under the limit is not under half of it"
1503        );
1504        while l.len() > 50 {
1505            l.drop_back(&limits);
1506        }
1507        assert_eq!(l.encoding(), Encoding::Listpack);
1508        assert_eq!(l.len(), 50);
1509        for i in 0..50 {
1510            assert_eq!(
1511                l.get(i).unwrap().to_vec(),
1512                format!("value:{i:0>60}").into_bytes(),
1513                "element {i} survived the demotion"
1514            );
1515        }
1516    }
1517
1518    /// And it can be pushed straight back up again afterwards, which is the
1519    /// part a demotion that left the wrong length behind would break.
1520    #[test]
1521    fn a_demoted_list_promotes_again() {
1522        let mut l = chunked(300);
1523        let limits = Limits::default();
1524        while l.len() > 20 {
1525            l.drop_back(&limits);
1526        }
1527        assert_eq!(l.encoding(), Encoding::Listpack);
1528        for i in 0..300 {
1529            l.push_back(format!("again:{i:0>60}").as_bytes(), &limits);
1530        }
1531        assert_eq!(l.encoding(), Encoding::Quicklist);
1532        assert_eq!(l.len(), 320);
1533        assert_eq!(
1534            l.get(19).unwrap().to_vec(),
1535            format!("value:{:0>60}", 19).into_bytes(),
1536            "the last of the elements that survived the demotion"
1537        );
1538        assert_eq!(
1539            l.get(20).unwrap().to_vec(),
1540            format!("again:{:0>60}", 0).into_bytes(),
1541            "the first of the elements pushed after it"
1542        );
1543    }
1544
1545    /// An integer element is stored as an integer in both bands, which is what
1546    /// makes a list of numbers cost two bytes an element.
1547    #[test]
1548    fn integers_stay_integers_across_the_band_change() {
1549        let mut l = List::new();
1550        let limits = Limits::default();
1551        for i in 0..300 {
1552            l.push_back(i.to_string().as_bytes(), &limits);
1553            l.push_back(vec![b'x'; 100].as_slice(), &limits);
1554        }
1555        assert_eq!(l.encoding(), Encoding::Quicklist);
1556        assert_eq!(l.get(0), Some(Entry::Int(0)));
1557        assert_eq!(l.get(2), Some(Entry::Int(1)));
1558        assert_eq!(l.len(), 600);
1559    }
1560
1561    /// A positive `list-max-listpack-size` is a count of elements, which is the
1562    /// other half of the configuration and the shape the Redis test suite sets
1563    /// when it wants a quicklist out of four elements.
1564    #[test]
1565    fn a_count_limit_promotes_on_the_count() {
1566        let limits = Limits::of(4);
1567        let mut l = List::new();
1568        for i in 0..4 {
1569            l.push_back(i.to_string().as_bytes(), &limits);
1570        }
1571        assert_eq!(l.encoding(), Encoding::Listpack);
1572        l.push_back(b"5", &limits);
1573        assert_eq!(l.encoding(), Encoding::Quicklist);
1574        assert_eq!(l.len(), 5);
1575    }
1576
1577    #[test]
1578    fn the_limits_are_redis_node_limits() {
1579        assert_eq!(Limits::of(-1).max_packed_bytes, 4096);
1580        assert_eq!(Limits::of(-2).max_packed_bytes, 8192);
1581        assert_eq!(Limits::of(-5).max_packed_bytes, 65536);
1582        assert_eq!(Limits::of(-9).max_packed_bytes, 65536);
1583        assert_eq!(Limits::of(128).max_packed_entries, Some(128));
1584        assert_eq!(Limits::of(0).max_packed_entries, Some(1));
1585        assert_eq!(Limits::of(-2), Limits::default());
1586    }
1587
1588    #[test]
1589    fn memory_is_counted_in_both_bands() {
1590        let mut l = List::new();
1591        let limits = Limits::default();
1592        assert!(l.memory_bytes() > 0);
1593        for i in 0..300 {
1594            l.push_back(format!("value:{i:0>60}").as_bytes(), &limits);
1595        }
1596        // Three hundred elements of sixty six bytes is about twenty kilobytes,
1597        // and the chunks holding them should not be far off that.
1598        let held = l.memory_bytes();
1599        assert!(held > 300 * 66, "{held} is less than the elements");
1600        assert!(held < 300 * 66 * 3, "{held} is three times the elements");
1601    }
1602
1603    /// What a list element costs on top of the bytes it holds.
1604    ///
1605    /// M4's exit gate asks for one byte or less per element and this is the
1606    /// number that says whether that is where we are. Printed rather than
1607    /// asserted, because the point is the breakdown and not a threshold. The
1608    /// guard below is the part that runs every time.
1609    ///
1610    /// Three element lengths, because the answer is a fixed cost per element
1611    /// plus a fixed cost per chunk, and one length cannot tell those apart.
1612    #[test]
1613    #[ignore = "a measurement, run it by name"]
1614    fn measure_bytes_per_element() {
1615        let limits = Limits::default();
1616        for len in [8usize, 16, 64] {
1617            for n in [128usize, 10_000, 1_000_000] {
1618                let (l, payload) = weighed(n, len, &limits);
1619                let total = l.memory_bytes();
1620                println!(
1621                    "n={n:<9} elem={len:<4} band={:<9} total={total:<11} payload={payload:<11} over_per_element={:.2}",
1622                    l.encoding().name(),
1623                    (total as f64 - payload as f64) / n as f64
1624                );
1625            }
1626        }
1627    }
1628
1629    /// A list of `n` elements of `len` bytes each, and what those bytes come to.
1630    fn weighed(n: usize, len: usize, limits: &Limits) -> (List, usize) {
1631        let mut l = List::new();
1632        let mut payload = 0usize;
1633        for i in 0..n {
1634            // A letter in front so that the element is stored as a string. A
1635            // listpack stores something that parses as an integer as one, which
1636            // would be measuring the integer encoding rather than the ring.
1637            let v = format!("e{i:0>w$}", w = len - 1);
1638            debug_assert_eq!(v.len(), len);
1639            payload += v.len();
1640            l.push_back(v.as_bytes(), limits);
1641        }
1642        (l, payload)
1643    }
1644
1645    /// The guard for the measurement above, at a size that runs every time.
1646    ///
1647    /// The threshold is loose on purpose: what it is here to catch is a chunk
1648    /// that stopped giving its spare room back when it was sealed, or a ring
1649    /// that started holding something per element, and either of those is a
1650    /// multiple rather than a few percent.
1651    #[test]
1652    fn a_long_list_does_not_hold_much_more_than_it_stores() {
1653        let limits = Limits::default();
1654        let n = 100_000;
1655        let (l, payload) = weighed(n, 16, &limits);
1656        assert_eq!(l.encoding(), Encoding::Quicklist);
1657        let total = l.memory_bytes();
1658        assert!(
1659            total < payload + n * 4,
1660            "{total} bytes for {payload} of elements, which is {:.2} an element over",
1661            (total as f64 - payload as f64) / n as f64
1662        );
1663    }
1664
1665    /// An element bigger than a whole chunk gets a chunk of its own, which is
1666    /// what Redis calls a plain node. Without it the list would count an element
1667    /// that a chunk sized for something else had refused.
1668    #[test]
1669    fn an_element_too_big_for_a_chunk_gets_one_of_its_own() {
1670        let mut l = List::new();
1671        let limits = Limits::default();
1672        let huge = vec![b'h'; 20_000];
1673        l.push_back(&huge, &limits);
1674        assert_eq!(l.len(), 1);
1675        assert_eq!(l.encoding(), Encoding::Quicklist);
1676        assert_eq!(l.front().unwrap().to_vec(), huge);
1677        l.push_back(b"after", &limits);
1678        l.push_front(b"before", &limits);
1679        assert_eq!(l.len(), 3);
1680        assert_eq!(l.get(1).unwrap().to_vec(), huge);
1681        assert_eq!(l.back().unwrap().to_vec(), b"after");
1682        assert_eq!(l.front().unwrap().to_vec(), b"before");
1683        assert_eq!(l.pop_front(&limits).unwrap(), b"before");
1684        assert_eq!(l.pop_front(&limits).unwrap(), huge);
1685    }
1686
1687    #[test]
1688    fn the_walk_backward_is_the_walk_forward_reversed() {
1689        for mut l in [List::new(), chunked(400)] {
1690            let limits = Limits::default();
1691            l.push_back(b"tail", &limits);
1692            let mut want = all(&l);
1693            want.reverse();
1694            let got: Vec<Vec<u8>> = l.iter_back().map(|e| e.to_vec()).collect();
1695            assert_eq!(got, want, "{:?}", l.encoding());
1696        }
1697    }
1698
1699    #[test]
1700    fn a_range_is_the_window_it_was_asked_for() {
1701        for l in both_bands(50) {
1702            let all_of_it = all(&l);
1703            for (start, count) in [(0, 0), (0, 5), (3, 4), (48, 9), (50, 3), (0, 50)] {
1704                let got: Vec<Vec<u8>> = l.range(start, count).map(|e| e.to_vec()).collect();
1705                let want = &all_of_it[start.min(50)..(start + count).min(50)];
1706                assert_eq!(got, want, "{start} for {count} in {:?}", l.encoding());
1707            }
1708        }
1709    }
1710
1711    /// A window that starts in the middle now steps over whole chunks to get
1712    /// there instead of decoding every element on the way, so every start
1713    /// position and every window that crosses a chunk boundary is worth
1714    /// checking rather than the handful the case above uses.
1715    #[test]
1716    fn a_window_lands_in_the_right_place_whatever_chunk_it_starts_in() {
1717        let limits = Limits::default();
1718        let mut l = List::new();
1719        // Long enough elements that this is many chunks and not one, and enough
1720        // of them that a start position lands in the middle of a chunk, at the
1721        // front of one, and at the back of one.
1722        for i in 0..500 {
1723            l.push_back(format!("e{i}:{}", "p".repeat(200)).as_bytes(), &limits);
1724        }
1725        assert_eq!(l.encoding(), Encoding::Quicklist);
1726        let all_of_it = all(&l);
1727
1728        for start in 0..=500 {
1729            for count in [0usize, 1, 7, 130, 500] {
1730                let got: Vec<Vec<u8>> = l.range(start, count).map(|e| e.to_vec()).collect();
1731                let want = &all_of_it[start.min(500)..(start + count).min(500)];
1732                assert_eq!(got, want, "{count} from {start}");
1733            }
1734        }
1735    }
1736
1737    /// The same over a packed list, which seeks by walking the blob from
1738    /// whichever end is nearer rather than by finding a chunk. A list in this
1739    /// band holds eight kilobytes, which is four hundred odd elements and not
1740    /// the hundred and twenty eight the other packed bands stop at, so the half
1741    /// of the blob that the two ended seek saves is worth having and the seam
1742    /// between the two directions is worth checking at every position.
1743    #[test]
1744    fn a_packed_window_lands_in_the_right_place_from_either_end() {
1745        let limits = Limits::default();
1746        let mut l = List::new();
1747        for i in 0..400 {
1748            l.push_back(format!("e{i:0>9}").as_bytes(), &limits);
1749        }
1750        assert_eq!(l.encoding(), Encoding::Listpack);
1751        let all_of_it = all(&l);
1752
1753        for start in 0..=400 {
1754            assert_eq!(
1755                l.get(start).map(|e| e.to_vec()).as_ref(),
1756                all_of_it.get(start),
1757                "element {start}"
1758            );
1759            for count in [0usize, 1, 7, 130, 400] {
1760                let got: Vec<Vec<u8>> = l.range(start, count).map(|e| e.to_vec()).collect();
1761                let want = &all_of_it[start.min(400)..(start + count).min(400)];
1762                assert_eq!(got, want, "{count} from {start}");
1763            }
1764        }
1765    }
1766
1767    /// The chunk start index has a floating origin so that work at the front of
1768    /// the list costs it nothing, which is the one part of it that is clever
1769    /// enough to be wrong. This is the shape that would catch it: a queue being
1770    /// drained and refilled at the head while something reads the middle, where
1771    /// an index of real positions would need every entry rewritten on every
1772    /// push and this one moves a single number.
1773    #[test]
1774    fn reading_the_middle_survives_a_head_that_keeps_moving() {
1775        let limits = Limits::default();
1776        let mut l = List::new();
1777        let mut want: Vec<Vec<u8>> = Vec::new();
1778        for i in 0..2000 {
1779            let v = format!("e{i}:{}", "p".repeat(100)).into_bytes();
1780            l.push_back(&v, &limits);
1781            want.push(v);
1782        }
1783        assert_eq!(l.encoding(), Encoding::Quicklist);
1784
1785        for round in 0..400 {
1786            // Enough pushes and pops to walk the head chunk across its own
1787            // boundary in both directions rather than only inside it.
1788            if round % 3 == 0 {
1789                for k in 0..7 {
1790                    let v = format!("h{round}:{k}:{}", "q".repeat(100)).into_bytes();
1791                    l.push_front(&v, &limits);
1792                    want.insert(0, v);
1793                }
1794            } else {
1795                for _ in 0..5 {
1796                    assert_eq!(l.pop_front(&limits), Some(want.remove(0)));
1797                }
1798            }
1799            assert_eq!(l.len(), want.len(), "length after round {round}");
1800            for at in [0, 1, want.len() / 3, want.len() / 2, want.len() - 1] {
1801                assert_eq!(
1802                    l.get(at).map(|e| e.to_vec()).as_ref(),
1803                    Some(&want[at]),
1804                    "element {at} after round {round}"
1805                );
1806            }
1807            let mid = want.len() / 2;
1808            let got: Vec<Vec<u8>> = l.range(mid, 30).map(|e| e.to_vec()).collect();
1809            assert_eq!(got, want[mid..mid + 30], "the window after round {round}");
1810            let Body::Chunks(d) = &l.body else {
1811                panic!("the list left the chunked band");
1812            };
1813            d.index_is_true();
1814        }
1815    }
1816
1817    #[test]
1818    fn setting_an_element_replaces_only_that_one() {
1819        for mut l in both_bands(50) {
1820            let limits = Limits::default();
1821            let before = all(&l);
1822            let band = l.encoding();
1823            for at in [0usize, 1, 25, 49] {
1824                let mut want = before.clone();
1825                for value in [
1826                    &b"z"[..],
1827                    &b"a much longer element than the one there"[..],
1828                    b"42",
1829                ] {
1830                    assert!(l.set(at, value, &limits), "setting {at} in {band:?}");
1831                    want[at] = value.to_vec();
1832                    assert_eq!(all(&l), want, "setting {at} to {value:?} in {band:?}");
1833                }
1834                l.set(at, &before[at], &limits);
1835            }
1836            assert!(!l.set(50, b"z", &limits), "past the end is not a set");
1837            assert_eq!(all(&l), before);
1838        }
1839    }
1840
1841    /// The pivot search stopped walking elements and started reading entry
1842    /// headers, and it runs over a ring of chunks rather than one blob, so the
1843    /// two things it could get wrong are the offset it adds for the chunks in
1844    /// front of the one it found the value in, and an element whose encoding
1845    /// takes the long way through the scan.
1846    ///
1847    /// So this puts every kind of element in a list long enough to be several
1848    /// hundred chunks, of mixed lengths so that the chunk boundaries fall in
1849    /// awkward places, and asks for each of them by value. Every answer has to be
1850    /// the position the element is actually at, which is checked against the
1851    /// element walk rather than against a number written down here.
1852    #[test]
1853    fn a_pivot_is_found_at_the_right_position_across_a_ring_of_chunks() {
1854        let limits = Limits::default();
1855        let mut l = List::new();
1856        let mut want: Vec<Vec<u8>> = Vec::new();
1857        for i in 0..4_000usize {
1858            // Four shapes, cycling: a plain number, a number too big for the
1859            // small encodings, a short string and a long one. The lengths vary
1860            // with the index so that no two chunks break in the same place.
1861            let v = match i % 4 {
1862                0 => i.to_string().into_bytes(),
1863                1 => (i64::MAX - i as i64).to_string().into_bytes(),
1864                2 => format!("v{i:0width$}", width = 1 + i % 30).into_bytes(),
1865                _ => format!("value:{i}:{}", "x".repeat(40 + i % 90)).into_bytes(),
1866            };
1867            l.push_back(&v, &limits);
1868            want.push(v);
1869        }
1870        assert_eq!(l.encoding(), Encoding::Quicklist, "this needs the ring");
1871        assert_eq!(l.len(), want.len());
1872        for (at, v) in want.iter().enumerate() {
1873            assert_eq!(l.find(v), Some(at), "element {at} is not where it is");
1874        }
1875        assert_eq!(l.find(b"not in here at all"), None);
1876        // A near miss of a real element at both ends of it, which is what the
1877        // two word comparison is for and what it would get wrong if it only
1878        // looked at one end.
1879        assert_eq!(l.find(b"v2000000000000000000000000000002"), None);
1880    }
1881
1882    /// `LPOS` reads the same headers the pivot search does, and over a ring it
1883    /// has two more things to get wrong: the offset for the chunks in front of
1884    /// this one, and the same offset counted the other way for a negative rank.
1885    /// A ring of several hundred chunks with a match every seventh element
1886    /// catches an off by one in either of them, and every answer is checked
1887    /// against the element walk rather than against a number written down here.
1888    #[test]
1889    fn positions_agree_with_the_element_walk_across_a_ring_of_chunks() {
1890        let limits = Limits::default();
1891        let mut l = List::new();
1892        for i in 0..4_000usize {
1893            let v = if i % 7 == 0 {
1894                b"wanted".to_vec()
1895            } else {
1896                format!("element:{i:0width$}", width = 8 + i % 40).into_bytes()
1897            };
1898            l.push_back(&v, &limits);
1899        }
1900        assert_eq!(l.encoding(), Encoding::Quicklist, "this needs the ring");
1901        let want: Vec<usize> = (0..4_000).filter(|i| i % 7 == 0).collect();
1902
1903        let mut got = Vec::new();
1904        assert_eq!(
1905            l.positions(b"wanted", 1, 0, 0, &mut |at| got.push(at)),
1906            want.len()
1907        );
1908        assert_eq!(got, want);
1909
1910        // The same from the back, which walks the chunks in reverse and turns
1911        // every index round twice.
1912        let mut got = Vec::new();
1913        l.positions(b"wanted", -1, 0, 0, &mut |at| got.push(at));
1914        got.reverse();
1915        assert_eq!(got, want, "the same matches, found the other way round");
1916
1917        // A rank in the middle, forward and back, which drops matches before
1918        // handing any over.
1919        let mut got = Vec::new();
1920        l.positions(b"wanted", 4, 3, 0, &mut |at| got.push(at));
1921        assert_eq!(got, want[3..6].to_vec());
1922        let mut got = Vec::new();
1923        l.positions(b"wanted", -4, 3, 0, &mut |at| got.push(at));
1924        let mut tail = want[want.len() - 6..want.len() - 3].to_vec();
1925        tail.reverse();
1926        assert_eq!(got, tail);
1927
1928        // And a budget, which has to be spent across the whole ring rather than
1929        // per chunk: a thousand elements reaches every match under a thousand.
1930        let mut got = Vec::new();
1931        l.positions(b"wanted", 1, 0, 1_000, &mut |at| got.push(at));
1932        assert_eq!(
1933            got,
1934            want.iter()
1935                .copied()
1936                .filter(|&i| i < 1_000)
1937                .collect::<Vec<_>>()
1938        );
1939        let mut got = Vec::new();
1940        l.positions(b"wanted", -1, 0, 1_000, &mut |at| got.push(at));
1941        got.reverse();
1942        assert_eq!(
1943            got,
1944            want.iter()
1945                .copied()
1946                .filter(|&i| i >= 3_000)
1947                .collect::<Vec<_>>()
1948        );
1949    }
1950
1951    /// `LREM` reads the same headers and then removes what it found, so on a
1952    /// ring the thing it can get wrong is an index that was right when it was
1953    /// collected and stale by the time it is used.
1954    #[test]
1955    fn removing_across_a_ring_takes_out_exactly_what_was_asked_for() {
1956        let limits = Limits::default();
1957        let build = || {
1958            let mut l = List::new();
1959            for i in 0..3_000usize {
1960                let v = if i % 5 == 0 {
1961                    b"gone".to_vec()
1962                } else {
1963                    format!("element:{i:0width$}", width = 8 + i % 30).into_bytes()
1964                };
1965                l.push_back(&v, &limits);
1966            }
1967            assert_eq!(l.encoding(), Encoding::Quicklist, "this needs the ring");
1968            l
1969        };
1970        let kept: Vec<Vec<u8>> = (0..3_000usize)
1971            .filter(|i| i % 5 != 0)
1972            .map(|i| format!("element:{i:0width$}", width = 8 + i % 30).into_bytes())
1973            .collect();
1974
1975        let mut l = build();
1976        assert_eq!(l.remove(0, b"gone", &limits), 600);
1977        assert_eq!(all(&l), kept);
1978
1979        // From the front, which takes the first ten and leaves the rest.
1980        let mut l = build();
1981        assert_eq!(l.remove(10, b"gone", &limits), 10);
1982        assert_eq!(l.len(), 2_990);
1983        assert_eq!(l.find(b"gone"), Some(40), "the eleventh was at 50");
1984
1985        // And from the back, which takes the last ten.
1986        let mut l = build();
1987        assert_eq!(l.remove(-10, b"gone", &limits), 10);
1988        assert_eq!(l.len(), 2_990);
1989        let mut last = 0usize;
1990        l.positions(b"gone", -1, 1, 0, &mut |at| last = at);
1991        // The last ten matches were at 2950 up, so 2945 is now the last one and
1992        // nothing in front of it moved.
1993        assert_eq!(last, 2_945);
1994    }
1995
1996    #[test]
1997    fn an_insert_goes_where_the_pivot_is() {
1998        for mut l in both_bands(50) {
1999            let limits = Limits::default();
2000            let before = all(&l);
2001            let band = l.encoding();
2002            let pivot = before[25].clone();
2003            assert_eq!(
2004                l.insert_at_pivot(&pivot, b"before", true, &limits),
2005                Some(51)
2006            );
2007            assert_eq!(
2008                l.insert_at_pivot(&pivot, b"after", false, &limits),
2009                Some(52)
2010            );
2011            assert_eq!(l.get(25).unwrap().to_vec(), b"before", "{band:?}");
2012            assert_eq!(l.get(26).unwrap().to_vec(), pivot, "{band:?}");
2013            assert_eq!(l.get(27).unwrap().to_vec(), b"after", "{band:?}");
2014            assert_eq!(l.len(), 52);
2015            assert_eq!(
2016                l.insert_at_pivot(b"nothing like it", b"x", true, &limits),
2017                None
2018            );
2019            assert_eq!(l.len(), 52);
2020        }
2021    }
2022
2023    #[test]
2024    fn an_insert_by_index_takes_both_ends_and_the_middle() {
2025        for at in [0usize, 1, 200, 399, 400] {
2026            let mut l = chunked(400);
2027            let limits = Limits::default();
2028            let mut want = all(&l);
2029            assert!(l.insert(at, b"new", &limits), "inserting at {at}");
2030            want.insert(at, b"new".to_vec());
2031            assert_eq!(all(&l), want, "inserting at {at}");
2032            assert_eq!(l.len(), 401);
2033        }
2034        let mut l = chunked(400);
2035        assert!(!l.insert(401, b"new", &Limits::default()));
2036    }
2037
2038    #[test]
2039    fn removing_by_value_counts_from_the_end_it_was_told_to() {
2040        let build = || {
2041            let mut l = List::new();
2042            let limits = Limits::default();
2043            for i in 0..40 {
2044                l.push_back(
2045                    if i % 3 == 0 {
2046                        b"x".to_vec()
2047                    } else {
2048                        format!("e{i}").into_bytes()
2049                    }
2050                    .as_slice(),
2051                    &limits,
2052                );
2053            }
2054            l
2055        };
2056        let limits = Limits::default();
2057
2058        let mut l = build();
2059        assert_eq!(l.remove(0, b"x", &limits), 14, "every one of them");
2060        assert!(!all(&l).contains(&b"x".to_vec()));
2061        assert_eq!(l.len(), 26);
2062
2063        let mut l = build();
2064        assert_eq!(l.remove(2, b"x", &limits), 2);
2065        assert_eq!(l.len(), 38);
2066        assert_eq!(l.get(0).unwrap().to_vec(), b"e1", "the first two went");
2067
2068        let mut l = build();
2069        assert_eq!(l.remove(-2, b"x", &limits), 2);
2070        assert_eq!(l.get(0).unwrap().to_vec(), b"x", "the last two went");
2071        assert_eq!(l.back().unwrap().to_vec(), b"e38");
2072
2073        let mut l = build();
2074        assert_eq!(l.remove(99, b"x", &limits), 14, "more than there are");
2075        assert_eq!(l.remove(1, b"nothing like it", &limits), 0);
2076    }
2077
2078    #[test]
2079    fn a_trim_keeps_the_window_and_nothing_else() {
2080        for (start, count) in [
2081            (0usize, 400usize),
2082            (0, 10),
2083            (390, 10),
2084            (100, 200),
2085            (0, 0),
2086            (399, 1),
2087        ] {
2088            let mut l = chunked(400);
2089            let limits = Limits::default();
2090            let want = all(&l)[start..start + count].to_vec();
2091            l.trim(start, count, &limits);
2092            assert_eq!(all(&l), want, "keeping {count} from {start}");
2093            assert_eq!(l.len(), count);
2094        }
2095    }
2096
2097    /// A trim that leaves a handful takes the list back to one blob, which is
2098    /// the shrinking rule reached the other way.
2099    #[test]
2100    fn a_trim_that_leaves_a_handful_goes_back_to_one_blob() {
2101        let mut l = chunked(400);
2102        let limits = Limits::default();
2103        l.trim(10, 5, &limits);
2104        assert_eq!(l.encoding(), Encoding::Listpack);
2105        assert_eq!(l.len(), 5);
2106        assert_eq!(
2107            l.get(0).unwrap().to_vec(),
2108            format!("value:{:0>60}", 10).into_bytes()
2109        );
2110    }
2111
2112    #[test]
2113    fn a_position_is_counted_from_the_end_the_rank_asked_for() {
2114        for mut l in [List::new(), chunked(300)] {
2115            let limits = Limits::default();
2116            let band = l.encoding();
2117            for m in [b"a", b"b", b"a", b"c", b"a"] {
2118                l.push_back(m, &limits);
2119            }
2120            let base = l.len() - 5;
2121            let mut out = Vec::new();
2122
2123            l.positions(b"a", 1, 1, 0, &mut |at| out.push(at));
2124            assert_eq!(out, vec![base], "{band:?}");
2125
2126            out.clear();
2127            l.positions(b"a", 2, 1, 0, &mut |at| out.push(at));
2128            assert_eq!(out, vec![base + 2], "the second from the front");
2129
2130            out.clear();
2131            l.positions(b"a", -1, 1, 0, &mut |at| out.push(at));
2132            assert_eq!(out, vec![base + 4], "the first from the back");
2133
2134            out.clear();
2135            l.positions(b"a", -2, 1, 0, &mut |at| out.push(at));
2136            assert_eq!(out, vec![base + 2], "the second from the back");
2137
2138            out.clear();
2139            l.positions(b"a", 1, 0, 0, &mut |at| out.push(at));
2140            assert_eq!(out, vec![base, base + 2, base + 4], "all of them");
2141
2142            out.clear();
2143            l.positions(b"a", -1, 0, 0, &mut |at| out.push(at));
2144            assert_eq!(out, vec![base + 4, base + 2, base], "all of them backward");
2145
2146            out.clear();
2147            l.positions(b"a", 1, 2, 0, &mut |at| out.push(at));
2148            assert_eq!(out, vec![base, base + 2], "two of them");
2149
2150            out.clear();
2151            l.positions(b"nothing like it", 1, 0, 0, &mut |at| out.push(at));
2152            assert!(out.is_empty());
2153
2154            out.clear();
2155            l.positions(b"a", 0, 0, 0, &mut |at| out.push(at));
2156            assert!(out.is_empty(), "a rank of zero is not a rank");
2157        }
2158    }
2159
2160    /// `MAXLEN` bounds the comparisons and not the answers, so a match past it
2161    /// is not found however few answers have been collected.
2162    #[test]
2163    fn maxlen_stops_the_walk_rather_than_the_answers() {
2164        let mut l = List::new();
2165        let limits = Limits::default();
2166        for i in 0..20 {
2167            l.push_back(
2168                if i == 15 {
2169                    b"x".to_vec()
2170                } else {
2171                    format!("e{i}").into_bytes()
2172                }
2173                .as_slice(),
2174                &limits,
2175            );
2176        }
2177        let mut out = Vec::new();
2178        l.positions(b"x", 1, 0, 10, &mut |at| out.push(at));
2179        assert!(out.is_empty(), "ten comparisons do not reach the sixteenth");
2180        out.clear();
2181        l.positions(b"x", 1, 0, 16, &mut |at| out.push(at));
2182        assert_eq!(out, vec![15]);
2183        out.clear();
2184        l.positions(b"x", -1, 0, 5, &mut |at| out.push(at));
2185        assert_eq!(out, vec![15], "five from the back does reach it");
2186    }
2187
2188    /// Every operation against a plain `Vec`, over a mix of element sizes that
2189    /// crosses the band boundary in both directions several times. A model test
2190    /// rather than more cases, because the interesting bugs here are the ones
2191    /// where a chunk splits and an index moves and the length stops agreeing.
2192    #[test]
2193    fn a_long_run_of_operations_agrees_with_a_vec() {
2194        // Twice, because the default limits keep a list of this size packed the
2195        // whole way and never walk the code that splits and joins chunks. A
2196        // `list-max-listpack-size` of 8 is a real setting and it puts the band
2197        // change within reach of a few pushes, so the second run crosses it in
2198        // both directions hundreds of times.
2199        let (_, chunked) = model_run(&Limits::default());
2200        assert_eq!(chunked, 0, "the default limits should not chunk this list");
2201        let (packed, chunked) = model_run(&Limits::of(8));
2202        assert!(packed > 200, "{packed} rounds packed");
2203        assert!(chunked > 200, "{chunked} rounds chunked");
2204    }
2205
2206    /// Four thousand rounds of a fixed sequence of list operations against a
2207    /// `Vec` that says what the answer is, and the two band counts it saw.
2208    fn model_run(limits: &Limits) -> (usize, usize) {
2209        let mut l = List::new();
2210        let mut want: Vec<Vec<u8>> = Vec::new();
2211        // A fixed sequence rather than a random one, so a failure is a failure
2212        // every time it is run.
2213        let mut seed = 0x2064_u64;
2214        let mut next = move || {
2215            seed = seed.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
2216            (seed >> 33) as usize
2217        };
2218        let (mut packed, mut chunked) = (0, 0);
2219        for round in 0..4000 {
2220            let n = next();
2221            let value = match n % 4 {
2222                0 => format!("{}", n % 97).into_bytes(),
2223                1 => vec![b'a' + (n % 26) as u8; 1 + n % 40],
2224                2 => vec![b'z'; 200 + n % 400],
2225                _ => format!("e{round}").into_bytes(),
2226            };
2227            match n % 9 {
2228                0 => {
2229                    l.push_front(&value, limits);
2230                    want.insert(0, value);
2231                }
2232                1 | 2 => {
2233                    l.push_back(&value, limits);
2234                    want.push(value);
2235                }
2236                3 if !want.is_empty() => {
2237                    let at = n % want.len();
2238                    assert!(l.insert(at, &value, limits));
2239                    want.insert(at, value);
2240                }
2241                4 if !want.is_empty() => {
2242                    let at = n % want.len();
2243                    assert!(l.set(at, &value, limits));
2244                    want[at] = value;
2245                }
2246                5 if !want.is_empty() => {
2247                    assert_eq!(l.pop_front(limits), Some(want.remove(0)));
2248                }
2249                6 if !want.is_empty() => {
2250                    assert_eq!(l.pop_back(limits), want.pop());
2251                }
2252                7 if want.len() > 4 => {
2253                    let start = n % (want.len() - 2);
2254                    let keep = 1 + n % (want.len() - start);
2255                    l.trim(start, keep, limits);
2256                    want = want[start..start + keep].to_vec();
2257                }
2258                8 if !want.is_empty() => {
2259                    let needle = want[n % want.len()].clone();
2260                    let count = [0i64, 1, -1, 3][n % 4];
2261                    let gone = l.remove(count, &needle, limits);
2262                    let mut hits: Vec<usize> = want
2263                        .iter()
2264                        .enumerate()
2265                        .filter(|(_, m)| **m == needle)
2266                        .map(|(i, _)| i)
2267                        .collect();
2268                    if count < 0 {
2269                        hits.reverse();
2270                    }
2271                    if count != 0 {
2272                        hits.truncate(count.unsigned_abs() as usize);
2273                    }
2274                    assert_eq!(gone, hits.len(), "round {round}");
2275                    hits.sort_unstable();
2276                    for at in hits.iter().rev() {
2277                        want.remove(*at);
2278                    }
2279                }
2280                _ => {}
2281            }
2282            assert_eq!(l.len(), want.len(), "length after round {round}");
2283            // Read at both ends and in the middle every round. That builds the
2284            // chunk start index back up after whatever the round did to it, so
2285            // the audit below is checking a filled index and not an empty one,
2286            // and a stale entry shows up here as the wrong element rather than
2287            // as nothing at all.
2288            if !want.is_empty() {
2289                for at in [0, want.len() / 2, want.len() - 1] {
2290                    assert_eq!(
2291                        l.get(at).map(|e| e.to_vec()).as_ref(),
2292                        Some(&want[at]),
2293                        "element {at} after round {round}"
2294                    );
2295                }
2296            }
2297            if let Body::Chunks(d) = &l.body {
2298                d.index_is_true();
2299            }
2300            match l.encoding() {
2301                Encoding::Listpack => packed += 1,
2302                Encoding::Quicklist => chunked += 1,
2303            }
2304            if round % 25 == 0 {
2305                assert_eq!(all(&l), want, "contents after round {round}");
2306                let mut back = all(&l);
2307                back.reverse();
2308                let walked: Vec<Vec<u8>> = l.iter_back().map(|e| e.to_vec()).collect();
2309                assert_eq!(walked, back, "the backward walk after round {round}");
2310                if let Some(first) = want.first() {
2311                    assert_eq!(l.front().unwrap().to_vec(), *first);
2312                    assert_eq!(l.back().unwrap().to_vec(), *want.last().unwrap());
2313                    assert_eq!(l.find(first), Some(0));
2314                }
2315            }
2316        }
2317        assert_eq!(all(&l), want);
2318        (packed, chunked)
2319    }
2320
2321    /// Freeze a list, read it back, and check nothing about it changed.
2322    fn round_trip(l: &List) -> List {
2323        let mut out = Vec::new();
2324        l.freeze(&mut out);
2325        let back = List::thaw(&out).expect("it came back");
2326        assert_eq!(back.len(), l.len(), "the length");
2327        assert_eq!(back.encoding(), l.encoding(), "the band");
2328        assert_eq!(all(&back), all(l), "the elements");
2329        let mut backward: Vec<Vec<u8>> = back.iter_back().map(|e| e.to_vec()).collect();
2330        backward.reverse();
2331        assert_eq!(backward, all(l), "and the walk the other way");
2332        back
2333    }
2334
2335    #[test]
2336    fn a_frozen_list_comes_back_in_the_band_it_left() {
2337        round_trip(&List::new());
2338        for l in both_bands(40) {
2339            round_trip(&l);
2340        }
2341        for l in both_bands(200) {
2342            round_trip(&l);
2343        }
2344    }
2345
2346    /// The ring comes back with the chunks it left with, not one long one.
2347    ///
2348    /// Both because `MEMORY USAGE` should say the same thing on both sides of a
2349    /// trip to the device, and because what an index costs is a walk over chunks
2350    /// and then a walk inside one.
2351    #[test]
2352    fn a_ring_comes_back_with_the_same_chunk_boundaries() {
2353        let l = chunked(2000);
2354        let Body::Chunks(before) = &l.body else {
2355            unreachable!("chunked built a ring");
2356        };
2357        let want: Vec<usize> = before.chunks.iter().map(Chunk::len).collect();
2358        assert!(want.len() > 2, "{} chunks is not a ring", want.len());
2359
2360        let back = round_trip(&l);
2361        let Body::Chunks(after) = &back.body else {
2362            unreachable!("it came back a ring");
2363        };
2364        let got: Vec<usize> = after.chunks.iter().map(Chunk::len).collect();
2365        assert_eq!(got, want);
2366    }
2367
2368    #[test]
2369    fn a_list_that_came_back_takes_more_elements_at_both_ends() {
2370        let limits = Limits::default();
2371        for l in both_bands(200) {
2372            let mut back = round_trip(&l);
2373            back.push_front(b"first", &limits);
2374            back.push_back(b"last", &limits);
2375            assert_eq!(back.len(), l.len() + 2);
2376            assert_eq!(back.front().expect("a front").to_vec(), b"first".to_vec());
2377            assert_eq!(back.back().expect("a back").to_vec(), b"last".to_vec());
2378            assert_eq!(back.get(1).expect("the old front").to_vec(), all(&l)[0]);
2379        }
2380    }
2381
2382    #[test]
2383    fn an_element_too_big_for_a_chunk_survives_the_trip() {
2384        let limits = Limits::default();
2385        let mut l = List::new();
2386        l.push_back(b"before", &limits);
2387        l.push_back(&vec![b'x'; CHUNK_BYTES * 2], &limits);
2388        l.push_back(b"after", &limits);
2389        assert_eq!(l.encoding(), Encoding::Quicklist);
2390        let back = round_trip(&l);
2391        assert_eq!(
2392            back.get(1).expect("the big one").to_vec().len(),
2393            CHUNK_BYTES * 2
2394        );
2395    }
2396
2397    #[test]
2398    fn a_frozen_list_that_arrives_damaged_is_an_error_and_not_a_panic() {
2399        for l in both_bands(40) {
2400            let mut out = Vec::new();
2401            l.freeze(&mut out);
2402            for cut in 0..out.len() {
2403                let _ = List::thaw(&out[..cut]);
2404            }
2405        }
2406        assert_eq!(List::thaw(&[]).err(), Some(Broken::Short));
2407        assert_eq!(List::thaw(&[9]).err(), Some(Broken::Form));
2408        assert_eq!(List::thaw(&[FORM_PACKED, 1, 2]).err(), Some(Broken::Body));
2409        // A chunk count no amount of what is left could fill, and then a chunk
2410        // claiming more elements than it has bytes for.
2411        assert_eq!(
2412            List::thaw(&[FORM_CHUNKS, 0xff, 0xff, 0x7f, 0]).err(),
2413            Some(Broken::Body)
2414        );
2415        assert_eq!(
2416            List::thaw(&[FORM_CHUNKS, 1, 9, 2, b'a', b'b']).err(),
2417            Some(Broken::Body)
2418        );
2419    }
2420}