Skip to main content

yo_kv/
stream.rs

1//! A stream, as a log of listpack nodes in ID order (`08` section 7).
2//!
3//! A stream is the one collection here that is only ever appended to at one end
4//! and only ever trimmed at the other. Nothing is inserted in the middle, ever,
5//! because the ID of a new entry has to be greater than the last one there. That
6//! is a much stronger promise than any other type makes, and the structure is
7//! built on it rather than around it.
8//!
9//! ```text
10//! +--------------+--------------+--------------+
11//! | node         | node         | node         |
12//! | master 5-0   | master 812-0 | master 990-0 |
13//! | 100 entries  | 100 entries  | 12 entries   |
14//! +--------------+--------------+--------------+
15//!   ^ trimmed from here          appended here ^
16//! ```
17//!
18//! # Why a node holds a hundred entries and not one
19//!
20//! One entry per allocation is what a naive log does and it is wrong twice over.
21//! It costs an allocator header and a pointer per entry, which for a stream of
22//! short entries is most of the memory, and it puts the entries in whatever
23//! order the allocator felt like, which is the opposite of what a range scan
24//! wants. A node is one blob holding a hundred consecutive entries, so a scan is
25//! a walk through memory the prefetcher can see coming, and the per entry
26//! overhead is a few bytes rather than a few dozen.
27//!
28//! Two tricks inside the node take it further down, and both are Redis's:
29//!
30//! The **ID is stored as a difference** from the node's first ID rather than
31//! whole. Entries arrive milliseconds apart and a difference of a few hundred is
32//! two bytes where a pair of 64 bit integers is sixteen.
33//!
34//! The **field names are stored once** for the node rather than once per entry.
35//! A stream is almost always the same shape repeated, `sensor` and `reading`
36//! over and over, so the first entry's field names become the node's master
37//! fields and every later entry with exactly those names, in that order, stores
38//! only its values. An entry with different fields stores its own names and
39//! costs what it would have cost anyway.
40//!
41//! # Why the nodes are a deque and not a radix tree
42//!
43//! Redis keeps its nodes in a rax keyed by the sixteen byte big endian ID, and
44//! `08` says radix log, so this is the place to say why there is no radix tree
45//! here. The keys are appended in sorted order and never inserted between, which
46//! means the index is a sorted array and stays one for free. Finding the node a
47//! range starts in is then a binary search over the node count, which for a
48//! million entries is fourteen steps over an array that is a few pages long. A
49//! radix tree over the same keys is four or five levels of pointer chasing, each
50//! one a cache miss, and it exists to solve the insertion problem that this
51//! structure does not have.
52//!
53//! Trimming is what a plain `Vec` would get wrong, since it takes from the front,
54//! so the nodes live in a `VecDeque` and dropping the oldest node is a pop.
55//!
56//! # The bytes are Redis's
57//!
58//! A node is a [`Listpack`] laid out exactly as `t_stream.c` lays one out, and
59//! the node's first ID is held beside it exactly as the rax key holds it there.
60//! That is not deference, it is the cheapest route to `DUMP` and `RESTORE` and
61//! an RDB that Redis can read, since the node is already the thing that goes on
62//! the wire and no conversion has to exist at all.
63//!
64//! ```text
65//! master entry
66//! +-------+---------+------------+---------+-----+---------+---+
67//! | count | deleted | num-fields | field-1 | ... | field-N | 0 |
68//! +-------+---------+------------+---------+-----+---------+---+
69//!
70//! an entry with the master's fields
71//! +-------+---------+----------+---------+-----+---------+----------+
72//! | flags | ms-diff | seq-diff | value-1 | ... | value-N | lp-count |
73//! +-------+---------+----------+---------+-----+---------+----------+
74//!
75//! an entry with its own
76//! +-------+---------+----------+------------+---------+---------+-----+----------+
77//! | flags | ms-diff | seq-diff | num-fields | field-1 | value-1 | ... | lp-count |
78//! +-------+---------+----------+------------+---------+---------+-----+----------+
79//! ```
80//!
81//! `lp-count` is how many listpack elements the entry occupies before it, which
82//! is what makes the node walkable backwards. It is written because Redis writes
83//! it and the bytes have to match, and nothing here reads it yet: `XREVRANGE`
84//! buffers a node's marks and hands them back reversed instead. That costs, and
85//! the benchmark says how much, 6.83 microseconds against 1.95 for the same
86//! hundred entries forwards. Stepping back over `lp-count` is the fix and it is
87//! its own change, because it needs its own before and after.
88//!
89//! Both ID halves are a plain wrapping difference from the master, which is what
90//! Redis writes and what it adds back. The sequence usually goes down when the
91//! millisecond goes up, so the second difference is usually negative, and
92//! wrapping arithmetic is the exact inverse either way.
93//!
94//! That claim is checked rather than asserted. A `DUMP` taken from Redis 8.10.1
95//! is hard coded in the tests, and two of them run it in both directions: one
96//! builds the same stream here and compares the node's bytes to Redis's byte for
97//! byte, the other takes Redis's node and reads the entries back out of it.
98//!
99//! # Deleting does not move anything
100//!
101//! `XDEL` sets a bit in the entry's flags and leaves the bytes where they are.
102//! Compacting the node would move every entry behind it, which on a node of a
103//! hundred is a memmove per delete, and a stream is not a structure people
104//! delete from in bulk. The node's master entry counts how many of its entries
105//! are dead, and a node whose last live entry goes is dropped whole.
106
107use std::cmp::Ordering;
108use std::collections::VecDeque;
109
110use yo_common::num::{DIGITS_MAX, i64_digits, push_u64, u64_digits};
111
112use crate::frozen::{self, Broken};
113use crate::listpack::{self, Entry, Listpack};
114
115pub mod groups;
116
117pub use groups::{Consumer, Filter, Group, Nack, Retry};
118
119/// How many bytes a node holds before the next entry starts a new one.
120///
121/// `stream-node-max-bytes`, which is 4096 in Redis and is here for the same
122/// reason: a node is rewritten in place when an entry is deleted from it and is
123/// copied whole when it is written out, so a node that grows without limit
124/// turns both of those into a problem.
125pub const NODE_BYTES: usize = 4096;
126
127/// How many entries a node holds before the next one starts a new node.
128///
129/// `stream-node-max-entries`, which is 100 in Redis.
130pub const NODE_ENTRIES: usize = 100;
131
132/// The entry is live.
133const LIVE: i64 = 0;
134
135/// The entry has been deleted and its bytes are still here.
136const DELETED: i64 = 1;
137
138/// The entry's field names are the node's master fields.
139const SAME_FIELDS: i64 = 2;
140
141/// Where the master entry's field names start.
142///
143/// After the count, the deleted count and the number of fields.
144const MASTER_FIELDS: usize = 3;
145
146/// An entry ID, which is a millisecond and a sequence number inside it.
147///
148/// Two 64 bit halves rather than one 128 bit number, because both halves are
149/// addressable on the wire: `XADD key 5-*` asks for the next sequence inside
150/// millisecond five, and `XRANGE key 5 5` is every sequence inside it.
151#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
152pub struct Id {
153    /// The millisecond, which is a unix timestamp for an ID the server made up.
154    pub ms: u64,
155    /// Which entry within that millisecond.
156    pub seq: u64,
157}
158
159impl Id {
160    /// The lowest ID there is, which is what `-` means in a range.
161    pub const MIN: Id = Id { ms: 0, seq: 0 };
162
163    /// The highest, which is what `+` means.
164    pub const MAX: Id = Id {
165        ms: u64::MAX,
166        seq: u64::MAX,
167    };
168
169    /// An ID from its two halves.
170    #[must_use]
171    #[inline]
172    pub const fn new(ms: u64, seq: u64) -> Id {
173        Id { ms, seq }
174    }
175
176    /// The next ID after this one, or `None` at [`Id::MAX`].
177    ///
178    /// What an exclusive range start turns into, and what `XADD key ms-*`
179    /// resolves to when the millisecond is already the last one used.
180    #[must_use]
181    pub const fn next(self) -> Option<Id> {
182        if self.seq != u64::MAX {
183            Some(Id {
184                ms: self.ms,
185                seq: self.seq + 1,
186            })
187        } else if self.ms != u64::MAX {
188            Some(Id {
189                ms: self.ms + 1,
190                seq: 0,
191            })
192        } else {
193            None
194        }
195    }
196
197    /// The ID before this one, or `None` at [`Id::MIN`].
198    #[must_use]
199    pub const fn prev(self) -> Option<Id> {
200        if self.seq != 0 {
201            Some(Id {
202                ms: self.ms,
203                seq: self.seq - 1,
204            })
205        } else if self.ms != 0 {
206            Some(Id {
207                ms: self.ms - 1,
208                seq: u64::MAX,
209            })
210        } else {
211            None
212        }
213    }
214
215    /// The sixteen big endian bytes Redis keys a node by.
216    ///
217    /// Big endian because that is the order that sorts, which is the whole
218    /// reason the format picked it.
219    #[must_use]
220    pub fn to_bytes(self) -> [u8; 16] {
221        let mut out = [0u8; 16];
222        out[..8].copy_from_slice(&self.ms.to_be_bytes());
223        out[8..].copy_from_slice(&self.seq.to_be_bytes());
224        out
225    }
226
227    /// The ID those bytes hold.
228    #[must_use]
229    pub fn from_bytes(bytes: [u8; 16]) -> Id {
230        let mut ms = [0u8; 8];
231        let mut seq = [0u8; 8];
232        ms.copy_from_slice(&bytes[..8]);
233        seq.copy_from_slice(&bytes[8..]);
234        Id {
235            ms: u64::from_be_bytes(ms),
236            seq: u64::from_be_bytes(seq),
237        }
238    }
239
240    /// `ms-seq`, which is how an ID looks everywhere a client can see one.
241    pub fn write_to(self, out: &mut Vec<u8>) {
242        push_u64(out, self.ms);
243        out.push(b'-');
244        push_u64(out, self.seq);
245    }
246
247    /// The same as a fresh `Vec`, for a caller that is not building a reply.
248    #[must_use]
249    pub fn to_vec(self) -> Vec<u8> {
250        let mut out = Vec::with_capacity(41);
251        self.write_to(&mut out);
252        out
253    }
254
255    /// `ms` or `ms-seq`, with a missing sequence read as `default`.
256    ///
257    /// `XRANGE key 5 5` means every entry in millisecond five, so the start
258    /// defaults its sequence to zero and the end to the largest there is. The
259    /// special forms a client can send, `-`, `+`, `$`, `*` and `ms-*`, are the
260    /// command layer's business and not this one's.
261    #[must_use]
262    pub fn parse(s: &[u8], default: u64) -> Option<Id> {
263        let (ms, seq) = match s.iter().position(|c| *c == b'-') {
264            Some(at) => (&s[..at], Some(&s[at + 1..])),
265            None => (s, None),
266        };
267        Some(Id {
268            ms: digits(ms)?,
269            seq: match seq {
270                Some(seq) => digits(seq)?,
271                None => default,
272            },
273        })
274    }
275}
276
277/// A run of digits as a `u64`, refusing a sign, a space or an empty string.
278///
279/// `parse_i64` would take `-1` and `+5` and this must not: an ID is unsigned
280/// and the minus is the separator.
281fn digits(s: &[u8]) -> Option<u64> {
282    if s.is_empty() || s.len() > 20 {
283        return None;
284    }
285    let mut n = 0u64;
286    for c in s {
287        let d = c.wrapping_sub(b'0');
288        if d > 9 {
289            return None;
290        }
291        n = n.checked_mul(10)?.checked_add(u64::from(d))?;
292    }
293    Some(n)
294}
295
296/// Why an append was refused.
297#[derive(Debug, Clone, Copy, PartialEq, Eq)]
298pub enum Refused {
299    /// The ID is not greater than the last one in the stream.
300    NotGreater,
301    /// The ID is zero, which no entry can have because nothing sorts below it.
302    Zero,
303    /// The stream is at [`Id::MAX`] and there is no next ID to hand out.
304    Full,
305}
306
307/// Where a node stops taking entries.
308#[derive(Debug, Clone, Copy, PartialEq, Eq)]
309pub struct Limits {
310    /// The most bytes a node holds.
311    pub max_node_bytes: usize,
312    /// The most entries a node holds, live and deleted together.
313    pub max_node_entries: usize,
314}
315
316impl Default for Limits {
317    /// `stream-node-max-bytes 4096` and `stream-node-max-entries 100`.
318    fn default() -> Limits {
319        Limits {
320            max_node_bytes: NODE_BYTES,
321            max_node_entries: NODE_ENTRIES,
322        }
323    }
324}
325
326/// What a delete does about the consumer groups still pointing at the entry.
327///
328/// `XDEL` takes an entry out from under whoever was handed it and leaves the
329/// pending list holding an ID that can never be read, which is a state `XCLAIM`
330/// then has to clean up. The 8.2 commands let a caller say what it wants
331/// instead, and the three answers are the three words here.
332#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
333pub enum Refs {
334    /// `KEEPREF`: take the entry and leave every pending list alone, which is
335    /// what `XDEL` has always done and is still the default.
336    #[default]
337    Keep,
338    /// `DELREF`: take the entry and take it out of every pending list with it.
339    Drop,
340    /// `ACKED`: only take the entry when no group could still be handed it.
341    Acked,
342}
343
344/// What one ID a delete was asked about came to.
345///
346/// The numbers are Redis's and they are not a success flag: a caller sending a
347/// list of IDs gets one of these each and has to be able to tell an entry that
348/// was never there from one that is still there on purpose.
349#[derive(Debug, Clone, Copy, PartialEq, Eq)]
350pub enum Fate {
351    /// There was nothing here to do.
352    Missing,
353    /// It went.
354    Gone,
355    /// Somebody could still be handed it, so it stayed.
356    Held,
357}
358
359impl Fate {
360    /// The integer this is on the wire.
361    #[must_use]
362    #[inline]
363    pub fn code(self) -> i64 {
364        match self {
365            Fate::Missing => -1,
366            Fate::Gone => 1,
367            Fate::Held => 2,
368        }
369    }
370}
371
372/// One node: a run of consecutive entries and the ID the run starts at.
373#[derive(Debug, Clone, PartialEq, Eq)]
374struct Node {
375    /// The ID every entry in this node stores its own as a difference from.
376    ///
377    /// It is the first entry's ID, and it stays that even after the first entry
378    /// is deleted, because the differences behind it are relative to it.
379    master: Id,
380    lp: Listpack,
381}
382
383/// What a stream looks like from the outside, for working out a group's
384/// counters.
385///
386/// Five numbers taken together at one moment. The reason they travel as a
387/// bundle is that both rules below read several of them and the answers have to
388/// come from the same instant, and the reason it is a private type is that
389/// nobody outside wants five loose numbers, they want [`Stream::lag`].
390#[derive(Debug, Clone, Copy)]
391struct Edges {
392    added: u64,
393    length: u64,
394    first: Option<Id>,
395    last: Id,
396    max_deleted: Id,
397}
398
399impl Edges {
400    /// How many entries a reader sitting at `id` must have passed, when that is
401    /// exactly knowable, which is the three positions where it is.
402    ///
403    /// At or past the last ID ever handed out, everything that was ever added
404    /// has gone by. Below the oldest entry left, with nothing deleted inside
405    /// what is left, everything that has gone is behind and the length is what
406    /// is in front. Exactly on the oldest entry left, the same plus one.
407    ///
408    /// Anywhere else the answer is genuinely unknown rather than approximate,
409    /// because working it out would mean counting the entries between here and
410    /// there, and that is the walk the whole counter exists to avoid.
411    fn estimate(&self, id: Id) -> Option<u64> {
412        if self.added == 0 {
413            return Some(0);
414        }
415        if id >= self.last {
416            return Some(self.added);
417        }
418        let first = self.first?;
419        // A hole below the oldest entry left is not a hole in what is left, so
420        // the subtraction below still holds. That is the case a trim makes, and
421        // it is why a trim does not cost a group its lag.
422        if self.max_deleted != Id::MIN && self.max_deleted >= first {
423            return None;
424        }
425        let behind = self.added - self.length;
426        match id.cmp(&first) {
427            Ordering::Less => Some(behind),
428            Ordering::Equal => Some(behind + 1),
429            Ordering::Greater => None,
430        }
431    }
432
433    /// Whether anything has been deleted at or above `id`.
434    ///
435    /// Only [`Stream::delete`] moves `max_deleted`, so a trim does not count,
436    /// and a stream with nothing left in it does not either, since there is no
437    /// gap in an empty stream for a reader to fall into.
438    fn holed_from(&self, id: Id) -> bool {
439        if self.length == 0 || self.max_deleted == Id::MIN {
440            return false;
441        }
442        if self.first.is_some_and(|first| first > self.max_deleted) {
443            return false;
444        }
445        id <= self.max_deleted
446    }
447
448    /// A group's lag, the good way first and the subtraction second.
449    fn lag(&self, group: &Group) -> Option<u64> {
450        if let Some(read) = self.estimate(group.last_id()) {
451            return Some(self.added.saturating_sub(read));
452        }
453        let read = group.entries_read()?;
454        if self.holed_from(group.last_id()) {
455            return None;
456        }
457        Some(self.added.saturating_sub(read))
458    }
459
460    /// What a group's read counter becomes once `id` has been handed out.
461    ///
462    /// One more than it was, while it is known and nothing has been deleted
463    /// ahead of the entry just delivered. Otherwise the estimate above gets a
464    /// go, which is what lets a group that has read all the way to the end come
465    /// back from not knowing.
466    fn on_deliver(&self, group: &Group, id: Id) -> Option<u64> {
467        match group.entries_read() {
468            Some(read) if !self.holed_from(id) => Some(read + 1),
469            _ => self.estimate(id),
470        }
471    }
472}
473
474/// The only frozen form there is: the nodes as they stand, then the groups.
475///
476/// There is no packed form to sit beside it the way the other collections have
477/// one, because a stream is a listpack per node from the first entry and never
478/// changes shape.
479const FORM_NODES: u8 = 1;
480
481/// A log of entries in ID order.
482#[derive(Debug, Clone, Default, PartialEq, Eq)]
483pub struct Stream {
484    nodes: VecDeque<Node>,
485    /// Live entries, which is what `XLEN` answers.
486    length: u64,
487    /// The greatest ID ever appended, which does not go down when it is deleted.
488    last: Id,
489    /// The greatest ID ever deleted, which `XINFO` reports.
490    max_deleted: Id,
491    /// How many entries have ever been appended.
492    ///
493    /// Not the length. It only goes up, and it is what a consumer group uses to
494    /// work out how far behind it is without walking anything.
495    added: u64,
496    /// The consumer groups, by name.
497    ///
498    /// A vector because a stream has a handful of groups and the name is looked
499    /// up once a command, so a linear scan beats hashing and brings nothing
500    /// with it. The same argument the group makes about its consumers.
501    groups: Vec<(Vec<u8>, Group)>,
502}
503
504impl Stream {
505    /// An empty stream.
506    #[must_use]
507    pub fn new() -> Stream {
508        Stream::default()
509    }
510
511    /// Write the stream out as the bytes a tier can hold, for
512    /// [`crate::keyspace::Keyspace`] to hand back to [`Stream::thaw`].
513    ///
514    /// The nodes go out as the listpacks they already are, one master ID and one
515    /// blob each. That is the whole point of the node layout: a run of entries
516    /// is already a flat sequence of bytes with no pointers in it, so freezing
517    /// one is a copy and thawing it is a length check. Only the counters and the
518    /// consumer groups need a form of their own.
519    pub fn freeze(&self, out: &mut Vec<u8>) {
520        out.push(FORM_NODES);
521        frozen::put_uint(out, self.length);
522        frozen::put_uint(out, self.last.ms);
523        frozen::put_uint(out, self.last.seq);
524        frozen::put_uint(out, self.max_deleted.ms);
525        frozen::put_uint(out, self.max_deleted.seq);
526        frozen::put_uint(out, self.added);
527
528        frozen::put_uint(out, self.nodes.len() as u64);
529        for node in &self.nodes {
530            frozen::put_uint(out, node.master.ms);
531            frozen::put_uint(out, node.master.seq);
532            frozen::put_bytes(out, node.lp.as_bytes());
533        }
534
535        frozen::put_uint(out, self.groups.len() as u64);
536        for (name, group) in &self.groups {
537            frozen::put_bytes(out, name);
538            group.freeze(out);
539        }
540    }
541
542    /// Read back a stream [`Stream::freeze`] wrote.
543    ///
544    /// What is inside a node is checked as far as [`Listpack::from_bytes`]
545    /// checks it, which is that the header, the lengths and the terminator all
546    /// agree, and no further. Every walk over a node's contents already returns
547    /// early on anything it does not understand rather than trusting what it
548    /// finds, so a structurally sound listpack full of nonsense answers an empty
549    /// range instead of panicking. That is the same trust a node written by
550    /// Redis and loaded from an RDB file gets today.
551    pub fn thaw(bytes: &[u8]) -> Result<Stream, Broken> {
552        let mut cut = frozen::Cut::new(bytes);
553        if cut.byte()? != FORM_NODES {
554            return Err(Broken::Form);
555        }
556        let length = cut.uint()?;
557        let last = Id::new(cut.uint()?, cut.uint()?);
558        let max_deleted = Id::new(cut.uint()?, cut.uint()?);
559        let added = cut.uint()?;
560        // Deleting an entry needs an entry, and so does reading one, so a stream
561        // that has lost more than it ever took or is holding more than it was
562        // ever given did not come from `freeze`.
563        if length > added || max_deleted > last {
564            return Err(Broken::Body);
565        }
566
567        let n = usize::try_from(cut.uint()?).map_err(|_| Broken::Short)?;
568        // A node is a master ID and a listpack, so it cannot be under a byte and
569        // a count past what is left is short rather than a reservation to make.
570        if n > cut.rest().len() {
571            return Err(Broken::Short);
572        }
573        let mut nodes = VecDeque::with_capacity(n);
574        let mut prev: Option<Id> = None;
575        for _ in 0..n {
576            let master = Id::new(cut.uint()?, cut.uint()?);
577            // Nodes are consecutive runs in ID order, so a master that does not
578            // beat the one before it would leave a lookup unable to pick the
579            // node an ID belongs in.
580            if prev.is_some_and(|p| p >= master) {
581                return Err(Broken::Body);
582            }
583            prev = Some(master);
584            let lp = Listpack::from_bytes(cut.bytes()?).map_err(|_| Broken::Body)?;
585            nodes.push_back(Node { master, lp });
586        }
587        if nodes.is_empty() && length != 0 {
588            return Err(Broken::Body);
589        }
590
591        let n = usize::try_from(cut.uint()?).map_err(|_| Broken::Short)?;
592        if n > cut.rest().len() {
593            return Err(Broken::Short);
594        }
595        let mut groups: Vec<(Vec<u8>, Group)> = Vec::with_capacity(n);
596        for _ in 0..n {
597            let name = cut.bytes()?;
598            // Groups are found by a scan for the name, so a repeat would leave
599            // the second one holding entries that nothing could acknowledge.
600            if groups.iter().any(|(had, _)| had == name) {
601                return Err(Broken::Body);
602            }
603            groups.push((name.to_vec(), Group::thaw(&mut cut)?));
604        }
605
606        Ok(Stream {
607            nodes,
608            length,
609            last,
610            max_deleted,
611            added,
612            groups,
613        })
614    }
615
616    /// How many live entries there are, which is `XLEN`.
617    #[must_use]
618    #[inline]
619    pub fn len(&self) -> u64 {
620        self.length
621    }
622
623    /// Whether there are no live entries.
624    ///
625    /// A stream can be empty and still exist, unlike every other collection
626    /// here, because `XADD` followed by `XDEL` leaves a key whose last ID a new
627    /// entry still has to beat.
628    #[must_use]
629    #[inline]
630    pub fn is_empty(&self) -> bool {
631        self.length == 0
632    }
633
634    /// The greatest ID ever appended, whether or not it is still here.
635    #[must_use]
636    #[inline]
637    pub fn last_id(&self) -> Id {
638        self.last
639    }
640
641    /// The greatest ID ever deleted, or [`Id::MIN`] if none ever was.
642    #[must_use]
643    #[inline]
644    pub fn max_deleted_id(&self) -> Id {
645        self.max_deleted
646    }
647
648    /// How many entries have ever been appended.
649    #[must_use]
650    #[inline]
651    pub fn added(&self) -> u64 {
652        self.added
653    }
654
655    /// The lowest live ID, or `None` when there are none.
656    ///
657    /// Walks the first node, because the first entry in it may have been
658    /// deleted and its bytes are still there. That is at most a node's worth of
659    /// steps and it is only asked for by `XINFO`.
660    #[must_use]
661    pub fn first_id(&self) -> Option<Id> {
662        let mut found = None;
663        self.walk(Id::MIN, Id::MAX, Some(1), &mut |id, _| {
664            found = Some(id);
665            false
666        });
667        found
668    }
669
670    /// The highest live ID, or `None` when there are none.
671    ///
672    /// Not the same as [`Stream::last_id`], which is the last ID handed out and
673    /// stays where it is when that entry is deleted. This is the greatest ID a
674    /// reader can still find, and `XSETID` is the one caller that needs the
675    /// difference, because it refuses to move the bookmark below an entry that
676    /// is still there.
677    #[must_use]
678    pub fn top_id(&self) -> Option<Id> {
679        let mut found = None;
680        self.rev_range(Id::MIN, Id::MAX, Some(1), |id, _| {
681            found = Some(id);
682            false
683        });
684        found
685    }
686
687    /// `XSETID`, which moves the bookmark and the two counters behind it.
688    ///
689    /// The bookmark decides what the next `XADD *` hands out and what a group
690    /// created at `$` starts from, so moving it is how a replica is made to
691    /// agree with a primary and how a stream is rebuilt from a log. The two
692    /// counters are optional because Redis added them later and a caller that
693    /// does not name them leaves them alone.
694    ///
695    /// # Errors
696    ///
697    /// [`Refused::NotGreater`] when `last` is below an entry that is still in
698    /// the stream, since a reader holding that entry's ID would then be reading
699    /// past the end of a stream that has not ended.
700    pub fn set_id(
701        &mut self,
702        last: Id,
703        added: Option<u64>,
704        max_deleted: Option<Id>,
705    ) -> Result<(), Refused> {
706        if self.top_id().is_some_and(|top| last < top) {
707            return Err(Refused::NotGreater);
708        }
709        self.last = last;
710        if let Some(added) = added {
711            self.added = added;
712        }
713        if let Some(id) = max_deleted {
714            self.max_deleted = id;
715        }
716        Ok(())
717    }
718
719    /// What the ID would be if `XADD key *` ran now with clock reading `now`.
720    ///
721    /// The clock unless the clock has not moved, or has gone backwards, in which
722    /// case it is the last ID with one added. A stream never goes back on its
723    /// word about ordering just because the machine's clock did.
724    #[must_use]
725    pub fn auto_id(&self, now: u64) -> Option<Id> {
726        if now > self.last.ms {
727            Some(Id { ms: now, seq: 0 })
728        } else {
729            self.last.next()
730        }
731    }
732
733    /// The next sequence inside `ms`, which is what `XADD key ms-*` asks for.
734    #[must_use]
735    pub fn auto_seq(&self, ms: u64) -> Option<Id> {
736        if ms > self.last.ms {
737            Some(Id { ms, seq: 0 })
738        } else if ms == self.last.ms {
739            self.last.next().filter(|id| id.ms == ms)
740        } else {
741            None
742        }
743    }
744
745    /// Append an entry, which is `XADD` once the ID has been settled.
746    ///
747    /// # Errors
748    ///
749    /// [`Refused`] when the ID is zero or is not greater than [`Stream::last_id`].
750    /// Nothing else can fail: an append never needs to move an entry that is
751    /// already here.
752    pub fn append(
753        &mut self,
754        id: Id,
755        fields: &[(&[u8], &[u8])],
756        limits: Limits,
757    ) -> Result<(), Refused> {
758        if id == Id::MIN {
759            return Err(Refused::Zero);
760        }
761        if id <= self.last {
762            return Err(Refused::NotGreater);
763        }
764
765        // Everything but the field names and values, which is why it is a guess
766        // rather than a measurement: the point is to start a new node before the
767        // current one goes past its limit, not to predict its size exactly.
768        let size: usize = fields
769            .iter()
770            .map(|(f, v)| f.len() + v.len() + 11)
771            .sum::<usize>()
772            + 32;
773
774        let fits = match self.nodes.back() {
775            Some(node) => {
776                let (count, deleted) = counts(&node.lp);
777                node.lp.byte_len() + size < limits.max_node_bytes
778                    && (count + deleted) < limits.max_node_entries as u64
779            }
780            None => false,
781        };
782
783        if !fits {
784            self.nodes.push_back(Node {
785                master: id,
786                lp: master_of(fields),
787            });
788        }
789        let node = self.nodes.back_mut().expect("a node was just made sure of");
790        let same = same_fields(&node.lp, fields);
791        write_entry(&mut node.lp, node.master, id, fields, same);
792        bump(&mut node.lp, 1, 0);
793
794        self.length += 1;
795        self.added += 1;
796        self.last = id;
797        Ok(())
798    }
799
800    /// Delete the entry with that ID, answering whether there was one.
801    ///
802    /// The bytes stay where they are and a bit says the entry is gone, unless it
803    /// was the last live entry in its node, in which case the node goes.
804    pub fn delete(&mut self, id: Id) -> bool {
805        if !self.remove(id) {
806            return false;
807        }
808        self.max_deleted = self.max_deleted.max(id);
809        true
810    }
811
812    /// Delete an entry, saying what to do about the groups, which is `XDELEX`.
813    ///
814    /// [`Refs::Acked`] is the interesting one and it asks a wider question than
815    /// its name does. An entry is safe to take when no group is holding it in a
816    /// pending list and no group's bookmark is still behind it, because a group
817    /// that has not reached the entry yet has not had its chance at it. So a
818    /// stream with one group sitting at `0-0` refuses every `ACKED` delete, and
819    /// that is a real server's answer and not an over careful reading of it.
820    pub fn delete_ref(&mut self, id: Id, refs: Refs) -> Fate {
821        if refs == Refs::Acked && self.still_wanted(id) {
822            return Fate::Held;
823        }
824        if !self.delete(id) {
825            return Fate::Missing;
826        }
827        if refs == Refs::Drop {
828            self.drop_refs(id);
829        }
830        Fate::Gone
831    }
832
833    /// Acknowledge an entry for one group and then delete it, which is `XACKDEL`.
834    ///
835    /// The acknowledgement is the part that decides the answer. An ID this group
836    /// was not holding is [`Fate::Missing`] whether or not the entry is in the
837    /// stream, and an ID it was holding is never `Missing`, so a caller reading
838    /// the reply is being told about its own pending list and not about the log.
839    pub fn ack_delete(&mut self, group: &[u8], id: Id, refs: Refs) -> Fate {
840        let Some(g) = self.group_mut(group) else {
841            return Fate::Missing;
842        };
843        if !g.ack(id) {
844            return Fate::Missing;
845        }
846        if refs == Refs::Acked && self.still_wanted(id) {
847            return Fate::Held;
848        }
849        self.delete(id);
850        if refs == Refs::Drop {
851            self.drop_refs(id);
852        }
853        Fate::Gone
854    }
855
856    /// Hand an entry back to a group without acknowledging it, which is `XNACK`.
857    ///
858    /// `force` makes a pending entry out of one that was not pending, and like
859    /// [`Stream::claim`]'s `FORCE` it only works on an entry that is really in
860    /// the stream. Answers whether anything happened, and `None` when there is
861    /// no such group.
862    pub fn nack(&mut self, group: &[u8], id: Id, retry: Retry, force: bool) -> Option<bool> {
863        let here = self.contains(id);
864        let g = self.group_mut(group)?;
865        if g.release(id, retry) {
866            return Some(true);
867        }
868        if force && here {
869            g.force_release(id, retry);
870            return Some(true);
871        }
872        Some(false)
873    }
874
875    /// Whether any group could still be handed `id`, which is what `ACKED` asks.
876    fn still_wanted(&self, id: Id) -> bool {
877        self.groups
878            .iter()
879            .any(|(_, g)| g.nack(id).is_some() || id > g.last_id())
880    }
881
882    /// Take an ID out of every group's pending list, which is what `DELREF` does.
883    fn drop_refs(&mut self, id: Id) {
884        for (_, g) in &mut self.groups {
885            g.forget(id);
886        }
887    }
888
889    /// The same without recording it.
890    ///
891    /// `XDEL` moves `max-deleted-entry-id` and trimming does not, which is
892    /// Redis's rule and a reasonable one: that field is there so a reader can
893    /// tell whether an ID it is holding was taken out from under it, and a
894    /// trim that took the oldest entries says nothing about that.
895    fn remove(&mut self, id: Id) -> bool {
896        let Some(at) = self.node_of(id) else {
897            return false;
898        };
899        let node = &self.nodes[at];
900        let Some((offset, flags)) = find(&node.lp, node.master, id) else {
901            return false;
902        };
903        if flags & DELETED != 0 {
904            return false;
905        }
906
907        let (count, _) = counts(&node.lp);
908        if count == 1 {
909            self.nodes.remove(at);
910        } else {
911            let node = &mut self.nodes[at];
912            set_int(&mut node.lp, offset, flags | DELETED);
913            bump(&mut node.lp, -1, 1);
914        }
915        self.length -= 1;
916        true
917    }
918
919    /// The five facts every counter a group keeps is worked out from.
920    ///
921    /// Read once and carried, rather than asked for again per entry, because
922    /// [`Stream::first_id`] walks a node and a delivery cannot change any of the
923    /// five: handing an entry to a consumer neither adds one nor removes one.
924    fn edges(&self) -> Edges {
925        Edges {
926            added: self.added,
927            length: self.length,
928            first: self.first_id(),
929            last: self.last,
930            max_deleted: self.max_deleted,
931        }
932    }
933
934    /// How far behind a group is, or `None` when that cannot be worked out.
935    ///
936    /// Two ways of answering and the good one is tried first. If the group's
937    /// bookmark is somewhere the distance from the start of time is exactly
938    /// known, which is the last ID, past it, or before the first entry left,
939    /// that distance is the answer. Otherwise the group's own counter will do,
940    /// but only while nothing has been deleted at or above the bookmark, since
941    /// a hole ahead of the group means it will read fewer entries than the
942    /// subtraction is expecting.
943    ///
944    /// Both paths and their order were read off Redis 8.10.1 rather than worked
945    /// out, because reasoning gives the wrong answer on the case that matters:
946    /// a group sitting at `0-0` on a stream trimmed from five entries to two
947    /// reports a lag of two and not five, which is the estimate winning over a
948    /// subtraction that is valid and is further from the truth.
949    #[must_use]
950    pub fn lag(&self, group: &Group) -> Option<u64> {
951        self.edges().lag(group)
952    }
953
954    /// Cut the stream down to `len` entries, dropping the oldest, which is
955    /// `XTRIM key MAXLEN len`. Answers how many went.
956    ///
957    /// `exact` is Redis's `=` against `~`. Without it only whole nodes are
958    /// dropped, so the stream is left at `len` or a little over and no node is
959    /// ever rewritten. That is the form to use, and it is why `~` exists.
960    ///
961    /// `limit` is Redis's `LIMIT`, which stops the trim once that many entries
962    /// have gone rather than once the stream is short enough. It exists because
963    /// a capped stream that has fallen a long way behind would otherwise spend
964    /// one command dropping millions of entries with the shard doing nothing
965    /// else, and the next write will carry on where this one stopped.
966    pub fn trim_maxlen(&mut self, len: u64, exact: bool, limit: Option<u64>) -> u64 {
967        let mut gone = 0;
968        while self.length > len && !limit.is_some_and(|cap| gone >= cap) {
969            let Some(node) = self.nodes.front() else {
970                break;
971            };
972            let (count, _) = counts(&node.lp);
973            if self.length - count >= len {
974                self.length -= count;
975                gone += count;
976                self.nodes.pop_front();
977                continue;
978            }
979            if !exact {
980                break;
981            }
982            let Some(id) = self.first_id() else { break };
983            self.remove(id);
984            gone += 1;
985        }
986        gone
987    }
988
989    /// Drop every entry below `id`, which is `XTRIM key MINID id`. Answers how
990    /// many went.
991    ///
992    /// `exact` and `limit` mean what they do for [`Stream::trim_maxlen`].
993    pub fn trim_minid(&mut self, id: Id, exact: bool, limit: Option<u64>) -> u64 {
994        let mut gone = 0;
995        while let Some(node) = self.nodes.front() {
996            if limit.is_some_and(|cap| gone >= cap) {
997                break;
998            }
999            let (count, _) = counts(&node.lp);
1000            if last_of(node) < id {
1001                self.length -= count;
1002                gone += count;
1003                self.nodes.pop_front();
1004                continue;
1005            }
1006            if !exact {
1007                break;
1008            }
1009            let Some(first) = self.first_id() else { break };
1010            if first >= id {
1011                break;
1012            }
1013            self.remove(first);
1014            gone += 1;
1015        }
1016        gone
1017    }
1018
1019    /// Every live entry from `start` to `end`, both ends included, oldest first.
1020    ///
1021    /// `count` stops the walk early, which is `XRANGE ... COUNT n`. The callback
1022    /// answers whether to carry on, so a caller filling a fixed reply can stop
1023    /// without knowing how many it wanted up front. Answers how many entries the
1024    /// callback saw.
1025    pub fn range<F>(&self, start: Id, end: Id, count: Option<usize>, mut f: F) -> usize
1026    where
1027        F: FnMut(Id, Fields<'_>) -> bool,
1028    {
1029        self.walk(start, end, count, &mut f)
1030    }
1031
1032    /// The same, newest first, which is `XREVRANGE`.
1033    ///
1034    /// `start` and `end` are still the low and the high end of the range, so a
1035    /// caller does not have to swap them and the command layer does, once, where
1036    /// the argument order is Redis's problem.
1037    pub fn rev_range<'s, F>(&'s self, start: Id, end: Id, count: Option<usize>, mut f: F) -> usize
1038    where
1039        F: FnMut(Id, Fields<'_>) -> bool,
1040    {
1041        let mut seen = 0;
1042        // A node is a hundred entries, so buffering one node's worth of marks
1043        // and handing them back in reverse is cheaper and a great deal clearer
1044        // than walking the blob backwards over the entry lengths. The buffer is
1045        // reused across nodes, so the whole reverse scan allocates once.
1046        let mut buf: Vec<(Id, Fields<'s>)> = Vec::new();
1047        // Straight to the node the high end falls in, the same binary search the
1048        // forward walk starts with. Walking back from the newest node instead
1049        // would skip over every node above `end` one at a time, which for a
1050        // window in the middle of a million entries is five thousand nodes
1051        // touched to read a hundred.
1052        let last = self.node_from(end);
1053        for node in self.nodes.iter().take(last + 1).rev() {
1054            // Only reachable when every node is above `end`, since the search
1055            // clamps to the front rather than saying there is nothing.
1056            if node.master > end {
1057                continue;
1058            }
1059            if last_of(node) < start {
1060                break;
1061            }
1062            buf.clear();
1063            each(&node.lp, node.master, &mut |id, fields| {
1064                if id >= start && id <= end {
1065                    buf.push((id, fields));
1066                }
1067                id <= end
1068            });
1069            for (id, fields) in buf.drain(..).rev() {
1070                if count.is_some_and(|want| seen >= want) {
1071                    return seen;
1072                }
1073                seen += 1;
1074                if !f(id, fields) {
1075                    return seen;
1076                }
1077            }
1078        }
1079        seen
1080    }
1081
1082    /// Whether an entry with this ID is there and live.
1083    ///
1084    /// What `XCLAIM` asks before it hands a pending entry to somebody, since an
1085    /// entry that has been deleted or trimmed away is work nobody can do.
1086    #[must_use]
1087    pub fn contains(&self, id: Id) -> bool {
1088        let Some(at) = self.node_of(id) else {
1089            return false;
1090        };
1091        let node = &self.nodes[at];
1092        find(&node.lp, node.master, id).is_some_and(|(_, flags)| flags & DELETED == 0)
1093    }
1094
1095    /// Make a consumer group, and say whether it was not already there.
1096    ///
1097    /// `XGROUP CREATE`. `last` is where it starts reading after, which is
1098    /// [`Stream::last_id`] for `$` and [`Id::MIN`] for `0`.
1099    pub fn create_group(&mut self, name: &[u8], last: Id, read: Option<u64>) -> bool {
1100        if self.group(name).is_some() {
1101            return false;
1102        }
1103        self.groups.push((name.to_vec(), Group::new(last, read)));
1104        true
1105    }
1106
1107    /// Take a group out, and say whether it was there.
1108    pub fn destroy_group(&mut self, name: &[u8]) -> bool {
1109        let Some(at) = self.groups.iter().position(|(n, _)| n == name) else {
1110            return false;
1111        };
1112        self.groups.remove(at);
1113        true
1114    }
1115
1116    /// One group by name.
1117    #[must_use]
1118    pub fn group(&self, name: &[u8]) -> Option<&Group> {
1119        self.groups
1120            .iter()
1121            .find(|(n, _)| n.as_slice() == name)
1122            .map(|(_, g)| g)
1123    }
1124
1125    /// One group by name, to change.
1126    pub fn group_mut(&mut self, name: &[u8]) -> Option<&mut Group> {
1127        self.groups
1128            .iter_mut()
1129            .find(|(n, _)| n.as_slice() == name)
1130            .map(|(_, g)| g)
1131    }
1132
1133    /// Every group, with its name.
1134    pub fn groups(&self) -> impl Iterator<Item = (&[u8], &Group)> + '_ {
1135        self.groups.iter().map(|(n, g)| (n.as_slice(), g))
1136    }
1137
1138    /// Hand new entries to a consumer, which is `XREADGROUP ... >`.
1139    ///
1140    /// Every entry after the group's bookmark, up to `count`, delivered to
1141    /// `consumer` and written into the pending list as it goes. The consumer is
1142    /// created if it is not there, because a consumer exists by turning up.
1143    ///
1144    /// `noack` is Redis's `NOACK`, which hands the entries over without writing
1145    /// them into the pending list at all. The group still counts them as read,
1146    /// so the lag is the same either way, and the consumer is on its own if it
1147    /// dies holding one.
1148    ///
1149    /// Answers how many entries the callback saw, or `None` when there is no
1150    /// such group.
1151    pub fn read_group<F>(
1152        &mut self,
1153        group: &[u8],
1154        consumer: &[u8],
1155        count: Option<usize>,
1156        noack: bool,
1157        now: u64,
1158        mut f: F,
1159    ) -> Option<usize>
1160    where
1161        F: FnMut(Id, Fields<'_>) -> bool,
1162    {
1163        // Before the split borrow, because it walks a node and the walk below
1164        // holds the nodes. Nothing a delivery does can change any of it.
1165        let edges = self.edges();
1166        // Field by field, so that walking the nodes and writing the pending list
1167        // are two borrows the compiler can see are disjoint.
1168        let Stream { nodes, groups, .. } = self;
1169        let (_, g) = groups.iter_mut().find(|(n, _)| n.as_slice() == group)?;
1170        let slot = g.consumer_or_create(consumer, now);
1171        let Some(from) = g.last_id().next() else {
1172            // The bookmark is at the very last ID there is, so there is nothing
1173            // after it and never will be.
1174            g.touch(slot, now, false);
1175            return Some(0);
1176        };
1177        let mut seen = 0;
1178        walk_nodes(nodes, from, Id::MAX, count, &mut |id, fields| {
1179            // Worked out before the bookmark moves, because the rule asks where
1180            // the group was when the entry was handed over.
1181            let read = edges.on_deliver(g, id);
1182            if noack {
1183                g.skip(id);
1184            } else {
1185                g.deliver(slot, id, now);
1186            }
1187            g.set_read(read);
1188            seen += 1;
1189            f(id, fields)
1190        });
1191        g.touch(slot, now, seen > 0);
1192        Some(seen)
1193    }
1194
1195    /// Re-read what a consumer is already holding, which is `XREADGROUP` with an
1196    /// ID rather than `>`.
1197    ///
1198    /// Every pending entry of that consumer after `after`, oldest first. Each
1199    /// one counts as handed out again, so its delivery time is reset and its
1200    /// count goes up. That is Redis's behaviour, checked rather than assumed,
1201    /// and it is the right one: the count is how many times a consumer has been
1202    /// told to do this work, and a consumer re-reading its backlog after a
1203    /// restart has been told again.
1204    ///
1205    /// An entry that has since been deleted or trimmed is still in the pending
1206    /// list and is handed to the callback with no fields, which is the null
1207    /// Redis puts in the reply. Clearing those out is [`Stream::claim`]'s job
1208    /// and not this one.
1209    pub fn read_group_pending<F>(
1210        &mut self,
1211        group: &[u8],
1212        consumer: &[u8],
1213        after: Id,
1214        count: Option<usize>,
1215        now: u64,
1216        mut f: F,
1217    ) -> Option<usize>
1218    where
1219        F: FnMut(Id, Option<Fields<'_>>) -> bool,
1220    {
1221        // Which IDs, decided before anything is touched, so that the redelivery
1222        // and the walk are two passes over a small list rather than one pass
1223        // holding the group and the nodes at the same time.
1224        //
1225        // The consumer is created rather than looked up, because a history read
1226        // by a name nobody has used is an empty list and not a missing group. A
1227        // worker that restarts under a new name and asks for its own backlog
1228        // first is exactly that case, and Redis answers it with an empty list
1229        // and the consumer left behind.
1230        let g = self.group_mut(group)?;
1231        let slot = g.consumer_or_create(consumer, now);
1232        let ids: Vec<Id> = g
1233            .consumer(slot)
1234            .expect("the slot that was just made")
1235            .pending()
1236            .filter(|&id| id > after)
1237            .take(count.unwrap_or(usize::MAX))
1238            .collect();
1239        for &id in &ids {
1240            g.redeliver(id, now);
1241        }
1242        g.touch(slot, now, !ids.is_empty());
1243
1244        let mut seen = 0;
1245        for &id in &ids {
1246            seen += 1;
1247            let mut go = true;
1248            let mut found = false;
1249            self.walk(id, id, Some(1), &mut |got, fields| {
1250                found = true;
1251                go = f(got, Some(fields));
1252                false
1253            });
1254            if !found {
1255                go = f(id, None);
1256            }
1257            if !go {
1258                break;
1259            }
1260        }
1261        Some(seen)
1262    }
1263
1264    /// Move pending entries to a consumer, which is `XCLAIM`.
1265    ///
1266    /// Only entries idle at least `min_idle` move. `time` is what the delivery
1267    /// time becomes, `retry` replaces the delivery count when it is given, and
1268    /// `bump` says whether to add one to it, which `JUSTID` turns off. `force`
1269    /// makes a pending entry for an ID that is in the stream but was not
1270    /// pending.
1271    ///
1272    /// An ID that is pending but no longer in the stream is dropped from the
1273    /// pending list rather than claimed, and reported through `gone`, which is
1274    /// what Redis does and what stops a deleted entry being handed round
1275    /// forever. Answers the IDs that moved.
1276    #[allow(clippy::too_many_arguments)]
1277    pub fn claim(
1278        &mut self,
1279        group: &[u8],
1280        consumer: &[u8],
1281        ids: &[Id],
1282        min_idle: u64,
1283        time: u64,
1284        retry: Option<u64>,
1285        bump: bool,
1286        force: bool,
1287        now: u64,
1288        gone: &mut Vec<Id>,
1289    ) -> Option<Vec<Id>> {
1290        // Before the loop, so that a claim which takes nothing still leaves the
1291        // consumer behind. Redis creates it either way, and an `XAUTOCLAIM`
1292        // against an empty pending list is the ordinary way that happens: the
1293        // consumer turns up in `XINFO CONSUMERS` straight after, holding
1294        // nothing.
1295        self.group_mut(group)?.consumer_or_create(consumer, now);
1296        let mut took = Vec::new();
1297        for &id in ids {
1298            let here = self.contains(id);
1299            let g = self.group_mut(group)?;
1300            let slot = g.consumer_or_create(consumer, now);
1301            match g.nack(id) {
1302                Some(nack) => {
1303                    if !here {
1304                        g.forget(id);
1305                        gone.push(id);
1306                        continue;
1307                    }
1308                    if nack.idle(now) < min_idle {
1309                        continue;
1310                    }
1311                    if g.claim(id, slot, time, retry, bump) {
1312                        took.push(id);
1313                    }
1314                }
1315                None => {
1316                    // FORCE makes one out of nothing, but only for an entry that
1317                    // is really there. Redis ignores the rest in silence.
1318                    if force && here && g.force(id, slot, time, retry.unwrap_or(1)) {
1319                        took.push(id);
1320                    }
1321                }
1322            }
1323        }
1324        // Active only when something moved, which is the same rule a read
1325        // follows. A claim that found nothing idle enough leaves the consumer
1326        // reading as never active.
1327        if !took.is_empty() {
1328            let g = self.group_mut(group).expect("the group found a moment ago");
1329            let slot = g.consumer_or_create(consumer, now);
1330            g.touch(slot, now, true);
1331        }
1332        Some(took)
1333    }
1334
1335    /// Sweep the pending list for stale entries and claim them, which is
1336    /// `XAUTOCLAIM`.
1337    ///
1338    /// Starts at `start` and takes up to `count` entries that have been idle at
1339    /// least `min_idle`. Answers where a following call should carry on from,
1340    /// which is `None` at the end of the list, along with what was claimed and
1341    /// what was dropped for no longer being in the stream.
1342    #[allow(clippy::too_many_arguments)]
1343    pub fn autoclaim(
1344        &mut self,
1345        group: &[u8],
1346        consumer: &[u8],
1347        start: Id,
1348        min_idle: u64,
1349        count: usize,
1350        bump: bool,
1351        now: u64,
1352        gone: &mut Vec<Id>,
1353    ) -> Option<(Option<Id>, Vec<Id>)> {
1354        let mut ids = Vec::new();
1355        let cursor = self
1356            .group(group)?
1357            .claimable(start, min_idle, now, count, &mut ids);
1358        let took = self.claim(
1359            group, consumer, &ids, min_idle, now, None, bump, false, now, gone,
1360        )?;
1361        Some((cursor, took))
1362    }
1363
1364    /// How many bytes the entries and the groups take, not counting this struct.
1365    ///
1366    /// The name is the one every other body in this crate uses, because the
1367    /// keyspace asks all of them the same question through one trait and a
1368    /// stream that answered it under a different name would need its own arm.
1369    #[must_use]
1370    pub fn memory_bytes(&self) -> usize {
1371        let nodes: usize = self
1372            .nodes
1373            .iter()
1374            .map(|node| node.lp.byte_len() + std::mem::size_of::<Node>())
1375            .sum();
1376        let groups: usize = self
1377            .groups
1378            .iter()
1379            .map(|(name, g)| {
1380                name.capacity() + std::mem::size_of::<(Vec<u8>, Group)>() + g.memory_bytes()
1381            })
1382            .sum();
1383        nodes + groups
1384    }
1385
1386    /// How many nodes there are, which only a test and `XINFO STREAM FULL` care
1387    /// about.
1388    #[must_use]
1389    pub fn nodes(&self) -> usize {
1390        self.nodes.len()
1391    }
1392
1393    /// The shared walk behind [`Stream::range`] and [`Stream::first_id`].
1394    fn walk<F>(&self, start: Id, end: Id, count: Option<usize>, f: &mut F) -> usize
1395    where
1396        F: FnMut(Id, Fields<'_>) -> bool,
1397    {
1398        walk_nodes(&self.nodes, start, end, count, f)
1399    }
1400
1401    /// The first node that can hold an entry at or after `id`.
1402    fn node_from(&self, id: Id) -> usize {
1403        node_from(&self.nodes, id)
1404    }
1405
1406    /// The node that would hold `id`, or `None` when no node covers it.
1407    fn node_of(&self, id: Id) -> Option<usize> {
1408        let at = self.node_from(id);
1409        let node = self.nodes.get(at)?;
1410        (node.master <= id && id <= last_of(node)).then_some(at)
1411    }
1412}
1413
1414/// The first node that can hold an entry at or after `id`.
1415///
1416/// The binary search the module docs are about. `partition_point` answers how
1417/// many nodes start strictly before `id`, and the one before that is the one
1418/// `id` would be in, since a node holds everything from its master ID up to the
1419/// next node's.
1420///
1421/// Free rather than a method so that a group read can hold the nodes and the
1422/// groups at the same time, which it has to because it walks the one to write
1423/// into the other.
1424fn node_from(nodes: &VecDeque<Node>, id: Id) -> usize {
1425    let after = nodes.partition_point(|node| node.master <= id);
1426    after.saturating_sub(1)
1427}
1428
1429/// Every live entry from `start` to `end`, both included, oldest first.
1430///
1431/// Free for the same reason [`node_from`] is.
1432fn walk_nodes<F>(
1433    nodes: &VecDeque<Node>,
1434    start: Id,
1435    end: Id,
1436    count: Option<usize>,
1437    f: &mut F,
1438) -> usize
1439where
1440    F: FnMut(Id, Fields<'_>) -> bool,
1441{
1442    let mut seen = 0;
1443    let mut stop = false;
1444    for node in nodes.iter().skip(node_from(nodes, start)) {
1445        if node.master > end {
1446            break;
1447        }
1448        each(&node.lp, node.master, &mut |id, fields| {
1449            if id > end {
1450                stop = true;
1451                return false;
1452            }
1453            if id < start {
1454                return true;
1455            }
1456            if count.is_some_and(|want| seen >= want) {
1457                stop = true;
1458                return false;
1459            }
1460            seen += 1;
1461            if !f(id, fields) {
1462                stop = true;
1463                return false;
1464            }
1465            true
1466        });
1467        if stop {
1468            break;
1469        }
1470    }
1471    seen
1472}
1473
1474/// The field names and values of one entry.
1475///
1476/// Two walks rather than one because an entry that shares the node's field names
1477/// reads them from the master entry and its values from itself, and the whole
1478/// point of that layout is that the names are not copied per entry. Neither walk
1479/// allocates and neither is a copy.
1480#[derive(Debug, Clone)]
1481pub struct Fields<'a> {
1482    /// Where the names come from, when they are not interleaved with the values.
1483    names: Option<listpack::Iter<'a>>,
1484    body: listpack::Iter<'a>,
1485    left: usize,
1486}
1487
1488impl<'a> Iterator for Fields<'a> {
1489    type Item = (Entry<'a>, Entry<'a>);
1490
1491    fn next(&mut self) -> Option<(Entry<'a>, Entry<'a>)> {
1492        if self.left == 0 {
1493            return None;
1494        }
1495        self.left -= 1;
1496        let name = match &mut self.names {
1497            Some(names) => names.next()?,
1498            None => self.body.next()?,
1499        };
1500        Some((name, self.body.next()?))
1501    }
1502
1503    fn size_hint(&self) -> (usize, Option<usize>) {
1504        (self.left, Some(self.left))
1505    }
1506}
1507
1508impl ExactSizeIterator for Fields<'_> {}
1509
1510impl Fields<'_> {
1511    /// Whether there are no fields left.
1512    #[must_use]
1513    #[inline]
1514    pub fn is_empty(&self) -> bool {
1515        self.left == 0
1516    }
1517}
1518
1519/// A fresh node whose master fields are this entry's.
1520fn master_of(fields: &[(&[u8], &[u8])]) -> Listpack {
1521    let mut lp = Listpack::new();
1522    push_int(&mut lp, 0);
1523    push_int(&mut lp, 0);
1524    push_int(&mut lp, fields.len() as i64);
1525    for (name, _) in fields {
1526        lp.push(name);
1527    }
1528    push_int(&mut lp, 0);
1529    lp
1530}
1531
1532/// Whether these fields are exactly the node's master fields, in order.
1533fn same_fields(lp: &Listpack, fields: &[(&[u8], &[u8])]) -> bool {
1534    let mut it = lp.iter();
1535    let (_, _, want) = match (it.next(), it.next(), it.next()) {
1536        (Some(_), Some(_), Some(Entry::Int(n))) => ((), (), n),
1537        _ => return false,
1538    };
1539    if want != fields.len() as i64 {
1540        return false;
1541    }
1542    fields.iter().all(|(name, _)| match it.next() {
1543        Some(Entry::Str(s)) => s == *name,
1544        Some(Entry::Int(n)) => {
1545            let mut buf = [0u8; DIGITS_MAX];
1546            i64_digits(&mut buf, n) == *name
1547        }
1548        None => false,
1549    })
1550}
1551
1552/// The master entry's live and deleted counts.
1553fn counts(lp: &Listpack) -> (u64, u64) {
1554    let mut it = lp.iter();
1555    let count = int_or_zero(it.next());
1556    let deleted = int_or_zero(it.next());
1557    (count.max(0) as u64, deleted.max(0) as u64)
1558}
1559
1560/// Add to the master entry's two counts.
1561fn bump(lp: &mut Listpack, live: i64, dead: i64) {
1562    let (count, deleted) = counts(lp);
1563    let mut buf = [0u8; DIGITS_MAX];
1564    let at = count as i64 + live;
1565    lp.replace(0, u64_digits(&mut buf, at.max(0) as u64));
1566    let at = deleted as i64 + dead;
1567    lp.replace(1, u64_digits(&mut buf, at.max(0) as u64));
1568}
1569
1570/// An entry as an integer, or zero for anything else.
1571fn int_or_zero(entry: Option<Entry<'_>>) -> i64 {
1572    match entry {
1573        Some(Entry::Int(n)) => n,
1574        _ => 0,
1575    }
1576}
1577
1578/// Append an integer, which the listpack encodes as one because it parses as one.
1579fn push_int(lp: &mut Listpack, n: i64) {
1580    let mut buf = [0u8; DIGITS_MAX];
1581    lp.push(i64_digits(&mut buf, n));
1582}
1583
1584/// Overwrite the element at `index` with an integer.
1585fn set_int(lp: &mut Listpack, index: usize, n: i64) {
1586    let mut buf = [0u8; DIGITS_MAX];
1587    lp.replace(index, i64_digits(&mut buf, n));
1588}
1589
1590/// Write one entry onto the end of a node.
1591fn write_entry(lp: &mut Listpack, master: Id, id: Id, fields: &[(&[u8], &[u8])], same: bool) {
1592    let flags = if same { LIVE | SAME_FIELDS } else { LIVE };
1593    push_int(lp, flags);
1594    // Both halves as a plain difference from the master, wrapping, which is what
1595    // Redis writes. The sequence often goes down when the millisecond goes up,
1596    // so that difference is usually negative and it does not matter: adding it
1597    // back is the exact inverse whichever way it went.
1598    push_int(lp, id.ms.wrapping_sub(master.ms) as i64);
1599    push_int(lp, id.seq.wrapping_sub(master.seq) as i64);
1600    if same {
1601        for (_, value) in fields {
1602            lp.push(value);
1603        }
1604        push_int(lp, fields.len() as i64 + 3);
1605    } else {
1606        push_int(lp, fields.len() as i64);
1607        for (name, value) in fields {
1608            lp.push(name);
1609            lp.push(value);
1610        }
1611        push_int(lp, fields.len() as i64 * 2 + 4);
1612    }
1613}
1614
1615/// The greatest ID in a node, live or not.
1616fn last_of(node: &Node) -> Id {
1617    let mut last = node.master;
1618    each(&node.lp, node.master, &mut |id, _| {
1619        last = id;
1620        true
1621    });
1622    last
1623}
1624
1625/// Where the entry with that ID starts, and its flags, or `None`.
1626///
1627/// The offset is a listpack element index rather than a byte offset, because
1628/// that is what `replace` takes.
1629fn find(lp: &Listpack, master: Id, id: Id) -> Option<(usize, i64)> {
1630    let mut at = None;
1631    walk_node(lp, master, &mut |found, index, flags, _| {
1632        if found == id {
1633            at = Some((index, flags));
1634            return false;
1635        }
1636        found < id
1637    });
1638    at
1639}
1640
1641/// Every entry in a node, live ones only, oldest first.
1642fn each<'a, F>(lp: &'a Listpack, master: Id, f: &mut F)
1643where
1644    F: FnMut(Id, Fields<'a>) -> bool,
1645{
1646    walk_node(lp, master, &mut |id, _, flags, fields| {
1647        if flags & DELETED != 0 {
1648            return true;
1649        }
1650        f(id, fields)
1651    });
1652}
1653
1654/// Every entry in a node, deleted ones included, with its element index.
1655///
1656/// One forward walk of the whole blob. Nothing here reaches into the middle by
1657/// index, because a listpack index is a walk from the front and doing that per
1658/// entry would turn a node scan into a quadratic one.
1659fn walk_node<'a, F>(lp: &'a Listpack, master: Id, f: &mut F)
1660where
1661    F: FnMut(Id, usize, i64, Fields<'a>) -> bool,
1662{
1663    let mut it = lp.iter();
1664    let (Some(_), Some(_), Some(Entry::Int(masters))) = (it.next(), it.next(), it.next()) else {
1665        return;
1666    };
1667    let masters = masters.max(0) as usize;
1668    // The master field names, kept as a mark to hand to the entries that share
1669    // them, and then stepped over along with the zero that ends the master entry.
1670    let names = it.clone();
1671    let mut index = MASTER_FIELDS;
1672    for _ in 0..=masters {
1673        if it.next().is_none() {
1674            return;
1675        }
1676        index += 1;
1677    }
1678
1679    loop {
1680        let at = index;
1681        let (Some(Entry::Int(flags)), Some(Entry::Int(ms)), Some(Entry::Int(seq))) =
1682            (it.next(), it.next(), it.next())
1683        else {
1684            return;
1685        };
1686        index += 3;
1687        let id = Id {
1688            ms: master.ms.wrapping_add(ms as u64),
1689            seq: master.seq.wrapping_add(seq as u64),
1690        };
1691
1692        let same = flags & SAME_FIELDS != 0;
1693        let (fields, skip) = if same {
1694            let fields = Fields {
1695                names: Some(names.clone()),
1696                body: it.clone(),
1697                left: masters,
1698            };
1699            (fields, masters + 1)
1700        } else {
1701            let Some(Entry::Int(n)) = it.next() else {
1702                return;
1703            };
1704            index += 1;
1705            let fields = Fields {
1706                names: None,
1707                body: it.clone(),
1708                left: n.max(0) as usize,
1709            };
1710            (fields, n.max(0) as usize * 2 + 1)
1711        };
1712
1713        if !f(id, at, flags, fields) {
1714            return;
1715        }
1716        for _ in 0..skip {
1717            if it.next().is_none() {
1718                return;
1719            }
1720            index += 1;
1721        }
1722    }
1723}
1724
1725#[cfg(test)]
1726mod tests {
1727    use super::*;
1728
1729    fn pairs<'a>(of: &'a [(&'a str, &'a str)]) -> Vec<(&'a [u8], &'a [u8])> {
1730        of.iter()
1731            .map(|(f, v)| (f.as_bytes(), v.as_bytes()))
1732            .collect()
1733    }
1734
1735    /// One entry, with its fields owned, which is what the tests compare.
1736    type Flat = (Id, Vec<(Vec<u8>, Vec<u8>)>);
1737
1738    /// Everything in the stream, oldest first.
1739    fn dump(s: &Stream) -> Vec<Flat> {
1740        let mut out = Vec::new();
1741        s.range(Id::MIN, Id::MAX, None, |id, fields| {
1742            out.push((id, fields.map(|(f, v)| (f.to_vec(), v.to_vec())).collect()));
1743            true
1744        });
1745        out
1746    }
1747
1748    fn add(s: &mut Stream, ms: u64, seq: u64, fields: &[(&str, &str)]) {
1749        s.append(Id::new(ms, seq), &pairs(fields), Limits::default())
1750            .expect("an append");
1751    }
1752
1753    #[test]
1754    fn an_entry_comes_back_as_it_went_in() {
1755        let mut s = Stream::new();
1756        add(&mut s, 5, 0, &[("sensor", "1"), ("reading", "23.4")]);
1757        let got = dump(&s);
1758        assert_eq!(got.len(), 1);
1759        assert_eq!(got[0].0, Id::new(5, 0));
1760        assert_eq!(
1761            got[0].1,
1762            vec![
1763                (b"sensor".to_vec(), b"1".to_vec()),
1764                (b"reading".to_vec(), b"23.4".to_vec())
1765            ]
1766        );
1767    }
1768
1769    #[test]
1770    fn entries_come_back_in_order() {
1771        let mut s = Stream::new();
1772        for ms in 1..200u64 {
1773            add(&mut s, ms, 0, &[("n", "x")]);
1774        }
1775        let got = dump(&s);
1776        assert_eq!(got.len(), 199);
1777        for (at, (id, _)) in got.iter().enumerate() {
1778            assert_eq!(*id, Id::new(at as u64 + 1, 0));
1779        }
1780        assert_eq!(s.len(), 199);
1781        assert_eq!(s.added(), 199);
1782        assert_eq!(s.last_id(), Id::new(199, 0));
1783    }
1784
1785    /// The whole reason a node holds a hundred entries.
1786    #[test]
1787    fn a_long_stream_is_many_nodes() {
1788        let mut s = Stream::new();
1789        for ms in 1..=1000u64 {
1790            add(&mut s, ms, 0, &[("n", "x")]);
1791        }
1792        assert_eq!(s.nodes(), 10, "a hundred entries a node");
1793        assert_eq!(dump(&s).len(), 1000);
1794    }
1795
1796    /// Sharing the field names is worth most of the entry on a real stream.
1797    #[test]
1798    fn the_field_names_are_not_stored_twice() {
1799        let mut shared = Stream::new();
1800        let mut apart = Stream::new();
1801        for ms in 1..=100u64 {
1802            add(
1803                &mut shared,
1804                ms,
1805                0,
1806                &[("temperature_celsius", "21"), ("relative_humidity", "44")],
1807            );
1808            let a = format!("temperature_celsius{ms}");
1809            let b = format!("relative_humidity{ms}");
1810            apart
1811                .append(
1812                    Id::new(ms, 0),
1813                    &[(a.as_bytes(), b"21"), (b.as_bytes(), b"44")],
1814                    Limits::default(),
1815                )
1816                .expect("an append");
1817        }
1818        assert!(
1819            shared.memory_bytes() * 3 < apart.memory_bytes(),
1820            "{} against {}",
1821            shared.memory_bytes(),
1822            apart.memory_bytes()
1823        );
1824    }
1825
1826    /// What a real stream entry costs, so that a change that quietly doubles it
1827    /// has somewhere to fail.
1828    ///
1829    /// Ten thousand `sensor` and `reading` entries a millisecond apart, which is
1830    /// the shape the benchmark uses and the shape a stream almost always has.
1831    /// That measures 23.9 bytes an entry today, against 48.7 for the same
1832    /// entries with field names that cannot be shared. Thirty two is a bar with
1833    /// room in it rather than a target, because the point is to catch a
1834    /// regression and not to freeze the encoder.
1835    #[test]
1836    fn an_entry_costs_about_two_dozen_bytes() {
1837        let mut s = Stream::new();
1838        for ms in 1..=10_000u64 {
1839            let reading = format!("{:.3}", ms as f64 / 7.0);
1840            s.append(
1841                Id::new(ms, 0),
1842                &[(b"sensor", b"a4"), (b"reading", reading.as_bytes())],
1843                Limits::default(),
1844            )
1845            .expect("an append");
1846        }
1847        let each = s.memory_bytes() as f64 / 10_000.0;
1848        assert!(each < 32.0, "{each:.2} bytes an entry");
1849    }
1850
1851    #[test]
1852    fn an_entry_with_its_own_fields_still_reads_back() {
1853        let mut s = Stream::new();
1854        add(&mut s, 1, 0, &[("a", "1"), ("b", "2")]);
1855        add(&mut s, 2, 0, &[("c", "3")]);
1856        add(&mut s, 3, 0, &[("a", "4"), ("b", "5")]);
1857        let got = dump(&s);
1858        assert_eq!(got[1].1, vec![(b"c".to_vec(), b"3".to_vec())]);
1859        assert_eq!(
1860            got[2].1,
1861            vec![
1862                (b"a".to_vec(), b"4".to_vec()),
1863                (b"b".to_vec(), b"5".to_vec())
1864            ]
1865        );
1866    }
1867
1868    /// The same names in a different order is not the same shape.
1869    #[test]
1870    fn the_order_of_the_names_matters() {
1871        let mut s = Stream::new();
1872        add(&mut s, 1, 0, &[("a", "1"), ("b", "2")]);
1873        add(&mut s, 2, 0, &[("b", "3"), ("a", "4")]);
1874        let got = dump(&s);
1875        assert_eq!(
1876            got[1].1,
1877            vec![
1878                (b"b".to_vec(), b"3".to_vec()),
1879                (b"a".to_vec(), b"4".to_vec())
1880            ]
1881        );
1882    }
1883
1884    #[test]
1885    fn an_id_must_beat_the_last_one() {
1886        let mut s = Stream::new();
1887        add(&mut s, 5, 5, &[("n", "x")]);
1888        let f = pairs(&[("n", "x")]);
1889        for id in [Id::new(5, 5), Id::new(5, 4), Id::new(1, 0)] {
1890            assert_eq!(
1891                s.append(id, &f, Limits::default()),
1892                Err(Refused::NotGreater),
1893                "{id:?}"
1894            );
1895        }
1896        assert_eq!(s.append(Id::new(5, 6), &f, Limits::default()), Ok(()));
1897    }
1898
1899    #[test]
1900    fn nothing_can_be_added_at_zero() {
1901        let mut s = Stream::new();
1902        assert_eq!(
1903            s.append(Id::MIN, &pairs(&[("n", "x")]), Limits::default()),
1904            Err(Refused::Zero)
1905        );
1906    }
1907
1908    #[test]
1909    fn a_range_takes_both_ends() {
1910        let mut s = Stream::new();
1911        for ms in 1..=10u64 {
1912            add(&mut s, ms, 0, &[("n", "x")]);
1913        }
1914        let mut seen = Vec::new();
1915        s.range(Id::new(3, 0), Id::new(6, 0), None, |id, _| {
1916            seen.push(id.ms);
1917            true
1918        });
1919        assert_eq!(seen, vec![3, 4, 5, 6]);
1920    }
1921
1922    /// A range whose ends fall between entries, and one that misses entirely.
1923    #[test]
1924    fn a_range_that_lands_between_entries() {
1925        let mut s = Stream::new();
1926        for ms in [10u64, 20, 30] {
1927            add(&mut s, ms, 0, &[("n", "x")]);
1928        }
1929        let mut seen = Vec::new();
1930        s.range(Id::new(11, 0), Id::new(29, 0), None, |id, _| {
1931            seen.push(id.ms);
1932            true
1933        });
1934        assert_eq!(seen, vec![20]);
1935
1936        let mut none = 0;
1937        s.range(Id::new(31, 0), Id::MAX, None, |_, _| {
1938            none += 1;
1939            true
1940        });
1941        assert_eq!(none, 0);
1942    }
1943
1944    #[test]
1945    fn a_count_stops_the_walk() {
1946        let mut s = Stream::new();
1947        for ms in 1..=500u64 {
1948            add(&mut s, ms, 0, &[("n", "x")]);
1949        }
1950        let mut seen = 0;
1951        let answered = s.range(Id::MIN, Id::MAX, Some(7), |_, _| {
1952            seen += 1;
1953            true
1954        });
1955        assert_eq!((seen, answered), (7, 7));
1956    }
1957
1958    #[test]
1959    fn the_callback_can_stop_the_walk() {
1960        let mut s = Stream::new();
1961        for ms in 1..=500u64 {
1962            add(&mut s, ms, 0, &[("n", "x")]);
1963        }
1964        let mut seen = 0;
1965        s.range(Id::MIN, Id::MAX, None, |_, _| {
1966            seen += 1;
1967            seen < 3
1968        });
1969        assert_eq!(seen, 3);
1970    }
1971
1972    #[test]
1973    fn a_reverse_range_is_the_forward_one_backwards() {
1974        let mut s = Stream::new();
1975        for ms in 1..=350u64 {
1976            add(&mut s, ms, 0, &[("n", "x")]);
1977        }
1978        let mut forward = Vec::new();
1979        s.range(Id::new(50, 0), Id::new(300, 0), None, |id, _| {
1980            forward.push(id);
1981            true
1982        });
1983        let mut back = Vec::new();
1984        s.rev_range(Id::new(50, 0), Id::new(300, 0), None, |id, _| {
1985            back.push(id);
1986            true
1987        });
1988        back.reverse();
1989        assert_eq!(forward, back);
1990        assert_eq!(forward.len(), 251);
1991    }
1992
1993    #[test]
1994    fn a_reverse_range_takes_a_count_from_the_new_end() {
1995        let mut s = Stream::new();
1996        for ms in 1..=350u64 {
1997            add(&mut s, ms, 0, &[("n", "x")]);
1998        }
1999        let mut seen = Vec::new();
2000        s.rev_range(Id::MIN, Id::MAX, Some(3), |id, _| {
2001            seen.push(id.ms);
2002            true
2003        });
2004        assert_eq!(seen, vec![350, 349, 348]);
2005    }
2006
2007    #[test]
2008    fn deleting_leaves_the_rest_readable() {
2009        let mut s = Stream::new();
2010        for ms in 1..=10u64 {
2011            add(&mut s, ms, 0, &[("n", "x")]);
2012        }
2013        assert!(s.delete(Id::new(4, 0)));
2014        assert!(!s.delete(Id::new(4, 0)), "twice is not twice");
2015        assert!(!s.delete(Id::new(99, 0)));
2016        assert_eq!(s.len(), 9);
2017        assert_eq!(s.max_deleted_id(), Id::new(4, 0));
2018        let seen: Vec<u64> = dump(&s).iter().map(|(id, _)| id.ms).collect();
2019        assert_eq!(seen, vec![1, 2, 3, 5, 6, 7, 8, 9, 10]);
2020    }
2021
2022    #[test]
2023    fn deleting_the_first_entry_moves_the_first_id() {
2024        let mut s = Stream::new();
2025        for ms in 1..=5u64 {
2026            add(&mut s, ms, 0, &[("n", "x")]);
2027        }
2028        assert_eq!(s.first_id(), Some(Id::new(1, 0)));
2029        s.delete(Id::new(1, 0));
2030        assert_eq!(s.first_id(), Some(Id::new(2, 0)));
2031    }
2032
2033    /// The last live entry going takes the node with it.
2034    #[test]
2035    fn emptying_a_node_drops_it() {
2036        let mut s = Stream::new();
2037        for ms in 1..=250u64 {
2038            add(&mut s, ms, 0, &[("n", "x")]);
2039        }
2040        assert_eq!(s.nodes(), 3);
2041        for ms in 1..=100u64 {
2042            assert!(s.delete(Id::new(ms, 0)), "{ms}");
2043        }
2044        assert_eq!(s.nodes(), 2);
2045        assert_eq!(s.len(), 150);
2046        assert_eq!(dump(&s).len(), 150);
2047    }
2048
2049    /// The stream can be empty and still know what came before.
2050    #[test]
2051    fn an_emptied_stream_still_remembers_its_last_id() {
2052        let mut s = Stream::new();
2053        add(&mut s, 7, 0, &[("n", "x")]);
2054        s.delete(Id::new(7, 0));
2055        assert!(s.is_empty());
2056        assert_eq!(s.last_id(), Id::new(7, 0));
2057        assert_eq!(s.added(), 1);
2058        assert_eq!(s.first_id(), None);
2059        assert_eq!(
2060            s.append(Id::new(7, 0), &pairs(&[("n", "x")]), Limits::default()),
2061            Err(Refused::NotGreater),
2062            "a deleted id is still used up"
2063        );
2064    }
2065
2066    #[test]
2067    fn trimming_to_a_length_takes_the_oldest() {
2068        let mut s = Stream::new();
2069        for ms in 1..=1000u64 {
2070            add(&mut s, ms, 0, &[("n", "x")]);
2071        }
2072        assert_eq!(s.trim_maxlen(150, true, None), 850);
2073        assert_eq!(s.len(), 150);
2074        assert_eq!(s.first_id(), Some(Id::new(851, 0)));
2075        assert_eq!(s.last_id(), Id::new(1000, 0));
2076    }
2077
2078    /// The point of `~`: whole nodes only, so nothing is rewritten.
2079    #[test]
2080    fn an_approximate_trim_stops_at_a_node() {
2081        let mut s = Stream::new();
2082        for ms in 1..=1000u64 {
2083            add(&mut s, ms, 0, &[("n", "x")]);
2084        }
2085        assert_eq!(s.trim_maxlen(150, false, None), 800);
2086        assert_eq!(s.len(), 200, "left at the node boundary above 150");
2087        assert_eq!(s.nodes(), 2);
2088    }
2089
2090    #[test]
2091    fn trimming_to_a_length_that_is_already_met_does_nothing() {
2092        let mut s = Stream::new();
2093        for ms in 1..=10u64 {
2094            add(&mut s, ms, 0, &[("n", "x")]);
2095        }
2096        assert_eq!(s.trim_maxlen(50, true, None), 0);
2097        assert_eq!(s.len(), 10);
2098    }
2099
2100    #[test]
2101    fn trimming_to_zero_empties_it() {
2102        let mut s = Stream::new();
2103        for ms in 1..=250u64 {
2104            add(&mut s, ms, 0, &[("n", "x")]);
2105        }
2106        assert_eq!(s.trim_maxlen(0, true, None), 250);
2107        assert!(s.is_empty());
2108        assert_eq!(s.nodes(), 0);
2109        assert_eq!(s.last_id(), Id::new(250, 0));
2110    }
2111
2112    #[test]
2113    fn trimming_below_an_id_takes_everything_under_it() {
2114        let mut s = Stream::new();
2115        for ms in 1..=1000u64 {
2116            add(&mut s, ms, 0, &[("n", "x")]);
2117        }
2118        assert_eq!(s.trim_minid(Id::new(400, 0), true, None), 399);
2119        assert_eq!(s.first_id(), Some(Id::new(400, 0)));
2120        assert_eq!(s.len(), 601);
2121    }
2122
2123    #[test]
2124    fn an_approximate_minid_trim_stops_at_a_node() {
2125        let mut s = Stream::new();
2126        for ms in 1..=1000u64 {
2127            add(&mut s, ms, 0, &[("n", "x")]);
2128        }
2129        assert_eq!(s.trim_minid(Id::new(450, 0), false, None), 400);
2130        assert_eq!(s.first_id(), Some(Id::new(401, 0)));
2131    }
2132
2133    #[test]
2134    fn several_entries_share_a_millisecond() {
2135        let mut s = Stream::new();
2136        for seq in 0..250u64 {
2137            add(&mut s, 5, seq, &[("n", "x")]);
2138        }
2139        let got = dump(&s);
2140        assert_eq!(got.len(), 250);
2141        for (at, (id, _)) in got.iter().enumerate() {
2142            assert_eq!(*id, Id::new(5, at as u64), "at {at}");
2143        }
2144        let mut seen = Vec::new();
2145        s.range(Id::new(5, 100), Id::new(5, 102), None, |id, _| {
2146            seen.push(id.seq);
2147            true
2148        });
2149        assert_eq!(seen, vec![100, 101, 102]);
2150    }
2151
2152    /// Sequence numbers that run on across a node boundary, which is where an
2153    /// ID stored as a difference is easiest to get wrong.
2154    #[test]
2155    fn a_sequence_that_crosses_a_node() {
2156        let mut s = Stream::new();
2157        for seq in 0..300u64 {
2158            add(&mut s, 1, seq, &[("n", "x")]);
2159        }
2160        assert!(s.nodes() > 1);
2161        let got = dump(&s);
2162        assert_eq!(got.len(), 300);
2163        assert_eq!(got[299].0, Id::new(1, 299));
2164        assert!(s.delete(Id::new(1, 250)));
2165        assert_eq!(dump(&s).len(), 299);
2166    }
2167
2168    #[test]
2169    fn an_entry_with_no_fields_is_still_an_entry() {
2170        let mut s = Stream::new();
2171        s.append(Id::new(1, 0), &[], Limits::default())
2172            .expect("an append");
2173        add(&mut s, 2, 0, &[("n", "x")]);
2174        let got = dump(&s);
2175        assert_eq!(got.len(), 2);
2176        assert!(got[0].1.is_empty());
2177    }
2178
2179    #[test]
2180    fn a_value_that_looks_like_a_number_comes_back_as_it_went_in() {
2181        let mut s = Stream::new();
2182        add(&mut s, 1, 0, &[("n", "007"), ("m", "7")]);
2183        let got = dump(&s);
2184        assert_eq!(got[0].1[0].1, b"007".to_vec());
2185        assert_eq!(got[0].1[1].1, b"7".to_vec());
2186    }
2187
2188    #[test]
2189    fn the_auto_id_follows_the_clock_and_never_goes_back() {
2190        let mut s = Stream::new();
2191        assert_eq!(s.auto_id(1000), Some(Id::new(1000, 0)));
2192        add(&mut s, 1000, 0, &[("n", "x")]);
2193        assert_eq!(s.auto_id(1000), Some(Id::new(1000, 1)), "same millisecond");
2194        assert_eq!(s.auto_id(900), Some(Id::new(1000, 1)), "clock went back");
2195        assert_eq!(s.auto_id(1001), Some(Id::new(1001, 0)));
2196    }
2197
2198    #[test]
2199    fn an_explicit_millisecond_takes_the_next_sequence() {
2200        let mut s = Stream::new();
2201        add(&mut s, 5, 0, &[("n", "x")]);
2202        assert_eq!(s.auto_seq(5), Some(Id::new(5, 1)));
2203        assert_eq!(s.auto_seq(6), Some(Id::new(6, 0)));
2204        assert_eq!(s.auto_seq(4), None, "below the last one");
2205    }
2206
2207    #[test]
2208    fn an_id_reads_and_writes() {
2209        for (text, default, want) in [
2210            (&b"5"[..], 0, Some(Id::new(5, 0))),
2211            (b"5", u64::MAX, Some(Id::new(5, u64::MAX))),
2212            (b"5-3", 0, Some(Id::new(5, 3))),
2213            (b"0-0", 0, Some(Id::MIN)),
2214            (b"", 0, None),
2215            (b"-1", 0, None),
2216            (b"5-", 0, None),
2217            (b"a", 0, None),
2218            (b"5-a", 0, None),
2219            (b"18446744073709551616", 0, None),
2220        ] {
2221            assert_eq!(
2222                Id::parse(text, default),
2223                want,
2224                "{:?}",
2225                String::from_utf8_lossy(text)
2226            );
2227        }
2228        assert_eq!(Id::new(5, 3).to_vec(), b"5-3".to_vec());
2229    }
2230
2231    #[test]
2232    fn an_id_round_trips_through_its_bytes() {
2233        for id in [Id::MIN, Id::MAX, Id::new(1, 2), Id::new(u64::MAX, 0)] {
2234            assert_eq!(Id::from_bytes(id.to_bytes()), id);
2235        }
2236        // Big endian is the order that sorts, which is why the format uses it.
2237        assert!(Id::new(1, 2).to_bytes() < Id::new(1, 3).to_bytes());
2238        assert!(Id::new(1, u64::MAX).to_bytes() < Id::new(2, 0).to_bytes());
2239    }
2240
2241    #[test]
2242    fn stepping_an_id_carries_and_stops() {
2243        assert_eq!(Id::new(1, 2).next(), Some(Id::new(1, 3)));
2244        assert_eq!(Id::new(1, u64::MAX).next(), Some(Id::new(2, 0)));
2245        assert_eq!(Id::MAX.next(), None);
2246        assert_eq!(Id::new(1, 3).prev(), Some(Id::new(1, 2)));
2247        assert_eq!(Id::new(2, 0).prev(), Some(Id::new(1, u64::MAX)));
2248        assert_eq!(Id::MIN.prev(), None);
2249    }
2250
2251    /// Nothing about the answer may depend on where the node boundaries fell.
2252    #[test]
2253    fn the_node_size_changes_nothing_but_the_node_count() {
2254        let mut want = None;
2255        for entries in [1usize, 2, 7, 100, 4096] {
2256            let mut s = Stream::new();
2257            let limits = Limits {
2258                max_node_bytes: NODE_BYTES,
2259                max_node_entries: entries,
2260            };
2261            for ms in 1..=400u64 {
2262                let value = format!("v{ms}");
2263                s.append(Id::new(ms, 0), &[(b"n", value.as_bytes())], limits)
2264                    .expect("an append");
2265            }
2266            for ms in (1..=400u64).step_by(7) {
2267                s.delete(Id::new(ms, 0));
2268            }
2269            let got = dump(&s);
2270            match &want {
2271                None => want = Some(got),
2272                Some(want) => assert_eq!(&got, want, "at {entries} entries a node"),
2273            }
2274        }
2275    }
2276
2277    /// A byte limit small enough that every entry is its own node.
2278    #[test]
2279    fn a_tiny_byte_limit_still_works() {
2280        let limits = Limits {
2281            max_node_bytes: 1,
2282            max_node_entries: NODE_ENTRIES,
2283        };
2284        let mut s = Stream::new();
2285        for ms in 1..=20u64 {
2286            s.append(Id::new(ms, 0), &[(b"n", b"x")], limits)
2287                .expect("an append");
2288        }
2289        assert_eq!(s.nodes(), 20);
2290        assert_eq!(dump(&s).len(), 20);
2291    }
2292
2293    /// A `DUMP` of a stream taken from a real Redis, as hexadecimal.
2294    ///
2295    /// Captured from Redis 8.10.1 in the official Docker image on 2026-09-02,
2296    /// from a server that had been given exactly this:
2297    ///
2298    /// ```text
2299    /// XADD s 1-1 temperature_celsius 21 relative_humidity 55
2300    /// XADD s 1-2 temperature_celsius 22 relative_humidity 56
2301    /// XADD s 2-1 temperature_celsius 23 relative_humidity 57
2302    /// XADD s 3-1 sensor a
2303    /// XDEL s 1-2
2304    /// ```
2305    ///
2306    /// Three entries share the master fields and one brings its own, one entry
2307    /// is deleted rather than taken out, and the ids climb in both halves, so
2308    /// between them the four cover every branch the encoder has.
2309    const REDIS_DUMP: &str = "1b0110000000000000000100000000000000014070\
2310        700000001f000301010102019374656d70657261747572655f63656c736975731491\
2311        72656c61746976655f68756d69646974791200010201000100011501370105010301\
2312        0001010116013801050102010101000117013901050100010201000101018673656e\
2313        736f72078161020601ff030301010101020400406440640000000f00239a5c2c7208\
2314        ea0a";
2315
2316    fn unhex(s: &str) -> Vec<u8> {
2317        let digits: Vec<u8> = s.bytes().filter(|b| !b.is_ascii_whitespace()).collect();
2318        digits
2319            .chunks(2)
2320            .map(|pair| {
2321                let of = |b: u8| (b as char).to_digit(16).expect("a hex digit") as u8;
2322                of(pair[0]) << 4 | of(pair[1])
2323            })
2324            .collect()
2325    }
2326
2327    /// One RDB length, and how many bytes it took.
2328    ///
2329    /// Only the two plain forms, six bits in one byte and fourteen in two,
2330    /// because those are the only ones this fixture uses and a test that
2331    /// quietly accepted more would be claiming to check something it does not.
2332    fn rdb_len(bytes: &[u8], at: usize) -> (usize, usize) {
2333        match bytes[at] >> 6 {
2334            0 => (usize::from(bytes[at] & 0x3F), 1),
2335            1 => (
2336                usize::from(bytes[at] & 0x3F) << 8 | usize::from(bytes[at + 1]),
2337                2,
2338            ),
2339            other => panic!("the fixture used length form {other}"),
2340        }
2341    }
2342
2343    /// The one node out of the captured dump, and the counters after it.
2344    fn redis_node() -> (Id, Vec<u8>, Vec<u8>) {
2345        let dump = unhex(REDIS_DUMP);
2346        assert_eq!(dump[0], 0x1B, "RDB_TYPE_STREAM_LISTPACKS_3");
2347        let (nodes, n) = rdb_len(&dump, 1);
2348        assert_eq!(nodes, 1, "the fixture is one node");
2349        let mut at = 1 + n;
2350        let (key, n) = rdb_len(&dump, at);
2351        assert_eq!(key, 16, "a node key is an id in sixteen bytes");
2352        at += n;
2353        let master = Id::from_bytes(dump[at..at + 16].try_into().expect("sixteen bytes"));
2354        at += 16;
2355        let (len, n) = rdb_len(&dump, at);
2356        at += n;
2357        let lp = dump[at..at + len].to_vec();
2358        // The last ten bytes are the RDB version and the checksum, which belong
2359        // to DUMP rather than to the stream.
2360        let rest = dump[at + len..dump.len() - 10].to_vec();
2361        (master, lp, rest)
2362    }
2363
2364    #[test]
2365    fn a_node_is_written_the_way_redis_writes_one() {
2366        let mut s = Stream::new();
2367        add(
2368            &mut s,
2369            1,
2370            1,
2371            &[("temperature_celsius", "21"), ("relative_humidity", "55")],
2372        );
2373        add(
2374            &mut s,
2375            1,
2376            2,
2377            &[("temperature_celsius", "22"), ("relative_humidity", "56")],
2378        );
2379        add(
2380            &mut s,
2381            2,
2382            1,
2383            &[("temperature_celsius", "23"), ("relative_humidity", "57")],
2384        );
2385        add(&mut s, 3, 1, &[("sensor", "a")]);
2386        assert!(s.delete(Id::new(1, 2)));
2387
2388        let (master, lp, _) = redis_node();
2389        assert_eq!(s.nodes(), 1, "all four fit in one node");
2390        assert_eq!(s.nodes[0].master, master);
2391        assert_eq!(s.nodes[0].lp.as_bytes(), &lp[..]);
2392    }
2393
2394    #[test]
2395    fn a_node_redis_wrote_reads_back() {
2396        let (master, lp, rest) = redis_node();
2397        let lp = Listpack::from_bytes(&lp).expect("a listpack Redis wrote");
2398        let s = Stream {
2399            nodes: VecDeque::from(vec![Node { master, lp }]),
2400            length: u64::from(rest[0]),
2401            last: Id::new(u64::from(rest[1]), u64::from(rest[2])),
2402            max_deleted: Id::new(u64::from(rest[5]), u64::from(rest[6])),
2403            added: u64::from(rest[7]),
2404            groups: Vec::new(),
2405        };
2406
2407        assert_eq!(s.len(), 3);
2408        assert_eq!(s.last_id(), Id::new(3, 1));
2409        assert_eq!(s.max_deleted_id(), Id::new(1, 2));
2410        assert_eq!(s.added(), 4);
2411        assert_eq!(s.first_id(), Some(Id::new(1, 1)));
2412        assert_eq!(
2413            dump(&s),
2414            vec![
2415                (
2416                    Id::new(1, 1),
2417                    vec![
2418                        (b"temperature_celsius".to_vec(), b"21".to_vec()),
2419                        (b"relative_humidity".to_vec(), b"55".to_vec())
2420                    ]
2421                ),
2422                (
2423                    Id::new(2, 1),
2424                    vec![
2425                        (b"temperature_celsius".to_vec(), b"23".to_vec()),
2426                        (b"relative_humidity".to_vec(), b"57".to_vec())
2427                    ]
2428                ),
2429                (Id::new(3, 1), vec![(b"sensor".to_vec(), b"a".to_vec())]),
2430            ]
2431        );
2432    }
2433
2434    /// A stream of `n` entries at 1-0 up to n-0, one field each.
2435    fn logged(n: u64) -> Stream {
2436        let mut s = Stream::new();
2437        for ms in 1..=n {
2438            add(&mut s, ms, 0, &[("job", "x")]);
2439        }
2440        s
2441    }
2442
2443    /// What a group read hands back, with the fields flattened.
2444    fn read(s: &mut Stream, group: &str, who: &str, count: Option<usize>, now: u64) -> Vec<Id> {
2445        let mut out = Vec::new();
2446        s.read_group(
2447            group.as_bytes(),
2448            who.as_bytes(),
2449            count,
2450            false,
2451            now,
2452            |id, _| {
2453                out.push(id);
2454                true
2455            },
2456        )
2457        .expect("the group");
2458        out
2459    }
2460
2461    #[test]
2462    fn a_group_is_made_once() {
2463        let mut s = logged(3);
2464        assert!(s.create_group(b"workers", Id::MIN, Some(0)));
2465        assert!(!s.create_group(b"workers", Id::MIN, Some(0)));
2466        assert!(s.group(b"workers").is_some());
2467        assert!(s.destroy_group(b"workers"));
2468        assert!(!s.destroy_group(b"workers"));
2469        assert!(s.group(b"workers").is_none());
2470    }
2471
2472    #[test]
2473    fn a_group_read_hands_out_what_comes_after_the_bookmark() {
2474        let mut s = logged(5);
2475        s.create_group(b"workers", Id::MIN, Some(0));
2476
2477        assert_eq!(
2478            read(&mut s, "workers", "alice", Some(2), 100),
2479            vec![Id::new(1, 0), Id::new(2, 0)]
2480        );
2481        // The bookmark moved, so bob gets what alice did not.
2482        assert_eq!(
2483            read(&mut s, "workers", "bob", Some(2), 100),
2484            vec![Id::new(3, 0), Id::new(4, 0)]
2485        );
2486        assert_eq!(
2487            read(&mut s, "workers", "alice", None, 100),
2488            vec![Id::new(5, 0)]
2489        );
2490        assert_eq!(read(&mut s, "workers", "alice", None, 100), vec![]);
2491    }
2492
2493    #[test]
2494    fn a_group_read_fills_the_pending_list() {
2495        let mut s = logged(3);
2496        s.create_group(b"workers", Id::MIN, Some(0));
2497        read(&mut s, "workers", "alice", None, 500);
2498
2499        let g = s.group(b"workers").expect("the group");
2500        assert_eq!(g.pending_len(), 3);
2501        assert_eq!(g.last_id(), Id::new(3, 0));
2502        assert_eq!(g.entries_read(), Some(3));
2503        assert_eq!(s.lag(g), Some(0));
2504        let c = g.consumer_named(b"alice").expect("alice");
2505        assert_eq!(c.len(), 3);
2506        assert_eq!(c.active(), Some(500));
2507    }
2508
2509    #[test]
2510    fn a_read_that_finds_nothing_is_seen_but_not_active() {
2511        let mut s = logged(1);
2512        s.create_group(b"workers", Id::MIN, Some(0));
2513        read(&mut s, "workers", "alice", None, 100);
2514        read(&mut s, "workers", "alice", None, 900);
2515
2516        let c = s
2517            .group(b"workers")
2518            .expect("the group")
2519            .consumer_named(b"alice")
2520            .expect("alice");
2521        assert_eq!((c.seen(), c.active()), (900, Some(100)));
2522    }
2523
2524    #[test]
2525    fn a_group_starting_at_the_end_reads_only_what_comes_next() {
2526        let mut s = logged(3);
2527        s.create_group(b"workers", s.last_id(), Some(s.added()));
2528        assert_eq!(read(&mut s, "workers", "alice", None, 1), vec![]);
2529        add(&mut s, 4, 0, &[("job", "x")]);
2530        assert_eq!(
2531            read(&mut s, "workers", "alice", None, 1),
2532            vec![Id::new(4, 0)]
2533        );
2534    }
2535
2536    #[test]
2537    fn reading_a_group_that_is_not_there_says_so() {
2538        let mut s = logged(1);
2539        assert!(
2540            s.read_group(b"nope", b"alice", None, false, 1, |_, _| true)
2541                .is_none()
2542        );
2543    }
2544
2545    #[test]
2546    fn a_consumer_can_re_read_what_it_is_holding() {
2547        let mut s = logged(4);
2548        s.create_group(b"workers", Id::MIN, Some(0));
2549        read(&mut s, "workers", "alice", Some(2), 1);
2550        read(&mut s, "workers", "bob", Some(2), 1);
2551
2552        let mut out = Vec::new();
2553        s.read_group_pending(b"workers", b"alice", Id::MIN, None, 2, |id, fields| {
2554            out.push((id, fields.map(|f| f.len())));
2555            true
2556        })
2557        .expect("the group");
2558        assert_eq!(
2559            out,
2560            vec![(Id::new(1, 0), Some(1)), (Id::new(2, 0), Some(1))]
2561        );
2562
2563        // From an ID, which is how a consumer pages through its own backlog.
2564        let mut after = Vec::new();
2565        s.read_group_pending(b"workers", b"alice", Id::new(1, 0), None, 2, |id, _| {
2566            after.push(id);
2567            true
2568        });
2569        assert_eq!(after, vec![Id::new(2, 0)]);
2570    }
2571
2572    /// A history read counts as a delivery, which is Redis's behaviour and not
2573    /// the one I would have guessed.
2574    ///
2575    /// Checked against Redis 8.10.1: an entry left idle for 2006 milliseconds
2576    /// and then read back through `XREADGROUP ... 0` came out idle for 2 with
2577    /// its delivery count up by one. The count is how many times a consumer has
2578    /// been told to do the work, and a consumer re-reading its backlog after a
2579    /// restart has been told again.
2580    #[test]
2581    fn re_reading_counts_as_being_handed_it_again() {
2582        let mut s = logged(2);
2583        s.create_group(b"workers", Id::MIN, Some(0));
2584        read(&mut s, "workers", "alice", None, 100);
2585        s.read_group_pending(
2586            b"workers",
2587            b"alice",
2588            Id::MIN,
2589            None,
2590            700,
2591            |_: Id, _: Option<Fields<'_>>| true,
2592        );
2593
2594        let g = s.group(b"workers").expect("the group");
2595        let nack = g.nack(Id::new(1, 0)).expect("a nack");
2596        assert_eq!((nack.count(), nack.time()), (2, 700));
2597        // The bookmark does not move, because nothing new was handed out.
2598        assert_eq!(g.last_id(), Id::new(2, 0));
2599        assert_eq!(g.pending_len(), 2);
2600    }
2601
2602    /// The lag and the read counter, which are two answers and not one.
2603    ///
2604    /// Every line here was run against Redis 8.10.1 first and the numbers are
2605    /// its numbers. The one worth pointing at is the last pair: the counter goes
2606    /// away and the group's bookmark keeps moving, because a delete ahead of a
2607    /// group makes the counter unknowable rather than merely stale.
2608    #[test]
2609    fn a_hole_in_front_of_a_group_takes_its_lag() {
2610        let mut s = logged(5);
2611        s.create_group(b"workers", Id::MIN, Some(0));
2612        read(&mut s, "workers", "alice", Some(3), 1);
2613        assert_eq!(counters(&s), (Some(3), Some(2)));
2614
2615        // Deleting something the group has already read leaves both alone,
2616        // since the hole is behind the bookmark and the entries in front of it
2617        // are all still there.
2618        assert!(s.delete(Id::new(1, 0)));
2619        assert_eq!(counters(&s), (Some(3), Some(2)));
2620
2621        // Deleting something it has not reached takes the lag, and leaves the
2622        // counter exactly where it was. Redis does not clear it here.
2623        assert!(s.delete(Id::new(5, 0)));
2624        assert_eq!(counters(&s), (Some(3), None));
2625
2626        // Reading what is left moves the bookmark to 4-0, which is not the last
2627        // ID the stream ever handed out, so there is still no way to say how far
2628        // along that is and the counter goes too.
2629        read(&mut s, "workers", "alice", None, 1);
2630        assert_eq!(s.group(b"workers").expect("g").last_id(), Id::new(4, 0));
2631        assert_eq!(counters(&s), (None, None));
2632    }
2633
2634    /// A trim is not a hole, so it costs a group nothing, and once it has cut
2635    /// past the bookmark the lag becomes what is left rather than nothing.
2636    ///
2637    /// Checked against Redis 8.10.1 at twenty entries, where the three lines
2638    /// below read 5, 5 and 2.
2639    #[test]
2640    fn trimming_past_a_group_leaves_it_the_length() {
2641        let mut s = logged(500);
2642        s.create_group(b"workers", Id::MIN, Some(0));
2643        read(&mut s, "workers", "alice", Some(400), 1);
2644        assert_eq!(counters(&s), (Some(400), Some(100)));
2645
2646        // Whole nodes off the front, all of them well behind the bookmark.
2647        assert_eq!(s.trim_maxlen(200, true, None), 300);
2648        assert_eq!(counters(&s), (Some(400), Some(100)));
2649
2650        // And now past it. The bookmark is below every entry left, so the lag is
2651        // the length: those are exactly the entries the group has still to read.
2652        assert_eq!(s.trim_maxlen(10, true, None), 190);
2653        assert_eq!(counters(&s), (Some(400), Some(10)));
2654    }
2655
2656    /// The counter and the lag of the one group, which every lag test reads.
2657    fn counters(s: &Stream) -> (Option<u64>, Option<u64>) {
2658        let g = s.group(b"workers").expect("the group");
2659        (g.entries_read(), s.lag(g))
2660    }
2661
2662    #[test]
2663    fn an_entry_that_went_away_still_comes_back_as_a_hole() {
2664        let mut s = logged(3);
2665        s.create_group(b"workers", Id::MIN, Some(0));
2666        read(&mut s, "workers", "alice", None, 1);
2667        assert!(s.delete(Id::new(2, 0)));
2668
2669        let mut out = Vec::new();
2670        s.read_group_pending(b"workers", b"alice", Id::MIN, None, 2, |id, fields| {
2671            out.push((id, fields.is_some()));
2672            true
2673        });
2674        assert_eq!(
2675            out,
2676            vec![
2677                (Id::new(1, 0), true),
2678                (Id::new(2, 0), false),
2679                (Id::new(3, 0), true)
2680            ]
2681        );
2682    }
2683
2684    #[test]
2685    fn acking_clears_the_pending_list() {
2686        let mut s = logged(3);
2687        s.create_group(b"workers", Id::MIN, Some(0));
2688        read(&mut s, "workers", "alice", None, 1);
2689        let g = s.group_mut(b"workers").expect("the group");
2690        assert!(g.ack(Id::new(2, 0)));
2691        assert_eq!(g.pending_len(), 2);
2692    }
2693
2694    #[test]
2695    fn a_claim_moves_work_off_a_consumer_that_stopped() {
2696        let mut s = logged(2);
2697        s.create_group(b"workers", Id::MIN, Some(0));
2698        read(&mut s, "workers", "alice", None, 100);
2699
2700        let mut gone = Vec::new();
2701        let took = s
2702            .claim(
2703                b"workers",
2704                b"bob",
2705                &[Id::new(1, 0), Id::new(2, 0)],
2706                500,
2707                5_000,
2708                None,
2709                true,
2710                false,
2711                5_000,
2712                &mut gone,
2713            )
2714            .expect("the group");
2715        assert_eq!(took, vec![Id::new(1, 0), Id::new(2, 0)]);
2716        assert!(gone.is_empty());
2717
2718        let g = s.group(b"workers").expect("the group");
2719        assert!(g.consumer_named(b"alice").expect("alice").is_empty());
2720        assert_eq!(g.consumer_named(b"bob").expect("bob").len(), 2);
2721        assert_eq!(g.nack(Id::new(1, 0)).expect("a nack").count(), 2);
2722    }
2723
2724    #[test]
2725    fn a_claim_leaves_work_that_is_not_idle_enough_alone() {
2726        let mut s = logged(1);
2727        s.create_group(b"workers", Id::MIN, Some(0));
2728        read(&mut s, "workers", "alice", None, 100);
2729
2730        let mut gone = Vec::new();
2731        let took = s
2732            .claim(
2733                b"workers",
2734                b"bob",
2735                &[Id::new(1, 0)],
2736                5_000,
2737                200,
2738                None,
2739                true,
2740                false,
2741                200,
2742                &mut gone,
2743            )
2744            .expect("the group");
2745        assert!(took.is_empty());
2746        assert_eq!(
2747            s.group(b"workers")
2748                .expect("the group")
2749                .consumer_named(b"alice")
2750                .expect("alice")
2751                .len(),
2752            1
2753        );
2754    }
2755
2756    #[test]
2757    fn claiming_an_entry_that_went_away_drops_it_instead() {
2758        let mut s = logged(2);
2759        s.create_group(b"workers", Id::MIN, Some(0));
2760        read(&mut s, "workers", "alice", None, 100);
2761        assert!(s.delete(Id::new(1, 0)));
2762
2763        let mut gone = Vec::new();
2764        let took = s
2765            .claim(
2766                b"workers",
2767                b"bob",
2768                &[Id::new(1, 0), Id::new(2, 0)],
2769                0,
2770                5_000,
2771                None,
2772                true,
2773                false,
2774                5_000,
2775                &mut gone,
2776            )
2777            .expect("the group");
2778        assert_eq!(took, vec![Id::new(2, 0)]);
2779        assert_eq!(gone, vec![Id::new(1, 0)]);
2780        assert_eq!(s.group(b"workers").expect("the group").pending_len(), 1);
2781    }
2782
2783    #[test]
2784    fn force_only_works_on_an_entry_that_is_really_there() {
2785        let mut s = logged(2);
2786        s.create_group(b"workers", s.last_id(), Some(2));
2787
2788        let mut gone = Vec::new();
2789        let took = s
2790            .claim(
2791                b"workers",
2792                b"bob",
2793                &[Id::new(1, 0), Id::new(99, 0)],
2794                0,
2795                100,
2796                None,
2797                true,
2798                true,
2799                100,
2800                &mut gone,
2801            )
2802            .expect("the group");
2803        assert_eq!(took, vec![Id::new(1, 0)], "99-0 is not in the stream");
2804        assert_eq!(s.group(b"workers").expect("the group").pending_len(), 1);
2805    }
2806
2807    #[test]
2808    fn autoclaim_sweeps_the_stale_ones_and_says_where_it_stopped() {
2809        let mut s = logged(6);
2810        s.create_group(b"workers", Id::MIN, Some(0));
2811        read(&mut s, "workers", "alice", Some(3), 100);
2812        read(&mut s, "workers", "alice", None, 900);
2813
2814        let mut gone = Vec::new();
2815        let (cursor, took) = s
2816            .autoclaim(
2817                b"workers",
2818                b"bob",
2819                Id::MIN,
2820                500,
2821                100,
2822                true,
2823                1_000,
2824                &mut gone,
2825            )
2826            .expect("the group");
2827        assert_eq!(cursor, None, "the sweep reached the end");
2828        assert_eq!(took, vec![Id::new(1, 0), Id::new(2, 0), Id::new(3, 0)]);
2829        assert_eq!(
2830            s.group(b"workers")
2831                .expect("the group")
2832                .consumer_named(b"bob")
2833                .expect("bob")
2834                .len(),
2835            3
2836        );
2837    }
2838
2839    #[test]
2840    fn autoclaim_hands_back_a_cursor_when_it_hits_the_count() {
2841        let mut s = logged(10);
2842        s.create_group(b"workers", Id::MIN, Some(0));
2843        read(&mut s, "workers", "alice", None, 100);
2844
2845        let mut gone = Vec::new();
2846        let (cursor, took) = s
2847            .autoclaim(b"workers", b"bob", Id::MIN, 0, 4, true, 1_000, &mut gone)
2848            .expect("the group");
2849        assert_eq!(took.len(), 4);
2850        assert_eq!(cursor, Some(Id::new(5, 0)));
2851
2852        // And carrying on from the cursor takes the rest.
2853        let (cursor, took) = s
2854            .autoclaim(
2855                b"workers",
2856                b"bob",
2857                cursor.expect("a cursor"),
2858                0,
2859                100,
2860                true,
2861                1_000,
2862                &mut gone,
2863            )
2864            .expect("the group");
2865        assert_eq!(took.len(), 6);
2866        assert_eq!(cursor, None);
2867    }
2868
2869    #[test]
2870    fn a_group_survives_the_stream_being_trimmed_under_it() {
2871        let mut s = logged(10);
2872        s.create_group(b"workers", Id::MIN, Some(0));
2873        read(&mut s, "workers", "alice", Some(5), 100);
2874        // Trimming takes entries alice is still holding.
2875        assert_eq!(s.trim_maxlen(3, true, None), 7);
2876
2877        assert_eq!(s.group(b"workers").expect("the group").pending_len(), 5);
2878        let mut gone = Vec::new();
2879        let took = s
2880            .claim(
2881                b"workers",
2882                b"bob",
2883                &(1..=5).map(|ms| Id::new(ms, 0)).collect::<Vec<_>>(),
2884                0,
2885                1_000,
2886                None,
2887                true,
2888                false,
2889                1_000,
2890                &mut gone,
2891            )
2892            .expect("the group");
2893        assert!(took.is_empty(), "none of them are there any more");
2894        assert_eq!(gone.len(), 5);
2895        assert_eq!(s.group(b"workers").expect("the group").pending_len(), 0);
2896    }
2897
2898    /// Out and back, checking everything a client can see on the way.
2899    fn round_trip(s: &Stream) -> Stream {
2900        let mut bytes = Vec::new();
2901        s.freeze(&mut bytes);
2902        let back = Stream::thaw(&bytes).expect("our own bytes");
2903        assert_eq!(dump(&back), dump(s), "the entries");
2904        assert_eq!(back.len(), s.len(), "the length");
2905        assert_eq!(back.added(), s.added(), "the count added");
2906        assert_eq!(back.last_id(), s.last_id(), "the last ID");
2907        assert_eq!(back.max_deleted_id(), s.max_deleted_id(), "the max deleted");
2908        assert_eq!(back.nodes(), s.nodes(), "the node count");
2909        assert_eq!(back, *s, "the whole thing");
2910        back
2911    }
2912
2913    #[test]
2914    fn a_frozen_stream_comes_back_with_every_entry_it_held() {
2915        let mut s = Stream::new();
2916        for ms in 1..=500u64 {
2917            add(&mut s, ms, 0, &[("job", "x"), ("n", "1")]);
2918        }
2919        add(&mut s, 500, 1, &[("job", "y")]);
2920        assert!(s.nodes() > 1, "more than one node, so the walk is tested");
2921        round_trip(&s);
2922    }
2923
2924    #[test]
2925    fn a_frozen_stream_keeps_the_holes_and_the_counters() {
2926        let mut s = logged(200);
2927        for ms in [3u64, 4, 5, 100, 199] {
2928            assert!(s.delete(Id::new(ms, 0)));
2929        }
2930        s.trim_minid(Id::new(20, 0), true, None);
2931        let back = round_trip(&s);
2932        assert_eq!(back.first_id(), Some(Id::new(20, 0)));
2933        assert!(!back.contains(Id::new(100, 0)), "a hole is still a hole");
2934        assert_eq!(back.max_deleted_id(), Id::new(199, 0));
2935    }
2936
2937    #[test]
2938    fn a_frozen_stream_keeps_its_groups_and_who_is_holding_what() {
2939        let mut s = logged(20);
2940        s.create_group(b"workers", Id::MIN, Some(0));
2941        s.create_group(b"audit", Id::new(5, 0), None);
2942        read(&mut s, "workers", "alice", Some(6), 1_000);
2943        read(&mut s, "workers", "bob", Some(4), 2_000);
2944        // One handed back to the group with nobody holding it, so the NACK count
2945        // has something to come back as.
2946        assert_eq!(
2947            s.nack(b"workers", Id::new(2, 0), Retry::Keep, true),
2948            Some(true)
2949        );
2950        // And one consumer deleted, so a slot in the middle is empty and the
2951        // slot numbers behind it have to survive.
2952        s.group_mut(b"workers")
2953            .expect("the group")
2954            .create_consumer(b"carol", 3_000);
2955        read(&mut s, "workers", "dave", Some(2), 4_000);
2956        s.group_mut(b"workers")
2957            .expect("the group")
2958            .delete_consumer(b"carol");
2959
2960        let back = round_trip(&s);
2961        let g = back.group(b"workers").expect("the group");
2962        assert_eq!(g.pending_len(), 12);
2963        assert_eq!(g.nacked_len(), 1);
2964        assert_eq!(g.entries_read(), Some(12));
2965        // Six, less the one handed back to the group.
2966        assert_eq!(g.consumer_named(b"alice").expect("alice").len(), 5);
2967        assert_eq!(g.consumer_named(b"bob").expect("bob").len(), 4);
2968        assert_eq!(g.consumer_named(b"dave").expect("dave").len(), 2);
2969        assert_eq!(g.consumer_named(b"carol"), None);
2970        // Dave came after carol, so his slot is the fourth one and reading the
2971        // empties back as empties is what keeps his entries his.
2972        assert_eq!(g.slot(b"dave"), Some(3));
2973        assert_eq!(g.nack(Id::new(1, 0)).expect("a nack").owner(), Some(0));
2974        assert_eq!(g.nack(Id::new(2, 0)).expect("a nack").owner(), None);
2975        assert_eq!(
2976            back.group(b"audit").expect("audit").last_id(),
2977            Id::new(5, 0)
2978        );
2979        assert_eq!(back.group(b"audit").expect("audit").entries_read(), None);
2980    }
2981
2982    #[test]
2983    fn a_stream_that_came_back_still_takes_entries_and_reads_them() {
2984        let mut s = logged(10);
2985        s.create_group(b"workers", Id::MIN, Some(0));
2986        read(&mut s, "workers", "alice", Some(4), 1_000);
2987
2988        let mut back = round_trip(&s);
2989        add(&mut back, 11, 0, &[("job", "new")]);
2990        assert_eq!(back.len(), 11);
2991        assert_eq!(
2992            read(&mut back, "workers", "alice", Some(3), 2_000),
2993            vec![Id::new(5, 0), Id::new(6, 0), Id::new(7, 0)]
2994        );
2995        assert!(
2996            back.group_mut(b"workers")
2997                .expect("the group")
2998                .ack(Id::new(1, 0))
2999        );
3000        assert_eq!(back.group(b"workers").expect("the group").pending_len(), 6);
3001    }
3002
3003    #[test]
3004    fn an_empty_stream_that_still_exists_comes_back() {
3005        let mut s = logged(3);
3006        for ms in 1..=3u64 {
3007            assert!(s.delete(Id::new(ms, 0)));
3008        }
3009        assert_eq!(s.nodes(), 0, "the last node went with the last entry");
3010        let back = round_trip(&s);
3011        assert!(back.is_empty());
3012        // The whole reason an empty stream is kept: a new entry still has to
3013        // beat the ID of one that is gone.
3014        assert_eq!(back.last_id(), Id::new(3, 0));
3015        round_trip(&Stream::new());
3016    }
3017
3018    #[test]
3019    fn a_frozen_stream_that_arrives_damaged_is_an_error_and_not_a_panic() {
3020        let mut s = logged(8);
3021        s.create_group(b"workers", Id::MIN, Some(0));
3022        read(&mut s, "workers", "alice", Some(3), 1_000);
3023        let mut bytes = Vec::new();
3024        s.freeze(&mut bytes);
3025
3026        for cut in 0..bytes.len() {
3027            assert!(Stream::thaw(&bytes[..cut]).is_err(), "cut at {cut}");
3028        }
3029        // Every bit of the header and the front of the first node, which is
3030        // where the counts and the lengths that a reader trusts all live.
3031        for at in 0..bytes.len().min(40) {
3032            for bit in 0..8 {
3033                let mut bad = bytes.clone();
3034                bad[at] ^= 1 << bit;
3035                // It either parses into some other stream or it does not. Either
3036                // way it comes back rather than going through a length that was
3037                // never checked.
3038                let _ = Stream::thaw(&bad);
3039            }
3040        }
3041        assert_eq!(Stream::thaw(&[]), Err(Broken::Short));
3042        assert_eq!(Stream::thaw(&[9]), Err(Broken::Form));
3043    }
3044
3045    #[test]
3046    fn nothing_at_all() {
3047        let mut s = Stream::new();
3048        assert!(s.is_empty());
3049        assert_eq!(s.len(), 0);
3050        assert_eq!(s.first_id(), None);
3051        assert_eq!(s.last_id(), Id::MIN);
3052        assert_eq!(s.trim_maxlen(0, true, None), 0);
3053        assert_eq!(s.trim_minid(Id::MAX, true, None), 0);
3054        assert_eq!(dump(&s), vec![]);
3055    }
3056}