Skip to main content

yo_kv/
streams.rs

1//! The stream commands.
2//!
3//! One method per Redis command on [`Keyspace`], the same arrangement the list
4//! and set commands use and for the same reason: a key belongs to the database
5//! and not to a type, so `XADD` against a string has to be able to see that it
6//! is a string. The log itself is [`crate::stream`] and the groups are
7//! [`crate::stream::groups`]. This file is what the wire and the embedded API
8//! both call.
9//!
10//! # An empty stream is still a stream
11//!
12//! Every other collection here disappears when its last element goes, so
13//! `EXISTS` answers zero after the last `LPOP`. A stream does not. `XDEL` of
14//! every entry leaves the key, and so does a `MAXLEN 0` trim, because the
15//! stream is still carrying the last ID it handed out and the groups reading
16//! it, and throwing those away because the entries have aged out would hand the
17//! same IDs out twice.
18//!
19//! That is why the methods here do not have the create on write and drop on
20//! empty pair the other four types have. There is a create, [`Keyspace::xadd`]
21//! and the `MKSTREAM` half of [`Keyspace::xgroup_create`], and there is no
22//! drop.
23//!
24//! # Reading is one method and not eight
25//!
26//! `XINFO` reads a dozen fields off a stream and none of them are decisions.
27//! Rather than a method here per field, [`Keyspace::stream`] hands the wire the
28//! stream and it reads what it needs, which is the same borrow it would have
29//! got and a great deal less to keep in step. Everything that changes the
30//! stream, or that has to say something about a key that is missing or is the
31//! wrong type, is a method.
32
33use yo_common::{Code, Error, Result};
34
35use crate::keyspace::Keyspace;
36use crate::stream::groups::Filter;
37use crate::stream::{Fate, Fields, Group, Id, Refs, Refused, Retry, Stream};
38use crate::value::{self, Kind};
39
40/// What every stream command says about an ID it cannot read.
41pub const BAD_ID: &str = "Invalid stream ID specified as stream command argument";
42
43/// What `XADD` says about an ID that is not above the last one.
44pub const NOT_GREATER: &str =
45    "The ID specified in XADD is equal or smaller than the target stream top item";
46
47/// What it says about `0-0`, which no entry can have because nothing sorts below it.
48pub const ZERO_ID: &str = "The ID specified in XADD must be greater than 0-0";
49
50/// What it says when the stream is at the last ID there is.
51pub const EXHAUSTED: &str =
52    "The stream has exhausted the last possible ID, unable to add more items";
53
54/// What `XGROUP` says about a key that is not there.
55///
56/// Redis's wording, run on sentence and all, because it goes on the wire
57/// verbatim and a client's test suite compares it.
58pub const NO_KEY_FOR_GROUP: &str = "The XGROUP subcommand requires the key to exist. Note that for CREATE you may want to use the MKSTREAM option to create an empty stream automatically.";
59
60/// What `XGROUP CREATE` says about a group that is already there.
61///
62/// This one goes out under a `BUSYGROUP` prefix rather than `ERR`, which the
63/// wire layer writes where it decides it, the same way it writes `NOPROTO` and
64/// `WRONGPASS`.
65pub const GROUP_EXISTS: &str = "Consumer Group name already exists";
66
67/// What `XSETID` says about an ID below an entry that is still there.
68pub const SETID_TOO_SMALL: &str =
69    "The ID specified in XSETID is smaller than the target stream top item";
70
71/// And what it says about an ID below the `MAXDELETEDID` it was handed.
72///
73/// A separate sentence because it is a separate mistake. The one above is about
74/// an entry the stream still holds and this is about one it says it deleted, and
75/// a stream whose last ID sat below its own high water mark for deletions would
76/// hand that ID out again.
77pub const SETID_BELOW_MAX_DELETED: &str =
78    "The ID specified in XSETID is smaller than the provided max_deleted_entry_id";
79
80/// What a command that needs the key says when it is not there.
81pub const NO_SUCH_KEY: &str = "no such key";
82
83/// What `XGROUP` and `XINFO CONSUMERS` say about a group that is not there.
84///
85/// A function rather than a constant because Redis puts the group and the key
86/// in it, and a client watching for a particular group in its logs is reading
87/// exactly that. It goes out under a `NOGROUP` prefix.
88///
89/// There are three of these and they are not interchangeable. This is the one
90/// the commands that only ever name one key use. Every wording here was read
91/// off Redis 8.10.1, since the difference between them is not something the
92/// documentation mentions and a client library matching on the text would break
93/// on a paraphrase.
94#[must_use]
95pub fn no_group(group: &[u8], key: &[u8]) -> String {
96    format!(
97        "No such consumer group '{}' for key name '{}'",
98        String::from_utf8_lossy(group),
99        String::from_utf8_lossy(key)
100    )
101}
102
103/// What `XPENDING`, `XCLAIM` and `XAUTOCLAIM` say instead.
104///
105/// The key comes first here and the sentence allows for either half being the
106/// missing one, because these commands cannot tell a stream with no such group
107/// from a key that is not a stream at all without looking twice.
108#[must_use]
109pub fn no_key_or_group(key: &[u8], group: &[u8]) -> String {
110    format!(
111        "No such key '{}' or consumer group '{}'",
112        String::from_utf8_lossy(key),
113        String::from_utf8_lossy(group)
114    )
115}
116
117/// And what `XREADGROUP` says, which is the one above with its own tail.
118///
119/// The tail is there because `XREADGROUP` is the command people send by
120/// accident against a stream they never made a group on, so Redis spells out
121/// which option is at fault.
122#[must_use]
123pub fn no_group_for_read(key: &[u8], group: &[u8]) -> String {
124    format!(
125        "No such key '{}' or consumer group '{}' in XREADGROUP with GROUP option",
126        String::from_utf8_lossy(key),
127        String::from_utf8_lossy(group)
128    )
129}
130
131/// What `XADD` was told to use for the ID.
132#[derive(Debug, Clone, Copy, PartialEq, Eq)]
133pub enum Add {
134    /// `*`, which is the clock, or the last ID with one added if the clock has
135    /// not moved.
136    Auto,
137    /// `5-*`, which is that millisecond and the next free sequence inside it.
138    Seq(u64),
139    /// `5-3`, which is exactly that and fails if it is not above the last one.
140    At(Id),
141}
142
143/// What a trim was told to cut down to.
144///
145/// `exact` is Redis's `=` against `~`. Without it only whole nodes go, so the
146/// stream is left at the threshold or a little over and no node is ever
147/// rewritten, which is the form to use and is why `~` exists.
148///
149/// `limit` only ever arrives with `~`, because Redis refuses the two together
150/// with `=` and the wire layer refuses it here for the same reason: the limit
151/// is a brake on how long one command runs, and an exact trim that stopped
152/// early would not have done what it was asked.
153#[derive(Debug, Clone, Copy, PartialEq, Eq)]
154pub enum Trim {
155    /// No trim, which is `XADD` without a `MAXLEN` or a `MINID`.
156    None,
157    /// `MAXLEN n`, which keeps the newest `n` entries.
158    MaxLen {
159        /// How many to keep.
160        len: u64,
161        /// Redis's `=` rather than `~`.
162        exact: bool,
163        /// `LIMIT`, which stops the trim after that many entries have gone.
164        limit: Option<u64>,
165    },
166    /// `MINID id`, which drops everything below `id`.
167    MinId {
168        /// The lowest ID to keep.
169        id: Id,
170        /// Redis's `=` rather than `~`.
171        exact: bool,
172        /// `LIMIT`, as above.
173        limit: Option<u64>,
174    },
175}
176
177/// Where a group's bookmark is being put, which is `XGROUP CREATE` and `SETID`.
178#[derive(Debug, Clone, Copy, PartialEq, Eq)]
179pub enum Start {
180    /// `$`, the last ID the stream has handed out, so the group sees only what
181    /// arrives after it was made.
182    Last,
183    /// An ID, usually `0`, so the group reads the stream from there.
184    At(Id),
185}
186
187/// What `XREADGROUP` was asked for.
188#[derive(Debug, Clone, Copy, PartialEq, Eq)]
189pub enum From {
190    /// `>`, meaning entries no consumer in this group has been given yet.
191    New,
192    /// An ID, meaning what this consumer is already holding above that ID.
193    ///
194    /// Redis counts this as a real delivery, so the entries come back with
195    /// their delivery time reset and their count up by one.
196    Pending(Id),
197}
198
199/// Everything `XREADGROUP` takes past the key.
200///
201/// A struct for the same reason [`Claim`] is one, and because the wire parses
202/// `GROUP g c`, `COUNT n` and `NOACK` in any order and would otherwise be
203/// carrying five loose values from the parse to the call.
204#[derive(Debug, Clone, Copy)]
205pub struct Read<'a> {
206    /// Which group is reading.
207    pub group: &'a [u8],
208    /// Which consumer inside it, created by turning up if it is new.
209    pub consumer: &'a [u8],
210    /// `>` or an ID, which are two quite different commands wearing one name.
211    pub from: From,
212    /// `COUNT`, or everything there is.
213    pub count: Option<usize>,
214    /// `NOACK`, which hands the entries over without writing them down.
215    pub noack: bool,
216}
217
218/// Everything `XCLAIM` takes past the key and the IDs.
219///
220/// A struct rather than seven more arguments because the command parses them as
221/// one thing and they travel together from the wire to here, and because
222/// `XAUTOCLAIM` takes the same set with one extra of its own.
223#[derive(Debug, Clone, Copy)]
224pub struct Claim<'a> {
225    /// Which group's pending list is being moved around.
226    pub group: &'a [u8],
227    /// Which consumer ends up holding what is claimed.
228    pub consumer: &'a [u8],
229    /// Skip anything idle less than this many milliseconds.
230    pub min_idle: u64,
231    /// What to set the delivery time to, which `IDLE` and `TIME` both work out.
232    pub time: u64,
233    /// `RETRYCOUNT`, or leave the count where it is.
234    pub retry: Option<u64>,
235    /// Whether the delivery count goes up, which it does unless `JUSTID` was
236    /// asked for.
237    pub bump: bool,
238    /// `FORCE`, which makes a pending entry for one that is in the stream and
239    /// was never handed out.
240    pub force: bool,
241}
242
243impl Default for Claim<'_> {
244    fn default() -> Claim<'static> {
245        Claim {
246            group: b"",
247            consumer: b"",
248            min_idle: 0,
249            time: 0,
250            retry: None,
251            bump: true,
252            force: false,
253        }
254    }
255}
256
257impl Keyspace {
258    /// The stream under `key`, for reading.
259    ///
260    /// `None` for a key that is not there or has expired, and an error for one
261    /// holding something else, which is the three way answer every type's entry
262    /// point here gives. `XINFO` and `XLEN` go through this rather than through
263    /// a method each, because reading a field off a stream is not a decision.
264    ///
265    /// # Errors
266    ///
267    /// [`Code::WrongType`] for a key holding anything but a stream.
268    pub fn stream(&mut self, key: &[u8]) -> Result<Option<&Stream>> {
269        let Some(at) = self.live_slot(key, Kind::Stream)? else {
270            return Ok(None);
271        };
272        Ok(self.streams.get(at))
273    }
274
275    /// The same, for a caller that is going to change it.
276    ///
277    /// # Errors
278    ///
279    /// [`Code::WrongType`] for a key holding anything but a stream.
280    pub fn stream_mut(&mut self, key: &[u8]) -> Result<Option<&mut Stream>> {
281        let Some(at) = self.live_slot(key, Kind::Stream)? else {
282            return Ok(None);
283        };
284        Ok(self.streams.get_mut(at))
285    }
286
287    /// `XADD key [NOMKSTREAM] [trim] id field value [field value ...]`.
288    ///
289    /// Answers the ID that was written, or `None` when `NOMKSTREAM` was asked
290    /// for and the key was not there.
291    ///
292    /// The trim runs after the append, which is Redis's order and matters when
293    /// the threshold is `MAXLEN 1`: the entry that was just written is the one
294    /// that survives.
295    ///
296    /// # Errors
297    ///
298    /// [`Code::WrongType`] for a key holding something else, and
299    /// [`Code::Invalid`] for an ID that is zero, is not above the last one, or
300    /// asks for a sequence inside a millisecond that has already filled up.
301    pub fn xadd(
302        &mut self,
303        key: &[u8],
304        id: Add,
305        fields: &[(&[u8], &[u8])],
306        trim: Trim,
307        mkstream: bool,
308        now: u64,
309    ) -> Result<Option<Id>> {
310        let limits = self.stream_limits;
311        let at = match self.live_slot(key, Kind::Stream)? {
312            Some(at) => at,
313            None if mkstream => self.new_stream(key),
314            None => return Ok(None),
315        };
316        let s = self.stream_at(at);
317        // Worked out against the stream as it is, before anything is written,
318        // so that a `*` that has nowhere to go is an error and not a panic.
319        let want = match id {
320            Add::Auto => s.auto_id(now).ok_or_else(exhausted)?,
321            Add::Seq(ms) => s.auto_seq(ms).ok_or_else(|| {
322                if ms < s.last_id().ms {
323                    Error::new(Code::Invalid, NOT_GREATER)
324                } else {
325                    exhausted()
326                }
327            })?,
328            Add::At(id) => id,
329        };
330        s.append(want, fields, limits).map_err(refused)?;
331        cut(s, trim);
332        Ok(Some(want))
333    }
334
335    /// `XDEL key id [id ...]`. Answers how many were there to delete.
336    ///
337    /// # Errors
338    ///
339    /// [`Code::WrongType`] for a key holding something else.
340    pub fn xdel(&mut self, key: &[u8], ids: impl Iterator<Item = Id>) -> Result<u64> {
341        let Some(at) = self.live_slot(key, Kind::Stream)? else {
342            return Ok(0);
343        };
344        let s = self.stream_at(at);
345        Ok(ids.filter(|&id| s.delete(id)).count() as u64)
346    }
347
348    /// `XDELEX key [KEEPREF|DELREF|ACKED] IDS numids id [id ...]`.
349    ///
350    /// The callback gets what became of each ID, in the order they were given. A
351    /// key that is not there is not an error and not a short reply either: every
352    /// ID gets [`Fate::Missing`], which is what a real server answers and is why
353    /// the ID list is walked even when there is nothing to walk it against.
354    ///
355    /// # Errors
356    ///
357    /// [`Code::WrongType`] for a key holding something else.
358    pub fn xdelex<F>(
359        &mut self,
360        key: &[u8],
361        refs: Refs,
362        ids: impl Iterator<Item = Id>,
363        mut f: F,
364    ) -> Result<()>
365    where
366        F: FnMut(Fate),
367    {
368        let Some(at) = self.live_slot(key, Kind::Stream)? else {
369            ids.for_each(|_| f(Fate::Missing));
370            return Ok(());
371        };
372        let s = self.stream_at(at);
373        ids.for_each(|id| f(s.delete_ref(id, refs)));
374        Ok(())
375    }
376
377    /// `XACKDEL key group [KEEPREF|DELREF|ACKED] IDS numids id [id ...]`.
378    ///
379    /// The same shape, and a group that is not there behaves like a key that is
380    /// not there rather than raising `NOGROUP`, because the answer this command
381    /// gives per ID is about the pending list and an absent group is holding
382    /// nothing.
383    ///
384    /// # Errors
385    ///
386    /// [`Code::WrongType`] for a key holding something else.
387    pub fn xackdel<F>(
388        &mut self,
389        key: &[u8],
390        group: &[u8],
391        refs: Refs,
392        ids: impl Iterator<Item = Id>,
393        mut f: F,
394    ) -> Result<()>
395    where
396        F: FnMut(Fate),
397    {
398        let Some(at) = self.live_slot(key, Kind::Stream)? else {
399            ids.for_each(|_| f(Fate::Missing));
400            return Ok(());
401        };
402        let s = self.stream_at(at);
403        ids.for_each(|id| f(s.ack_delete(group, id, refs)));
404        Ok(())
405    }
406
407    /// `XNACK key group <SILENT|FAIL|FATAL> IDS numids id [id ...] [RETRYCOUNT n] [FORCE]`.
408    ///
409    /// Answers how many entries were released, and `None` when there is no such
410    /// key or group, which this command does raise `NOGROUP` for.
411    ///
412    /// # Errors
413    ///
414    /// [`Code::WrongType`] for a key holding something else.
415    pub fn xnack(
416        &mut self,
417        key: &[u8],
418        group: &[u8],
419        retry: Retry,
420        force: bool,
421        ids: impl Iterator<Item = Id>,
422    ) -> Result<Option<u64>> {
423        let Some(at) = self.live_slot(key, Kind::Stream)? else {
424            return Ok(None);
425        };
426        let s = self.stream_at(at);
427        if s.group(group).is_none() {
428            return Ok(None);
429        }
430        let mut done = 0;
431        for id in ids {
432            done += u64::from(s.nack(group, id, retry, force).unwrap_or(false));
433        }
434        Ok(Some(done))
435    }
436
437    /// `XTRIM key strategy`. Answers how many entries went.
438    ///
439    /// # Errors
440    ///
441    /// [`Code::WrongType`] for a key holding something else.
442    pub fn xtrim(&mut self, key: &[u8], trim: Trim) -> Result<u64> {
443        let Some(at) = self.live_slot(key, Kind::Stream)? else {
444            return Ok(0);
445        };
446        Ok(cut(self.stream_at(at), trim))
447    }
448
449    /// `XSETID key id [ENTRIESADDED n] [MAXDELETEDID id]`.
450    ///
451    /// # Errors
452    ///
453    /// [`Code::WrongType`] for a key holding something else,
454    /// [`Code::NotFound`] for a key that is not there, and [`Code::Invalid`]
455    /// for an ID below an entry the stream still holds.
456    pub fn xsetid(
457        &mut self,
458        key: &[u8],
459        last: Id,
460        added: Option<u64>,
461        max_deleted: Option<Id>,
462    ) -> Result<()> {
463        let Some(at) = self.live_slot(key, Kind::Stream)? else {
464            return Err(Error::new(Code::NotFound, NO_SUCH_KEY));
465        };
466        // Checked here rather than in `Stream::set_id`, because it is a rule
467        // about the two arguments and not about the stream: the pair is
468        // contradictory whatever the stream currently holds.
469        if max_deleted.is_some_and(|id| last < id) {
470            return Err(Error::new(Code::Invalid, SETID_BELOW_MAX_DELETED));
471        }
472        self.stream_at(at)
473            .set_id(last, added, max_deleted)
474            .map_err(|_| Error::new(Code::Invalid, SETID_TOO_SMALL))
475    }
476
477    /// `XRANGE` and `XREVRANGE`, which differ only in the direction.
478    ///
479    /// `start` and `end` are the low and the high end either way, so the wire
480    /// layer swaps `XREVRANGE`'s arguments once rather than every reader here
481    /// working out which is which. Answers how many entries the callback saw.
482    ///
483    /// # Errors
484    ///
485    /// [`Code::WrongType`] for a key holding something else.
486    pub fn xrange_into<F>(
487        &mut self,
488        key: &[u8],
489        start: Id,
490        end: Id,
491        count: Option<usize>,
492        rev: bool,
493        f: F,
494    ) -> Result<usize>
495    where
496        F: FnMut(Id, Fields<'_>) -> bool,
497    {
498        let Some(s) = self.stream(key)? else {
499            return Ok(0);
500        };
501        Ok(if rev {
502            s.rev_range(start, end, count, f)
503        } else {
504            s.range(start, end, count, f)
505        })
506    }
507
508    /// `XREAD ... STREAMS key id`, which is a plain range with no group.
509    ///
510    /// Everything after `after`, up to `count`. Answers how many the callback
511    /// saw, which is zero for a key that is not there, because `XREAD` on a
512    /// missing key is nothing to report rather than an error.
513    ///
514    /// # Errors
515    ///
516    /// [`Code::WrongType`] for a key holding something else.
517    pub fn xread_into<F>(
518        &mut self,
519        key: &[u8],
520        after: Id,
521        count: Option<usize>,
522        f: F,
523    ) -> Result<usize>
524    where
525        F: FnMut(Id, Fields<'_>) -> bool,
526    {
527        let Some(from) = after.next() else {
528            return Ok(0);
529        };
530        self.xrange_into(key, from, Id::MAX, count, false, f)
531    }
532
533    /// `XGROUP CREATE key group id [MKSTREAM] [ENTRIESREAD n]`.
534    ///
535    /// Answers whether the group was made, which is `false` when one of that
536    /// name was already there and is the `BUSYGROUP` the wire reports.
537    ///
538    /// # Errors
539    ///
540    /// [`Code::WrongType`] for a key holding something else, and
541    /// [`Code::NotFound`] for a key that is not there without `MKSTREAM`.
542    pub fn xgroup_create(
543        &mut self,
544        key: &[u8],
545        group: &[u8],
546        at: Start,
547        mkstream: bool,
548        read: Option<u64>,
549    ) -> Result<bool> {
550        let slot = match self.live_slot(key, Kind::Stream)? {
551            Some(slot) => slot,
552            None if mkstream => self.new_stream(key),
553            None => return Err(Error::new(Code::NotFound, NO_KEY_FOR_GROUP)),
554        };
555        let s = self.stream_at(slot);
556        let last = position(s, at);
557        // `read` and not a zero for a group with no `ENTRIESREAD`. A fresh group
558        // does not know how many entries are behind it, and saying zero would be
559        // a claim rather than a default: `XINFO GROUPS` reports the counter as
560        // null on a real server until something sets it, and the lag is worked
561        // out from where the bookmark sits instead.
562        let read = capped(read, s);
563        Ok(s.create_group(group, last, read))
564    }
565
566    /// `XGROUP DESTROY key group`. Answers whether there was one.
567    ///
568    /// A group that is not there is a zero and a key that is not there is an
569    /// error, which is Redis's rule for every `XGROUP` subcommand and is worth
570    /// stating because the two look like the same kind of nothing from a client.
571    /// They are not: destroying a group nobody made is a no op, and destroying a
572    /// group on a key nobody made is a mistake about which key.
573    ///
574    /// # Errors
575    ///
576    /// [`Code::WrongType`] for a key holding something else, and
577    /// [`Code::NotFound`] for a key that is not there.
578    pub fn xgroup_destroy(&mut self, key: &[u8], group: &[u8]) -> Result<bool> {
579        let Some(at) = self.live_slot(key, Kind::Stream)? else {
580            return Err(Error::new(Code::NotFound, NO_KEY_FOR_GROUP));
581        };
582        Ok(self.stream_at(at).destroy_group(group))
583    }
584
585    /// `XGROUP SETID key group id [ENTRIESREAD n]`.
586    ///
587    /// `None` when there is no such group, which the wire reports as `NOGROUP`.
588    ///
589    /// # Errors
590    ///
591    /// [`Code::WrongType`] for a key holding something else, and
592    /// [`Code::NotFound`] for a key that is not there.
593    pub fn xgroup_setid(
594        &mut self,
595        key: &[u8],
596        group: &[u8],
597        at: Start,
598        read: Option<u64>,
599    ) -> Result<Option<()>> {
600        let Some(slot) = self.live_slot(key, Kind::Stream)? else {
601            return Err(Error::new(Code::NotFound, NO_KEY_FOR_GROUP));
602        };
603        let s = self.stream_at(slot);
604        let last = position(s, at);
605        let read = capped(read, s);
606        let Some(g) = s.group_mut(group) else {
607            return Ok(None);
608        };
609        // `read` and not the group's old counter when nothing was named, so
610        // `XGROUP SETID key group 0` gives the counter up rather than leaving
611        // one that was true of somewhere else. That is what a real server does
612        // and it is visible immediately: `XINFO GROUPS` reports both the counter
613        // and the lag as null afterwards, until a read or an `ENTRIESREAD` puts
614        // a number back.
615        g.set_id(last, read);
616        Ok(Some(()))
617    }
618
619    /// `XGROUP CREATECONSUMER key group consumer`.
620    ///
621    /// Answers whether the consumer was made, and `None` when there is no such
622    /// group.
623    ///
624    /// # Errors
625    ///
626    /// [`Code::WrongType`] for a key holding something else, and
627    /// [`Code::NotFound`] for a key that is not there.
628    pub fn xgroup_create_consumer(
629        &mut self,
630        key: &[u8],
631        group: &[u8],
632        consumer: &[u8],
633        now: u64,
634    ) -> Result<Option<bool>> {
635        let Some(g) = self.group_mut_of(key, group)? else {
636            return Ok(None);
637        };
638        Ok(Some(g.create_consumer(consumer, now)))
639    }
640
641    /// `XGROUP DELCONSUMER key group consumer`.
642    ///
643    /// Answers how many pending entries went with it, and `None` when there is
644    /// no such group.
645    ///
646    /// # Errors
647    ///
648    /// [`Code::WrongType`] for a key holding something else, and
649    /// [`Code::NotFound`] for a key that is not there.
650    pub fn xgroup_del_consumer(
651        &mut self,
652        key: &[u8],
653        group: &[u8],
654        consumer: &[u8],
655    ) -> Result<Option<u64>> {
656        let Some(g) = self.group_mut_of(key, group)? else {
657            return Ok(None);
658        };
659        Ok(Some(g.delete_consumer(consumer)))
660    }
661
662    /// `XREADGROUP GROUP group consumer [COUNT n] [NOACK] STREAMS key id`.
663    ///
664    /// Answers how many entries the callback saw, and `None` when there is no
665    /// such group. The callback takes an `Option` because a history read can
666    /// name an entry that has since been deleted, and Redis puts a null in the
667    /// reply for it rather than leaving it out.
668    ///
669    /// # Errors
670    ///
671    /// [`Code::WrongType`] for a key holding something else, and
672    /// [`Code::NotFound`] for a key that is not there, which is the `NOGROUP`
673    /// Redis answers because a missing key cannot have the group either.
674    pub fn xreadgroup_into<F>(
675        &mut self,
676        key: &[u8],
677        want: Read<'_>,
678        now: u64,
679        mut f: F,
680    ) -> Result<Option<usize>>
681    where
682        F: FnMut(Id, Option<Fields<'_>>) -> bool,
683    {
684        let Some(at) = self.live_slot(key, Kind::Stream)? else {
685            return Ok(None);
686        };
687        let s = self.stream_at(at);
688        Ok(match want.from {
689            From::New => s.read_group(
690                want.group,
691                want.consumer,
692                want.count,
693                want.noack,
694                now,
695                |id, fields| f(id, Some(fields)),
696            ),
697            From::Pending(after) => {
698                s.read_group_pending(want.group, want.consumer, after, want.count, now, &mut f)
699            }
700        })
701    }
702
703    /// `XACK key group id [id ...]`. Answers how many were pending.
704    ///
705    /// # Errors
706    ///
707    /// [`Code::WrongType`] for a key holding something else.
708    pub fn xack(&mut self, key: &[u8], group: &[u8], ids: impl Iterator<Item = Id>) -> Result<u64> {
709        let Some(at) = self.live_slot(key, Kind::Stream)? else {
710            return Ok(0);
711        };
712        let Some(g) = self.stream_at(at).group_mut(group) else {
713            return Ok(0);
714        };
715        Ok(ids.filter(|&id| g.ack(id)).count() as u64)
716    }
717
718    /// `XPENDING key group [[IDLE ms] start end count [consumer]]`, the long form.
719    ///
720    /// The callback gets each entry with its NACK and its owner. Answers how
721    /// many it saw, and `None` when there is no such group.
722    ///
723    /// # Errors
724    ///
725    /// [`Code::WrongType`] for a key holding something else.
726    pub fn xpending_into<F>(
727        &mut self,
728        key: &[u8],
729        group: &[u8],
730        want: Filter,
731        now: u64,
732        f: F,
733    ) -> Result<Option<usize>>
734    where
735        F: FnMut(Id, &crate::stream::Nack, Option<&crate::stream::Consumer>) -> bool,
736    {
737        let Some(s) = self.stream(key)? else {
738            return Ok(None);
739        };
740        let Some(g) = s.group(group) else {
741            return Ok(None);
742        };
743        Ok(Some(g.pending_range(want, now, f)))
744    }
745
746    /// `XCLAIM key group consumer min-idle-time id [id ...]`.
747    ///
748    /// Answers what was claimed, and fills `gone` with the IDs that were in the
749    /// pending list and are no longer in the stream, which the claim clears out
750    /// on the way past because nobody can ever finish them. `None` when there
751    /// is no such group.
752    ///
753    /// # Errors
754    ///
755    /// [`Code::WrongType`] for a key holding something else.
756    pub fn xclaim(
757        &mut self,
758        key: &[u8],
759        ids: &[Id],
760        how: Claim<'_>,
761        now: u64,
762        gone: &mut Vec<Id>,
763    ) -> Result<Option<Vec<Id>>> {
764        let Some(at) = self.live_slot(key, Kind::Stream)? else {
765            return Ok(None);
766        };
767        Ok(self.stream_at(at).claim(
768            how.group,
769            how.consumer,
770            ids,
771            how.min_idle,
772            how.time,
773            how.retry,
774            how.bump,
775            how.force,
776            now,
777            gone,
778        ))
779    }
780
781    /// `XAUTOCLAIM key group consumer min-idle-time start [COUNT n] [JUSTID]`.
782    ///
783    /// Answers where a following call should carry on from, which is `None` at
784    /// the end of the list and is the `0-0` Redis replies with, along with what
785    /// was claimed. `gone` is filled the same way [`Keyspace::xclaim`] fills it.
786    ///
787    /// # Errors
788    ///
789    /// [`Code::WrongType`] for a key holding something else.
790    pub fn xautoclaim(
791        &mut self,
792        key: &[u8],
793        start: Id,
794        how: Claim<'_>,
795        count: usize,
796        now: u64,
797        gone: &mut Vec<Id>,
798    ) -> Result<Option<(Option<Id>, Vec<Id>)>> {
799        let Some(at) = self.live_slot(key, Kind::Stream)? else {
800            return Ok(None);
801        };
802        Ok(self.stream_at(at).autoclaim(
803            how.group,
804            how.consumer,
805            start,
806            how.min_idle,
807            count,
808            how.bump,
809            now,
810            gone,
811        ))
812    }
813
814    /// The group under a key, for the three commands that only touch the group.
815    ///
816    /// # Errors
817    ///
818    /// [`Code::WrongType`] for a key holding something else, and
819    /// [`Code::NotFound`] for a key that is not there, which is what `XGROUP`
820    /// says about all of its subcommands.
821    fn group_mut_of(&mut self, key: &[u8], group: &[u8]) -> Result<Option<&mut Group>> {
822        let Some(at) = self.live_slot(key, Kind::Stream)? else {
823            return Err(Error::new(Code::NotFound, NO_KEY_FOR_GROUP));
824        };
825        Ok(self.stream_at(at).group_mut(group))
826    }
827
828    fn stream_at(&mut self, at: u32) -> &mut Stream {
829        self.streams
830            .get_mut(at)
831            .expect("the record points at its body")
832    }
833
834    fn new_stream(&mut self, key: &[u8]) -> u32 {
835        let at = self.streams.insert(Stream::new());
836        let len = value::slot_record_len(false);
837        self.write_rec(key, len, |out| {
838            value::write_slot_record(out, Kind::Stream, at, None);
839        });
840        self.bodies += 1;
841        at
842    }
843}
844
845/// Where a bookmark goes for a `$` or for an ID.
846fn position(s: &Stream, at: Start) -> Id {
847    match at {
848        Start::Last => s.last_id(),
849        Start::At(id) => id,
850    }
851}
852
853/// An `ENTRIESREAD` held down to what the stream has ever added.
854///
855/// A group cannot have read more entries than were ever written, so a client
856/// that says it has is corrected rather than believed. Redis does the same and
857/// it is visible: `XGROUP CREATE key g 0 ENTRIESREAD 99` on a stream of three
858/// reports three afterwards, not ninety nine. It matters because the number is
859/// subtracted from the entry count to get the lag, and an inflated one would
860/// make the lag come out at zero on a group that has read nothing.
861fn capped(read: Option<u64>, s: &Stream) -> Option<u64> {
862    read.map(|n| n.min(s.added()))
863}
864
865/// Run a trim, whichever kind it is. Answers how many entries went.
866fn cut(s: &mut Stream, trim: Trim) -> u64 {
867    match trim {
868        Trim::None => 0,
869        Trim::MaxLen { len, exact, limit } => s.trim_maxlen(len, exact, limit),
870        Trim::MinId { id, exact, limit } => s.trim_minid(id, exact, limit),
871    }
872}
873
874fn exhausted() -> Error {
875    Error::new(Code::Invalid, EXHAUSTED)
876}
877
878fn refused(why: Refused) -> Error {
879    match why {
880        Refused::Zero => Error::new(Code::Invalid, ZERO_ID),
881        Refused::NotGreater => Error::new(Code::Invalid, NOT_GREATER),
882        Refused::Full => exhausted(),
883    }
884}
885
886#[cfg(test)]
887mod tests {
888    use super::*;
889
890    fn db() -> Keyspace {
891        Keyspace::new()
892    }
893
894    /// One entry, with the two fields a reading has.
895    fn add(d: &mut Keyspace, key: &[u8], id: Add) -> Id {
896        d.xadd(
897            key,
898            id,
899            &[(b"sensor", b"a4"), (b"reading", b"21.5")],
900            Trim::None,
901            true,
902            1_000,
903        )
904        .expect("a stream")
905        .expect("an ID")
906    }
907
908    /// Every ID in the stream, oldest first.
909    fn ids(d: &mut Keyspace, key: &[u8]) -> Vec<Id> {
910        let mut out = Vec::new();
911        d.xrange_into(key, Id::MIN, Id::MAX, None, false, |id, _| {
912            out.push(id);
913            true
914        })
915        .expect("a stream");
916        out
917    }
918
919    #[test]
920    fn a_write_makes_the_key_and_a_read_finds_it() {
921        let mut d = db();
922        assert_eq!(d.kind_of(b"s"), None);
923        let id = add(&mut d, b"s", Add::At(Id::new(5, 0)));
924        assert_eq!(id, Id::new(5, 0));
925        assert_eq!(d.kind_of(b"s"), Some(Kind::Stream));
926        assert_eq!(d.type_name(b"s"), Some("stream"));
927        assert_eq!(d.encoding_name(b"s"), Some("stream"));
928        assert_eq!(ids(&mut d, b"s"), vec![Id::new(5, 0)]);
929    }
930
931    #[test]
932    fn nomkstream_leaves_a_missing_key_missing() {
933        let mut d = db();
934        let got = d
935            .xadd(b"s", Add::Auto, &[(b"f", b"v")], Trim::None, false, 1_000)
936            .expect("a stream");
937        assert_eq!(got, None);
938        assert_eq!(d.kind_of(b"s"), None);
939    }
940
941    #[test]
942    fn an_auto_id_follows_the_clock_and_then_the_last_id() {
943        let mut d = db();
944        assert_eq!(add(&mut d, b"s", Add::Auto), Id::new(1_000, 0));
945        // The clock has not moved, so the sequence does.
946        assert_eq!(add(&mut d, b"s", Add::Auto), Id::new(1_000, 1));
947        assert_eq!(add(&mut d, b"s", Add::Seq(1_000)), Id::new(1_000, 2));
948        assert_eq!(add(&mut d, b"s", Add::Seq(2_000)), Id::new(2_000, 0));
949    }
950
951    #[test]
952    fn an_id_that_is_not_above_the_last_one_is_refused() {
953        let mut d = db();
954        add(&mut d, b"s", Add::At(Id::new(5, 0)));
955        let e = d
956            .xadd(
957                b"s",
958                Add::At(Id::new(5, 0)),
959                &[(b"f", b"v")],
960                Trim::None,
961                true,
962                1_000,
963            )
964            .expect_err("not above the last one");
965        assert_eq!(e.message(), NOT_GREATER);
966        // And a sequence asked for inside a millisecond that has gone by.
967        let e = d
968            .xadd(b"s", Add::Seq(4), &[(b"f", b"v")], Trim::None, true, 1_000)
969            .expect_err("a millisecond that has gone by");
970        assert_eq!(e.message(), NOT_GREATER);
971    }
972
973    #[test]
974    fn zero_is_refused_and_says_so_in_its_own_words() {
975        let mut d = db();
976        let e = d
977            .xadd(
978                b"s",
979                Add::At(Id::MIN),
980                &[(b"f", b"v")],
981                Trim::None,
982                true,
983                1_000,
984            )
985            .expect_err("nothing sorts below zero");
986        assert_eq!(e.message(), ZERO_ID);
987    }
988
989    #[test]
990    fn a_stream_is_not_deleted_when_the_last_entry_goes() {
991        let mut d = db();
992        let id = add(&mut d, b"s", Add::At(Id::new(5, 0)));
993        assert_eq!(d.xdel(b"s", [id].into_iter()).expect("a stream"), 1);
994        assert_eq!(d.xdel(b"s", [id].into_iter()).expect("a stream"), 0);
995        assert_eq!(d.kind_of(b"s"), Some(Kind::Stream), "the key is still here");
996        // And the ID it handed out is still remembered, so the next one is above
997        // it rather than the same one again.
998        assert_eq!(add(&mut d, b"s", Add::Auto), Id::new(1_000, 0));
999    }
1000
1001    #[test]
1002    fn a_trim_runs_after_the_append() {
1003        let mut d = db();
1004        for ms in 1..=10u64 {
1005            add(&mut d, b"s", Add::At(Id::new(ms, 0)));
1006        }
1007        let trim = Trim::MaxLen {
1008            len: 1,
1009            exact: true,
1010            limit: None,
1011        };
1012        let id = d
1013            .xadd(
1014                b"s",
1015                Add::At(Id::new(11, 0)),
1016                &[(b"f", b"v")],
1017                trim,
1018                true,
1019                1,
1020            )
1021            .expect("a stream")
1022            .expect("an ID");
1023        // The entry that was just written is the one that survives, which is the
1024        // whole reason the order matters.
1025        assert_eq!(ids(&mut d, b"s"), vec![id]);
1026    }
1027
1028    #[test]
1029    fn a_limit_stops_a_trim_at_the_next_node_boundary() {
1030        let mut d = db();
1031        for ms in 1..=1_000u64 {
1032            add(&mut d, b"s", Add::At(Id::new(ms, 0)));
1033        }
1034        let trim = Trim::MaxLen {
1035            len: 0,
1036            exact: false,
1037            limit: Some(10),
1038        };
1039        // A node is a hundred entries and a node is what goes, so asking to stop
1040        // after ten stops after the first node rather than in the middle of it.
1041        // That is what Redis does too, and it is why `LIMIT` is only allowed
1042        // with `~`: the limit is a brake on how long the command runs and not a
1043        // count of what it is allowed to remove.
1044        assert_eq!(d.xtrim(b"s", trim).expect("a stream"), 100);
1045        assert_eq!(
1046            d.stream(b"s").expect("a stream").expect("the key").len(),
1047            900
1048        );
1049    }
1050
1051    #[test]
1052    fn setid_moves_the_bookmark_and_refuses_to_go_below_an_entry() {
1053        let mut d = db();
1054        add(&mut d, b"s", Add::At(Id::new(5, 0)));
1055        let e = d
1056            .xsetid(b"s", Id::new(4, 0), None, None)
1057            .expect_err("below an entry that is still there");
1058        assert_eq!(e.message(), SETID_TOO_SMALL);
1059        d.xsetid(b"s", Id::new(9, 0), Some(41), None)
1060            .expect("above it");
1061        let s = d.stream(b"s").expect("a stream").expect("the key");
1062        assert_eq!((s.last_id(), s.added()), (Id::new(9, 0), 41));
1063    }
1064
1065    #[test]
1066    fn setid_on_a_key_that_is_not_there_says_so() {
1067        let mut d = db();
1068        let e = d
1069            .xsetid(b"s", Id::new(1, 0), None, None)
1070            .expect_err("no key");
1071        assert_eq!((e.code(), e.message()), (Code::NotFound, NO_SUCH_KEY));
1072    }
1073
1074    #[test]
1075    fn every_command_sees_the_wrong_type() {
1076        let mut d = db();
1077        d.set_plain(b"s", b"a string").expect("a string");
1078        for e in [
1079            d.xadd(b"s", Add::Auto, &[(b"f", b"v")], Trim::None, true, 1)
1080                .expect_err("a string"),
1081            d.xdel(b"s", [Id::new(1, 0)].into_iter())
1082                .expect_err("a string"),
1083            d.xtrim(b"s", Trim::None).expect_err("a string"),
1084            d.xrange_into(b"s", Id::MIN, Id::MAX, None, false, |_, _| true)
1085                .expect_err("a string"),
1086            d.xack(b"s", b"g", [Id::new(1, 0)].into_iter())
1087                .expect_err("a string"),
1088        ] {
1089            assert_eq!(e.code(), Code::WrongType);
1090        }
1091    }
1092
1093    /// A new group has no read counter and still has a lag, because the two are
1094    /// worked out separately.
1095    ///
1096    /// Both lines are Redis 8.10.1's: `XINFO GROUPS` on a group made with no
1097    /// `ENTRIESREAD` reports the counter as null whichever position it was made
1098    /// at, and reports a lag of the whole stream at `0` and of zero at `$`.
1099    #[test]
1100    fn a_new_group_has_no_read_counter() {
1101        let mut d = db();
1102        for ms in 1..=5 {
1103            add(&mut d, b"s", Add::At(Id::new(ms, 0)));
1104        }
1105
1106        assert!(
1107            d.xgroup_create(b"s", b"early", Start::At(Id::MIN), false, None)
1108                .expect("a stream")
1109        );
1110        assert!(
1111            d.xgroup_create(b"s", b"late", Start::Last, false, None)
1112                .expect("a stream")
1113        );
1114        let s = d.stream(b"s").expect("a stream").expect("the key");
1115        let early = s.group(b"early").expect("the early group");
1116        assert_eq!(early.entries_read(), None);
1117        assert_eq!(s.lag(early), Some(5), "everything is still in front of it");
1118        let late = s.group(b"late").expect("the late group");
1119        assert_eq!(late.entries_read(), None);
1120        assert_eq!(s.lag(late), Some(0), "and nothing is in front of this one");
1121    }
1122
1123    /// A read counter a client hands in is held down to what was ever written,
1124    /// and giving none at all on a `SETID` gives the counter up.
1125    ///
1126    /// Both are Redis 8.10.1's. The first matters because the number is
1127    /// subtracted from the entry count to work the lag out, so believing a
1128    /// client that says ninety nine would report a lag of zero on a group that
1129    /// has read nothing.
1130    #[test]
1131    fn a_read_counter_that_is_too_big_is_brought_back_down() {
1132        let mut d = db();
1133        for ms in 1..=3 {
1134            add(&mut d, b"s", Add::At(Id::new(ms, 0)));
1135        }
1136
1137        d.xgroup_create(b"s", b"g", Start::At(Id::MIN), false, Some(99))
1138            .expect("a stream");
1139        assert_eq!(
1140            counter(&mut d, b"g"),
1141            Some(3),
1142            "held down to what was added"
1143        );
1144
1145        d.xgroup_setid(b"s", b"g", Start::At(Id::MIN), Some(2))
1146            .expect("a stream")
1147            .expect("the group");
1148        assert_eq!(counter(&mut d, b"g"), Some(2), "and left alone below that");
1149
1150        d.xgroup_setid(b"s", b"g", Start::At(Id::MIN), None)
1151            .expect("a stream")
1152            .expect("the group");
1153        assert_eq!(
1154            counter(&mut d, b"g"),
1155            None,
1156            "and given up when none is named"
1157        );
1158    }
1159
1160    /// The one group's read counter, which the test above reads three times.
1161    fn counter(d: &mut Keyspace, group: &[u8]) -> Option<u64> {
1162        d.stream(b"s")
1163            .expect("a stream")
1164            .expect("the key")
1165            .group(group)
1166            .expect("the group")
1167            .entries_read()
1168    }
1169
1170    /// `XSETID` refuses a last ID below the deletion mark it was handed.
1171    ///
1172    /// Its own sentence and not the one about the top item, because it is its
1173    /// own mistake: the pair contradicts itself whatever the stream holds, and a
1174    /// stream whose last ID sat below its own deletion mark would hand an ID out
1175    /// twice.
1176    #[test]
1177    fn setid_refuses_a_last_id_under_its_own_deletion_mark() {
1178        let mut d = db();
1179        add(&mut d, b"s", Add::At(Id::new(5, 0)));
1180
1181        let e = d
1182            .xsetid(b"s", Id::new(9, 9), None, Some(Id::new(99, 99)))
1183            .expect_err("the pair contradicts itself");
1184        assert_eq!(e.message(), SETID_BELOW_MAX_DELETED);
1185
1186        d.xsetid(b"s", Id::new(99, 99), None, Some(Id::new(9, 9)))
1187            .expect("the other way round is fine");
1188        let s = d.stream(b"s").expect("a stream").expect("the key");
1189        assert_eq!(s.last_id(), Id::new(99, 99));
1190        assert_eq!(s.max_deleted_id(), Id::new(9, 9));
1191    }
1192
1193    /// A history read makes the consumer it was sent as, and answers nothing.
1194    ///
1195    /// The case is a worker that restarts under a new name and asks for its own
1196    /// backlog before it asks for new work. There is no backlog because the name
1197    /// is new, and that is an empty list rather than a missing group, which is
1198    /// the answer the wire has to be able to tell apart from `NOGROUP`.
1199    #[test]
1200    fn a_history_read_by_a_name_nobody_has_used_makes_the_consumer() {
1201        let mut d = db();
1202        add(&mut d, b"s", Add::At(Id::new(1, 0)));
1203        d.xgroup_create(b"s", b"g", Start::At(Id::MIN), false, None)
1204            .expect("a stream");
1205
1206        let want = Read {
1207            group: b"g",
1208            consumer: b"newbie",
1209            from: From::Pending(Id::MIN),
1210            count: None,
1211            noack: false,
1212        };
1213        let seen = d
1214            .xreadgroup_into(b"s", want, 500, |_, _| true)
1215            .expect("a stream")
1216            .expect("the group is there");
1217        assert_eq!(seen, 0, "nothing was ever handed to this name");
1218
1219        let s = d.stream(b"s").expect("a stream").expect("the key");
1220        let c = s
1221            .group(b"g")
1222            .expect("the group")
1223            .consumer_named(b"newbie")
1224            .expect("the read made it");
1225        assert_eq!(c.seen(), 500, "it was heard from");
1226        assert_eq!(c.active(), None, "and it has never had anything");
1227    }
1228
1229    #[test]
1230    fn a_group_reads_what_arrives_after_it_was_made() {
1231        let mut d = db();
1232        add(&mut d, b"s", Add::At(Id::new(1, 0)));
1233        assert!(
1234            d.xgroup_create(b"s", b"workers", Start::Last, false, None)
1235                .expect("a stream")
1236        );
1237        // Making it again is not an error here, it is a false, and the wire
1238        // turns that into BUSYGROUP.
1239        assert!(
1240            !d.xgroup_create(b"s", b"workers", Start::Last, false, None)
1241                .expect("a stream")
1242        );
1243        add(&mut d, b"s", Add::At(Id::new(2, 0)));
1244
1245        let mut got = Vec::new();
1246        let seen = d
1247            .xreadgroup_into(
1248                b"s",
1249                Read {
1250                    group: b"workers",
1251                    consumer: b"alice",
1252                    from: From::New,
1253                    count: None,
1254                    noack: false,
1255                },
1256                1_000,
1257                |id, fields| {
1258                    got.push((id, fields.is_some()));
1259                    true
1260                },
1261            )
1262            .expect("a stream")
1263            .expect("a group");
1264        assert_eq!(seen, 1, "only what arrived after the group was made");
1265        assert_eq!(got, vec![(Id::new(2, 0), true)]);
1266        assert_eq!(
1267            d.xack(b"s", b"workers", [Id::new(2, 0)].into_iter())
1268                .expect("a stream"),
1269            1
1270        );
1271    }
1272
1273    #[test]
1274    fn noack_hands_the_entry_over_without_writing_it_down() {
1275        let mut d = db();
1276        add(&mut d, b"s", Add::At(Id::new(1, 0)));
1277        d.xgroup_create(b"s", b"workers", Start::At(Id::MIN), false, None)
1278            .expect("a stream");
1279        let seen = d
1280            .xreadgroup_into(
1281                b"s",
1282                Read {
1283                    group: b"workers",
1284                    consumer: b"alice",
1285                    from: From::New,
1286                    count: None,
1287                    noack: true,
1288                },
1289                1_000,
1290                |_, _| true,
1291            )
1292            .expect("a stream")
1293            .expect("a group");
1294        assert_eq!(seen, 1);
1295        let s = d.stream(b"s").expect("a stream").expect("the key");
1296        let g = s.group(b"workers").expect("the group");
1297        assert_eq!(g.pending_len(), 0, "nothing was written down");
1298        assert_eq!(g.last_id(), Id::new(1, 0), "the bookmark still moved");
1299        assert_eq!(s.lag(g), Some(0), "and the group has caught up");
1300    }
1301
1302    #[test]
1303    fn a_missing_group_is_a_none_and_not_an_error() {
1304        let mut d = db();
1305        add(&mut d, b"s", Add::At(Id::new(1, 0)));
1306        let got = d
1307            .xreadgroup_into(
1308                b"s",
1309                Read {
1310                    group: b"nope",
1311                    consumer: b"alice",
1312                    from: From::New,
1313                    count: None,
1314                    noack: false,
1315                },
1316                1,
1317                |_, _| true,
1318            )
1319            .expect("a stream");
1320        assert_eq!(got, None);
1321        assert!(!d.xgroup_destroy(b"s", b"nope").expect("a stream"));
1322        assert_eq!(
1323            d.xgroup_create_consumer(b"s", b"nope", b"alice", 1)
1324                .expect("a stream"),
1325            None
1326        );
1327    }
1328
1329    #[test]
1330    fn xgroup_needs_the_key_and_says_which_option_makes_one() {
1331        let mut d = db();
1332        let e = d
1333            .xgroup_create(b"s", b"workers", Start::Last, false, None)
1334            .expect_err("no key");
1335        assert_eq!((e.code(), e.message()), (Code::NotFound, NO_KEY_FOR_GROUP));
1336        assert!(
1337            d.xgroup_create(b"s", b"workers", Start::Last, true, None)
1338                .expect("mkstream made one")
1339        );
1340        assert_eq!(d.kind_of(b"s"), Some(Kind::Stream));
1341    }
1342
1343    #[test]
1344    fn a_claim_moves_an_idle_entry_and_drops_one_that_has_gone() {
1345        let mut d = db();
1346        for ms in 1..=2u64 {
1347            add(&mut d, b"s", Add::At(Id::new(ms, 0)));
1348        }
1349        d.xgroup_create(b"s", b"workers", Start::At(Id::MIN), false, None)
1350            .expect("a stream");
1351        d.xreadgroup_into(
1352            b"s",
1353            Read {
1354                group: b"workers",
1355                consumer: b"alice",
1356                from: From::New,
1357                count: None,
1358                noack: false,
1359            },
1360            100,
1361            |_, _| true,
1362        )
1363        .expect("a stream")
1364        .expect("a group");
1365        d.xdel(b"s", [Id::new(1, 0)].into_iter()).expect("a stream");
1366
1367        let mut gone = Vec::new();
1368        let how = Claim {
1369            group: b"workers",
1370            consumer: b"bob",
1371            min_idle: 500,
1372            time: 1_000,
1373            ..Claim::default()
1374        };
1375        let took = d
1376            .xclaim(b"s", &[Id::new(1, 0), Id::new(2, 0)], how, 1_000, &mut gone)
1377            .expect("a stream")
1378            .expect("a group");
1379        assert_eq!(took, vec![Id::new(2, 0)]);
1380        assert_eq!(gone, vec![Id::new(1, 0)], "no one can ever finish that one");
1381    }
1382
1383    #[test]
1384    fn an_autoclaim_sweeps_from_a_cursor() {
1385        let mut d = db();
1386        for ms in 1..=10u64 {
1387            add(&mut d, b"s", Add::At(Id::new(ms, 0)));
1388        }
1389        d.xgroup_create(b"s", b"workers", Start::At(Id::MIN), false, None)
1390            .expect("a stream");
1391        d.xreadgroup_into(
1392            b"s",
1393            Read {
1394                group: b"workers",
1395                consumer: b"alice",
1396                from: From::New,
1397                count: None,
1398                noack: false,
1399            },
1400            100,
1401            |_, _| true,
1402        )
1403        .expect("a stream")
1404        .expect("a group");
1405
1406        let mut gone = Vec::new();
1407        let how = Claim {
1408            group: b"workers",
1409            consumer: b"bob",
1410            min_idle: 500,
1411            time: 1_000,
1412            ..Claim::default()
1413        };
1414        let (cursor, took) = d
1415            .xautoclaim(b"s", Id::MIN, how, 4, 1_000, &mut gone)
1416            .expect("a stream")
1417            .expect("a group");
1418        assert_eq!(took.len(), 4);
1419        assert_eq!(cursor, Some(Id::new(5, 0)), "where the next call starts");
1420        assert!(gone.is_empty());
1421    }
1422
1423    #[test]
1424    fn a_pending_window_carries_the_owner_and_the_counts() {
1425        let mut d = db();
1426        for ms in 1..=3u64 {
1427            add(&mut d, b"s", Add::At(Id::new(ms, 0)));
1428        }
1429        d.xgroup_create(b"s", b"workers", Start::At(Id::MIN), false, None)
1430            .expect("a stream");
1431        d.xreadgroup_into(
1432            b"s",
1433            Read {
1434                group: b"workers",
1435                consumer: b"alice",
1436                from: From::New,
1437                count: None,
1438                noack: false,
1439            },
1440            100,
1441            |_, _| true,
1442        )
1443        .expect("a stream")
1444        .expect("a group");
1445
1446        let mut out = Vec::new();
1447        let seen = d
1448            .xpending_into(b"s", b"workers", Filter::default(), 600, |id, nack, c| {
1449                out.push((
1450                    id,
1451                    nack.count(),
1452                    nack.idle(600),
1453                    c.expect("an owner").name().to_vec(),
1454                ));
1455                true
1456            })
1457            .expect("a stream")
1458            .expect("a group");
1459        assert_eq!(seen, 3);
1460        assert_eq!(out[0], (Id::new(1, 0), 1, 500, b"alice".to_vec()));
1461    }
1462
1463    #[test]
1464    fn a_history_read_hands_back_a_null_for_an_entry_that_has_gone() {
1465        let mut d = db();
1466        for ms in 1..=2u64 {
1467            add(&mut d, b"s", Add::At(Id::new(ms, 0)));
1468        }
1469        d.xgroup_create(b"s", b"workers", Start::At(Id::MIN), false, None)
1470            .expect("a stream");
1471        d.xreadgroup_into(
1472            b"s",
1473            Read {
1474                group: b"workers",
1475                consumer: b"alice",
1476                from: From::New,
1477                count: None,
1478                noack: false,
1479            },
1480            100,
1481            |_, _| true,
1482        )
1483        .expect("a stream")
1484        .expect("a group");
1485        d.xdel(b"s", [Id::new(1, 0)].into_iter()).expect("a stream");
1486
1487        let mut out = Vec::new();
1488        d.xreadgroup_into(
1489            b"s",
1490            Read {
1491                group: b"workers",
1492                consumer: b"alice",
1493                from: From::Pending(Id::MIN),
1494                count: None,
1495                noack: false,
1496            },
1497            2_000,
1498            |id, fields| {
1499                out.push((id, fields.is_some()));
1500                true
1501            },
1502        )
1503        .expect("a stream")
1504        .expect("a group");
1505        assert_eq!(out, vec![(Id::new(1, 0), false), (Id::new(2, 0), true)]);
1506    }
1507
1508    #[test]
1509    fn a_stream_counts_against_the_memory_total() {
1510        let mut d = db();
1511        let before = d.memory_bytes();
1512        for ms in 1..=1_000u64 {
1513            add(&mut d, b"s", Add::At(Id::new(ms, 0)));
1514        }
1515        let after = d.memory_bytes();
1516        assert!(after > before + 1_000, "{before} then {after}");
1517        assert!(d.del(b"s"), "the key was there");
1518        assert_eq!(d.kind_of(b"s"), None);
1519    }
1520
1521    #[test]
1522    fn a_deadline_goes_on_a_stream_the_same_as_on_anything_else() {
1523        let mut d = db();
1524        add(&mut d, b"s", Add::At(Id::new(1, 0)));
1525        assert!(d.set_expiry(b"s", Some(1 << 45)));
1526        assert_eq!(d.kind_of(b"s"), Some(Kind::Stream));
1527        assert_eq!(ids(&mut d, b"s"), vec![Id::new(1, 0)], "the body is intact");
1528    }
1529}