1use 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
40pub const BAD_ID: &str = "Invalid stream ID specified as stream command argument";
42
43pub const NOT_GREATER: &str =
45 "The ID specified in XADD is equal or smaller than the target stream top item";
46
47pub const ZERO_ID: &str = "The ID specified in XADD must be greater than 0-0";
49
50pub const EXHAUSTED: &str =
52 "The stream has exhausted the last possible ID, unable to add more items";
53
54pub 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
60pub const GROUP_EXISTS: &str = "Consumer Group name already exists";
66
67pub const SETID_TOO_SMALL: &str =
69 "The ID specified in XSETID is smaller than the target stream top item";
70
71pub const SETID_BELOW_MAX_DELETED: &str =
78 "The ID specified in XSETID is smaller than the provided max_deleted_entry_id";
79
80pub const NO_SUCH_KEY: &str = "no such key";
82
83#[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#[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#[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
133pub enum Add {
134 Auto,
137 Seq(u64),
139 At(Id),
141}
142
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
154pub enum Trim {
155 None,
157 MaxLen {
159 len: u64,
161 exact: bool,
163 limit: Option<u64>,
165 },
166 MinId {
168 id: Id,
170 exact: bool,
172 limit: Option<u64>,
174 },
175}
176
177#[derive(Debug, Clone, Copy, PartialEq, Eq)]
179pub enum Start {
180 Last,
183 At(Id),
185}
186
187#[derive(Debug, Clone, Copy, PartialEq, Eq)]
189pub enum From {
190 New,
192 Pending(Id),
197}
198
199#[derive(Debug, Clone, Copy)]
205pub struct Read<'a> {
206 pub group: &'a [u8],
208 pub consumer: &'a [u8],
210 pub from: From,
212 pub count: Option<usize>,
214 pub noack: bool,
216}
217
218#[derive(Debug, Clone, Copy)]
224pub struct Claim<'a> {
225 pub group: &'a [u8],
227 pub consumer: &'a [u8],
229 pub min_idle: u64,
231 pub time: u64,
233 pub retry: Option<u64>,
235 pub bump: bool,
238 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 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 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 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 Ok(self
311 .xadd_trimmed(key, id, fields, trim, mkstream, now)?
312 .map(|(id, _)| id))
313 }
314
315 pub fn xadd_trimmed(
327 &mut self,
328 key: &[u8],
329 id: Add,
330 fields: &[(&[u8], &[u8])],
331 trim: Trim,
332 mkstream: bool,
333 now: u64,
334 ) -> Result<Option<(Id, u64)>> {
335 let limits = self.stream_limits;
336 let at = match self.live_slot(key, Kind::Stream)? {
337 Some(at) => at,
338 None if mkstream => self.new_stream(key),
339 None => return Ok(None),
340 };
341 let s = self.stream_at(at);
342 let want = match id {
345 Add::Auto => s.auto_id(now).ok_or_else(exhausted)?,
346 Add::Seq(ms) => s.auto_seq(ms).ok_or_else(|| {
347 if ms < s.last_id().ms {
348 Error::new(Code::Invalid, NOT_GREATER)
349 } else {
350 exhausted()
351 }
352 })?,
353 Add::At(id) => id,
354 };
355 s.append(want, fields, limits).map_err(refused)?;
356 let cut = cut(s, trim);
357 Ok(Some((want, cut)))
358 }
359
360 pub fn xdel(&mut self, key: &[u8], ids: impl Iterator<Item = Id>) -> Result<u64> {
366 let Some(at) = self.live_slot(key, Kind::Stream)? else {
367 return Ok(0);
368 };
369 let s = self.stream_at(at);
370 Ok(ids.filter(|&id| s.delete(id)).count() as u64)
371 }
372
373 pub fn xdelex<F>(
385 &mut self,
386 key: &[u8],
387 refs: Refs,
388 ids: impl Iterator<Item = Id>,
389 mut f: F,
390 ) -> Result<u64>
391 where
392 F: FnMut(Fate),
393 {
394 let Some(at) = self.live_slot(key, Kind::Stream)? else {
395 ids.for_each(|_| f(Fate::Missing));
396 return Ok(0);
397 };
398 let s = self.stream_at(at);
399 let mut gone = 0;
400 ids.for_each(|id| {
401 let fate = s.delete_ref(id, refs);
402 gone += u64::from(fate == Fate::Gone);
403 f(fate);
404 });
405 Ok(gone)
406 }
407
408 pub fn xackdel<F>(
422 &mut self,
423 key: &[u8],
424 group: &[u8],
425 refs: Refs,
426 ids: impl Iterator<Item = Id>,
427 mut f: F,
428 ) -> Result<u64>
429 where
430 F: FnMut(Fate),
431 {
432 let Some(at) = self.live_slot(key, Kind::Stream)? else {
433 ids.for_each(|_| f(Fate::Missing));
434 return Ok(0);
435 };
436 let s = self.stream_at(at);
437 let mut gone = 0;
438 ids.for_each(|id| {
439 let (fate, took) = s.ack_delete(group, id, refs);
440 gone += u64::from(took);
441 f(fate);
442 });
443 Ok(gone)
444 }
445
446 pub fn xnack(
455 &mut self,
456 key: &[u8],
457 group: &[u8],
458 retry: Retry,
459 force: bool,
460 ids: impl Iterator<Item = Id>,
461 ) -> Result<Option<u64>> {
462 let Some(at) = self.live_slot(key, Kind::Stream)? else {
463 return Ok(None);
464 };
465 let s = self.stream_at(at);
466 if s.group(group).is_none() {
467 return Ok(None);
468 }
469 let mut done = 0;
470 for id in ids {
471 done += u64::from(s.nack(group, id, retry, force).unwrap_or(false));
472 }
473 Ok(Some(done))
474 }
475
476 pub fn xtrim(&mut self, key: &[u8], trim: Trim) -> Result<u64> {
482 let Some(at) = self.live_slot(key, Kind::Stream)? else {
483 return Ok(0);
484 };
485 Ok(cut(self.stream_at(at), trim))
486 }
487
488 pub fn xsetid(
496 &mut self,
497 key: &[u8],
498 last: Id,
499 added: Option<u64>,
500 max_deleted: Option<Id>,
501 ) -> Result<()> {
502 let Some(at) = self.live_slot(key, Kind::Stream)? else {
503 return Err(Error::new(Code::NotFound, NO_SUCH_KEY));
504 };
505 if max_deleted.is_some_and(|id| last < id) {
509 return Err(Error::new(Code::Invalid, SETID_BELOW_MAX_DELETED));
510 }
511 self.stream_at(at)
512 .set_id(last, added, max_deleted)
513 .map_err(|_| Error::new(Code::Invalid, SETID_TOO_SMALL))
514 }
515
516 pub fn xrange_into<F>(
526 &mut self,
527 key: &[u8],
528 start: Id,
529 end: Id,
530 count: Option<usize>,
531 rev: bool,
532 f: F,
533 ) -> Result<usize>
534 where
535 F: FnMut(Id, Fields<'_>) -> bool,
536 {
537 let Some(s) = self.stream(key)? else {
538 return Ok(0);
539 };
540 Ok(if rev {
541 s.rev_range(start, end, count, f)
542 } else {
543 s.range(start, end, count, f)
544 })
545 }
546
547 pub fn xread_into<F>(
557 &mut self,
558 key: &[u8],
559 after: Id,
560 count: Option<usize>,
561 f: F,
562 ) -> Result<usize>
563 where
564 F: FnMut(Id, Fields<'_>) -> bool,
565 {
566 let Some(from) = after.next() else {
567 return Ok(0);
568 };
569 self.xrange_into(key, from, Id::MAX, count, false, f)
570 }
571
572 pub fn xgroup_create(
582 &mut self,
583 key: &[u8],
584 group: &[u8],
585 at: Start,
586 mkstream: bool,
587 read: Option<u64>,
588 ) -> Result<bool> {
589 let slot = match self.live_slot(key, Kind::Stream)? {
590 Some(slot) => slot,
591 None if mkstream => self.new_stream(key),
592 None => return Err(Error::new(Code::NotFound, NO_KEY_FOR_GROUP)),
593 };
594 let s = self.stream_at(slot);
595 let last = position(s, at);
596 let read = capped(read, s);
602 Ok(s.create_group(group, last, read))
603 }
604
605 pub fn xgroup_destroy(&mut self, key: &[u8], group: &[u8]) -> Result<bool> {
618 let Some(at) = self.live_slot(key, Kind::Stream)? else {
619 return Err(Error::new(Code::NotFound, NO_KEY_FOR_GROUP));
620 };
621 Ok(self.stream_at(at).destroy_group(group))
622 }
623
624 pub fn xgroup_setid(
633 &mut self,
634 key: &[u8],
635 group: &[u8],
636 at: Start,
637 read: Option<u64>,
638 ) -> Result<Option<()>> {
639 let Some(slot) = self.live_slot(key, Kind::Stream)? else {
640 return Err(Error::new(Code::NotFound, NO_KEY_FOR_GROUP));
641 };
642 let s = self.stream_at(slot);
643 let last = position(s, at);
644 let read = capped(read, s);
645 let Some(g) = s.group_mut(group) else {
646 return Ok(None);
647 };
648 g.set_id(last, read);
655 Ok(Some(()))
656 }
657
658 pub fn xgroup_create_consumer(
668 &mut self,
669 key: &[u8],
670 group: &[u8],
671 consumer: &[u8],
672 now: u64,
673 ) -> Result<Option<bool>> {
674 let Some(g) = self.group_mut_of(key, group)? else {
675 return Ok(None);
676 };
677 Ok(Some(g.create_consumer(consumer, now)))
678 }
679
680 pub fn xgroup_del_consumer(
690 &mut self,
691 key: &[u8],
692 group: &[u8],
693 consumer: &[u8],
694 ) -> Result<Option<u64>> {
695 let Some(g) = self.group_mut_of(key, group)? else {
696 return Ok(None);
697 };
698 Ok(Some(g.delete_consumer(consumer)))
699 }
700
701 pub fn xreadgroup_into<F>(
714 &mut self,
715 key: &[u8],
716 want: Read<'_>,
717 now: u64,
718 mut f: F,
719 ) -> Result<Option<usize>>
720 where
721 F: FnMut(Id, Option<Fields<'_>>) -> bool,
722 {
723 let Some(at) = self.live_slot(key, Kind::Stream)? else {
724 return Ok(None);
725 };
726 let s = self.stream_at(at);
727 Ok(match want.from {
728 From::New => s.read_group(
729 want.group,
730 want.consumer,
731 want.count,
732 want.noack,
733 now,
734 |id, fields| f(id, Some(fields)),
735 ),
736 From::Pending(after) => {
737 s.read_group_pending(want.group, want.consumer, after, want.count, now, &mut f)
738 }
739 })
740 }
741
742 pub fn xack(&mut self, key: &[u8], group: &[u8], ids: impl Iterator<Item = Id>) -> Result<u64> {
748 let Some(at) = self.live_slot(key, Kind::Stream)? else {
749 return Ok(0);
750 };
751 let Some(g) = self.stream_at(at).group_mut(group) else {
752 return Ok(0);
753 };
754 Ok(ids.filter(|&id| g.ack(id)).count() as u64)
755 }
756
757 pub fn xpending_into<F>(
766 &mut self,
767 key: &[u8],
768 group: &[u8],
769 want: Filter,
770 now: u64,
771 f: F,
772 ) -> Result<Option<usize>>
773 where
774 F: FnMut(Id, &crate::stream::Nack, Option<&crate::stream::Consumer>) -> bool,
775 {
776 let Some(s) = self.stream(key)? else {
777 return Ok(None);
778 };
779 let Some(g) = s.group(group) else {
780 return Ok(None);
781 };
782 Ok(Some(g.pending_range(want, now, f)))
783 }
784
785 pub fn xclaim(
796 &mut self,
797 key: &[u8],
798 ids: &[Id],
799 how: Claim<'_>,
800 now: u64,
801 gone: &mut Vec<Id>,
802 ) -> Result<Option<Vec<Id>>> {
803 let Some(at) = self.live_slot(key, Kind::Stream)? else {
804 return Ok(None);
805 };
806 Ok(self.stream_at(at).claim(
807 how.group,
808 how.consumer,
809 ids,
810 how.min_idle,
811 how.time,
812 how.retry,
813 how.bump,
814 how.force,
815 now,
816 gone,
817 ))
818 }
819
820 pub fn xautoclaim(
830 &mut self,
831 key: &[u8],
832 start: Id,
833 how: Claim<'_>,
834 count: usize,
835 now: u64,
836 gone: &mut Vec<Id>,
837 ) -> Result<Option<(Option<Id>, Vec<Id>)>> {
838 let Some(at) = self.live_slot(key, Kind::Stream)? else {
839 return Ok(None);
840 };
841 Ok(self.stream_at(at).autoclaim(
842 how.group,
843 how.consumer,
844 start,
845 how.min_idle,
846 count,
847 how.bump,
848 now,
849 gone,
850 ))
851 }
852
853 fn group_mut_of(&mut self, key: &[u8], group: &[u8]) -> Result<Option<&mut Group>> {
861 let Some(at) = self.live_slot(key, Kind::Stream)? else {
862 return Err(Error::new(Code::NotFound, NO_KEY_FOR_GROUP));
863 };
864 Ok(self.stream_at(at).group_mut(group))
865 }
866
867 fn stream_at(&mut self, at: u32) -> &mut Stream {
868 self.streams
869 .get_mut(at)
870 .expect("the record points at its body")
871 }
872
873 fn new_stream(&mut self, key: &[u8]) -> u32 {
874 let at = self.streams.insert(Stream::new());
875 let len = value::slot_record_len(false);
876 self.write_rec(key, len, |out| {
877 value::write_slot_record(out, Kind::Stream, at, None);
878 });
879 self.bodies += 1;
880 at
881 }
882}
883
884fn position(s: &Stream, at: Start) -> Id {
886 match at {
887 Start::Last => s.last_id(),
888 Start::At(id) => id,
889 }
890}
891
892fn capped(read: Option<u64>, s: &Stream) -> Option<u64> {
901 read.map(|n| n.min(s.added()))
902}
903
904fn cut(s: &mut Stream, trim: Trim) -> u64 {
906 match trim {
907 Trim::None => 0,
908 Trim::MaxLen { len, exact, limit } => s.trim_maxlen(len, exact, limit),
909 Trim::MinId { id, exact, limit } => s.trim_minid(id, exact, limit),
910 }
911}
912
913fn exhausted() -> Error {
914 Error::new(Code::Invalid, EXHAUSTED)
915}
916
917fn refused(why: Refused) -> Error {
918 match why {
919 Refused::Zero => Error::new(Code::Invalid, ZERO_ID),
920 Refused::NotGreater => Error::new(Code::Invalid, NOT_GREATER),
921 Refused::Full => exhausted(),
922 }
923}
924
925#[cfg(test)]
926mod tests {
927 use super::*;
928
929 fn db() -> Keyspace {
930 Keyspace::new()
931 }
932
933 fn add(d: &mut Keyspace, key: &[u8], id: Add) -> Id {
935 d.xadd(
936 key,
937 id,
938 &[(b"sensor", b"a4"), (b"reading", b"21.5")],
939 Trim::None,
940 true,
941 1_000,
942 )
943 .expect("a stream")
944 .expect("an ID")
945 }
946
947 fn ids(d: &mut Keyspace, key: &[u8]) -> Vec<Id> {
949 let mut out = Vec::new();
950 d.xrange_into(key, Id::MIN, Id::MAX, None, false, |id, _| {
951 out.push(id);
952 true
953 })
954 .expect("a stream");
955 out
956 }
957
958 #[test]
959 fn a_write_makes_the_key_and_a_read_finds_it() {
960 let mut d = db();
961 assert_eq!(d.kind_of(b"s"), None);
962 let id = add(&mut d, b"s", Add::At(Id::new(5, 0)));
963 assert_eq!(id, Id::new(5, 0));
964 assert_eq!(d.kind_of(b"s"), Some(Kind::Stream));
965 assert_eq!(d.type_name(b"s"), Some("stream"));
966 assert_eq!(d.encoding_name(b"s"), Some("stream"));
967 assert_eq!(ids(&mut d, b"s"), vec![Id::new(5, 0)]);
968 }
969
970 #[test]
971 fn nomkstream_leaves_a_missing_key_missing() {
972 let mut d = db();
973 let got = d
974 .xadd(b"s", Add::Auto, &[(b"f", b"v")], Trim::None, false, 1_000)
975 .expect("a stream");
976 assert_eq!(got, None);
977 assert_eq!(d.kind_of(b"s"), None);
978 }
979
980 #[test]
981 fn an_auto_id_follows_the_clock_and_then_the_last_id() {
982 let mut d = db();
983 assert_eq!(add(&mut d, b"s", Add::Auto), Id::new(1_000, 0));
984 assert_eq!(add(&mut d, b"s", Add::Auto), Id::new(1_000, 1));
986 assert_eq!(add(&mut d, b"s", Add::Seq(1_000)), Id::new(1_000, 2));
987 assert_eq!(add(&mut d, b"s", Add::Seq(2_000)), Id::new(2_000, 0));
988 }
989
990 #[test]
991 fn an_id_that_is_not_above_the_last_one_is_refused() {
992 let mut d = db();
993 add(&mut d, b"s", Add::At(Id::new(5, 0)));
994 let e = d
995 .xadd(
996 b"s",
997 Add::At(Id::new(5, 0)),
998 &[(b"f", b"v")],
999 Trim::None,
1000 true,
1001 1_000,
1002 )
1003 .expect_err("not above the last one");
1004 assert_eq!(e.message(), NOT_GREATER);
1005 let e = d
1007 .xadd(b"s", Add::Seq(4), &[(b"f", b"v")], Trim::None, true, 1_000)
1008 .expect_err("a millisecond that has gone by");
1009 assert_eq!(e.message(), NOT_GREATER);
1010 }
1011
1012 #[test]
1013 fn zero_is_refused_and_says_so_in_its_own_words() {
1014 let mut d = db();
1015 let e = d
1016 .xadd(
1017 b"s",
1018 Add::At(Id::MIN),
1019 &[(b"f", b"v")],
1020 Trim::None,
1021 true,
1022 1_000,
1023 )
1024 .expect_err("nothing sorts below zero");
1025 assert_eq!(e.message(), ZERO_ID);
1026 }
1027
1028 #[test]
1029 fn a_stream_is_not_deleted_when_the_last_entry_goes() {
1030 let mut d = db();
1031 let id = add(&mut d, b"s", Add::At(Id::new(5, 0)));
1032 assert_eq!(d.xdel(b"s", [id].into_iter()).expect("a stream"), 1);
1033 assert_eq!(d.xdel(b"s", [id].into_iter()).expect("a stream"), 0);
1034 assert_eq!(d.kind_of(b"s"), Some(Kind::Stream), "the key is still here");
1035 assert_eq!(add(&mut d, b"s", Add::Auto), Id::new(1_000, 0));
1038 }
1039
1040 #[test]
1041 fn a_trim_runs_after_the_append() {
1042 let mut d = db();
1043 for ms in 1..=10u64 {
1044 add(&mut d, b"s", Add::At(Id::new(ms, 0)));
1045 }
1046 let trim = Trim::MaxLen {
1047 len: 1,
1048 exact: true,
1049 limit: None,
1050 };
1051 let id = d
1052 .xadd(
1053 b"s",
1054 Add::At(Id::new(11, 0)),
1055 &[(b"f", b"v")],
1056 trim,
1057 true,
1058 1,
1059 )
1060 .expect("a stream")
1061 .expect("an ID");
1062 assert_eq!(ids(&mut d, b"s"), vec![id]);
1065 }
1066
1067 #[test]
1068 fn a_limit_stops_a_trim_at_the_next_node_boundary() {
1069 let mut d = db();
1070 for ms in 1..=1_000u64 {
1071 add(&mut d, b"s", Add::At(Id::new(ms, 0)));
1072 }
1073 let trim = Trim::MaxLen {
1074 len: 0,
1075 exact: false,
1076 limit: Some(10),
1077 };
1078 assert_eq!(d.xtrim(b"s", trim).expect("a stream"), 100);
1084 assert_eq!(
1085 d.stream(b"s").expect("a stream").expect("the key").len(),
1086 900
1087 );
1088 }
1089
1090 #[test]
1091 fn setid_moves_the_bookmark_and_refuses_to_go_below_an_entry() {
1092 let mut d = db();
1093 add(&mut d, b"s", Add::At(Id::new(5, 0)));
1094 let e = d
1095 .xsetid(b"s", Id::new(4, 0), None, None)
1096 .expect_err("below an entry that is still there");
1097 assert_eq!(e.message(), SETID_TOO_SMALL);
1098 d.xsetid(b"s", Id::new(9, 0), Some(41), None)
1099 .expect("above it");
1100 let s = d.stream(b"s").expect("a stream").expect("the key");
1101 assert_eq!((s.last_id(), s.added()), (Id::new(9, 0), 41));
1102 }
1103
1104 #[test]
1105 fn setid_on_a_key_that_is_not_there_says_so() {
1106 let mut d = db();
1107 let e = d
1108 .xsetid(b"s", Id::new(1, 0), None, None)
1109 .expect_err("no key");
1110 assert_eq!((e.code(), e.message()), (Code::NotFound, NO_SUCH_KEY));
1111 }
1112
1113 #[test]
1114 fn every_command_sees_the_wrong_type() {
1115 let mut d = db();
1116 d.set_plain(b"s", b"a string").expect("a string");
1117 for e in [
1118 d.xadd(b"s", Add::Auto, &[(b"f", b"v")], Trim::None, true, 1)
1119 .expect_err("a string"),
1120 d.xdel(b"s", [Id::new(1, 0)].into_iter())
1121 .expect_err("a string"),
1122 d.xtrim(b"s", Trim::None).expect_err("a string"),
1123 d.xrange_into(b"s", Id::MIN, Id::MAX, None, false, |_, _| true)
1124 .expect_err("a string"),
1125 d.xack(b"s", b"g", [Id::new(1, 0)].into_iter())
1126 .expect_err("a string"),
1127 ] {
1128 assert_eq!(e.code(), Code::WrongType);
1129 }
1130 }
1131
1132 #[test]
1139 fn a_new_group_has_no_read_counter() {
1140 let mut d = db();
1141 for ms in 1..=5 {
1142 add(&mut d, b"s", Add::At(Id::new(ms, 0)));
1143 }
1144
1145 assert!(
1146 d.xgroup_create(b"s", b"early", Start::At(Id::MIN), false, None)
1147 .expect("a stream")
1148 );
1149 assert!(
1150 d.xgroup_create(b"s", b"late", Start::Last, false, None)
1151 .expect("a stream")
1152 );
1153 let s = d.stream(b"s").expect("a stream").expect("the key");
1154 let early = s.group(b"early").expect("the early group");
1155 assert_eq!(early.entries_read(), None);
1156 assert_eq!(s.lag(early), Some(5), "everything is still in front of it");
1157 let late = s.group(b"late").expect("the late group");
1158 assert_eq!(late.entries_read(), None);
1159 assert_eq!(s.lag(late), Some(0), "and nothing is in front of this one");
1160 }
1161
1162 #[test]
1170 fn a_read_counter_that_is_too_big_is_brought_back_down() {
1171 let mut d = db();
1172 for ms in 1..=3 {
1173 add(&mut d, b"s", Add::At(Id::new(ms, 0)));
1174 }
1175
1176 d.xgroup_create(b"s", b"g", Start::At(Id::MIN), false, Some(99))
1177 .expect("a stream");
1178 assert_eq!(
1179 counter(&mut d, b"g"),
1180 Some(3),
1181 "held down to what was added"
1182 );
1183
1184 d.xgroup_setid(b"s", b"g", Start::At(Id::MIN), Some(2))
1185 .expect("a stream")
1186 .expect("the group");
1187 assert_eq!(counter(&mut d, b"g"), Some(2), "and left alone below that");
1188
1189 d.xgroup_setid(b"s", b"g", Start::At(Id::MIN), None)
1190 .expect("a stream")
1191 .expect("the group");
1192 assert_eq!(
1193 counter(&mut d, b"g"),
1194 None,
1195 "and given up when none is named"
1196 );
1197 }
1198
1199 fn counter(d: &mut Keyspace, group: &[u8]) -> Option<u64> {
1201 d.stream(b"s")
1202 .expect("a stream")
1203 .expect("the key")
1204 .group(group)
1205 .expect("the group")
1206 .entries_read()
1207 }
1208
1209 #[test]
1216 fn setid_refuses_a_last_id_under_its_own_deletion_mark() {
1217 let mut d = db();
1218 add(&mut d, b"s", Add::At(Id::new(5, 0)));
1219
1220 let e = d
1221 .xsetid(b"s", Id::new(9, 9), None, Some(Id::new(99, 99)))
1222 .expect_err("the pair contradicts itself");
1223 assert_eq!(e.message(), SETID_BELOW_MAX_DELETED);
1224
1225 d.xsetid(b"s", Id::new(99, 99), None, Some(Id::new(9, 9)))
1226 .expect("the other way round is fine");
1227 let s = d.stream(b"s").expect("a stream").expect("the key");
1228 assert_eq!(s.last_id(), Id::new(99, 99));
1229 assert_eq!(s.max_deleted_id(), Id::new(9, 9));
1230 }
1231
1232 #[test]
1239 fn a_history_read_by_a_name_nobody_has_used_makes_the_consumer() {
1240 let mut d = db();
1241 add(&mut d, b"s", Add::At(Id::new(1, 0)));
1242 d.xgroup_create(b"s", b"g", Start::At(Id::MIN), false, None)
1243 .expect("a stream");
1244
1245 let want = Read {
1246 group: b"g",
1247 consumer: b"newbie",
1248 from: From::Pending(Id::MIN),
1249 count: None,
1250 noack: false,
1251 };
1252 let seen = d
1253 .xreadgroup_into(b"s", want, 500, |_, _| true)
1254 .expect("a stream")
1255 .expect("the group is there");
1256 assert_eq!(seen, 0, "nothing was ever handed to this name");
1257
1258 let s = d.stream(b"s").expect("a stream").expect("the key");
1259 let c = s
1260 .group(b"g")
1261 .expect("the group")
1262 .consumer_named(b"newbie")
1263 .expect("the read made it");
1264 assert_eq!(c.seen(), 500, "it was heard from");
1265 assert_eq!(c.active(), None, "and it has never had anything");
1266 }
1267
1268 #[test]
1269 fn a_group_reads_what_arrives_after_it_was_made() {
1270 let mut d = db();
1271 add(&mut d, b"s", Add::At(Id::new(1, 0)));
1272 assert!(
1273 d.xgroup_create(b"s", b"workers", Start::Last, false, None)
1274 .expect("a stream")
1275 );
1276 assert!(
1279 !d.xgroup_create(b"s", b"workers", Start::Last, false, None)
1280 .expect("a stream")
1281 );
1282 add(&mut d, b"s", Add::At(Id::new(2, 0)));
1283
1284 let mut got = Vec::new();
1285 let seen = d
1286 .xreadgroup_into(
1287 b"s",
1288 Read {
1289 group: b"workers",
1290 consumer: b"alice",
1291 from: From::New,
1292 count: None,
1293 noack: false,
1294 },
1295 1_000,
1296 |id, fields| {
1297 got.push((id, fields.is_some()));
1298 true
1299 },
1300 )
1301 .expect("a stream")
1302 .expect("a group");
1303 assert_eq!(seen, 1, "only what arrived after the group was made");
1304 assert_eq!(got, vec![(Id::new(2, 0), true)]);
1305 assert_eq!(
1306 d.xack(b"s", b"workers", [Id::new(2, 0)].into_iter())
1307 .expect("a stream"),
1308 1
1309 );
1310 }
1311
1312 #[test]
1313 fn noack_hands_the_entry_over_without_writing_it_down() {
1314 let mut d = db();
1315 add(&mut d, b"s", Add::At(Id::new(1, 0)));
1316 d.xgroup_create(b"s", b"workers", Start::At(Id::MIN), false, None)
1317 .expect("a stream");
1318 let seen = d
1319 .xreadgroup_into(
1320 b"s",
1321 Read {
1322 group: b"workers",
1323 consumer: b"alice",
1324 from: From::New,
1325 count: None,
1326 noack: true,
1327 },
1328 1_000,
1329 |_, _| true,
1330 )
1331 .expect("a stream")
1332 .expect("a group");
1333 assert_eq!(seen, 1);
1334 let s = d.stream(b"s").expect("a stream").expect("the key");
1335 let g = s.group(b"workers").expect("the group");
1336 assert_eq!(g.pending_len(), 0, "nothing was written down");
1337 assert_eq!(g.last_id(), Id::new(1, 0), "the bookmark still moved");
1338 assert_eq!(s.lag(g), Some(0), "and the group has caught up");
1339 }
1340
1341 #[test]
1342 fn a_missing_group_is_a_none_and_not_an_error() {
1343 let mut d = db();
1344 add(&mut d, b"s", Add::At(Id::new(1, 0)));
1345 let got = d
1346 .xreadgroup_into(
1347 b"s",
1348 Read {
1349 group: b"nope",
1350 consumer: b"alice",
1351 from: From::New,
1352 count: None,
1353 noack: false,
1354 },
1355 1,
1356 |_, _| true,
1357 )
1358 .expect("a stream");
1359 assert_eq!(got, None);
1360 assert!(!d.xgroup_destroy(b"s", b"nope").expect("a stream"));
1361 assert_eq!(
1362 d.xgroup_create_consumer(b"s", b"nope", b"alice", 1)
1363 .expect("a stream"),
1364 None
1365 );
1366 }
1367
1368 #[test]
1369 fn xgroup_needs_the_key_and_says_which_option_makes_one() {
1370 let mut d = db();
1371 let e = d
1372 .xgroup_create(b"s", b"workers", Start::Last, false, None)
1373 .expect_err("no key");
1374 assert_eq!((e.code(), e.message()), (Code::NotFound, NO_KEY_FOR_GROUP));
1375 assert!(
1376 d.xgroup_create(b"s", b"workers", Start::Last, true, None)
1377 .expect("mkstream made one")
1378 );
1379 assert_eq!(d.kind_of(b"s"), Some(Kind::Stream));
1380 }
1381
1382 #[test]
1383 fn a_claim_moves_an_idle_entry_and_drops_one_that_has_gone() {
1384 let mut d = db();
1385 for ms in 1..=2u64 {
1386 add(&mut d, b"s", Add::At(Id::new(ms, 0)));
1387 }
1388 d.xgroup_create(b"s", b"workers", Start::At(Id::MIN), false, None)
1389 .expect("a stream");
1390 d.xreadgroup_into(
1391 b"s",
1392 Read {
1393 group: b"workers",
1394 consumer: b"alice",
1395 from: From::New,
1396 count: None,
1397 noack: false,
1398 },
1399 100,
1400 |_, _| true,
1401 )
1402 .expect("a stream")
1403 .expect("a group");
1404 d.xdel(b"s", [Id::new(1, 0)].into_iter()).expect("a stream");
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 took = d
1415 .xclaim(b"s", &[Id::new(1, 0), Id::new(2, 0)], how, 1_000, &mut gone)
1416 .expect("a stream")
1417 .expect("a group");
1418 assert_eq!(took, vec![Id::new(2, 0)]);
1419 assert_eq!(gone, vec![Id::new(1, 0)], "no one can ever finish that one");
1420 }
1421
1422 #[test]
1423 fn an_autoclaim_sweeps_from_a_cursor() {
1424 let mut d = db();
1425 for ms in 1..=10u64 {
1426 add(&mut d, b"s", Add::At(Id::new(ms, 0)));
1427 }
1428 d.xgroup_create(b"s", b"workers", Start::At(Id::MIN), false, None)
1429 .expect("a stream");
1430 d.xreadgroup_into(
1431 b"s",
1432 Read {
1433 group: b"workers",
1434 consumer: b"alice",
1435 from: From::New,
1436 count: None,
1437 noack: false,
1438 },
1439 100,
1440 |_, _| true,
1441 )
1442 .expect("a stream")
1443 .expect("a group");
1444
1445 let mut gone = Vec::new();
1446 let how = Claim {
1447 group: b"workers",
1448 consumer: b"bob",
1449 min_idle: 500,
1450 time: 1_000,
1451 ..Claim::default()
1452 };
1453 let (cursor, took) = d
1454 .xautoclaim(b"s", Id::MIN, how, 4, 1_000, &mut gone)
1455 .expect("a stream")
1456 .expect("a group");
1457 assert_eq!(took.len(), 4);
1458 assert_eq!(cursor, Some(Id::new(5, 0)), "where the next call starts");
1459 assert!(gone.is_empty());
1460 }
1461
1462 #[test]
1463 fn a_pending_window_carries_the_owner_and_the_counts() {
1464 let mut d = db();
1465 for ms in 1..=3u64 {
1466 add(&mut d, b"s", Add::At(Id::new(ms, 0)));
1467 }
1468 d.xgroup_create(b"s", b"workers", Start::At(Id::MIN), false, None)
1469 .expect("a stream");
1470 d.xreadgroup_into(
1471 b"s",
1472 Read {
1473 group: b"workers",
1474 consumer: b"alice",
1475 from: From::New,
1476 count: None,
1477 noack: false,
1478 },
1479 100,
1480 |_, _| true,
1481 )
1482 .expect("a stream")
1483 .expect("a group");
1484
1485 let mut out = Vec::new();
1486 let seen = d
1487 .xpending_into(b"s", b"workers", Filter::default(), 600, |id, nack, c| {
1488 out.push((
1489 id,
1490 nack.count(),
1491 nack.idle(600),
1492 c.expect("an owner").name().to_vec(),
1493 ));
1494 true
1495 })
1496 .expect("a stream")
1497 .expect("a group");
1498 assert_eq!(seen, 3);
1499 assert_eq!(out[0], (Id::new(1, 0), 1, 500, b"alice".to_vec()));
1500 }
1501
1502 #[test]
1503 fn a_history_read_hands_back_a_null_for_an_entry_that_has_gone() {
1504 let mut d = db();
1505 for ms in 1..=2u64 {
1506 add(&mut d, b"s", Add::At(Id::new(ms, 0)));
1507 }
1508 d.xgroup_create(b"s", b"workers", Start::At(Id::MIN), false, None)
1509 .expect("a stream");
1510 d.xreadgroup_into(
1511 b"s",
1512 Read {
1513 group: b"workers",
1514 consumer: b"alice",
1515 from: From::New,
1516 count: None,
1517 noack: false,
1518 },
1519 100,
1520 |_, _| true,
1521 )
1522 .expect("a stream")
1523 .expect("a group");
1524 d.xdel(b"s", [Id::new(1, 0)].into_iter()).expect("a stream");
1525
1526 let mut out = Vec::new();
1527 d.xreadgroup_into(
1528 b"s",
1529 Read {
1530 group: b"workers",
1531 consumer: b"alice",
1532 from: From::Pending(Id::MIN),
1533 count: None,
1534 noack: false,
1535 },
1536 2_000,
1537 |id, fields| {
1538 out.push((id, fields.is_some()));
1539 true
1540 },
1541 )
1542 .expect("a stream")
1543 .expect("a group");
1544 assert_eq!(out, vec![(Id::new(1, 0), false), (Id::new(2, 0), true)]);
1545 }
1546
1547 #[test]
1548 fn a_stream_counts_against_the_memory_total() {
1549 let mut d = db();
1550 let before = d.memory_bytes();
1551 for ms in 1..=1_000u64 {
1552 add(&mut d, b"s", Add::At(Id::new(ms, 0)));
1553 }
1554 let after = d.memory_bytes();
1555 assert!(after > before + 1_000, "{before} then {after}");
1556 assert!(d.del(b"s"), "the key was there");
1557 assert_eq!(d.kind_of(b"s"), None);
1558 }
1559
1560 #[test]
1561 fn a_deadline_goes_on_a_stream_the_same_as_on_anything_else() {
1562 let mut d = db();
1563 add(&mut d, b"s", Add::At(Id::new(1, 0)));
1564 assert!(d.set_expiry(b"s", Some(1 << 45)));
1565 assert_eq!(d.kind_of(b"s"), Some(Kind::Stream));
1566 assert_eq!(ids(&mut d, b"s"), vec![Id::new(1, 0)], "the body is intact");
1567 }
1568}