Skip to main content

yo_kv/
chunk.rs

1//! A run of packed elements with a cursor at each end.
2//!
3//! This is what a large list is made of. A [`Listpack`](crate::listpack) is a
4//! blob with a header and a terminator, and taking the first element out of one
5//! means moving every byte behind it left. That is what a quicklist does on
6//! every `LPOP` and it is most of why aki lost that row. A chunk holds the same
7//! entries in the same encoding and puts a cursor at each end instead:
8//!
9//! ```text
10//! +--------------+---------+-----+---------+--------------+
11//! | free         | entry 0 | ... | entry k | free         |
12//! +--------------+---------+-----+---------+--------------+
13//!                ^                         ^
14//!                head                      tail
15//! ```
16//!
17//! Taking from the front moves `head` right and takes from the back moves `tail`
18//! left. Neither touches a byte the other end owns, which is the whole of
19//! `04` section 6 for a list: a chunk that is being popped at one end and pushed
20//! at the other has two cursors that never meet in one cache line.
21//!
22//! # Which way a chunk grows
23//!
24//! A chunk is made for the end it is going to serve. One made for the back
25//! starts with both cursors at zero and grows right; one made for the front
26//! starts with both at the end of its buffer and grows left. That is a hint and
27//! not a rule: a push at the end with no room left slides the live bytes over
28//! and splits what is free between the two ends, so a chunk that was filled by
29//! `RPUSH` and is then pushed at the front shifts once and then serves both.
30//! Without that a list that is pushed at one end and popped at the same end
31//! would allocate eight kilobytes per push, because the chunk it emptied would
32//! be the wrong way round every time.
33//!
34//! # Sealing
35//!
36//! A chunk in the middle of a list is never written to again, so it gives back
37//! the room it was holding for growth it will not see. [`Chunk::seal`] slides
38//! the live bytes to the front and shrinks the buffer to them, which is one copy
39//! of at most a couple of kilobytes once per chunk. Without it a list of a
40//! million small integers would hold four times the bytes it needs, because the
41//! room a chunk keeps for the next push is a per chunk cost and there is a chunk
42//! per hundred and twenty eight elements.
43
44use crate::listpack::{Entry, backlen_len, decode, entry_len, read_backlen, write_entry};
45
46/// How much room a chunk asks for when it is made.
47///
48/// Eight kilobytes, which is what `list-max-listpack-size -2` gives a quicklist
49/// node in a default Redis and therefore the size a list of a given length has
50/// been measured against for a decade. It is also what the packed band holds
51/// before it promotes, so the listpack a list arrives as becomes exactly one
52/// chunk with nothing left over.
53pub const CHUNK_BYTES: usize = 8192;
54
55/// How many elements a chunk holds before a new one starts.
56///
57/// Redis puts no count limit on a node at the default fill and we do, because
58/// eight kilobytes of two byte integers is four thousand elements and everything
59/// that reaches a list by index walks chunks first and elements second. Five
60/// hundred and twelve bounds the walk inside a chunk without making the walk
61/// over chunks long: a million small integers is under two thousand chunks
62/// either way, and the descriptor cache that makes that a lookup rather than a
63/// walk is `08` section 5's K10 and is not here yet.
64pub const CHUNK_ENTRIES: usize = 512;
65
66/// A run of entries, filled from one end or the other.
67#[derive(Debug, Clone, PartialEq, Eq)]
68pub struct Chunk {
69    bytes: Vec<u8>,
70    /// First live byte.
71    head: usize,
72    /// One past the last live byte.
73    tail: usize,
74    count: usize,
75}
76
77impl Chunk {
78    /// An empty chunk that grows toward the back.
79    #[must_use]
80    pub fn for_back() -> Chunk {
81        Chunk {
82            bytes: vec![0; CHUNK_BYTES],
83            head: 0,
84            tail: 0,
85            count: 0,
86        }
87    }
88
89    /// An empty chunk that grows toward the front.
90    #[must_use]
91    pub fn for_front() -> Chunk {
92        Chunk {
93            bytes: vec![0; CHUNK_BYTES],
94            head: CHUNK_BYTES,
95            tail: CHUNK_BYTES,
96            count: 0,
97        }
98    }
99
100    /// A chunk of its own for one element too big for an ordinary one.
101    ///
102    /// A list element can be half a gigabyte and a chunk is eight kilobytes, so
103    /// something has to give. Redis calls this a plain node and so does this:
104    /// the chunk is exactly the size of the element, it is full the moment it is
105    /// made, and every push against it is refused, which puts the next element
106    /// in a chunk of its own rather than growing this one to hold both.
107    #[must_use]
108    pub fn plain(value: &[u8]) -> Chunk {
109        let mut c = Chunk {
110            bytes: vec![0; entry_len(value)],
111            head: 0,
112            tail: 0,
113            count: 0,
114        };
115        let put = c.push_back(value);
116        debug_assert!(put, "a chunk sized for one element refused it");
117        c
118    }
119
120    /// A chunk holding entries somebody else already encoded.
121    ///
122    /// The promotion out of the packed band, where the bytes in question are a
123    /// listpack's entry region. They are in this encoding already, so the band
124    /// change is one copy and not a re-encode of every element, and the chunk
125    /// that comes out of it grows toward the back because a list that has just
126    /// outgrown a listpack is nearly always one that is being appended to.
127    #[must_use]
128    pub fn adopt(entries: &[u8], count: usize) -> Chunk {
129        let mut bytes = vec![0; CHUNK_BYTES.max(entries.len())];
130        bytes[..entries.len()].copy_from_slice(entries);
131        Chunk {
132            bytes,
133            head: 0,
134            tail: entries.len(),
135            count,
136        }
137    }
138
139    /// How many elements are in it.
140    #[must_use]
141    #[inline]
142    pub const fn len(&self) -> usize {
143        self.count
144    }
145
146    /// Whether it holds nothing.
147    #[must_use]
148    #[inline]
149    pub const fn is_empty(&self) -> bool {
150        self.count == 0
151    }
152
153    /// What it costs, buffer included.
154    #[must_use]
155    pub fn memory_bytes(&self) -> usize {
156        self.bytes.capacity() + size_of::<Chunk>()
157    }
158
159    /// How many bytes the entries themselves take.
160    #[must_use]
161    #[inline]
162    pub const fn live_bytes(&self) -> usize {
163        self.tail - self.head
164    }
165
166    /// The encoded entries, without the dead space at either end.
167    ///
168    /// The reverse of [`Chunk::adopt`], and the two are a pair: what comes out
169    /// of here goes back in there and gives a chunk holding the same elements.
170    /// That is what a demotion needs, because the ring is rebuilt on the way
171    /// back and rebuilding it by pushing every element one at a time would
172    /// re-encode a list that arrived already encoded.
173    #[must_use]
174    #[inline]
175    pub fn entries(&self) -> &[u8] {
176        &self.bytes[self.head..self.tail]
177    }
178
179    /// Put `value` at the back, or say there was no room.
180    ///
181    /// The count cap is checked here rather than by the caller because a chunk
182    /// that is full for either reason is full in exactly the same way, and a
183    /// caller that had to check one of the two would eventually forget.
184    pub fn push_back(&mut self, value: &[u8]) -> bool {
185        if self.count >= CHUNK_ENTRIES {
186            return false;
187        }
188        let need = entry_len(value);
189        if self.tail + need > self.bytes.len() && !self.shift(need, false) {
190            return false;
191        }
192        write_entry(&mut self.bytes[self.tail..], value);
193        self.tail += need;
194        self.count += 1;
195        true
196    }
197
198    /// Put `value` at the front, or say there was no room.
199    pub fn push_front(&mut self, value: &[u8]) -> bool {
200        if self.count >= CHUNK_ENTRIES {
201            return false;
202        }
203        let need = entry_len(value);
204        if need > self.head && !self.shift(need, true) {
205            return false;
206        }
207        self.head -= need;
208        write_entry(&mut self.bytes[self.head..], value);
209        self.count += 1;
210        true
211    }
212
213    /// Slide the live bytes so that `need` more of them fit at the end asking.
214    ///
215    /// Says no when the buffer as a whole does not have the room, which is the
216    /// full chunk the caller above turns into a new one. When it does have the
217    /// room it gives the asking end what it asked for and splits the rest
218    /// evenly, so that a chunk being pushed at both ends shifts once rather than
219    /// on every second push.
220    fn shift(&mut self, need: usize, front: bool) -> bool {
221        let live = self.tail - self.head;
222        let free = self.bytes.len() - live;
223        if free < need {
224            return false;
225        }
226        let spare = (free - need) / 2;
227        let head = if front { need + spare } else { spare };
228        self.bytes.copy_within(self.head..self.tail, head);
229        self.head = head;
230        self.tail = head + live;
231        true
232    }
233
234    /// The first element, without taking it out.
235    #[must_use]
236    pub fn front(&self) -> Option<Entry<'_>> {
237        if self.count == 0 {
238            return None;
239        }
240        decode(&self.bytes[self.head..self.tail]).map(|(e, _)| e)
241    }
242
243    /// The last element, without taking it out.
244    #[must_use]
245    pub fn back(&self) -> Option<Entry<'_>> {
246        let at = self.back_at()?;
247        decode(&self.bytes[at..self.tail]).map(|(e, _)| e)
248    }
249
250    /// The element at `index` from the front.
251    #[must_use]
252    pub fn get(&self, index: usize) -> Option<Entry<'_>> {
253        let at = self.offset_of(index)?;
254        decode(&self.bytes[at..self.tail]).map(|(e, _)| e)
255    }
256
257    /// Drop the first element, and say whether there was one.
258    ///
259    /// The bytes stay where they are. A chunk that has been emptied from the
260    /// front is dropped whole by the deque above, so there is nobody left to
261    /// care that the room it is holding is at the wrong end.
262    pub fn drop_front(&mut self) -> bool {
263        if self.count == 0 {
264            return false;
265        }
266        let Some(step) = self.step(self.head) else {
267            return false;
268        };
269        self.head += step;
270        self.count -= 1;
271        true
272    }
273
274    /// Drop the last element, and say whether there was one.
275    pub fn drop_back(&mut self) -> bool {
276        let Some(at) = self.back_at() else {
277            return false;
278        };
279        self.tail = at;
280        self.count -= 1;
281        true
282    }
283
284    /// Drop the first `n` elements.
285    ///
286    /// A walk of `n` entries and one move of the head cursor, with no bytes
287    /// touched at all, which is what makes `LTRIM` of the front of a long list
288    /// cost the walk and nothing else. Stops early and says how many it really
289    /// dropped if the chunk runs out first.
290    pub fn drop_front_n(&mut self, n: usize) -> usize {
291        let mut at = self.head;
292        let took = n.min(self.count);
293        for _ in 0..took {
294            let Some(step) = self.step(at) else {
295                break;
296            };
297            at += step;
298        }
299        self.count -= took;
300        self.head = at;
301        took
302    }
303
304    /// Drop the last `n` elements.
305    pub fn drop_back_n(&mut self, n: usize) -> usize {
306        let took = n.min(self.count);
307        for _ in 0..took {
308            let Some(at) = self.back_at() else {
309                break;
310            };
311            self.tail = at;
312            self.count -= 1;
313        }
314        took
315    }
316
317    /// Put `value` in at `index`, pushing what was there along.
318    ///
319    /// Says no when the chunk has no room, which the deque above answers by
320    /// splitting the chunk and asking one of the halves. The bytes on one side
321    /// of the hole do have to move, because the elements between the cursors are
322    /// a run, but it is whichever side is shorter and it is at most the size of
323    /// a chunk. That is the same move a quicklist makes for an insert and the
324    /// difference is that a list only inserts in the middle when a client asks
325    /// it to, where a quicklist does it on every `LPOP`.
326    pub fn insert_at(&mut self, index: usize, value: &[u8]) -> bool {
327        if index > self.count {
328            return false;
329        }
330        if index == self.count {
331            return self.push_back(value);
332        }
333        if index == 0 {
334            return self.push_front(value);
335        }
336        if self.count >= CHUNK_ENTRIES {
337            return false;
338        }
339        let need = entry_len(value);
340        let Some(at) = self.offset_of(index) else {
341            return false;
342        };
343        // Move the front half back or the back half forward, whichever end has
344        // the room, preferring the shorter side when both do.
345        let front = need <= self.head;
346        let back = self.tail + need <= self.bytes.len();
347        let at = if (front && !back) || (front && back && index * 2 <= self.count) {
348            self.bytes.copy_within(self.head..at, self.head - need);
349            self.head -= need;
350            at - need
351        } else if back {
352            self.bytes.copy_within(at..self.tail, at + need);
353            self.tail += need;
354            at
355        } else {
356            // No room at either end but perhaps room in total, in which case the
357            // shift makes some and the offset has to be found again.
358            if !self.shift(need, false) {
359                return false;
360            }
361            let at = self.offset_of(index).expect("the entries did not move");
362            self.bytes.copy_within(at..self.tail, at + need);
363            self.tail += need;
364            at
365        };
366        write_entry(&mut self.bytes[at..], value);
367        self.count += 1;
368        true
369    }
370
371    /// Take the element at `index` out, closing the gap behind it.
372    pub fn remove_at(&mut self, index: usize) -> bool {
373        if index >= self.count {
374            return false;
375        }
376        if index == 0 {
377            return self.drop_front();
378        }
379        if index + 1 == self.count {
380            return self.drop_back();
381        }
382        let Some(at) = self.offset_of(index) else {
383            return false;
384        };
385        let Some(span) = self.step(at) else {
386            return false;
387        };
388        // Close the gap from whichever side is shorter, the same as an insert.
389        if index * 2 <= self.count {
390            self.bytes.copy_within(self.head..at, self.head + span);
391            self.head += span;
392        } else {
393            self.bytes.copy_within(at + span..self.tail, at);
394            self.tail -= span;
395        }
396        self.count -= 1;
397        true
398    }
399
400    /// Put `value` where the element at `index` was.
401    ///
402    /// The common case is a value the same size as the one it replaces, which is
403    /// written where it lies. Anything else is a remove and an insert, and it
404    /// can fail for the same reason an insert can.
405    pub fn replace_at(&mut self, index: usize, value: &[u8]) -> bool {
406        let Some(at) = self.offset_of(index) else {
407            return false;
408        };
409        let need = entry_len(value);
410        if self.step(at) == Some(need) {
411            write_entry(&mut self.bytes[at..], value);
412            return true;
413        }
414        // Insert first and remove after, because an insert is the half that can
415        // fail and undoing it would mean holding the old bytes somewhere.
416        if !self.insert_at(index, value) {
417            return false;
418        }
419        self.remove_at(index + 1)
420    }
421
422    /// Split this chunk in two, keeping the first `index` elements.
423    ///
424    /// The entries are a run in one encoding, so the tail of the run is already
425    /// a chunk's worth of bytes and the split is one copy. This is how an insert
426    /// into a full chunk gets its room: the deque splits at the insertion point
427    /// and both halves come back with space.
428    #[must_use]
429    pub fn split_off(&mut self, index: usize) -> Chunk {
430        let at = self.offset_of(index).unwrap_or(self.tail);
431        let rest = Chunk::adopt(&self.bytes[at..self.tail], self.count - index);
432        self.tail = at;
433        self.count = index;
434        rest
435    }
436
437    /// Give back the room this chunk was keeping for pushes it will not see.
438    ///
439    /// Called when a chunk stops being an end of the list. It is a copy of the
440    /// live bytes and a shrink, and it happens once per chunk in the life of a
441    /// list that is only ever appended to.
442    pub fn seal(&mut self) {
443        if self.head > 0 {
444            self.bytes.copy_within(self.head..self.tail, 0);
445            self.tail -= self.head;
446            self.head = 0;
447        }
448        self.bytes.truncate(self.tail);
449        self.bytes.shrink_to_fit();
450    }
451
452    /// Where `value` is in this chunk, or nothing.
453    ///
454    /// The same walk [`crate::listpack::Listpack::find_parsed`] does and the
455    /// same code, because a chunk is the same entries in a run with a cursor at
456    /// each end rather than a blob with a header. `LINSERT` on a long list is
457    /// almost entirely this call repeated over a few thousand chunks, so it
458    /// reads headers and rejects on length rather than decoding every element
459    /// into an [`crate::listpack::Entry`] on the way past.
460    #[must_use]
461    pub fn find(&self, value: &[u8], as_int: Option<i64>) -> Option<usize> {
462        crate::listpack::scan_for(&self.bytes[self.head..self.tail], value, as_int, 1)
463    }
464
465    /// Every place `value` is in this chunk, front to back.
466    ///
467    /// `limit` caps how many elements are looked at with 0 meaning no cap, `hit`
468    /// says whether to carry on, and what comes back is how many elements were
469    /// looked at so a caller walking a ring can carry one budget across it.
470    pub fn find_each(
471        &self,
472        value: &[u8],
473        as_int: Option<i64>,
474        limit: usize,
475        hit: &mut dyn FnMut(usize) -> bool,
476    ) -> usize {
477        crate::listpack::scan_each(&self.bytes[self.head..self.tail], value, as_int, limit, hit)
478    }
479
480    /// The same from the back, with indexes counted from the last element here.
481    pub fn find_each_back(
482        &self,
483        value: &[u8],
484        as_int: Option<i64>,
485        limit: usize,
486        hit: &mut dyn FnMut(usize) -> bool,
487    ) -> usize {
488        crate::listpack::scan_each_back(
489            &self.bytes[self.head..self.tail],
490            value,
491            as_int,
492            limit,
493            hit,
494        )
495    }
496
497    /// A forward walk over what is here.
498    #[must_use]
499    pub fn iter(&self) -> Iter<'_> {
500        Iter {
501            bytes: &self.bytes[self.head..self.tail],
502            at: 0,
503        }
504    }
505
506    /// The same walk the other way.
507    ///
508    /// Every entry carries its own length behind it, which is what the back
509    /// cursor reads to find the entry before it, so this costs the same per
510    /// element as the forward walk rather than being a forward walk per element.
511    /// `LPOS` with a negative rank is the reason it exists.
512    #[must_use]
513    pub fn iter_back(&self) -> RevIter<'_> {
514        RevIter {
515            bytes: &self.bytes[self.head..self.tail],
516            at: self.tail - self.head,
517        }
518    }
519
520    /// A forward walk that starts at `index` rather than at the front.
521    ///
522    /// `LRANGE` in the middle of a list lands in the middle of a chunk, and the
523    /// only other way to start there is to walk the entries in front of it and
524    /// throw them away. This finds the byte offset by whichever end is closer
525    /// and hands back a walk from there.
526    #[must_use]
527    pub fn iter_from(&self, index: usize) -> Iter<'_> {
528        let at = self.offset_of(index).unwrap_or(self.tail);
529        Iter {
530            bytes: &self.bytes[self.head..self.tail],
531            at: at - self.head,
532        }
533    }
534
535    /// Where the entry at `index` starts, or nothing if there is no such entry.
536    ///
537    /// From whichever end of the chunk is closer. A chunk holds up to five
538    /// hundred and twelve entries, and half of them is the difference between
539    /// a microsecond and half of one on a `LINDEX` that lands in the middle of
540    /// a big list. Going backward is what the length behind each entry is for,
541    /// and it is the same field `iter_back` reads.
542    fn offset_of(&self, index: usize) -> Option<usize> {
543        if index >= self.count {
544            return None;
545        }
546        if index * 2 <= self.count {
547            let mut at = self.head;
548            for _ in 0..index {
549                at += self.step(at)?;
550            }
551            return Some(at);
552        }
553        let mut end = self.tail;
554        for _ in index..self.count {
555            let len = read_backlen(&self.bytes[self.head..end])?;
556            end = end.checked_sub(len + backlen_len(len))?;
557        }
558        Some(end)
559    }
560
561    /// Where the last entry starts.
562    fn back_at(&self) -> Option<usize> {
563        if self.count == 0 {
564            return None;
565        }
566        let len = read_backlen(&self.bytes[self.head..self.tail])?;
567        self.tail.checked_sub(len + backlen_len(len))
568    }
569
570    /// How many bytes the entry at `at` takes, back length included.
571    fn step(&self, at: usize) -> Option<usize> {
572        let (_, len) = decode(&self.bytes[at..self.tail])?;
573        Some(len + backlen_len(len))
574    }
575}
576
577/// A forward walk over a chunk.
578#[derive(Debug)]
579pub struct Iter<'a> {
580    bytes: &'a [u8],
581    at: usize,
582}
583
584impl<'a> Iterator for Iter<'a> {
585    type Item = Entry<'a>;
586
587    #[inline]
588    fn next(&mut self) -> Option<Entry<'a>> {
589        if self.at >= self.bytes.len() {
590            return None;
591        }
592        let (entry, len) = decode(&self.bytes[self.at..])?;
593        self.at += len + backlen_len(len);
594        Some(entry)
595    }
596}
597
598/// A backward walk over a chunk.
599#[derive(Debug)]
600pub struct RevIter<'a> {
601    bytes: &'a [u8],
602    at: usize,
603}
604
605impl<'a> Iterator for RevIter<'a> {
606    type Item = Entry<'a>;
607
608    #[inline]
609    fn next(&mut self) -> Option<Entry<'a>> {
610        if self.at == 0 {
611            return None;
612        }
613        let len = read_backlen(&self.bytes[..self.at])?;
614        let start = self.at.checked_sub(len + backlen_len(len))?;
615        let (entry, _) = decode(&self.bytes[start..self.at])?;
616        self.at = start;
617        Some(entry)
618    }
619}
620
621#[cfg(test)]
622mod tests {
623    use super::*;
624
625    fn all(c: &Chunk) -> Vec<Vec<u8>> {
626        c.iter().map(|e| e.to_vec()).collect()
627    }
628
629    #[test]
630    fn a_back_chunk_fills_from_the_front_of_its_buffer() {
631        let mut c = Chunk::for_back();
632        assert!(c.push_back(b"a"));
633        assert!(c.push_back(b"b"));
634        assert_eq!(all(&c), vec![b"a".to_vec(), b"b".to_vec()]);
635        assert_eq!(c.len(), 2);
636        assert_eq!(c.front().unwrap().to_vec(), b"a");
637        assert_eq!(c.back().unwrap().to_vec(), b"b");
638    }
639
640    #[test]
641    fn a_front_chunk_fills_backward_and_reads_forward() {
642        let mut c = Chunk::for_front();
643        assert!(c.push_front(b"b"));
644        assert!(c.push_front(b"a"));
645        assert_eq!(all(&c), vec![b"a".to_vec(), b"b".to_vec()]);
646        assert_eq!(c.front().unwrap().to_vec(), b"a");
647        assert_eq!(c.back().unwrap().to_vec(), b"b");
648    }
649
650    /// Both ends at once, which is the shape the deque above puts a chunk in
651    /// when a list is pushed at one end and popped at the other.
652    #[test]
653    fn pushing_and_popping_at_both_ends_stays_in_order() {
654        let mut c = Chunk::for_back();
655        for m in [b"c", b"d"] {
656            assert!(c.push_back(m));
657        }
658        for m in [b"b", b"a"] {
659            assert!(c.push_front(m));
660        }
661        assert_eq!(
662            all(&c),
663            vec![b"a".to_vec(), b"b".to_vec(), b"c".to_vec(), b"d".to_vec()]
664        );
665        assert!(c.drop_front());
666        assert!(c.drop_back());
667        assert_eq!(all(&c), vec![b"b".to_vec(), b"c".to_vec()]);
668        assert_eq!(c.len(), 2);
669    }
670
671    /// A back chunk has nothing in front of it, so the first front push slides
672    /// what is there over. After that both ends have room and neither shifts
673    /// again, which is the part that keeps a same end push and pop from
674    /// allocating a chunk per operation.
675    #[test]
676    fn a_back_chunk_makes_room_at_the_front_once() {
677        let mut c = Chunk::for_back();
678        assert!(c.push_back(b"a"));
679        assert!(c.push_front(b"z"));
680        assert_eq!(all(&c), vec![b"z".to_vec(), b"a".to_vec()]);
681        let head = c.head;
682        for i in 0..100 {
683            assert!(c.push_front(i.to_string().as_bytes()), "at {i}");
684        }
685        assert!(c.head < head, "the front pushes went somewhere else");
686        assert_eq!(c.len(), 102);
687        assert_eq!(c.back().unwrap().to_vec(), b"a");
688    }
689
690    /// A chunk with no room anywhere says so rather than shifting bytes that
691    /// have nowhere to go.
692    #[test]
693    fn a_full_chunk_refuses_both_ends() {
694        let mut c = Chunk::for_back();
695        let big = vec![b'x'; 500];
696        while c.push_back(&big) {}
697        assert!(!c.push_back(&big));
698        assert!(!c.push_front(&big));
699        assert!(c.push_front(b"1"), "there is still room for a short one");
700    }
701
702    #[test]
703    fn an_empty_chunk_answers_nothing_rather_than_panicking() {
704        let mut c = Chunk::for_back();
705        assert!(c.front().is_none());
706        assert!(c.back().is_none());
707        assert!(c.get(0).is_none());
708        assert!(!c.drop_front());
709        assert!(!c.drop_back());
710    }
711
712    #[test]
713    fn the_element_cap_is_what_stops_a_chunk_of_small_members() {
714        let mut c = Chunk::for_back();
715        for i in 0..CHUNK_ENTRIES {
716            assert!(c.push_back(i.to_string().as_bytes()), "at {i}");
717        }
718        assert!(!c.push_back(b"1"));
719        assert_eq!(c.len(), CHUNK_ENTRIES);
720    }
721
722    #[test]
723    fn the_byte_cap_is_what_stops_a_chunk_of_large_members() {
724        let mut c = Chunk::for_back();
725        let big = vec![b'x'; 300];
726        let mut n = 0;
727        while c.push_back(&big) {
728            n += 1;
729        }
730        assert!(n < CHUNK_ENTRIES, "{n} entries fitted, which is too many");
731        assert_eq!(c.len(), n);
732        assert!(c.live_bytes() <= CHUNK_BYTES);
733    }
734
735    #[test]
736    fn indexing_walks_from_the_head_cursor_and_not_from_the_buffer() {
737        let mut c = Chunk::for_front();
738        for m in [b"d", b"c", b"b", b"a"] {
739            assert!(c.push_front(m));
740        }
741        for (i, want) in [b"a", b"b", b"c", b"d"].iter().enumerate() {
742            assert_eq!(c.get(i).unwrap().to_vec(), want.to_vec(), "at {i}");
743        }
744        assert!(c.get(4).is_none());
745    }
746
747    #[test]
748    fn sealing_keeps_the_elements_and_gives_back_the_room() {
749        let mut c = Chunk::for_front();
750        for m in [b"c", b"b", b"a"] {
751            assert!(c.push_front(m));
752        }
753        let before = c.memory_bytes();
754        let live = c.live_bytes();
755        c.seal();
756        assert_eq!(
757            all(&c),
758            vec![b"a".to_vec(), b"b".to_vec(), b"c".to_vec()],
759            "sealing changed what is in it"
760        );
761        assert_eq!(c.live_bytes(), live);
762        assert!(c.memory_bytes() < before);
763        // The cursors came with the bytes rather than being left where they
764        // used to be, so both ends still read. There is no room left for a push
765        // and there is not meant to be: a chunk is sealed when it stops being an
766        // end of the list, and one that becomes an end again is refused and
767        // replaced rather than grown back.
768        assert_eq!(c.front().unwrap().to_vec(), b"a");
769        assert_eq!(c.back().unwrap().to_vec(), b"c");
770        assert_eq!(c.get(1).unwrap().to_vec(), b"b");
771        assert!(!c.push_back(b"d"));
772        assert!(!c.push_front(b"d"));
773        assert!(c.drop_front());
774        assert_eq!(c.front().unwrap().to_vec(), b"b");
775    }
776
777    /// An integer element is stored as an integer, the same as it is in a
778    /// listpack, because it is the same codec.
779    #[test]
780    fn integers_come_back_as_integers() {
781        let mut c = Chunk::for_back();
782        assert!(c.push_back(b"42"));
783        assert!(c.push_back(b"007"));
784        assert_eq!(c.get(0), Some(Entry::Int(42)));
785        assert_eq!(c.get(1), Some(Entry::Str(b"007")));
786    }
787
788    /// Every length either side of a back length boundary, so that a chunk
789    /// whose entries cross into a two byte back length still walks backward.
790    #[test]
791    fn the_back_walk_survives_a_long_entry() {
792        for len in [1usize, 63, 64, 120, 126, 127, 128, 200] {
793            let mut c = Chunk::for_back();
794            let v = vec![b'x'; len];
795            assert!(c.push_back(b"first"));
796            assert!(c.push_back(&v), "{len} did not fit");
797            assert_eq!(c.back().unwrap().to_vec(), v, "back of a {len} byte entry");
798            assert!(c.drop_back());
799            assert_eq!(c.back().unwrap().to_vec(), b"first");
800        }
801    }
802
803    /// A chunk with room at both ends, so that an insert can choose a side.
804    fn abcde() -> Chunk {
805        let mut c = Chunk::for_back();
806        for m in [b"c", b"d", b"e"] {
807            assert!(c.push_back(m));
808        }
809        for m in [b"b", b"a"] {
810            assert!(c.push_front(m));
811        }
812        c
813    }
814
815    #[test]
816    fn an_insert_lands_where_it_was_asked_to_from_either_side() {
817        for at in 0..=5 {
818            let mut c = abcde();
819            assert!(c.insert_at(at, b"new"), "inserting at {at}");
820            let mut want: Vec<Vec<u8>> = [b"a", b"b", b"c", b"d", b"e"]
821                .iter()
822                .map(|m| m.to_vec())
823                .collect();
824            want.insert(at, b"new".to_vec());
825            assert_eq!(all(&c), want, "inserting at {at}");
826            assert_eq!(c.len(), 6);
827        }
828        let mut c = abcde();
829        assert!(!c.insert_at(6, b"new"), "past the end is not an insert");
830    }
831
832    #[test]
833    fn a_remove_closes_the_gap_from_either_side() {
834        for at in 0..5 {
835            let mut c = abcde();
836            assert!(c.remove_at(at), "removing at {at}");
837            let mut want: Vec<Vec<u8>> = [b"a", b"b", b"c", b"d", b"e"]
838                .iter()
839                .map(|m| m.to_vec())
840                .collect();
841            want.remove(at);
842            assert_eq!(all(&c), want, "removing at {at}");
843            assert_eq!(c.len(), 4);
844            assert_eq!(c.back().unwrap().to_vec(), want[3]);
845        }
846        let mut c = abcde();
847        assert!(!c.remove_at(5));
848    }
849
850    /// The same length, a longer one and a shorter one, because only the first
851    /// is written where the old element lay.
852    #[test]
853    fn a_replace_takes_a_value_of_any_length() {
854        for value in [&b"z"[..], &b"much longer than what was there"[..], b"7"] {
855            for at in 0..5 {
856                let mut c = abcde();
857                assert!(c.replace_at(at, value), "replacing at {at}");
858                let mut want: Vec<Vec<u8>> = [b"a", b"b", b"c", b"d", b"e"]
859                    .iter()
860                    .map(|m| m.to_vec())
861                    .collect();
862                want[at] = value.to_vec();
863                assert_eq!(all(&c), want, "replacing at {at}");
864                assert_eq!(c.len(), 5);
865            }
866        }
867        let mut c = abcde();
868        assert!(!c.replace_at(5, b"z"));
869    }
870
871    #[test]
872    fn dropping_many_from_an_end_is_the_walk_and_nothing_else() {
873        let mut c = abcde();
874        assert_eq!(c.drop_front_n(2), 2);
875        assert_eq!(all(&c), vec![b"c".to_vec(), b"d".to_vec(), b"e".to_vec()]);
876        assert_eq!(c.drop_back_n(2), 2);
877        assert_eq!(all(&c), vec![b"c".to_vec()]);
878        assert_eq!(c.drop_front_n(9), 1, "it stops when it runs out");
879        assert!(c.is_empty());
880        assert_eq!(c.drop_back_n(3), 0);
881    }
882
883    #[test]
884    fn a_split_leaves_both_halves_readable_and_with_room() {
885        for at in 0..=5 {
886            let mut c = abcde();
887            let mut rest = c.split_off(at);
888            let want: Vec<Vec<u8>> = [b"a", b"b", b"c", b"d", b"e"]
889                .iter()
890                .map(|m| m.to_vec())
891                .collect();
892            assert_eq!(all(&c), want[..at].to_vec(), "the front half of {at}");
893            assert_eq!(all(&rest), want[at..].to_vec(), "the back half of {at}");
894            assert_eq!(c.len() + rest.len(), 5);
895            assert!(rest.push_back(b"more"), "the back half has no room");
896            assert_eq!(rest.back().unwrap().to_vec(), b"more");
897        }
898    }
899
900    /// An insert into a chunk that is full at both ends is refused, and the
901    /// split is what the deque does about it.
902    #[test]
903    fn a_full_chunk_refuses_an_insert_and_a_split_fixes_it() {
904        let mut c = Chunk::for_back();
905        let big = vec![b'x'; 500];
906        while c.push_back(&big) {}
907        let held = c.len();
908        assert!(!c.insert_at(held / 2, &big));
909        let mut rest = c.split_off(held / 2);
910        assert!(c.push_back(&big) || rest.push_front(&big));
911        assert_eq!(c.len() + rest.len(), held + 1);
912    }
913}