1mod args;
55mod arrays;
56mod backup;
57mod bits;
58mod blocking;
59mod bloom;
60mod cms;
61mod cpu;
62mod cuckoo;
63mod geo;
64mod graph;
65mod hashes;
66mod himport;
67mod hll;
68mod indexing;
69mod json;
70mod keyspace;
71mod lists;
72mod migrate;
73mod scan;
74mod scripting;
75mod search;
76mod server;
77mod sets;
78mod streams;
79mod strings;
80pub mod table;
81mod tdigest;
82mod topk;
83mod ts;
84mod vectors;
85mod vfilter;
86mod zsets;
87
88pub use args::Args;
89pub use blocking::{Parked, Waiters};
90pub use server::parse_memory;
91pub use table::{COMMANDS, Spec, arity_ok, lookup};
92
93use crate::reply::Out;
94use std::cell::Cell;
95use std::path::{Path, PathBuf};
96use std::sync::atomic::Ordering::{Acquire, Relaxed, Release};
97use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize};
98use yo_common::lock::{Held, Lock};
99use yo_common::{Code, Error};
100use yo_kv::cold::Store;
101use yo_kv::{Clock, Db, Keyspace};
102use yo_search::Registry;
103
104use search::cursor::Cursors;
105
106pub const DATABASES: usize = 16;
113
114const ALL_DATABASES: u64 = if DATABASES == 64 {
121 u64::MAX
122} else {
123 (1u64 << DATABASES) - 1
124};
125const _: () = assert!(DATABASES <= 64);
126
127const EVICT_BUDGET: usize = 64;
139
140const NO_MAXSTORE: u64 = u64::MAX;
148
149const OOM: &[u8] = b"command not allowed when used memory > 'maxmemory'.";
154
155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
157pub enum Flow {
158 Continue,
160 Close,
162 Block,
169}
170
171#[derive(Debug, Default)]
181pub struct Counter(AtomicU64);
182
183impl Counter {
184 fn bump(&self) {
186 self.0.store(self.get().wrapping_add(1), Relaxed);
187 }
188
189 fn drop_one(&self) {
196 self.0.store(self.get().saturating_sub(1), Relaxed);
197 }
198
199 fn get(&self) -> u64 {
201 self.0.load(Relaxed)
202 }
203
204 fn zero(&self) {
206 self.0.store(0, Relaxed);
207 }
208}
209
210#[derive(Debug, Default)]
216pub struct Stats {
217 clients: Counter,
219 connections: Counter,
221 commands: Counter,
223}
224
225impl Stats {
226 pub fn opened(&self) {
228 self.clients.bump();
229 self.connections.bump();
230 }
231
232 pub fn closed(&self) {
234 self.clients.drop_one();
235 }
236}
237
238#[derive(Debug, Clone, Copy, Default)]
240pub struct Totals {
241 pub clients: u64,
243 pub connections: u64,
245 pub commands: u64,
247}
248
249thread_local! {
250 static SLOT: Cell<usize> = const { Cell::new(usize::MAX) };
258}
259
260#[derive(Debug)]
271#[repr(align(64))]
272struct Local {
273 stats: Stats,
275 cmdstats: CommandStats,
277 dirty: AtomicU64,
286 turn: AtomicU64,
299}
300
301impl Default for Local {
302 fn default() -> Local {
303 Local {
304 stats: Stats::default(),
305 cmdstats: CommandStats::default(),
306 dirty: AtomicU64::new(0),
307 turn: AtomicU64::new(ALL_DATABASES),
308 }
309 }
310}
311
312impl Local {
313 fn mark(&self, dbs: u64) {
315 self.dirty.store(self.dirty.load(Relaxed) | dbs, Relaxed);
316 }
317
318 fn note(&self, dbs: u64) {
320 self.turn.store(self.turn.load(Relaxed) | dbs, Relaxed);
321 }
322
323 fn done(&self, at: usize) {
325 self.turn
326 .store(self.turn.load(Relaxed) & !(1u64 << at), Relaxed);
327 }
328
329 fn wanted(&self, at: usize) -> bool {
331 self.turn.load(Relaxed) & (1u64 << at) != 0
332 }
333}
334
335fn one_thread() -> Box<[Local]> {
337 slots(1)
338}
339
340fn slots(threads: usize) -> Box<[Local]> {
342 (0..threads.max(1)).map(|_| Local::default()).collect()
343}
344
345fn working_dir() -> PathBuf {
352 std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
353}
354
355#[derive(Debug, Clone, Copy, Default)]
364pub struct CommandStat {
365 pub calls: u64,
367 pub rejected: u64,
370 pub failed: u64,
372}
373
374impl CommandStat {
375 const fn seen(&self) -> bool {
381 self.calls != 0 || self.rejected != 0 || self.failed != 0
382 }
383}
384
385#[derive(Debug, Default)]
390struct Row {
391 calls: Counter,
393 rejected: Counter,
395 failed: Counter,
397}
398
399#[derive(Debug)]
406struct CommandStats(Box<[Row]>);
407
408impl Default for CommandStats {
409 fn default() -> CommandStats {
410 CommandStats((0..table::count()).map(|_| Row::default()).collect())
411 }
412}
413
414impl CommandStats {
415 fn at(&self, spec: &'static Spec) -> &Row {
417 &self.0[table::index_of(spec)]
418 }
419}
420
421pub type StoreSource = dyn FnMut(usize) -> Option<Store> + Send;
427
428const _: () = {
433 const fn shareable<T: Send + Sync>() {}
434 shareable::<Server>();
435};
436
437pub struct Server {
445 dbs: Vec<Db>,
446 width: usize,
452 clock: Clock,
453 started_ms: u64,
454 next_db: AtomicUsize,
463 conn_bytes: AtomicUsize,
480 maxmemory: AtomicU64,
488 store: Lock<Option<Box<StoreSource>>>,
505 maxstore: AtomicU64,
527 used: AtomicUsize,
544 evict_db: AtomicUsize,
554 expire_db: AtomicUsize,
561 expire_ms: AtomicU64,
571 waiters: Lock<Waiters>,
579 parked: AtomicUsize,
592 peers: Lock<migrate::Peers>,
604 locals: Box<[Local]>,
610 claimed: AtomicUsize,
612 next_client: AtomicU64,
620 dir: PathBuf,
628 backup: Lock<backup::State>,
638 sealed: AtomicBool,
648 search: Lock<Registry>,
663 cursors: Lock<Cursors>,
672 stopping: AtomicBool,
681}
682
683impl Server {
684 #[must_use]
686 pub fn new() -> Server {
687 let clock = Clock::system();
688 Server {
689 dbs: (0..DATABASES)
690 .map(|_| Db::with_clock(clock.clone(), 1))
691 .collect(),
692 width: 1,
693 started_ms: clock.now_ms(),
694 clock,
695 next_db: AtomicUsize::new(0),
696 conn_bytes: AtomicUsize::new(0),
697 maxmemory: AtomicU64::new(0),
698 store: Lock::new(None),
699 maxstore: AtomicU64::new(NO_MAXSTORE),
700 used: AtomicUsize::new(0),
701 evict_db: AtomicUsize::new(0),
702 expire_db: AtomicUsize::new(0),
703 expire_ms: AtomicU64::new(0),
704 waiters: Lock::default(),
705 parked: AtomicUsize::new(0),
706 peers: Lock::default(),
707 locals: one_thread(),
708 claimed: AtomicUsize::new(0),
709 next_client: AtomicU64::new(1),
710 dir: working_dir(),
711 backup: Lock::default(),
712 sealed: AtomicBool::new(false),
713 search: Lock::new(Registry::new()),
714 cursors: Lock::default(),
715 stopping: AtomicBool::new(false),
716 }
717 }
718
719 #[must_use]
730 pub fn with_width(width: usize) -> Server {
731 let mut server = Server::new();
732 let clock = server.clock.clone();
736 server.dbs = (0..DATABASES)
737 .map(|_| Db::with_clock(clock.clone(), width))
738 .collect();
739 server.width = server.dbs[0].width();
740 server
741 }
742
743 #[must_use]
745 pub fn with_clock(clock: Clock) -> Server {
746 Server {
747 dbs: (0..DATABASES)
748 .map(|_| Db::with_clock(clock.clone(), 1))
749 .collect(),
750 width: 1,
751 started_ms: clock.now_ms(),
752 clock,
753 next_db: AtomicUsize::new(0),
754 conn_bytes: AtomicUsize::new(0),
755 maxmemory: AtomicU64::new(0),
756 store: Lock::new(None),
757 maxstore: AtomicU64::new(NO_MAXSTORE),
758 used: AtomicUsize::new(0),
759 evict_db: AtomicUsize::new(0),
760 expire_db: AtomicUsize::new(0),
761 expire_ms: AtomicU64::new(0),
762 waiters: Lock::default(),
763 parked: AtomicUsize::new(0),
764 peers: Lock::default(),
765 locals: one_thread(),
766 claimed: AtomicUsize::new(0),
767 next_client: AtomicU64::new(1),
768 dir: working_dir(),
769 backup: Lock::default(),
770 sealed: AtomicBool::new(false),
771 search: Lock::new(Registry::new()),
772 cursors: Lock::default(),
773 stopping: AtomicBool::new(false),
774 }
775 }
776
777 pub fn striped(&self, i: usize) -> &Db {
801 self.mine().mark(1u64 << i);
802 &self.dbs[i]
803 }
804
805 fn keyspaces(&self) -> impl Iterator<Item = Held<'_, Keyspace>> {
812 self.dbs
813 .iter()
814 .flat_map(|db| (0..db.width()).map(|i| db.hold_stripe(i)))
815 }
816
817 const fn slots(&self) -> usize {
823 DATABASES * self.width
824 }
825
826 const fn slot_db(&self, i: usize) -> usize {
828 i / self.width
829 }
830
831 fn slot(&self, i: usize) -> Held<'_, Keyspace> {
833 let (db, stripe) = (i / self.width, i % self.width);
834 self.dbs[db].hold_stripe(stripe)
835 }
836
837 #[must_use]
839 pub fn dir(&self) -> &Path {
840 &self.dir
841 }
842
843 pub fn set_dir(&mut self, dir: PathBuf) {
850 self.dir = dir;
851 }
852
853 pub fn backup_expire(&self) {
859 backup::expire(self);
860 }
861
862 pub fn stop(&self) {
868 self.stopping.store(true, Release);
869 }
870
871 #[must_use]
877 pub fn stopping(&self) -> bool {
878 self.stopping.load(Acquire)
879 }
880
881 #[must_use]
888 pub fn striped_ref(&self, i: usize) -> &Db {
889 &self.dbs[i]
890 }
891
892 fn settings(&self) -> Held<'_, Keyspace> {
900 self.dbs[0].hold_stripe(0)
901 }
902
903 pub fn refresh_clock(&self) {
914 self.clock.refresh();
915 }
916
917 pub fn advance_clock_ms(&self, ms: u64) {
923 let now = self.clock.now_ms() + ms;
924 self.set_clock_ms(now);
925 }
926
927 pub fn set_clock_ms(&self, ms: u64) {
935 self.clock.set(ms);
936 }
937
938 #[must_use]
940 pub fn uptime_secs(&self) -> u64 {
941 self.clock.now_ms().saturating_sub(self.started_ms) / 1000
942 }
943
944 #[must_use]
952 pub fn memory_bytes(&self) -> usize {
953 self.keyspaces().map(|db| db.memory_bytes()).sum::<usize>() + self.conn_bytes()
954 }
955
956 #[must_use]
962 pub fn dataset_bytes(&self) -> usize {
963 self.keyspaces()
964 .map(|db| db.map().arena().live_bytes() as usize)
965 .sum()
966 }
967
968 #[must_use]
970 pub fn arena_bytes(&self) -> usize {
971 self.keyspaces()
972 .map(|db| db.map().arena().reserved_bytes() as usize)
973 .sum()
974 }
975
976 #[must_use]
978 pub fn index_bytes(&self) -> usize {
979 self.keyspaces()
980 .map(|db| db.map().index().memory_bytes())
981 .sum()
982 }
983
984 #[must_use]
991 pub fn compaction(&self) -> yo_kv::Compaction {
992 self.keyspaces().map(|db| db.map().compaction()).fold(
993 yo_kv::Compaction::default(),
994 |a, b| yo_kv::Compaction {
995 walked: a.walked + b.walked,
996 moved: a.moved + b.moved,
997 bytes: a.bytes + b.bytes,
998 },
999 )
1000 }
1001
1002 #[must_use]
1004 pub fn segment_count(&self) -> usize {
1005 self.keyspaces()
1006 .map(|db| db.map().arena().resident_segments())
1007 .sum()
1008 }
1009
1010 #[must_use]
1012 pub fn conn_bytes(&self) -> usize {
1013 self.conn_bytes.load(Relaxed)
1014 }
1015
1016 pub fn note_conn_bytes(&self, delta: isize) {
1024 self.conn_bytes
1030 .store(self.conn_bytes().saturating_add_signed(delta), Relaxed);
1031 }
1032
1033 #[must_use]
1035 pub fn expired_keys(&self) -> u64 {
1036 self.keyspaces().map(|db| db.expired_keys()).sum()
1037 }
1038
1039 #[must_use]
1041 pub fn evicted_keys(&self) -> u64 {
1042 self.keyspaces().map(|db| db.evicted_keys()).sum()
1043 }
1044
1045 pub fn command_stats(&self) -> impl Iterator<Item = (&'static str, CommandStat)> {
1051 (0..table::count())
1052 .map(|at| (table::name_at(at), self.command_stat(at)))
1053 .filter(|(_, row)| row.seen())
1054 }
1055
1056 fn command_stat(&self, at: usize) -> CommandStat {
1058 let mut sum = CommandStat::default();
1059 for thread in &self.locals {
1060 let row = &thread.cmdstats.0[at];
1061 sum.calls += row.calls.get();
1062 sum.rejected += row.rejected.get();
1063 sum.failed += row.failed.get();
1064 }
1065 sum
1066 }
1067
1068 pub fn counted(&self) -> &Stats {
1077 &self.mine().stats
1078 }
1079
1080 pub fn next_client(&self) -> u64 {
1085 self.next_client.fetch_add(1, Relaxed)
1086 }
1087
1088 pub fn my_slot(&self) -> usize {
1095 self.mine_at()
1096 }
1097
1098 fn mine(&self) -> &Local {
1100 &self.locals[self.mine_at()]
1101 }
1102
1103 fn mine_at(&self) -> usize {
1110 let mut slot = SLOT.get();
1111 if slot == usize::MAX {
1112 slot = self.claimed.fetch_add(1, Relaxed);
1113 SLOT.set(slot);
1114 }
1115 slot % self.locals.len()
1116 }
1117
1118 #[must_use]
1120 pub fn totals(&self) -> Totals {
1121 let mut sum = Totals::default();
1122 for thread in &self.locals {
1123 sum.clients += thread.stats.clients.get();
1124 sum.connections += thread.stats.connections.get();
1125 sum.commands += thread.stats.commands.get();
1126 }
1127 sum
1128 }
1129
1130 pub fn reset_stats(&self) {
1137 for thread in &self.locals {
1138 thread.stats.connections.zero();
1139 thread.stats.commands.zero();
1140 }
1141 }
1142
1143 pub fn set_threads(&mut self, threads: usize) {
1150 self.locals = slots(threads);
1151 self.claimed = AtomicUsize::new(0);
1152 }
1153
1154 #[must_use]
1156 pub fn maxmemory(&self) -> u64 {
1157 self.maxmemory.load(Relaxed)
1158 }
1159
1160 pub fn set_maxmemory(&self, bytes: u64) {
1174 self.maxmemory.store(bytes, Relaxed);
1175 for db in &self.dbs {
1176 db.track_memory(bytes != 0);
1177 }
1178 self.used.store(self.settled_memory(), Relaxed);
1179 }
1180
1181 pub fn set_store_source(
1191 &mut self,
1192 source: impl FnMut(usize) -> Option<Store> + Send + 'static,
1193 ) {
1194 *self.store.lock() = Some(Box::new(source));
1195 }
1196
1197 #[must_use]
1199 pub fn has_store_source(&self) -> bool {
1200 self.store.lock().is_some()
1201 }
1202
1203 fn attach_store(&self, at: usize) {
1210 if self.slot(at).store_bytes().is_some() {
1211 return;
1212 }
1213 let mut source = self.store.lock();
1217 let Some(source) = source.as_mut() else {
1218 return;
1219 };
1220 if let Some(blocks) = source(at) {
1221 self.slot(at).attach(blocks);
1222 }
1223 }
1224
1225 #[must_use]
1227 pub fn maxstore(&self) -> Option<u64> {
1228 match self.maxstore.load(Relaxed) {
1229 NO_MAXSTORE => None,
1230 bytes => Some(bytes),
1231 }
1232 }
1233
1234 pub fn set_maxstore(&self, bytes: Option<u64>) {
1240 self.maxstore.store(bytes.unwrap_or(NO_MAXSTORE), Relaxed);
1241 }
1242
1243 #[must_use]
1249 pub fn store_bytes(&self) -> u64 {
1250 self.keyspaces().filter_map(|db| db.store_bytes()).sum()
1251 }
1252
1253 #[must_use]
1268 pub fn cold_stats(&self) -> yo_kv::tier::Stats {
1269 let mut total = yo_kv::tier::Stats::default();
1270 for db in self.keyspaces() {
1271 let Some(tier) = db.tier() else { continue };
1272 let s = tier.stats();
1273 total.demoted += s.demoted;
1274 total.promoted += s.promoted;
1275 total.faults += s.faults;
1276 total.served += s.served;
1277 total.bytes_out += s.bytes_out;
1278 total.bytes_in += s.bytes_in;
1279 }
1280 total
1281 }
1282
1283 #[must_use]
1290 pub fn regime(&self) -> &'static str {
1291 if (0..self.slots()).any(|at| self.migrates(at)) {
1292 "migrate"
1293 } else {
1294 "evict"
1295 }
1296 }
1297
1298 fn migrates(&self, at: usize) -> bool {
1310 let cap = self.maxstore();
1311 if cap == Some(0) {
1312 return false;
1313 }
1314 let bytes = self.slot(at).store_bytes();
1318 match bytes {
1319 Some(held) => cap.is_none_or(|cap| held < cap),
1320 None => self.store.lock().is_some(),
1324 }
1325 }
1326
1327 pub fn refresh_memory(&self) {
1332 if self.maxmemory() != 0 {
1333 self.used.store(self.settled_memory(), Relaxed);
1334 }
1335 }
1336
1337 fn settled_memory(&self) -> usize {
1344 self.keyspaces()
1345 .map(|mut db| db.settled_memory_bytes())
1346 .sum::<usize>()
1347 + self.conn_bytes()
1348 }
1349
1350 pub fn make_room(&self) -> bool {
1384 let limit = self.maxmemory();
1385 if limit == 0 || self.used.load(Relaxed) as u64 <= limit {
1386 return true;
1387 }
1388 let mut used = self.settled_memory();
1393 self.used.store(used, Relaxed);
1394 let mut budget = EVICT_BUDGET;
1395 while used as u64 > limit {
1396 let over = used - limit as usize;
1397 if !self.relieve_step(over) {
1398 return false;
1399 }
1400 self.compact_hard_step();
1401 used = self.settled_memory();
1402 self.used.store(used, Relaxed);
1403 budget -= 1;
1404 if budget == 0 {
1405 break;
1406 }
1407 }
1408 true
1409 }
1410
1411 fn relieve_step(&self, over: usize) -> bool {
1428 let from = self.evict_db.load(Relaxed);
1429 for turn in 0..self.slots() {
1430 let i = (from + turn) % self.slots();
1431 let used = !self.slot(i).is_empty();
1434 let gave = if used && self.migrates(i) {
1435 self.attach_store(i);
1436 self.slot(i)
1441 .relieve(over)
1442 .is_ok_and(yo_kv::tier::Relief::made_room)
1443 } else {
1444 self.slot(i).evict_one()
1445 };
1446 if gave {
1447 self.evict_db.store((i + 1) % self.slots(), Relaxed);
1448 self.mine().mark(1u64 << self.slot_db(i));
1449 return true;
1450 }
1451 }
1452 false
1453 }
1454
1455 pub fn expire_slice(&self, budget: usize) -> usize {
1470 let now = self.clock.now_ms();
1471 if now == self.expire_ms.load(Relaxed) {
1472 return 0;
1473 }
1474 self.expire_ms.store(now, Relaxed);
1475 self.expire_step(budget)
1476 }
1477
1478 pub fn expire_step(&self, budget: usize) -> usize {
1494 let mut spent = 0;
1495 let from = self.expire_db.load(Relaxed);
1496 for turn in 0..self.slots() {
1497 if spent >= budget {
1498 break;
1499 }
1500 let i = (from + turn) % self.slots();
1501 let c = self.slot(i).expire_cycle(budget - spent);
1502 spent += c.examined;
1503 if c.expired > 0 {
1504 self.expire_db.store((i + 1) % self.slots(), Relaxed);
1505 self.mine().note(1u64 << self.slot_db(i));
1506 }
1507 }
1508 spent
1509 }
1510
1511 fn compact_hard_step(&self) -> Option<usize> {
1517 let from = self.next_db.load(Relaxed);
1518 for turn in 0..self.slots() {
1519 let i = (from + turn) % self.slots();
1520 if let Some(moved) = self.slot(i).compact_hard() {
1521 self.next_db.store((i + 1) % self.slots(), Relaxed);
1522 return Some(moved);
1523 }
1524 }
1525 None
1526 }
1527
1528 fn collect_marks(&self) {
1537 let mut marked = 0;
1538 for thread in &self.locals {
1539 marked |= thread.dirty.swap(0, Relaxed);
1540 }
1541 self.mine().note(marked);
1542 }
1543
1544 pub fn compact_step(&self) -> Option<usize> {
1557 self.collect_marks();
1558 let mine = self.mine();
1559 let from = self.next_db.load(Relaxed);
1560 for turn in 0..self.slots() {
1561 let i = (from + turn) % self.slots();
1562 let at = self.slot_db(i);
1566 if !mine.wanted(at) {
1567 continue;
1568 }
1569 if let Some(moved) = self.slot(i).compact_step() {
1570 self.next_db.store((i + 1) % self.slots(), Relaxed);
1571 return Some(moved);
1572 }
1573 if i % self.width == self.width - 1 {
1577 mine.done(at);
1578 }
1579 }
1580 None
1581 }
1582}
1583
1584impl Default for Server {
1585 fn default() -> Server {
1586 Server::new()
1587 }
1588}
1589
1590pub struct Session {
1592 db: usize,
1593 id: u64,
1594 name: Vec<u8>,
1595 sets: himport::Fieldsets,
1601}
1602
1603impl Session {
1604 #[must_use]
1606 pub fn new(id: u64) -> Session {
1607 Session {
1608 db: 0,
1609 id,
1610 name: Vec::new(),
1611 sets: himport::Fieldsets::default(),
1612 }
1613 }
1614
1615 #[must_use]
1617 pub const fn id(&self) -> u64 {
1618 self.id
1619 }
1620
1621 #[must_use]
1623 pub const fn db(&self) -> usize {
1624 self.db
1625 }
1626
1627 #[must_use]
1629 pub fn name(&self) -> &[u8] {
1630 &self.name
1631 }
1632
1633 pub fn reset(&mut self) {
1638 self.db = 0;
1639 self.name.clear();
1640 self.sets.clear();
1644 }
1645
1646 fn set_name(&mut self, name: &[u8]) {
1648 yo_alloc::allow(|| {
1649 self.name.clear();
1650 self.name.extend_from_slice(name);
1651 });
1652 }
1653}
1654
1655pub fn execute(server: &Server, session: &mut Session, args: Args<'_>, out: &mut Out) -> Flow {
1660 if args.is_empty() {
1663 return Flow::Continue;
1664 }
1665 resolved(server, session, lookup(args.name()), args, out)
1666}
1667
1668pub fn resolved(
1680 server: &Server,
1681 session: &mut Session,
1682 spec: Option<&'static Spec>,
1683 args: Args<'_>,
1684 out: &mut Out,
1685) -> Flow {
1686 if args.is_empty() {
1687 return Flow::Continue;
1688 }
1689 server.mine().stats.commands.bump();
1690
1691 let Some(spec) = spec else {
1692 write_error(out, &args::unknown_command(args));
1693 return Flow::Continue;
1694 };
1695 if !arity_ok(spec, args.len()) {
1696 server.mine().cmdstats.at(spec).rejected.bump();
1697 write_error(out, &args::wrong_arity(spec.name));
1698 return Flow::Continue;
1699 }
1700
1701 if server.maxmemory() != 0 && !server.make_room() && spec.flags.contains(&"denyoom") {
1711 server.mine().cmdstats.at(spec).rejected.bump();
1712 out.error_line(b"OOM ", OOM);
1713 return Flow::Continue;
1714 }
1715
1716 server.mine().mark(match spec.group {
1723 "string" | "bitmap" | "hyperloglog" | "geo" | "set" | "hash" | "list" | "zset"
1724 | "array" | "stream" | "bloom" | "cuckoo" | "cms" | "topk" | "tdigest" | "ts" => {
1725 1u64 << session.db
1726 }
1727 _ => ALL_DATABASES,
1728 });
1729
1730 let mark = out.len();
1731 let done = if spec.flags.contains(&"blocking") {
1738 blocking::execute(server, session, spec, args, out)
1739 } else {
1740 match spec.group {
1741 "string" => {
1742 let db = session.db;
1743 strings::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1744 }
1745 "bitmap" => {
1749 let db = session.db;
1750 bits::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1751 }
1752 "hyperloglog" => {
1755 let db = session.db;
1756 hll::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1757 }
1758 "set" => {
1759 let db = session.db;
1760 sets::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1761 }
1762 "hash" if spec.name == "himport" => {
1767 let db = session.db;
1768 himport::execute(&server.dbs[db], &mut session.sets, args, out)
1769 .map(|()| Flow::Continue)
1770 }
1771 "hash" => {
1776 let db = session.db;
1777 let changed = hashes::execute(&server.dbs[db], spec, args, out);
1778 changed.map(|changed| {
1779 indexing::changed(server, db, args.get(1), changed);
1780 Flow::Continue
1781 })
1782 }
1783 "list" => {
1784 let db = session.db;
1785 lists::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1786 }
1787 "zset" => {
1788 let db = session.db;
1789 zsets::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1790 }
1791 "geo" => {
1795 let db = session.db;
1796 geo::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1797 }
1798 "array" => {
1799 let db = session.db;
1800 arrays::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1801 }
1802 "graph" => {
1803 let db = session.db;
1804 graph::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1805 }
1806 "json" => {
1810 let db = session.db;
1811 json::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1812 }
1813 "vector" => {
1814 let db = session.db;
1815 vectors::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1816 }
1817 "bloom" => {
1818 let db = session.db;
1819 bloom::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1820 }
1821 "cuckoo" => {
1822 let db = session.db;
1823 cuckoo::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1824 }
1825 "cms" => {
1826 let db = session.db;
1827 cms::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1828 }
1829 "topk" => {
1830 let db = session.db;
1831 topk::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1832 }
1833 "tdigest" => {
1834 let db = session.db;
1835 tdigest::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1836 }
1837 "ts" => {
1838 let db = session.db;
1839 ts::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1840 }
1841 "stream" => {
1846 let db = session.db;
1847 let now = server.now_ms();
1848 streams::execute(&server.dbs[db], spec, args, now, out).map(|()| Flow::Continue)
1849 }
1850 "keyspace" if spec.name == "migrate" => {
1854 migrate::execute(server, session.db, args, out).map(|()| Flow::Continue)
1855 }
1856 "keyspace" => {
1862 let mut touched = indexing::Touched::new(server);
1863 let done =
1864 keyspace::execute(&server.dbs, session.db, spec, args, out, &mut touched);
1865 done.map(|()| {
1866 indexing::touched(server, &touched);
1867 Flow::Continue
1868 })
1869 }
1870 "search" if spec.name == "FT.SEARCH" => {
1877 search::find(server, session.db, args, out).map(|()| Flow::Continue)
1882 }
1883 "search" if spec.name == "FT.AGGREGATE" => {
1884 search::roll(server, session.db, args, out).map(|()| Flow::Continue)
1885 }
1886 "search" if spec.name == "FT.CURSOR" => {
1887 search::cursor::execute(server, args, out).map(|()| Flow::Continue)
1891 }
1892 "search" => {
1893 let db = session.db;
1894 let made = search::execute(server, &mut server.search.lock(), db, spec, args, out);
1895 made.map(|made| {
1896 if let Some(name) = made {
1897 indexing::scan(server, db, name);
1898 }
1899 Flow::Continue
1900 })
1901 }
1902 "scripting" => scripting::execute(spec, args, out).map(|()| Flow::Continue),
1903 _ => server::execute(server, session, spec, args, out),
1904 }
1905 };
1906 let flow = match done {
1907 Ok(flow) => flow,
1908 Err(e) => {
1909 out.truncate(mark);
1910 write_error(out, &e);
1911 Flow::Continue
1912 }
1913 };
1914
1915 let row = server.mine().cmdstats.at(spec);
1926 row.calls.bump();
1927 if matches!(out.as_slice().get(mark), Some(b'-' | b'!')) {
1928 row.failed.bump();
1929 }
1930 flow
1931}
1932
1933fn write_error(out: &mut Out, e: &Error) {
1943 let prefix: &[u8] = match e.code() {
1944 Code::WrongType => b"WRONGTYPE ",
1945 Code::Corrupt => b"INVALIDOBJ ",
1949 _ => b"ERR ",
1950 };
1951 out.error_line(prefix, e.message().as_bytes());
1952}
1953
1954#[cfg(test)]
1955mod tests {
1956 use super::*;
1957 use crate::proto::{Limits, Proto};
1958 use crate::request::Argv;
1959
1960 pub(crate) fn encode(parts: &[&[u8]]) -> Vec<u8> {
1965 let mut wire = format!("*{}\r\n", parts.len()).into_bytes();
1966 for p in parts {
1967 wire.extend_from_slice(format!("${}\r\n", p.len()).as_bytes());
1968 wire.extend_from_slice(p);
1969 wire.extend_from_slice(b"\r\n");
1970 }
1971 wire
1972 }
1973
1974 struct Fixture {
1976 server: Server,
1977 session: Session,
1978 argv: Argv,
1979 out: Out,
1980 }
1981
1982 impl Fixture {
1983 fn new() -> Fixture {
1984 Fixture::on(Server::new())
1985 }
1986
1987 fn striped(width: usize) -> Fixture {
1989 Fixture::on(Server::with_width(width))
1990 }
1991
1992 fn on(server: Server) -> Fixture {
1993 Fixture {
1994 server,
1995 session: Session::new(7),
1996 argv: Argv::new(),
1997 out: Out::new(Proto::Resp2),
1998 }
1999 }
2000
2001 fn run(&mut self, parts: &[&[u8]]) -> String {
2003 self.flow(parts).1
2004 }
2005
2006 fn raw(&mut self, parts: &[&[u8]]) -> Vec<u8> {
2012 let wire = encode(parts);
2013 self.argv.decode(&wire, &Limits::default()).unwrap();
2014 self.out.clear();
2015 execute(
2016 &self.server,
2017 &mut self.session,
2018 Args::new(&self.argv, &wire),
2019 &mut self.out,
2020 );
2021 self.out.as_slice().to_vec()
2022 }
2023
2024 fn advance(&mut self, ms: u64) {
2026 self.server.advance_clock_ms(ms);
2027 }
2028
2029 fn flow(&mut self, parts: &[&[u8]]) -> (Flow, String) {
2031 let wire = encode(parts);
2032 self.argv.decode(&wire, &Limits::default()).unwrap();
2033 self.out.clear();
2034 let flow = execute(
2035 &self.server,
2036 &mut self.session,
2037 Args::new(&self.argv, &wire),
2038 &mut self.out,
2039 );
2040 (
2041 flow,
2042 String::from_utf8_lossy(self.out.as_slice()).into_owned(),
2043 )
2044 }
2045 }
2046
2047 #[test]
2051 fn rewriting_the_same_keys_does_not_grow_the_server() {
2052 let mut f = Fixture::new();
2053 let val = vec![b'v'; 1024];
2054 let keys: Vec<Vec<u8>> = (0..64).map(|i| format!("key:{i}").into_bytes()).collect();
2055
2056 for k in &keys {
2057 f.run(&[b"SET", k, &val]);
2058 }
2059 f.server.compact_step();
2060 let after_first = f.server.memory_bytes();
2061
2062 for _ in 0..500 {
2067 for k in &keys {
2068 f.run(&[b"SET", k, &val]);
2069 }
2070 f.server.compact_step();
2071 }
2072
2073 assert!(
2074 f.server.memory_bytes() <= after_first * 2,
2075 "held {} after five hundred passes against {after_first} after one",
2076 f.server.memory_bytes()
2077 );
2078 assert_eq!(f.run(&[b"DBSIZE"]), format!(":{}\r\n", keys.len()));
2079 assert_eq!(f.run(&[b"STRLEN", b"key:7"]), ":1024\r\n");
2080 }
2081
2082 #[test]
2096 fn a_database_nobody_started_on_is_still_collected() {
2097 let mut f = Fixture::new();
2098 assert_eq!(f.run(&[b"SELECT", b"9"]), "+OK\r\n");
2099 let val = vec![b'v'; 1024];
2100 let keys: Vec<Vec<u8>> = (0..64).map(|i| format!("key:{i}").into_bytes()).collect();
2101
2102 for k in &keys {
2103 f.run(&[b"SET", k, &val]);
2104 }
2105 while f.server.compact_step().is_some() {}
2106 assert!(
2107 !f.server.mine().wanted(9),
2108 "database nine was drained and should not be asked again until it is written to"
2109 );
2110 let after_first = f.server.memory_bytes();
2111
2112 for _ in 0..500 {
2113 for k in &keys {
2114 f.run(&[b"SET", k, &val]);
2115 }
2116 f.server.compact_step();
2117 }
2118
2119 assert!(
2120 f.server.memory_bytes() <= after_first * 2,
2121 "held {} after five hundred passes against {after_first} after one",
2122 f.server.memory_bytes()
2123 );
2124 assert_eq!(f.run(&[b"DBSIZE"]), format!(":{}\r\n", keys.len()));
2125 assert_eq!(f.run(&[b"STRLEN", b"key:7"]), ":1024\r\n");
2126 f.run(&[b"SELECT", b"0"]);
2128 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
2129 }
2130
2131 #[test]
2132 fn a_command_goes_from_bytes_to_bytes() {
2133 let mut f = Fixture::new();
2134 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
2135 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
2136 assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
2137 assert_eq!(f.run(&[b"STRLEN", b"k"]), ":1\r\n");
2138 assert_eq!(f.run(&[b"set", b"k", b"v2", b"xx"]), "+OK\r\n");
2140 assert_eq!(f.run(&[b"GET", b"k"]), "$2\r\nv2\r\n");
2141 }
2142
2143 #[test]
2144 fn deleting_counts_keys_removed_and_existing_counts_arguments_matched() {
2145 let mut f = Fixture::new();
2146 f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
2147 assert_eq!(f.run(&[b"EXISTS", b"a", b"a", b"nosuch"]), ":2\r\n");
2150 assert_eq!(f.run(&[b"DEL", b"a", b"a", b"nosuch"]), ":1\r\n");
2151 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
2152 assert_eq!(f.run(&[b"UNLINK", b"b", b"c"]), ":2\r\n");
2154 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
2155 }
2156
2157 #[test]
2158 fn type_is_a_simple_string_and_says_none_for_a_key_that_is_not_there() {
2159 let mut f = Fixture::new();
2160 f.run(&[b"SET", b"k", b"v"]);
2161 assert_eq!(f.run(&[b"TYPE", b"k"]), "+string\r\n");
2164 assert_eq!(f.run(&[b"TYPE", b"nosuch"]), "+none\r\n");
2165 }
2166
2167 #[test]
2168 fn touch_counts_the_way_exists_counts() {
2169 let mut f = Fixture::new();
2170 f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
2171 assert_eq!(f.run(&[b"TOUCH", b"a", b"b"]), ":2\r\n");
2172 assert_eq!(
2173 f.run(&[b"TOUCH", b"a", b"a"]),
2174 ":2\r\n",
2175 "twice counts twice"
2176 );
2177 assert_eq!(f.run(&[b"TOUCH", b"a", b"nosuch"]), ":1\r\n");
2178 assert_eq!(f.run(&[b"TOUCH", b"nosuch"]), ":0\r\n");
2179 }
2180
2181 #[test]
2182 fn a_rename_moves_the_deadline_with_the_value_and_drops_the_one_it_lands_on() {
2183 let mut f = Fixture::new();
2184 f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
2185 f.run(&[b"SET", b"b", b"v2", b"EX", b"500"]);
2186
2187 assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "+OK\r\n");
2188 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
2189 assert_eq!(
2190 f.run(&[b"TTL", b"b"]),
2191 ":100\r\n",
2192 "the source's and not b's"
2193 );
2194 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
2195 }
2196
2197 #[test]
2198 fn a_rename_with_no_source_is_an_error_and_not_a_zero() {
2199 let mut f = Fixture::new();
2200 assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "-ERR no such key\r\n");
2201 assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), "-ERR no such key\r\n");
2204 }
2205
2206 #[test]
2207 fn renamenx_refuses_a_taken_name_including_the_one_it_already_has() {
2208 let mut f = Fixture::new();
2209 f.run(&[b"MSET", b"a", b"v1", b"b", b"v2"]);
2210
2211 assert_eq!(f.run(&[b"RENAMENX", b"a", b"b"]), ":0\r\n");
2212 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
2213 assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), ":0\r\n");
2216 assert_eq!(f.run(&[b"RENAME", b"a", b"a"]), "+OK\r\n");
2217 assert_eq!(f.run(&[b"RENAMENX", b"a", b"c"]), ":1\r\n");
2218 assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\nv1\r\n");
2219 }
2220
2221 #[test]
2222 fn renaming_a_set_does_not_touch_a_member() {
2223 let mut f = Fixture::new();
2224 for i in 0..300 {
2225 f.run(&[b"SADD", b"s", format!("m{i}").as_bytes()]);
2226 }
2227 let before = f.server.memory_bytes();
2228
2229 assert_eq!(f.run(&[b"RENAME", b"s", b"t"]), "+OK\r\n");
2230 assert_eq!(f.run(&[b"SCARD", b"t"]), ":300\r\n");
2231 assert_eq!(f.run(&[b"TYPE", b"t"]), "+set\r\n");
2232 assert!(
2233 f.server.memory_bytes().abs_diff(before) < 256,
2234 "the members were copied: {} against {before}",
2235 f.server.memory_bytes()
2236 );
2237 }
2238
2239 #[test]
2240 fn a_copy_is_a_second_value_and_not_a_second_name() {
2241 let mut f = Fixture::new();
2242 f.run(&[b"SADD", b"s", b"m1", b"m2"]);
2243
2244 assert_eq!(f.run(&[b"COPY", b"s", b"t"]), ":1\r\n");
2245 f.run(&[b"SADD", b"t", b"m3"]);
2246 assert_eq!(f.run(&[b"SCARD", b"s"]), ":2\r\n", "the original is intact");
2247 assert_eq!(f.run(&[b"SCARD", b"t"]), ":3\r\n");
2248 }
2249
2250 #[test]
2260 fn every_type_can_be_copied() {
2261 let mut f = Fixture::new();
2262 f.run(&[b"SET", b"str", b"v1"]);
2263 f.run(&[b"SADD", b"set", b"m1"]);
2264 f.run(&[b"HSET", b"hash", b"f", b"v"]);
2265 f.run(&[b"RPUSH", b"list", b"a", b"b"]);
2266 f.run(&[b"ZADD", b"zset", b"1", b"m1"]);
2267
2268 for name in [
2269 &b"str"[..],
2270 &b"set"[..],
2271 &b"hash"[..],
2272 &b"list"[..],
2273 &b"zset"[..],
2274 ] {
2275 let dst = [name, b":copy"].concat();
2276 assert_eq!(
2277 f.run(&[b"COPY", name, &dst]),
2278 ":1\r\n",
2279 "copying {}",
2280 String::from_utf8_lossy(name)
2281 );
2282 assert_eq!(f.run(&[b"TYPE", name]), f.run(&[b"TYPE", &dst]));
2283 }
2284
2285 assert_eq!(f.run(&[b"LRANGE", b"list:copy", b"0", b"-1"]), {
2286 let mut want = String::from("*2\r\n");
2287 want.push_str("$1\r\na\r\n$1\r\nb\r\n");
2288 want
2289 });
2290 assert_eq!(f.run(&[b"ZSCORE", b"zset:copy", b"m1"]), "$1\r\n1\r\n");
2291
2292 f.run(&[b"RPUSH", b"list:copy", b"c"]);
2294 assert_eq!(f.run(&[b"LLEN", b"list"]), ":2\r\n");
2295 assert_eq!(f.run(&[b"LLEN", b"list:copy"]), ":3\r\n");
2296 }
2297
2298 #[test]
2299 fn a_copy_refuses_a_taken_destination_until_it_is_told_it_can_have_it() {
2300 let mut f = Fixture::new();
2301 f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
2302 f.run(&[b"SET", b"b", b"v2"]);
2303
2304 assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":0\r\n");
2305 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
2306 assert_eq!(f.run(&[b"COPY", b"a", b"b", b"REPLACE"]), ":1\r\n");
2307 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
2308 assert_eq!(f.run(&[b"TTL", b"b"]), ":100\r\n", "the deadline came too");
2309 assert_eq!(f.run(&[b"COPY", b"nosuch", b"z"]), ":0\r\n");
2310 }
2311
2312 #[test]
2313 fn a_copy_into_another_database_is_a_copy_and_onto_itself_there_is_too() {
2314 let mut f = Fixture::new();
2315 f.run(&[b"SET", b"a", b"v1"]);
2316
2317 assert_eq!(f.run(&[b"COPY", b"a", b"a", b"DB", b"1"]), ":1\r\n");
2320 f.run(&[b"SELECT", b"1"]);
2321 assert_eq!(f.run(&[b"GET", b"a"]), "$2\r\nv1\r\n");
2322 assert_eq!(
2323 f.run(&[b"COPY", b"a", b"a", b"DB", b"0"]),
2324 ":0\r\n",
2325 "taken"
2326 );
2327 assert_eq!(
2328 f.run(&[b"COPY", b"a", b"a", b"DB", b"0", b"REPLACE"]),
2329 ":1\r\n"
2330 );
2331 }
2332
2333 #[test]
2334 fn sort_takes_its_options_in_any_order_and_the_last_one_wins() {
2335 let mut f = Fixture::new();
2336 f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
2337 assert_eq!(
2338 f.run(&[b"SORT", b"l"]),
2339 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
2340 );
2341 assert_eq!(
2343 f.run(&[b"SORT", b"l", b"DESC", b"asc"]),
2344 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
2345 );
2346 assert_eq!(
2347 f.run(&[b"sort", b"l", b"LIMIT", b"1", b"1", b"DESC"]),
2348 "*1\r\n$1\r\n2\r\n"
2349 );
2350 }
2351
2352 #[test]
2353 fn sort_reads_a_key_per_element_for_by_and_for_get() {
2354 let mut f = Fixture::new();
2355 f.run(&[b"RPUSH", b"l", b"a", b"b"]);
2356 f.run(&[b"MSET", b"w_a", b"2", b"w_b", b"1", b"d_b", b"bee"]);
2357 assert_eq!(
2360 f.run(&[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"]),
2361 "*4\r\n$1\r\nb\r\n$3\r\nbee\r\n$1\r\na\r\n$-1\r\n"
2362 );
2363 }
2364
2365 #[test]
2366 fn sort_store_writes_a_list_and_answers_its_length() {
2367 let mut f = Fixture::new();
2368 f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
2369 assert_eq!(f.run(&[b"SORT", b"l", b"STORE", b"out"]), ":3\r\n");
2370 assert_eq!(f.run(&[b"TYPE", b"out"]), "+list\r\n");
2371 assert_eq!(
2372 f.run(&[b"LRANGE", b"out", b"0", b"-1"]),
2373 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
2374 );
2375 assert_eq!(f.run(&[b"SORT", b"missing", b"STORE", b"out"]), ":0\r\n");
2378 assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
2379 }
2380
2381 #[test]
2382 fn sort_ro_does_not_know_the_word_store() {
2383 let mut f = Fixture::new();
2384 f.run(&[b"RPUSH", b"l", b"2", b"1"]);
2385 assert_eq!(f.run(&[b"SORT_RO", b"l"]), "*2\r\n$1\r\n1\r\n$1\r\n2\r\n");
2386 assert_eq!(
2387 f.run(&[b"SORT_RO", b"l", b"STORE", b"d"]),
2388 "-ERR syntax error\r\n"
2389 );
2390 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
2391 }
2392
2393 #[test]
2394 fn sort_refuses_what_it_cannot_sort() {
2395 let mut f = Fixture::new();
2396 assert_eq!(f.run(&[b"SORT", b"nosuchkey"]), "*0\r\n");
2397 f.run(&[b"SET", b"s", b"x"]);
2398 assert_eq!(
2399 f.run(&[b"SORT", b"s"]),
2400 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
2401 );
2402 f.run(&[b"RPUSH", b"words", b"one", b"two"]);
2403 assert_eq!(
2404 f.run(&[b"SORT", b"words"]),
2405 "-ERR One or more scores can't be converted into double\r\n"
2406 );
2407 assert_eq!(
2408 f.run(&[b"SORT", b"words", b"ALPHA"]),
2409 "*2\r\n$3\r\none\r\n$3\r\ntwo\r\n"
2410 );
2411 assert_eq!(f.run(&[b"SORT", b"words", b"BY"]), "-ERR syntax error\r\n");
2412 }
2413
2414 #[test]
2415 fn move_takes_the_key_out_of_one_database_and_puts_it_in_another() {
2416 let mut f = Fixture::new();
2417 assert_eq!(f.run(&[b"RPUSH", b"l", b"a", b"b"]), ":2\r\n");
2418 assert_eq!(f.run(&[b"MOVE", b"l", b"1"]), ":1\r\n");
2419 assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
2420 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2421 assert_eq!(
2422 f.run(&[b"LRANGE", b"l", b"0", b"-1"]),
2423 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
2424 );
2425 assert_eq!(f.run(&[b"MOVE", b"l", b"0"]), ":1\r\n");
2428 assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
2429 }
2430
2431 #[test]
2432 fn move_answers_zero_when_either_end_says_no() {
2433 let mut f = Fixture::new();
2434 assert_eq!(f.run(&[b"MOVE", b"nope", b"1"]), ":0\r\n");
2435 assert_eq!(f.run(&[b"SET", b"a", b"here"]), "+OK\r\n");
2436 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2437 assert_eq!(f.run(&[b"SET", b"a", b"there"]), "+OK\r\n");
2438 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
2439 assert_eq!(f.run(&[b"MOVE", b"a", b"1"]), ":0\r\n");
2442 assert_eq!(f.run(&[b"GET", b"a"]), "$4\r\nhere\r\n");
2443 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2444 assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nthere\r\n");
2445 }
2446
2447 #[test]
2448 fn move_refuses_a_database_that_is_not_one_and_the_one_it_is_on() {
2449 let mut f = Fixture::new();
2450 assert_eq!(
2451 f.run(&[b"MOVE", b"a", b"0"]),
2452 "-ERR source and destination objects are the same\r\n"
2453 );
2454 assert_eq!(
2455 f.run(&[b"MOVE", b"a", b"99"]),
2456 "-ERR DB index is out of range\r\n"
2457 );
2458 assert_eq!(
2459 f.run(&[b"MOVE", b"a", b"-1"]),
2460 "-ERR DB index is out of range\r\n"
2461 );
2462 assert_eq!(
2463 f.run(&[b"MOVE", b"a", b"x"]),
2464 "-ERR value is not an integer or out of range\r\n"
2465 );
2466 }
2467
2468 #[test]
2469 fn swapdb_swaps_what_two_connections_would_see() {
2470 let mut f = Fixture::new();
2471 assert_eq!(f.run(&[b"SET", b"k", b"zero"]), "+OK\r\n");
2472 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2473 assert_eq!(f.run(&[b"SET", b"k", b"one"]), "+OK\r\n");
2474 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
2475
2476 assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
2477 assert_eq!(f.run(&[b"GET", b"k"]), "$3\r\none\r\n");
2479 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2480 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
2481 assert_eq!(f.run(&[b"SWAPDB", b"1", b"1"]), "+OK\r\n");
2483 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
2484 }
2485
2486 #[test]
2491 fn a_wide_server_puts_its_databases_on_its_own_clock() {
2492 let mut f = Fixture::striped(8);
2493 f.server.set_clock_ms(1_700_000_000_000);
2494 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"100"]), "+OK\r\n");
2495 assert_eq!(f.run(&[b"EXPIRETIME", b"k"]), ":1700000100\r\n");
2496 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
2497 f.server.set_clock_ms(1_700_000_050_000);
2498 assert_eq!(f.run(&[b"TTL", b"k"]), ":50\r\n");
2499 }
2500
2501 #[test]
2506 fn swapdb_swaps_every_stripe_of_a_wide_database() {
2507 let mut f = Fixture::striped(4);
2508 for i in 0..16u32 {
2509 let key = format!("k{i}");
2510 assert_eq!(f.run(&[b"SET", key.as_bytes(), b"zero"]), "+OK\r\n");
2511 }
2512 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2513 assert_eq!(f.run(&[b"SET", b"only", b"one"]), "+OK\r\n");
2514 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
2515
2516 assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
2517 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
2518 assert_eq!(f.run(&[b"GET", b"only"]), "$3\r\none\r\n");
2519 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2520 assert_eq!(f.run(&[b"DBSIZE"]), ":16\r\n");
2521 for i in 0..16u32 {
2522 let key = format!("k{i}");
2523 assert_eq!(f.run(&[b"GET", key.as_bytes()]), "$4\r\nzero\r\n");
2524 }
2525 }
2526
2527 #[test]
2528 fn swapdb_says_which_index_it_could_not_read() {
2529 let mut f = Fixture::new();
2530 assert_eq!(
2531 f.run(&[b"SWAPDB", b"x", b"1"]),
2532 "-ERR invalid first DB index\r\n"
2533 );
2534 assert_eq!(
2535 f.run(&[b"SWAPDB", b"0", b"y"]),
2536 "-ERR invalid second DB index\r\n"
2537 );
2538 assert_eq!(
2542 f.run(&[b"SWAPDB", b"99999999999999", b"1"]),
2543 "-ERR invalid first DB index\r\n"
2544 );
2545 assert_eq!(
2546 f.run(&[b"SWAPDB", b"0", b"99"]),
2547 "-ERR DB index is out of range\r\n"
2548 );
2549 assert_eq!(
2550 f.run(&[b"SWAPDB", b"-1", b"0"]),
2551 "-ERR DB index is out of range\r\n"
2552 );
2553 }
2554
2555 #[test]
2556 fn wait_answers_zero_replicas_without_waiting() {
2557 let mut f = Fixture::new();
2558 assert_eq!(f.run(&[b"SET", b"a", b"v"]), "+OK\r\n");
2559 assert_eq!(f.run(&[b"WAIT", b"0", b"0"]), ":0\r\n");
2560 assert_eq!(f.run(&[b"WAIT", b"3", b"1000"]), ":0\r\n");
2563 assert_eq!(f.run(&[b"WAIT", b"-1", b"0"]), ":0\r\n");
2566 assert_eq!(
2567 f.run(&[b"WAIT", b"x", b"0"]),
2568 "-ERR value is not an integer or out of range\r\n"
2569 );
2570 assert_eq!(
2571 f.run(&[b"WAIT", b"0", b"-1"]),
2572 "-ERR timeout is negative\r\n"
2573 );
2574 assert_eq!(
2575 f.run(&[b"WAIT", b"0", b"1.5"]),
2576 "-ERR timeout is not an integer or out of range\r\n"
2577 );
2578 }
2579
2580 #[test]
2581 fn waitaof_answers_two_zeroes_and_refuses_a_local_wait() {
2582 let mut f = Fixture::new();
2583 assert_eq!(f.run(&[b"WAITAOF", b"0", b"0", b"0"]), "*2\r\n:0\r\n:0\r\n");
2584 assert_eq!(
2585 f.run(&[b"WAITAOF", b"1", b"0", b"0"]),
2586 "-ERR WAITAOF cannot be used when numlocal is set but appendonly is disabled.\r\n"
2587 );
2588 assert_eq!(
2589 f.run(&[b"WAITAOF", b"2", b"0", b"0"]),
2590 "-ERR value is out of range, value must between 0 and 1\r\n"
2591 );
2592 assert_eq!(
2593 f.run(&[b"WAITAOF", b"0", b"-1", b"0"]),
2594 "-ERR value is out of range, must be positive\r\n"
2595 );
2596 assert_eq!(
2599 f.run(&[b"WAITAOF", b"1", b"0", b"-5"]),
2600 "-ERR timeout is negative\r\n"
2601 );
2602 }
2603
2604 fn payload(reply: &[u8]) -> Vec<u8> {
2608 let head = reply.windows(2).position(|w| w == b"\r\n").unwrap();
2609 reply[head + 2..reply.len() - 2].to_vec()
2610 }
2611
2612 #[test]
2613 fn a_value_survives_a_dump_and_a_restore() {
2614 let mut f = Fixture::new();
2615 f.run(&[b"SET", b"s", b"hello"]);
2616 f.run(&[b"RPUSH", b"l", b"a", b"b", b"c"]);
2617 f.run(&[b"SADD", b"t", b"1", b"2", b"3"]);
2618 f.run(&[b"SADD", b"u", b"x", b"y"]);
2619 f.run(&[b"HSET", b"h", b"f", b"1", b"g", b"2"]);
2620 f.run(&[b"ZADD", b"z", b"1.5", b"a", b"2.5", b"b"]);
2621
2622 for key in [&b"s"[..], b"l", b"t", b"u", b"h", b"z"] {
2623 let mut copy = key.to_vec();
2624 copy.push(b'2');
2625 let bytes = payload(&f.raw(&[b"DUMP", key]));
2626 assert_eq!(f.run(&[b"RESTORE", ©, b"0", &bytes]), "+OK\r\n");
2627 assert_eq!(f.run(&[b"TYPE", ©]), f.run(&[b"TYPE", key]));
2628 }
2629
2630 assert_eq!(f.run(&[b"GET", b"s2"]), "$5\r\nhello\r\n");
2631 assert_eq!(
2632 f.run(&[b"LRANGE", b"l2", b"0", b"-1"]),
2633 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
2634 );
2635 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"t2"])), ["1", "2", "3"]);
2636 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"u2"])), ["x", "y"]);
2637 assert_eq!(f.run(&[b"HGET", b"h2", b"g"]), "$1\r\n2\r\n");
2638 assert_eq!(f.run(&[b"ZSCORE", b"z2", b"b"]), "$3\r\n2.5\r\n");
2639 assert_eq!(
2642 f.run(&[b"OBJECT", b"ENCODING", b"t2"]),
2643 f.run(&[b"OBJECT", b"ENCODING", b"t"])
2644 );
2645 }
2646
2647 #[test]
2648 fn a_dumped_hash_keeps_its_field_deadlines() {
2649 let mut f = Fixture::new();
2650 f.run(&[b"HSET", b"h", b"keep", b"1", b"go", b"2"]);
2651 assert_eq!(
2652 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"go"]),
2653 "*1\r\n:1\r\n"
2654 );
2655 let bytes = payload(&f.raw(&[b"DUMP", b"h"]));
2656 assert_eq!(f.run(&[b"RESTORE", b"h2", b"0", &bytes]), "+OK\r\n");
2657 assert_eq!(
2658 f.run(&[b"HTTL", b"h2", b"FIELDS", b"2", b"keep", b"go"]),
2659 "*2\r\n:-1\r\n:100\r\n"
2660 );
2661 }
2662
2663 #[test]
2664 fn dump_leaves_the_deadline_behind_and_restore_is_given_a_new_one() {
2665 let mut f = Fixture::new();
2666 f.run(&[b"SET", b"a", b"v", b"EX", b"100"]);
2667 let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
2668 assert_eq!(f.run(&[b"RESTORE", b"b", b"0", &bytes]), "+OK\r\n");
2669 assert_eq!(f.run(&[b"TTL", b"b"]), ":-1\r\n");
2670 assert_eq!(f.run(&[b"RESTORE", b"c", b"5000", &bytes]), "+OK\r\n");
2671 assert_eq!(f.run(&[b"TTL", b"c"]), ":5\r\n");
2672 assert_eq!(
2675 f.run(&[b"RESTORE", b"d", b"1", &bytes, b"ABSTTL"]),
2676 "+OK\r\n"
2677 );
2678 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
2679 }
2680
2681 #[test]
2682 fn dump_answers_nothing_for_a_key_that_is_not_there() {
2683 let mut f = Fixture::new();
2684 assert_eq!(f.run(&[b"DUMP", b"nope"]), "$-1\r\n");
2685 f.run(&[b"SET", b"gone", b"v", b"PX", b"10"]);
2686 f.advance(50);
2687 assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
2688 }
2689
2690 #[test]
2691 fn restore_refuses_a_key_that_is_there_unless_it_is_told_to_replace() {
2692 let mut f = Fixture::new();
2693 f.run(&[b"SET", b"a", b"first"]);
2694 f.run(&[b"SET", b"b", b"second"]);
2695 let bytes = payload(&f.raw(&[b"DUMP", b"b"]));
2696 assert_eq!(
2697 f.run(&[b"RESTORE", b"a", b"0", &bytes]),
2698 "-BUSYKEY Target key name already exists.\r\n"
2699 );
2700 assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nfirst\r\n");
2701 assert_eq!(
2702 f.run(&[b"RESTORE", b"a", b"0", &bytes, b"REPLACE"]),
2703 "+OK\r\n"
2704 );
2705 assert_eq!(f.run(&[b"GET", b"a"]), "$6\r\nsecond\r\n");
2706 }
2707
2708 #[test]
2712 fn restore_asks_about_the_key_before_it_looks_at_the_bytes() {
2713 let mut f = Fixture::new();
2714 f.run(&[b"SET", b"a", b"v"]);
2715 assert_eq!(
2716 f.run(&[b"RESTORE", b"a", b"0", b"rubbish"]),
2717 "-BUSYKEY Target key name already exists.\r\n"
2718 );
2719 assert_eq!(
2722 f.run(&[b"RESTORE", b"a", b"0", b"rubbish", b"FREQ", b"300"]),
2723 "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
2724 );
2725 }
2726
2727 #[test]
2728 fn restore_can_tell_a_bad_footer_from_bad_bytes() {
2729 let mut f = Fixture::new();
2730 f.run(&[b"SET", b"a", b"hello"]);
2731 let good = payload(&f.raw(&[b"DUMP", b"a"]));
2732
2733 let mut flipped = good.clone();
2734 flipped[2] ^= 0x40;
2735 assert_eq!(
2736 f.run(&[b"RESTORE", b"b", b"0", &flipped]),
2737 "-ERR DUMP payload version or checksum are wrong\r\n"
2738 );
2739 assert_eq!(
2740 f.run(&[b"RESTORE", b"b", b"0", b"short"]),
2741 "-ERR DUMP payload version or checksum are wrong\r\n"
2742 );
2743 let mut truncated = good[..1].to_vec();
2747 truncated.extend_from_slice(&good[good.len() - 10..good.len() - 8]);
2748 let crc = yo_common::crc::crc64(0, &truncated);
2749 truncated.extend_from_slice(&crc.to_le_bytes());
2750 assert_eq!(
2751 f.run(&[b"RESTORE", b"b", b"0", &truncated]),
2752 "-ERR Bad data format\r\n"
2753 );
2754 assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
2755 }
2756
2757 #[test]
2758 fn restore_checks_the_three_numbers_a_client_can_get_wrong() {
2759 let mut f = Fixture::new();
2760 f.run(&[b"SET", b"a", b"v"]);
2761 let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
2762 assert_eq!(
2763 f.run(&[b"RESTORE", b"b", b"-1", &bytes]),
2764 "-ERR Invalid TTL value, must be >= 0\r\n"
2765 );
2766 assert_eq!(
2767 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"-1"]),
2768 "-ERR Invalid IDLETIME value, must be >= 0\r\n"
2769 );
2770 assert_eq!(
2771 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ", b"256"]),
2772 "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
2773 );
2774 assert_eq!(
2776 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"90"]),
2777 "+OK\r\n"
2778 );
2779 assert_eq!(
2780 f.run(&[b"RESTORE", b"c", b"0", &bytes, b"FREQ", b"200", b"REPLACE"]),
2781 "+OK\r\n"
2782 );
2783 }
2784
2785 #[test]
2789 fn restore_takes_idletime_or_freq_and_not_both() {
2790 let mut f = Fixture::new();
2791 f.run(&[b"SET", b"a", b"v"]);
2792 let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
2793 assert_eq!(
2794 f.run(&[
2795 b"RESTORE",
2796 b"b",
2797 b"0",
2798 &bytes,
2799 b"IDLETIME",
2800 b"1",
2801 b"FREQ",
2802 b"2"
2803 ]),
2804 "-ERR syntax error\r\n"
2805 );
2806 assert_eq!(
2807 f.run(&[
2808 b"RESTORE",
2809 b"b",
2810 b"0",
2811 &bytes,
2812 b"FREQ",
2813 b"2",
2814 b"IDLETIME",
2815 b"1"
2816 ]),
2817 "-ERR syntax error\r\n"
2818 );
2819 assert_eq!(
2820 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ"]),
2821 "-ERR syntax error\r\n"
2822 );
2823 assert_eq!(
2824 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"NOSUCH"]),
2825 "-ERR syntax error\r\n"
2826 );
2827 }
2828
2829 #[test]
2830 fn copy_checks_its_options_before_it_looks_for_anything() {
2831 let mut f = Fixture::new();
2832 assert_eq!(
2835 f.run(&[b"COPY", b"a", b"b", b"DB", b"99"]),
2836 "-ERR DB index is out of range\r\n"
2837 );
2838 assert_eq!(
2839 f.run(&[b"COPY", b"a", b"b", b"DB", b"-1"]),
2840 "-ERR DB index is out of range\r\n"
2841 );
2842 assert_eq!(
2843 f.run(&[b"COPY", b"a", b"b", b"DB", b"x"]),
2844 "-ERR value is not an integer or out of range\r\n"
2845 );
2846 assert_eq!(
2847 f.run(&[b"COPY", b"a", b"b", b"nonsense"]),
2848 "-ERR syntax error\r\n"
2849 );
2850 assert_eq!(
2851 f.run(&[b"COPY", b"a", b"a"]),
2852 "-ERR source and destination objects are the same\r\n"
2853 );
2854 assert_eq!(
2856 f.run(&[b"COPY", b"a", b"b", b"dB", b"1", b"rEpLaCe", b"db", b"2"]),
2857 ":0\r\n"
2858 );
2859 }
2860
2861 #[test]
2862 fn time_is_two_bulk_strings_and_moves() {
2863 let mut f = Fixture::new();
2864 let first = f.run(&[b"TIME"]);
2865 assert!(first.starts_with("*2\r\n$"), "got {first}");
2866 let parts: Vec<&str> = first.split("\r\n").collect();
2867 let secs: i64 = parts[2].parse().expect("seconds as decimal text");
2868 let micros: i64 = parts[4].parse().expect("microseconds as decimal text");
2869 assert!(secs > 1_700_000_000, "a real wall clock, got {secs}");
2870 assert!((0..1_000_000).contains(µs), "got {micros}");
2871 assert_ne!(first, f.run(&[b"TIME"]));
2875 }
2876
2877 #[test]
2878 fn a_keyspace_scan_walks_every_key_once() {
2879 let mut f = Fixture::new();
2880 for i in 0..500 {
2881 f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
2882 }
2883
2884 let mut seen: Vec<String> = Vec::new();
2885 let mut cursor = "0".to_owned();
2886 let mut calls = 0;
2887 loop {
2888 let (next, keys) = scan_reply(&f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"32"]));
2889 seen.extend(keys);
2890 cursor = next;
2891 calls += 1;
2892 assert!(calls < 10_000, "the cursor is not advancing");
2893 if cursor == "0" {
2894 break;
2895 }
2896 }
2897
2898 seen.sort();
2899 seen.dedup();
2900 assert_eq!(seen.len(), 500, "every key once and only once");
2901 assert!(calls > 1, "500 keys came back in one batch");
2904 }
2905
2906 #[test]
2907 fn a_scan_narrows_by_pattern_and_by_type() {
2908 let mut f = Fixture::new();
2909 f.run(&[b"SET", b"str", b"v"]);
2910 f.run(&[b"SADD", b"members", b"a"]);
2911 f.run(&[b"HSET", b"fields", b"f", b"v"]);
2912
2913 let all = |f: &mut Fixture, args: &[&[u8]]| {
2914 let mut out: Vec<String> = Vec::new();
2915 let mut cursor = "0".to_owned();
2916 loop {
2917 let mut line: Vec<&[u8]> = vec![b"SCAN", cursor.as_bytes()];
2918 line.extend_from_slice(args);
2919 let (next, keys) = scan_reply(&f.run(&line));
2920 out.extend(keys);
2921 cursor = next;
2922 if cursor == "0" {
2923 break;
2924 }
2925 }
2926 out.sort();
2927 out
2928 };
2929
2930 assert_eq!(all(&mut f, &[]), ["fields", "members", "str"]);
2931 assert_eq!(all(&mut f, &[b"MATCH", b"*e*"]), ["fields", "members"]);
2932 assert_eq!(all(&mut f, &[b"TYPE", b"set"]), ["members"]);
2933 assert_eq!(all(&mut f, &[b"TYPE", b"HASH"]), ["fields"]);
2935 assert!(all(&mut f, &[b"TYPE", b"list"]).is_empty());
2937 assert!(all(&mut f, &[b"TYPE", b"banana"]).is_empty());
2938 assert!(all(&mut f, &[b"MATCH", b"str*", b"TYPE", b"set"]).is_empty());
2940 }
2941
2942 #[test]
2943 fn a_scan_says_what_is_wrong_with_it() {
2944 let mut f = Fixture::new();
2945 assert_eq!(f.run(&[b"SCAN", b"nope"]), "-ERR invalid cursor\r\n");
2946 assert_eq!(f.run(&[b"SCAN", b"-1"]), "-ERR invalid cursor\r\n");
2947 assert_eq!(f.run(&[b"SCAN", b"0", b"MATCH"]), "-ERR syntax error\r\n");
2948 assert_eq!(
2949 f.run(&[b"SCAN", b"0", b"COUNT", b"0"]),
2950 "-ERR syntax error\r\n"
2951 );
2952 assert_eq!(
2953 f.run(&[b"SCAN", b"0", b"COUNT", b"x"]),
2954 "-ERR value is not an integer or out of range\r\n"
2955 );
2956 assert_eq!(
2957 f.run(&[b"SCAN", b"0", b"WAT", b"1"]),
2958 "-ERR syntax error\r\n"
2959 );
2960 assert!(f.run(&[b"SCAN", b"18446744073709551615"]).starts_with("*2"));
2965 }
2966
2967 #[test]
2968 fn keys_and_randomkey_look_at_the_whole_database() {
2969 let mut f = Fixture::new();
2970 assert_eq!(f.run(&[b"KEYS", b"*"]), "*0\r\n");
2971 assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
2972
2973 for name in ["one", "two", "three"] {
2974 f.run(&[b"SET", name.as_bytes(), b"v"]);
2975 }
2976 assert_eq!(sorted(&f.run(&[b"KEYS", b"*"])), ["one", "three", "two"]);
2977 assert_eq!(sorted(&f.run(&[b"KEYS", b"t*"])), ["three", "two"]);
2978 assert_eq!(f.run(&[b"KEYS", b"nothing"]), "*0\r\n");
2979
2980 for _ in 0..50 {
2981 let got = f.run(&[b"RANDOMKEY"]);
2982 assert!(
2983 ["$3\r\none\r\n", "$3\r\ntwo\r\n", "$5\r\nthree\r\n"].contains(&got.as_str()),
2984 "got {got}"
2985 );
2986 }
2987 }
2988
2989 #[test]
2990 fn a_walk_does_not_answer_keys_that_have_expired() {
2991 let mut f = Fixture::new();
2992 f.run(&[b"SET", b"alive", b"v"]);
2993 f.run(&[b"SET", b"dead", b"v", b"PX", b"1"]);
2994 f.server.advance_clock_ms(2);
2995 assert_eq!(
2996 f.run(&[b"DBSIZE"]),
2997 ":2\r\n",
2998 "nothing has collected it yet"
2999 );
3000
3001 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$5\r\nalive\r\n");
3002 let (_, keys) = scan_reply(&f.run(&[b"SCAN", b"0", b"COUNT", b"1000"]));
3003 assert_eq!(keys, ["alive"]);
3004 for _ in 0..20 {
3005 assert_eq!(f.run(&[b"RANDOMKEY"]), "$5\r\nalive\r\n");
3006 }
3007 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
3010 }
3011
3012 #[test]
3013 fn a_key_deadline_goes_on_and_comes_back_in_all_four_units() {
3014 let mut f = Fixture::new();
3015 f.run(&[b"SET", b"k", b"v"]);
3016 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "there and no deadline");
3017 assert_eq!(f.run(&[b"TTL", b"nosuch"]), ":-2\r\n", "not there at all");
3018
3019 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100"]), ":1\r\n");
3020 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
3021 let ms = int(&f.run(&[b"PTTL", b"k"]));
3022 assert!((99_000..=100_000).contains(&ms), "got {ms}");
3023
3024 let at = int(&f.run(&[b"EXPIRETIME", b"k"]));
3026 let at_ms = int(&f.run(&[b"PEXPIRETIME", b"k"]));
3027 assert_eq!(at, (at_ms + 500) / 1000);
3028 assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
3029
3030 assert_eq!(f.run(&[b"PERSIST", b"k"]), ":1\r\n");
3031 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
3032 assert_eq!(
3033 f.run(&[b"PERSIST", b"k"]),
3034 ":0\r\n",
3035 "nothing to take off the second time"
3036 );
3037 assert_eq!(f.run(&[b"PERSIST", b"nosuch"]), ":0\r\n");
3038 assert_eq!(
3039 f.run(&[b"GET", b"k"]),
3040 "$1\r\nv\r\n",
3041 "and the value went through all of that untouched"
3042 );
3043 }
3044
3045 #[test]
3046 fn every_type_can_be_given_a_deadline_and_it_is_the_same_deadline() {
3047 let mut f = Fixture::new();
3048 f.run(&[b"SET", b"str", b"v"]);
3049 f.run(&[b"SADD", b"set", b"a", b"b"]);
3050 f.run(&[b"HSET", b"hash", b"f", b"v"]);
3051
3052 for key in [b"str".as_slice(), b"set", b"hash"] {
3053 assert_eq!(f.run(&[b"EXPIRE", key, b"100"]), ":1\r\n");
3054 assert_eq!(f.run(&[b"TTL", key]), ":100\r\n");
3055 }
3056 assert_eq!(f.run(&[b"SCARD", b"set"]), ":2\r\n");
3059 assert_eq!(f.run(&[b"HGET", b"hash", b"f"]), "$1\r\nv\r\n");
3060 assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
3061 }
3062
3063 #[test]
3064 fn a_deadline_that_has_already_gone_deletes_the_key_now() {
3065 let mut f = Fixture::new();
3066 for key in [b"a".as_slice(), b"b", b"c", b"d"] {
3067 f.run(&[b"SET", key, b"v"]);
3068 }
3069 assert_eq!(f.run(&[b"EXPIRE", b"a", b"0"]), ":1\r\n");
3073 assert_eq!(f.run(&[b"EXPIRE", b"b", b"-1"]), ":1\r\n");
3074 assert_eq!(f.run(&[b"EXPIREAT", b"c", b"1"]), ":1\r\n");
3075 assert_eq!(f.run(&[b"PEXPIREAT", b"d", b"1"]), ":1\r\n");
3076 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3077 assert_eq!(
3078 f.run(&[b"EXPIRE", b"a", b"100"]),
3079 ":0\r\n",
3080 "and the key really went, so there is nothing to put a deadline on"
3081 );
3082 }
3083
3084 #[test]
3085 fn the_four_conditions_decide_whether_the_deadline_moves() {
3086 let mut f = Fixture::new();
3087 f.run(&[b"SET", b"k", b"v"]);
3088
3089 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX"]), ":0\r\n");
3090 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "and XX left it alone");
3091 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"GT"]), ":0\r\n");
3092 assert_eq!(
3093 f.run(&[b"EXPIRE", b"k", b"100", b"LT"]),
3094 ":1\r\n",
3095 "no deadline reads as infinitely far away, so LT passes where GT fails"
3096 );
3097
3098 assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"NX"]), ":0\r\n");
3099 assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"GT"]), ":0\r\n");
3100 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
3101 assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"LT"]), ":1\r\n");
3102 assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"GT"]), ":1\r\n");
3103 assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
3104
3105 assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"NX"]), ":0\r\n");
3108 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":1\r\n");
3109 assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"XX"]), ":1\r\n");
3110 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n", "and XX let it through");
3111 }
3112
3113 #[test]
3114 fn the_conditions_are_a_set_and_not_a_keyword() {
3115 let mut f = Fixture::new();
3116 f.run(&[b"SET", b"k", b"v"]);
3117
3118 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"nx"]), ":1\r\n");
3119 assert_eq!(
3120 f.run(&[b"EXPIRE", b"k", b"100", b"nx", b"nx"]),
3121 ":0\r\n",
3122 "the same keyword twice means it once, and NX now has a deadline to fail on"
3123 );
3124
3125 assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"xx", b"gt"]), ":1\r\n");
3128 assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
3129 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"gt", b"xx"]), ":0\r\n");
3130 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]), ":1\r\n");
3131 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
3132 f.run(&[b"PERSIST", b"k"]);
3133 assert_eq!(
3134 f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]),
3135 ":0\r\n",
3136 "where LT on its own would have taken it"
3137 );
3138 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"LT"]), ":1\r\n");
3139 }
3140
3141 #[test]
3142 fn a_key_is_gone_once_its_moment_passes() {
3143 let mut f = Fixture::new();
3144 f.run(&[b"SET", b"k", b"v"]);
3145 f.run(&[b"EXPIRE", b"k", b"100"]);
3146
3147 let at = int(&f.run(&[b"PEXPIRETIME", b"k"]));
3148 f.server.set_clock_ms(at as u64 + 1);
3149 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
3150 assert_eq!(f.run(&[b"TTL", b"k"]), ":-2\r\n");
3151 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
3152 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3153 }
3154
3155 #[test]
3156 fn the_expiry_commands_refuse_what_a_real_server_refuses() {
3157 let mut f = Fixture::new();
3158 f.run(&[b"SET", b"k", b"v"]);
3159 for (bad, want) in [
3160 (
3161 &[b"EXPIRE".as_slice(), b"k", b"soon"][..],
3162 "-ERR value is not an integer or out of range\r\n",
3163 ),
3164 (
3165 &[b"EXPIRE", b"k", b"100", b"MAYBE"],
3166 "-ERR Unsupported option MAYBE\r\n",
3167 ),
3168 (
3169 &[b"EXPIRE", b"k", b"100", b"NX", b"XX"],
3170 "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
3171 ),
3172 (
3173 &[b"EXPIRE", b"k", b"100", b"NX", b"GT"],
3174 "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
3175 ),
3176 (
3177 &[b"EXPIRE", b"k", b"100", b"GT", b"LT", b"GT"],
3178 "-ERR GT and LT options at the same time are not compatible\r\n",
3179 ),
3180 (
3183 &[b"EXPIRE", b"k", b"9223372036854775807"],
3184 "-ERR invalid expire time in 'expire' command\r\n",
3185 ),
3186 (
3187 &[b"EXPIREAT", b"k", b"9223372036854775807"],
3188 "-ERR invalid expire time in 'expireat' command\r\n",
3189 ),
3190 (
3191 &[b"PEXPIRE", b"k", b"9223372036854775807"],
3192 "-ERR invalid expire time in 'pexpire' command\r\n",
3193 ),
3194 ] {
3195 assert_eq!(f.run(bad), want, "for {bad:?}");
3196 }
3197 assert_eq!(
3198 f.run(&[b"TTL", b"k"]),
3199 ":-1\r\n",
3200 "and none of those put a deadline on anything"
3201 );
3202
3203 assert_eq!(
3207 f.run(&[b"PEXPIREAT", b"k", b"9223372036854775807"]),
3208 ":1\r\n"
3209 );
3210 assert_eq!(f.run(&[b"PEXPIRETIME", b"k"]), ":70368744177663\r\n");
3211 }
3212
3213 #[test]
3214 fn flushing_empties_this_database_or_every_one_of_them() {
3215 let mut f = Fixture::new();
3216 f.run(&[b"SELECT", b"0"]);
3217 f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
3218 f.run(&[b"SELECT", b"1"]);
3219 f.run(&[b"SET", b"c", b"3"]);
3220 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
3221 assert_eq!(f.run(&[b"FLUSHDB", b"async"]), "+OK\r\n");
3224 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3225 f.run(&[b"SELECT", b"0"]);
3227 assert_eq!(f.run(&[b"DBSIZE"]), ":2\r\n");
3228 assert_eq!(f.run(&[b"FLUSHALL", b"SYNC"]), "+OK\r\n");
3229 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3230 f.run(&[b"SELECT", b"1"]);
3231 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3232 assert_eq!(f.run(&[b"FLUSHALL", b"nope"]), "-ERR syntax error\r\n");
3235 assert_eq!(
3236 f.run(&[b"FLUSHDB", b"sync", b"sync"]),
3237 "-ERR syntax error\r\n"
3238 );
3239 }
3240
3241 #[test]
3242 fn the_script_cache_and_the_library_set_answer_for_being_empty() {
3243 let mut f = Fixture::new();
3244 assert_eq!(f.run(&[b"SCRIPT", b"FLUSH"]), "+OK\r\n");
3245 assert_eq!(f.run(&[b"SCRIPT", b"FLUSH", b"async"]), "+OK\r\n");
3246 assert_eq!(f.run(&[b"FUNCTION", b"FLUSH", b"SYNC"]), "+OK\r\n");
3247 assert_eq!(
3250 f.run(&[b"SCRIPT", b"EXISTS", b"aaaa", b"bbbb"]),
3251 "*2\r\n:0\r\n:0\r\n"
3252 );
3253 assert_eq!(f.run(&[b"FUNCTION", b"LIST"]), "*0\r\n");
3254 assert_eq!(
3255 f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"x", b"WITHCODE"]),
3256 "*0\r\n"
3257 );
3258 assert_eq!(
3259 f.run(&[b"FUNCTION", b"DELETE", b"nosuch"]),
3260 "-ERR Library not found\r\n"
3261 );
3262
3263 assert_eq!(
3266 f.run(&[b"SCRIPT", b"FLUSH", b"nope"]),
3267 "-ERR SCRIPT FLUSH only support SYNC|ASYNC option\r\n"
3268 );
3269 assert_eq!(
3270 f.run(&[b"FUNCTION", b"FLUSH", b"nope"]),
3271 "-ERR FUNCTION FLUSH only supports SYNC|ASYNC option\r\n"
3272 );
3273 assert_eq!(
3276 f.run(&[b"FUNCTION", b"FLUSH", b"sync", b"sync"]),
3277 "-ERR unknown subcommand or wrong number of arguments for 'flush'. Try FUNCTION HELP.\r\n"
3278 );
3279 assert_eq!(
3280 f.run(&[b"FUNCTION", b"LIST", b"bogus"]),
3281 "-ERR Unknown argument bogus\r\n"
3282 );
3283 assert_eq!(
3284 f.run(&[b"SCRIPT", b"EXISTS"]),
3285 "-ERR wrong number of arguments for 'script|exists' command\r\n"
3286 );
3287
3288 assert_eq!(
3291 f.run(&[b"SCRIPT", b"LOAD", b"return 1"]),
3292 "-ERR unknown subcommand 'LOAD'. Try SCRIPT HELP.\r\n"
3293 );
3294 assert_eq!(
3295 f.run(&[b"FUNCTION", b"STATS"]),
3296 "-ERR unknown subcommand 'STATS'. Try FUNCTION HELP.\r\n"
3297 );
3298 }
3299
3300 #[test]
3301 fn a_counter_is_an_integer_and_not_a_string_of_digits() {
3302 let mut f = Fixture::new();
3303 assert_eq!(f.run(&[b"INCR", b"c"]), ":1\r\n");
3304 assert_eq!(f.run(&[b"INCRBY", b"c", b"41"]), ":42\r\n");
3305 assert_eq!(f.run(&[b"DECRBY", b"c", b"2"]), ":40\r\n");
3306 assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\n40\r\n");
3309 assert_eq!(f.run(&[b"INCRBYFLOAT", b"c", b"0.5"]), "$4\r\n40.5\r\n");
3310 f.run(&[b"SET", b"k", b"hello"]);
3313 assert_eq!(
3314 f.run(&[b"INCR", b"k"]),
3315 "-ERR value is not an integer or out of range\r\n"
3316 );
3317 assert_eq!(
3318 f.run(&[b"INCRBYFLOAT", b"c", b"inf"]),
3319 "-ERR increment would produce NaN or Infinity\r\n"
3320 );
3321 }
3322
3323 #[test]
3328 fn the_newer_commands_reply_in_the_shapes_a_real_server_sends() {
3329 let mut f = Fixture::new();
3330 assert_eq!(f.run(&[b"SET", b"k", b"hello"]), "+OK\r\n");
3331 assert_eq!(f.run(&[b"DIGEST", b"k"]), "$16\r\n9555e8555c62dcfd\r\n");
3334 assert_eq!(f.run(&[b"DIGEST", b"nosuch"]), "$-1\r\n");
3335 assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"1"]), ":1\r\n");
3336 assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"2", b"NX"]), ":0\r\n");
3337 assert_eq!(f.run(&[b"GET", b"a"]), "$1\r\n1\r\n");
3338 assert_eq!(f.run(&[b"INCREX", b"n"]), "*2\r\n:1\r\n:1\r\n");
3339 assert_eq!(
3340 f.run(&[b"INCREX", b"n", b"BYINT", b"5", b"UBOUND", b"3"]),
3341 "*2\r\n:1\r\n:0\r\n",
3342 "a refused increment reports the value it left alone and applied nothing"
3343 );
3344 assert_eq!(
3345 f.run(&[
3346 b"INCREX",
3347 b"n",
3348 b"BYINT",
3349 b"5",
3350 b"UBOUND",
3351 b"3",
3352 b"SATURATE"
3353 ]),
3354 "*2\r\n:3\r\n:2\r\n"
3355 );
3356 assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"2"]), ":0\r\n");
3357 assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"1"]), ":1\r\n");
3358 }
3359
3360 #[test]
3361 fn the_same_answers_come_out_in_resp3_spelling() {
3362 let mut f = Fixture::new();
3363 assert!(f.run(&[b"HELLO", b"3"]).starts_with("%7\r\n"));
3364 assert_eq!(f.run(&[b"GET", b"nosuch"]), "_\r\n");
3365 assert_eq!(
3368 f.run(&[b"INCREX", b"c", b"BYFLOAT", b"1.5"]),
3369 "*2\r\n,1.5\r\n,1.5\r\n"
3370 );
3371 assert_eq!(f.run(&[b"INCRBYFLOAT", b"f", b"2.5"]), "$3\r\n2.5\r\n");
3372 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
3375 assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
3376 }
3377
3378 #[test]
3379 fn a_command_nobody_has_heard_of_is_an_error_and_not_a_closed_socket() {
3380 let mut f = Fixture::new();
3381 let (flow, reply) = f.flow(&[b"NOPE", b"a", b"b"]);
3382 assert_eq!(flow, Flow::Continue);
3383 assert_eq!(
3384 reply,
3385 "-ERR unknown command 'NOPE', with args beginning with: 'a' 'b' \r\n"
3386 );
3387 let reply = f.run(&[b"NO\r\n+PONG\r\nPE"]);
3390 assert_eq!(reply.matches("\r\n").count(), 1);
3391 }
3392
3393 #[test]
3394 fn arity_is_checked_before_the_command_is() {
3395 let mut f = Fixture::new();
3396 assert_eq!(
3397 f.run(&[b"GET"]),
3398 "-ERR wrong number of arguments for 'get' command\r\n"
3399 );
3400 assert_eq!(
3401 f.run(&[b"MSET", b"k"]),
3402 "-ERR wrong number of arguments for 'mset' command\r\n"
3403 );
3404 assert_eq!(
3408 f.run(&[b"PING", b"a", b"b"]),
3409 "-ERR wrong number of arguments for 'ping' command\r\n"
3410 );
3411 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
3412 assert_eq!(f.run(&[b"PING", b"hi"]), "$2\r\nhi\r\n");
3413 assert_eq!(
3415 f.run(&[b"DELEX", b"k", b"IFEQ"]),
3416 "-ERR wrong number of arguments for 'delex' command\r\n"
3417 );
3418 }
3419
3420 #[test]
3424 fn the_option_combinations_are_the_ones_a_real_server_accepts() {
3425 let mut f = Fixture::new();
3426 let syntax = "-ERR syntax error\r\n";
3427 assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"XX"]), syntax);
3428 assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"IFEQ", b"a"]), syntax);
3429 assert_eq!(
3430 f.run(&[b"SET", b"k", b"v", b"KEEPTTL", b"EX", b"5"]),
3431 syntax
3432 );
3433 assert_eq!(
3434 f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"PX", b"5"]),
3435 syntax
3436 );
3437 assert_eq!(f.run(&[b"SET", b"k", b"v", b"PERSIST"]), syntax);
3438 assert_eq!(
3440 f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"EX", b"100"]),
3441 "+OK\r\n"
3442 );
3443 assert_eq!(f.run(&[b"SET", b"k", b"v", b"XX", b"XX"]), "+OK\r\n");
3444 assert_eq!(f.run(&[b"SET", b"k", b"v", b"GET", b"GET"]), "$1\r\nv\r\n");
3445 assert_eq!(
3447 f.run(&[b"INCREX", b"n", b"BYINT", b"1", b"BYINT", b"2"]),
3448 syntax
3449 );
3450 assert_eq!(
3451 f.run(&[b"INCREX", b"n", b"ENX"]),
3452 "-ERR ENX flag requires an expiration\r\n"
3453 );
3454 assert_eq!(
3455 f.run(&[b"INCREX", b"n", b"UBOUND", b"abc"]),
3456 "-ERR UBOUND is not an integer or out of range\r\n"
3457 );
3458 assert_eq!(
3459 f.run(&[b"INCREX", b"n", b"LBOUND", b"10", b"UBOUND", b"5"]),
3460 "-ERR LBOUND can't be greater than UBOUND\r\n"
3461 );
3462 assert_eq!(
3463 f.run(&[b"LCS", b"a", b"b", b"LEN", b"IDX"]),
3464 "-ERR If you want both the length and indexes, please just use IDX.\r\n"
3465 );
3466 }
3467
3468 #[test]
3472 fn the_expiry_rules_are_redis_own() {
3473 let mut f = Fixture::new();
3474 let bad = "-ERR invalid expire time in 'set' command\r\n";
3475 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"0"]), bad);
3476 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"-1"]), bad);
3477 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"0"]), bad);
3478 assert_eq!(
3479 f.run(&[b"SET", b"k", b"v", b"EX", b"9999999999999999"]),
3480 bad
3481 );
3482 assert_eq!(
3483 f.run(&[b"SET", b"k", b"v", b"PX", b"99999999999999999999"]),
3484 "-ERR value is not an integer or out of range\r\n"
3485 );
3486 assert_eq!(
3487 f.run(&[b"SETEX", b"k", b"0", b"v"]),
3488 "-ERR invalid expire time in 'setex' command\r\n"
3489 );
3490 assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"0"]), "$-1\r\n");
3491 assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"abc"]), "$-1\r\n");
3492 assert_eq!(
3493 f.run(&[b"GETEX", b"nosuch", b"KEEPTTL"]),
3494 "-ERR syntax error\r\n",
3495 "the option list is still checked before the key is looked up"
3496 );
3497 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
3499 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"1"]), "+OK\r\n");
3500 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
3501 }
3502
3503 #[test]
3504 fn mset_takes_its_pairs_from_the_read_buffer() {
3505 let mut f = Fixture::new();
3506 assert_eq!(f.run(&[b"MSET", b"a", b"1", b"b", b"2"]), "+OK\r\n");
3507 assert_eq!(
3508 f.run(&[b"MGET", b"a", b"b", b"nosuch"]),
3509 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$-1\r\n"
3510 );
3511 assert_eq!(f.run(&[b"MSETNX", b"b", b"9", b"c", b"3"]), ":0\r\n");
3512 assert_eq!(f.run(&[b"MSETNX", b"c", b"3", b"d", b"4"]), ":1\r\n");
3513 assert_eq!(
3514 f.run(&[b"MSETEX", b"2", b"e", b"5"]),
3515 "-ERR wrong number of key-value pairs\r\n"
3516 );
3517 assert_eq!(
3518 f.run(&[b"MSETEX", b"0", b"e", b"5"]),
3519 "-ERR invalid numkeys value\r\n"
3520 );
3521 assert_eq!(
3522 f.run(&[b"MSETEX", b"abc", b"e", b"5"]),
3523 "-ERR invalid numkeys value\r\n"
3524 );
3525 }
3526
3527 #[test]
3528 fn lcs_answers_the_length_the_string_and_the_runs() {
3529 let mut f = Fixture::new();
3530 f.run(&[b"MSET", b"a", b"ohmytext", b"b", b"mynewtext"]);
3531 assert_eq!(f.run(&[b"LCS", b"a", b"b"]), "$6\r\nmytext\r\n");
3532 assert_eq!(f.run(&[b"LCS", b"a", b"b", b"LEN"]), ":6\r\n");
3533 assert_eq!(
3534 f.run(&[b"LCS", b"a", b"b", b"IDX", b"MINMATCHLEN", b"4"]),
3535 "*4\r\n$7\r\nmatches\r\n*1\r\n*2\r\n*2\r\n:4\r\n:7\r\n*2\r\n:5\r\n:8\r\n$3\r\nlen\r\n:6\r\n"
3536 );
3537 assert_eq!(
3540 f.run(&[b"LCS", b"a", b"b", b"MINMATCHLEN", b"4", b"WITHMATCHLEN"]),
3541 "$6\r\nmytext\r\n"
3542 );
3543 }
3544
3545 #[test]
3546 fn select_moves_the_connection_and_the_databases_stay_apart() {
3547 let mut f = Fixture::new();
3548 f.run(&[b"SET", b"k", b"zero"]);
3549 assert_eq!(f.run(&[b"SELECT", b"4"]), "+OK\r\n");
3550 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
3551 f.run(&[b"SET", b"k", b"four"]);
3552 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
3553 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
3554 assert_eq!(
3555 f.run(&[b"SELECT", b"99"]),
3556 "-ERR DB index is out of range\r\n"
3557 );
3558 assert_eq!(
3559 f.run(&[b"SELECT", b"-1"]),
3560 "-ERR DB index is out of range\r\n"
3561 );
3562 assert_eq!(
3563 f.run(&[b"SELECT", b"abc"]),
3564 "-ERR value is not an integer or out of range\r\n"
3565 );
3566 f.run(&[b"SELECT", b"4"]);
3568 f.run(&[b"RESET"]);
3569 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
3570 }
3571
3572 #[test]
3573 fn hello_agrees_on_a_protocol_and_refuses_the_ones_that_do_not_exist() {
3574 let mut f = Fixture::new();
3575 let reply = f.run(&[b"HELLO"]);
3576 assert!(reply.starts_with("*14\r\n"), "{reply}");
3577 assert!(reply.contains("$5\r\nredis\r\n"), "{reply}");
3578 assert!(reply.contains("$5\r\n8.8.0\r\n"), "{reply}");
3579 assert!(
3580 reply.contains(":7\r\n"),
3581 "the connection id is in there: {reply}"
3582 );
3583 assert_eq!(
3584 f.run(&[b"HELLO", b"4"]),
3585 "-NOPROTO unsupported protocol version\r\n"
3586 );
3587 assert_eq!(
3588 f.run(&[b"HELLO", b"abc"]),
3589 "-ERR Protocol version is not an integer or out of range\r\n"
3590 );
3591 assert_eq!(
3592 f.run(&[b"HELLO", b"3", b"SETNAME"]),
3593 "-ERR Syntax error in HELLO option 'SETNAME'\r\n"
3594 );
3595 assert!(
3596 f.run(&[b"HELLO", b"3", b"SETNAME", b"bob"])
3597 .starts_with("%7\r\n")
3598 );
3599 assert_eq!(f.session.name(), b"bob");
3600 f.run(&[b"RESET"]);
3601 assert_eq!(f.session.name(), b"");
3602 }
3603
3604 #[test]
3605 fn command_describes_this_server_in_the_shape_a_driver_reads() {
3606 let mut f = Fixture::new();
3607 let count = format!(":{}\r\n", COMMANDS.len());
3608 assert_eq!(f.run(&[b"COMMAND", b"COUNT"]), count);
3609 let info = f.run(&[b"COMMAND", b"INFO", b"get"]);
3610 assert_eq!(
3611 info,
3612 "*1\r\n*10\r\n$3\r\nget\r\n:2\r\n*2\r\n+readonly\r\n+fast\r\n:1\r\n:1\r\n:1\r\n\
3613 *3\r\n+@read\r\n+@string\r\n+@fast\r\n*0\r\n*0\r\n*0\r\n"
3614 );
3615 assert_eq!(f.run(&[b"COMMAND", b"INFO", b"nosuch"]), "*1\r\n$-1\r\n");
3617 assert_eq!(
3618 f.run(&[b"COMMAND", b"LIST", b"FILTERBY", b"PATTERN", b"getr*"]),
3619 "*1\r\n$8\r\ngetrange\r\n"
3620 );
3621 assert_eq!(
3622 f.run(&[b"COMMAND", b"NOPE"]),
3623 "-ERR unknown subcommand 'NOPE'. Try COMMAND HELP.\r\n"
3624 );
3625 }
3626
3627 #[test]
3631 fn command_getkeys_finds_the_keys_including_the_hidden_ones() {
3632 let mut f = Fixture::new();
3633 assert_eq!(
3634 f.run(&[b"COMMAND", b"GETKEYS", b"get", b"k"]),
3635 "*1\r\n$1\r\nk\r\n"
3636 );
3637 assert_eq!(
3638 f.run(&[b"COMMAND", b"GETKEYS", b"mset", b"a", b"1", b"b", b"2"]),
3639 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
3640 );
3641 assert_eq!(
3642 f.run(&[
3643 b"COMMAND", b"GETKEYS", b"msetex", b"2", b"a", b"1", b"b", b"2"
3644 ]),
3645 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
3646 );
3647 assert_eq!(
3648 f.run(&[b"COMMAND", b"GETKEYS", b"ping"]),
3649 "-ERR The command has no key arguments\r\n"
3650 );
3651 assert_eq!(
3652 f.run(&[b"COMMAND", b"GETKEYS", b"set"]),
3653 "-ERR Invalid number of arguments specified for command\r\n"
3654 );
3655 }
3656
3657 #[test]
3658 fn config_answers_what_it_can_and_refuses_what_it_cannot() {
3659 let mut f = Fixture::new();
3660 assert_eq!(
3661 f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
3662 "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n"
3663 );
3664 let both = f.run(&[b"CONFIG", b"GET", b"maxmemory*", b"maxmemory"]);
3667 assert!(both.starts_with("*6\r\n"), "{both}");
3668 assert_eq!(f.run(&[b"CONFIG", b"GET", b"nosuch"]), "*0\r\n");
3669 assert_eq!(f.run(&[b"CONFIG", b"SET", b"appendonly", b"no"]), "+OK\r\n");
3670 assert_eq!(
3671 f.run(&[b"CONFIG", b"SET", b"appendonly", b"yes"]),
3672 "-ERR CONFIG SET failed (possibly related to argument 'appendonly') - can't set immutable config\r\n"
3673 );
3674 assert_eq!(
3675 f.run(&[b"CONFIG", b"SET", b"nosuch", b"1"]),
3676 "-ERR Unknown option or number of arguments for CONFIG SET - 'nosuch'\r\n"
3677 );
3678 assert_eq!(
3679 f.run(&[b"CONFIG", b"GET"]),
3680 "-ERR wrong number of arguments for 'config|get' command\r\n"
3681 );
3682 assert_eq!(
3686 f.run(&[b"CONFIG", b"SET", b"appendonly"]),
3687 "-ERR wrong number of arguments for 'config|set' command\r\n"
3688 );
3689 assert_eq!(
3690 f.run(&[b"CONFIG", b"SET", b"appendonly", b"no", b"maxmemory"]),
3691 "-ERR syntax error\r\n"
3692 );
3693 assert_eq!(f.run(&[b"CONFIG", b"RESETSTAT"]), "+OK\r\n");
3694 assert_eq!(
3695 f.run(&[b"CONFIG", b"REWRITE"]),
3696 "-ERR The server is running without a config file\r\n"
3697 );
3698 }
3699
3700 #[test]
3701 fn the_eviction_policy_reads_back_what_was_written_to_it() {
3702 let mut f = Fixture::new();
3703 assert_eq!(
3704 f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
3705 "*2\r\n$16\r\nmaxmemory-policy\r\n$10\r\nnoeviction\r\n"
3706 );
3707 assert_eq!(
3708 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"AllKeys-LFU"]),
3709 "+OK\r\n",
3710 "the name is matched without regard to case, like every other one"
3711 );
3712 assert_eq!(
3713 f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
3714 "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
3715 );
3716 assert!(
3718 f.run(&[b"INFO", b"memory"])
3719 .contains("maxmemory_policy:allkeys-lfu"),
3720 "INFO and CONFIG disagree about the policy"
3721 );
3722 assert_eq!(
3726 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"garbage"]),
3727 "-ERR CONFIG SET failed (possibly related to argument 'maxmemory-policy') - argument(s) must be one of the following: volatile-lru, volatile-lfu, volatile-random, volatile-ttl, volatile-lrm, allkeys-lru, allkeys-lfu, allkeys-random, allkeys-lrm, noeviction\r\n"
3728 );
3729 assert_eq!(
3732 f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
3733 "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
3734 );
3735 f.run(&[
3736 b"CONFIG",
3737 b"SET",
3738 b"hash-max-listpack-entries",
3739 b"7",
3740 b"maxmemory-policy",
3741 b"nonsense",
3742 ]);
3743 assert_eq!(
3744 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
3745 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n"
3746 );
3747 }
3748
3749 #[test]
3750 fn the_three_eviction_numbers_read_back_too() {
3751 let mut f = Fixture::new();
3752 for (name, default, set) in [
3753 ("maxmemory-samples", "5", "12"),
3754 ("lfu-log-factor", "10", "3"),
3755 ("lfu-decay-time", "1", "60"),
3756 ] {
3757 let get = || {
3758 format!(
3759 "*2\r\n${}\r\n{name}\r\n${}\r\n{default}\r\n",
3760 name.len(),
3761 default.len()
3762 )
3763 };
3764 assert_eq!(f.run(&[b"CONFIG", b"GET", name.as_bytes()]), get());
3765 assert_eq!(
3766 f.run(&[b"CONFIG", b"SET", name.as_bytes(), set.as_bytes()]),
3767 "+OK\r\n"
3768 );
3769 assert_eq!(
3770 f.run(&[b"CONFIG", b"GET", name.as_bytes()]),
3771 format!(
3772 "*2\r\n${}\r\n{name}\r\n${}\r\n{set}\r\n",
3773 name.len(),
3774 set.len()
3775 )
3776 );
3777 assert_eq!(
3780 f.run(&[b"CONFIG", b"SET", name.as_bytes(), b"soon"]),
3781 format!(
3782 "-ERR CONFIG SET failed (possibly related to argument '{name}') - argument couldn't be parsed into an integer\r\n"
3783 )
3784 );
3785 }
3786 }
3787
3788 #[test]
3789 fn the_memory_limit_reads_back_in_bytes_whatever_the_unit_was() {
3790 let mut f = Fixture::new();
3791 assert_eq!(
3792 f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
3793 "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n",
3794 "no limit is the default"
3795 );
3796 for (typed, bytes) in [
3799 (&b"1024"[..], "1024"),
3800 (b"1k", "1000"),
3801 (b"1kb", "1024"),
3802 (b"1M", "1000000"),
3803 (b"1Mb", "1048576"),
3804 (b"1gb", "1073741824"),
3805 (b"100mb", "104857600"),
3806 ] {
3807 assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxmemory", typed]), "+OK\r\n");
3808 assert_eq!(
3809 f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
3810 format!("*2\r\n$9\r\nmaxmemory\r\n${}\r\n{bytes}\r\n", bytes.len()),
3811 "set {}",
3812 String::from_utf8_lossy(typed)
3813 );
3814 }
3815 assert!(
3816 f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
3817 "the report agrees with the setting"
3818 );
3819
3820 for bad in [&b"1tb"[..], b"-1", b"", b"lots"] {
3823 assert_eq!(
3824 f.run(&[b"CONFIG", b"SET", b"maxmemory", bad]),
3825 "-ERR CONFIG SET failed (possibly related to argument 'maxmemory') - argument must be a memory value\r\n",
3826 "refused {}",
3827 String::from_utf8_lossy(bad)
3828 );
3829 }
3830 assert!(
3831 f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
3832 "and the refusal left the old one alone"
3833 );
3834 }
3835
3836 #[test]
3837 fn a_write_is_refused_when_there_is_no_room_and_nothing_to_evict() {
3838 let mut f = Fixture::new();
3839 f.run(&[b"SET", b"here", b"already"]);
3840 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1"]);
3844 assert_eq!(
3845 f.run(&[b"SET", b"k", b"v"]),
3846 "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
3847 );
3848 assert_eq!(
3849 f.run(&[b"LPUSH", b"l", b"v"]),
3850 "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
3851 );
3852 assert_eq!(f.run(&[b"GET", b"here"]), "$7\r\nalready\r\n");
3854 assert_eq!(f.run(&[b"DEL", b"here"]), ":1\r\n");
3855 assert!(f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"));
3856
3857 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
3859 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
3860 }
3861
3862 #[test]
3863 fn an_allkeys_policy_makes_room_instead_of_refusing() {
3864 let mut f = Fixture::new();
3865 let val = vec![b'v'; 256];
3866 for i in 0..24000u32 {
3867 let k = format!("key:{i:08}");
3868 f.run(&[b"SET", k.as_bytes(), &val]);
3869 }
3870 let full = f.server.memory_bytes();
3871 assert!(
3872 full > 3 * 1024 * 1024,
3873 "the arena is several segments: {full}"
3874 );
3875
3876 let limit = full - 2 * 1024 * 1024;
3880 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
3881 f.run(&[
3882 b"CONFIG",
3883 b"SET",
3884 b"maxmemory",
3885 limit.to_string().as_bytes(),
3886 ]);
3887
3888 for i in 0..2000u32 {
3892 let k = format!("new:{i:08}");
3893 assert_eq!(
3894 f.run(&[b"SET", k.as_bytes(), &val]),
3895 "+OK\r\n",
3896 "write {i} was refused"
3897 );
3898 f.server.refresh_memory();
3899 if f.server.memory_bytes() <= limit {
3900 break;
3901 }
3902 }
3903 assert!(
3904 f.server.memory_bytes() <= limit,
3905 "it never got under: {} against {limit}",
3906 f.server.memory_bytes()
3907 );
3908 let info = f.run(&[b"INFO", b"stats"]);
3909 assert!(!info.contains("evicted_keys:0"), "{info}");
3910 assert!(
3911 f.run(&[b"DBSIZE"]) != ":0\r\n",
3912 "and it did not empty the database to get there"
3913 );
3914 }
3915
3916 #[test]
3917 fn the_running_total_and_the_walk_agree_on_a_mixed_keyspace() {
3918 let mut f = Fixture::new();
3925 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
3926 let big = vec![b'v'; 200];
3927
3928 for i in 0..400u32 {
3929 let n = i.to_string();
3930 let n = n.as_bytes();
3931 f.run(&[b"SADD", b"s", n]);
3932 f.run(&[b"SADD", b"s2", &big]);
3933 f.run(&[b"HSET", b"h", n, &big]);
3934 f.run(&[b"RPUSH", b"l", &big]);
3935 f.run(&[b"ZADD", b"z", n, n]);
3936 f.run(&[b"ARSET", b"a", n, &big]);
3937 if i % 7 == 0 {
3938 f.run(&[b"SREM", b"s", n]);
3939 f.run(&[b"HDEL", b"h", n]);
3940 f.run(&[b"LPOP", b"l"]);
3941 f.run(&[b"ZREM", b"z", n]);
3942 f.run(&[b"ARDEL", b"a", n]);
3943 }
3944 if i % 53 == 0 {
3945 f.run(&[b"DEL", b"s2"]);
3948 }
3949 assert_eq!(
3950 f.server.settled_memory(),
3951 f.server.memory_bytes(),
3952 "after round {i}"
3953 );
3954 }
3955
3956 assert_eq!(f.run(&[b"DBSIZE"]), ":6\r\n");
3959 assert!(
3960 f.server.memory_bytes() > 512 * 1024,
3961 "{}",
3962 f.server.memory_bytes()
3963 );
3964
3965 f.run(&[b"FLUSHALL"]);
3967 assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
3968 }
3969
3970 #[test]
3971 fn taking_the_limit_away_stops_the_counting_and_putting_it_back_starts_again() {
3972 let mut f = Fixture::new();
3977 for i in 0..200u32 {
3978 let n = i.to_string();
3979 f.run(&[b"SADD", b"s", n.as_bytes()]);
3980 f.run(&[b"HSET", b"h", n.as_bytes(), b"value"]);
3981 }
3982 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
3983 assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
3984
3985 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
3986 for i in 200..400u32 {
3987 let n = i.to_string();
3988 f.run(&[b"SADD", b"s", n.as_bytes()]);
3989 }
3990 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
3991 assert_eq!(
3992 f.server.settled_memory(),
3993 f.server.memory_bytes(),
3994 "the writes it was not watching are in the number it started from"
3995 );
3996 }
3997
3998 #[test]
3999 fn evicted_keys_and_expired_keys_are_different_numbers() {
4000 let mut f = Fixture::new();
4001 f.run(&[b"SET", b"gone", b"v", b"PX", b"1"]);
4004 f.server.advance_clock_ms(20);
4005 f.run(&[b"GET", b"gone"]);
4006 let info = f.run(&[b"INFO", b"stats"]);
4007 assert!(info.contains("expired_keys:1"), "{info}");
4008 assert!(info.contains("evicted_keys:0"), "{info}");
4009 }
4010
4011 #[test]
4012 fn the_object_subcommands_follow_the_policy() {
4013 let mut f = Fixture::new();
4014 f.run(&[b"SET", b"s", b"v"]);
4015 assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
4019 assert!(
4020 f.run(&[b"OBJECT", b"FREQ", b"s"])
4021 .starts_with("-ERR An LFU maxmemory policy is not selected"),
4022 );
4023
4024 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lfu"]);
4025 assert!(
4026 f.run(&[b"OBJECT", b"IDLETIME", b"s"])
4027 .starts_with("-ERR An LFU maxmemory policy is selected"),
4028 );
4029 assert!(
4034 f.run(&[b"OBJECT", b"FREQ", b"s"]).starts_with(':'),
4035 "FREQ should answer under an LFU policy"
4036 );
4037 }
4038
4039 #[test]
4040 fn object_says_which_rung_of_the_ladder_a_key_is_on() {
4041 let mut f = Fixture::new();
4042 f.run(&[b"SET", b"s", b"hello"]);
4043 f.run(&[b"SET", b"n", b"123"]);
4044 f.run(&[b"SADD", b"si", b"1", b"2", b"3"]);
4045 f.run(&[b"SADD", b"ss", b"a", b"b"]);
4046 f.run(&[b"HSET", b"h", b"f", b"v"]);
4047 for (key, want) in [
4048 (b"s".as_slice(), "embstr"),
4049 (b"n", "int"),
4050 (b"si", "intset"),
4051 (b"ss", "listpack"),
4052 (b"h", "listpack"),
4053 ] {
4054 let reply = f.run(&[b"OBJECT", b"ENCODING", key]);
4055 assert_eq!(reply, format!("${}\r\n{want}\r\n", want.len()));
4056 }
4057
4058 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"f"]);
4061 assert_eq!(
4062 f.run(&[b"OBJECT", b"ENCODING", b"h"]),
4063 "$10\r\nlistpackex\r\n"
4064 );
4065
4066 assert_eq!(f.run(&[b"OBJECT", b"REFCOUNT", b"s"]), ":1\r\n");
4067 assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
4068 assert!(f.run(&[b"OBJECT", b"HELP"]).starts_with("*14\r\n+OBJECT "));
4069 }
4070
4071 #[test]
4072 fn object_answers_nil_for_a_key_that_is_not_there() {
4073 let mut f = Fixture::new();
4074 for sub in [b"ENCODING".as_slice(), b"REFCOUNT", b"IDLETIME", b"FREQ"] {
4075 assert_eq!(
4076 f.run(&[b"OBJECT", sub, b"nokey"]),
4077 "$-1\r\n",
4078 "a nil and not an error, which is what 8.10.1 does"
4079 );
4080 }
4081 f.run(&[b"SET", b"s", b"v"]);
4084 assert!(
4085 f.run(&[b"OBJECT", b"FREQ", b"s"])
4086 .starts_with("-ERR An LFU maxmemory policy is not"),
4087 );
4088 assert_eq!(
4089 f.run(&[b"OBJECT", b"NOPE", b"s"]),
4090 "-ERR unknown subcommand 'NOPE'. Try OBJECT HELP.\r\n"
4091 );
4092 assert_eq!(
4093 f.run(&[b"OBJECT", b"ENCODING"]),
4094 "-ERR wrong number of arguments for 'object|encoding' command\r\n"
4095 );
4096 assert_eq!(
4097 f.run(&[b"OBJECT", b"ENCODING", b"s", b"extra"]),
4098 "-ERR wrong number of arguments for 'object|encoding' command\r\n"
4099 );
4100 assert_eq!(
4101 f.run(&[b"OBJECT"]),
4102 "-ERR wrong number of arguments for 'object' command\r\n"
4103 );
4104 }
4105
4106 #[test]
4107 fn config_moves_the_ladder_and_object_encoding_agrees() {
4108 let mut f = Fixture::new();
4109 assert_eq!(
4110 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
4111 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
4112 "512 and not the 128 everyone remembers, which is what 8.10.1 says"
4113 );
4114 assert_eq!(
4117 f.run(&[b"CONFIG", b"GET", b"hash-max-ziplist-entries"]),
4118 "*2\r\n$24\r\nhash-max-ziplist-entries\r\n$3\r\n512\r\n"
4119 );
4120 assert!(
4121 f.run(&[b"CONFIG", b"GET", b"hash-max-*"])
4122 .starts_with("*8\r\n")
4123 );
4124 assert!(
4125 f.run(&[b"CONFIG", b"GET", b"set-max-*"])
4126 .starts_with("*6\r\n")
4127 );
4128
4129 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2", b"c", b"3"]);
4130 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$8\r\nlistpack\r\n");
4131
4132 assert_eq!(
4133 f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"2"]),
4134 "+OK\r\n",
4135 "written under the old name and read back under the new one"
4136 );
4137 assert_eq!(
4138 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
4139 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n"
4140 );
4141 assert_eq!(
4142 f.run(&[b"OBJECT", b"ENCODING", b"h"]),
4143 "$8\r\nlistpack\r\n",
4144 "the hash that already exists is left exactly where it was"
4145 );
4146 f.run(&[b"HSET", b"h2", b"a", b"1", b"b", b"2", b"c", b"3"]);
4147 assert_eq!(
4148 f.run(&[b"OBJECT", b"ENCODING", b"h2"]),
4149 "$9\r\nhashtable\r\n",
4150 "and the next one built goes straight to a table"
4151 );
4152
4153 f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"2"]);
4155 f.run(&[b"SADD", b"s", b"1", b"2", b"3"]);
4156 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$8\r\nlistpack\r\n");
4157 f.run(&[b"CONFIG", b"SET", b"set-max-listpack-value", b"2"]);
4158 f.run(&[b"SADD", b"s2", b"abcdefgh"]);
4159 assert_eq!(
4160 f.run(&[b"OBJECT", b"ENCODING", b"s2"]),
4161 "$9\r\nhashtable\r\n"
4162 );
4163 }
4164
4165 #[test]
4166 fn config_set_takes_all_of_the_ladder_or_none_of_it() {
4167 let mut f = Fixture::new();
4168 assert_eq!(
4169 f.run(&[
4170 b"CONFIG",
4171 b"SET",
4172 b"hash-max-listpack-entries",
4173 b"7",
4174 b"set-max-listpack-entries",
4175 b"abc"
4176 ]),
4177 "-ERR CONFIG SET failed (possibly related to argument 'set-max-listpack-entries') - argument couldn't be parsed into an integer\r\n"
4178 );
4179 assert_eq!(
4180 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
4181 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
4182 "the pair in front of the bad one did not go in"
4183 );
4184 assert_eq!(
4187 f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"abc"]),
4188 "-ERR CONFIG SET failed (possibly related to argument 'hash-max-ziplist-entries') - argument couldn't be parsed into an integer\r\n"
4189 );
4190 assert_eq!(
4191 f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"-1"]),
4192 "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument must be between 0 and 9223372036854775807 inclusive\r\n"
4193 );
4194 assert_eq!(
4197 f.run(&[
4198 b"CONFIG",
4199 b"SET",
4200 b"set-max-intset-entries",
4201 b"99999999999999999999"
4202 ]),
4203 "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument couldn't be parsed into an integer\r\n"
4204 );
4205 assert_eq!(
4206 f.run(&[
4207 b"CONFIG",
4208 b"SET",
4209 b"set-max-intset-entries",
4210 b"9223372036854775807"
4211 ]),
4212 "+OK\r\n"
4213 );
4214 }
4215
4216 #[test]
4217 fn a_setting_moved_on_one_database_moved_on_all_of_them() {
4218 let mut f = Fixture::new();
4219 f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"1"]);
4220 f.run(&[b"SELECT", b"3"]);
4221 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
4222 assert_eq!(
4223 f.run(&[b"OBJECT", b"ENCODING", b"h"]),
4224 "$9\r\nhashtable\r\n",
4225 "these are one server wide number in Redis, whatever a Keyspace carries"
4226 );
4227 }
4228
4229 #[test]
4230 fn info_reports_the_numbers_it_can_stand_behind() {
4231 let mut f = Fixture::new();
4232 f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
4233 let all = f.run(&[b"INFO"]);
4234 assert!(all.contains("redis_version:8.8.0"), "{all}");
4235 assert!(
4236 all.contains(concat!("yo_version:", env!("CARGO_PKG_VERSION"))),
4237 "{all}"
4238 );
4239 assert!(all.contains("db0:keys=2,expires=0,avg_ttl=0"), "{all}");
4240 assert!(all.contains("role:master"), "{all}");
4241 let clients = f.run(&[b"INFO", b"clients"]);
4243 assert!(clients.contains("connected_clients:0"), "{clients}");
4244 assert!(!clients.contains("redis_version"), "{clients}");
4245 assert_eq!(f.run(&[b"INFO", b"nosuch"]), "$0\r\n\r\n");
4246 }
4247
4248 #[test]
4255 fn commandstats_is_asked_for_and_replication_is_not() {
4256 let mut f = Fixture::new();
4257 for arg in ["", "all", "default", "everything"] {
4258 let info = if arg.is_empty() {
4259 f.run(&[b"INFO"])
4260 } else {
4261 f.run(&[b"INFO", arg.as_bytes()])
4262 };
4263 assert!(info.contains("redis_version"), "{arg}: {info}");
4264 assert!(info.contains("used_cpu_user"), "{arg}: {info}");
4265 assert!(info.contains("used_memory"), "{arg}: {info}");
4266 assert!(!info.contains("sentinel_tilt"), "{arg}: {info}");
4267 let asked = arg == "all" || arg == "everything";
4268 assert_eq!(
4269 info.contains("rejected_calls"),
4270 asked,
4271 "{arg} should{} carry the command counters: {info}",
4272 if asked { "" } else { " not" }
4273 );
4274 }
4275
4276 let cpu = f.run(&[b"INFO", b"cpu"]);
4277 assert!(cpu.contains("used_cpu_user"), "{cpu}");
4278 assert!(!cpu.contains("used_memory"), "{cpu}");
4279
4280 let stats = f.run(&[b"INFO", b"commandSTATS"]);
4283 assert!(!stats.contains("used_memory"), "{stats}");
4284 assert!(stats.contains("rejected_calls"), "{stats}");
4285
4286 let pair = f.run(&[b"INFO", b"cpu", b"sentinel"]);
4288 assert!(pair.contains("used_cpu_user"), "{pair}");
4289 assert!(!pair.contains("master_repl_offset"), "{pair}");
4290
4291 let with_all = f.run(&[b"INFO", b"cpu", b"all"]);
4292 assert!(with_all.contains("used_memory"), "{with_all}");
4293 assert!(with_all.contains("master_repl_offset"), "{with_all}");
4294 assert!(with_all.contains("rejected_calls"), "{with_all}");
4295 assert_eq!(
4297 with_all.matches("used_cpu_user_children").count(),
4298 1,
4299 "{with_all}"
4300 );
4301
4302 let with_default = f.run(&[b"INFO", b"cpu", b"default"]);
4303 assert!(with_default.contains("used_memory"), "{with_default}");
4304 assert!(
4305 with_default.contains("master_repl_offset"),
4306 "{with_default}"
4307 );
4308 assert!(!with_default.contains("rejected_calls"), "{with_default}");
4309 assert_eq!(
4310 with_default.matches("used_cpu_user_children").count(),
4311 1,
4312 "{with_default}"
4313 );
4314 }
4315
4316 #[test]
4325 fn info_memory_reports_the_cap_and_the_quarter_of_it_that_gets_used() {
4326 let mut f = Fixture::new();
4327 let info = f.run(&[b"INFO", b"memory"]);
4328 for field in [
4329 "total_system_memory:",
4330 "mem_cgroup_limit:",
4331 "mem_limit:",
4332 "mem_budget:",
4333 ] {
4334 assert!(info.contains(field), "no {field} in {info}");
4335 }
4336
4337 let field = |name: &str| -> u64 {
4338 info.lines()
4339 .find_map(|l| l.strip_prefix(name))
4340 .unwrap_or_else(|| panic!("no {name} in {info}"))
4341 .trim()
4342 .parse()
4343 .unwrap_or_else(|_| panic!("{name} is not a number in {info}"))
4344 };
4345 let limit = field("mem_limit:");
4346 assert_eq!(field("mem_budget:"), limit / 4, "{info}");
4347 if limit != 0 {
4350 let host = field("total_system_memory:");
4351 let cgroup = field("mem_cgroup_limit:");
4352 assert!(
4353 limit == host || limit == cgroup,
4354 "the limit came from neither number: {info}"
4355 );
4356 }
4357 }
4358
4359 #[test]
4368 fn a_command_counts_what_it_did_separately_from_what_it_refused() {
4369 let mut f = Fixture::new();
4370 f.run(&[b"SET", b"k", b"v"]);
4371 f.run(&[b"SET", b"k", b"w"]);
4372 f.run(&[b"LPUSH", b"k", b"x"]);
4374 f.run(&[b"LPUSH", b"k"]);
4376
4377 let stats = f.run(&[b"INFO", b"commandstats"]);
4378 assert!(
4379 stats.contains("cmdstat_set:calls=2,rejected_calls=0,failed_calls=0"),
4380 "{stats}"
4381 );
4382 assert!(
4383 stats.contains("cmdstat_lpush:calls=1,rejected_calls=1,failed_calls=1"),
4384 "{stats}"
4385 );
4386 assert!(
4387 !stats.contains("cmdstat_zadd"),
4388 "a command nobody has sent has no row: {stats}"
4389 );
4390 }
4391
4392 #[test]
4396 fn the_active_sweep_reclaims_keys_no_client_comes_back_for() {
4397 let mut f = Fixture::new();
4398 for i in 0..3_000u32 {
4399 f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
4400 }
4401 for i in 0..1_000u32 {
4402 f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
4403 }
4404 assert_eq!(f.run(&[b"DBSIZE"]), ":4000\r\n");
4405 f.advance(100);
4406 assert_eq!(
4407 f.run(&[b"DBSIZE"]),
4408 ":4000\r\n",
4409 "DBSIZE counts records and nothing has read past the dead ones yet"
4410 );
4411
4412 let mut spent = 0;
4414 for _ in 0..2_000 {
4415 spent += f.server.expire_step(4096);
4416 if f.run(&[b"DBSIZE"]) == ":1000\r\n" {
4417 break;
4418 }
4419 }
4420 assert_eq!(f.run(&[b"DBSIZE"]), ":1000\r\n", "spent {spent} looks");
4421 assert!(f.run(&[b"INFO", b"stats"]).contains("expired_keys:3000"));
4422 for i in 0..1_000u32 {
4423 assert_eq!(
4424 f.run(&[b"GET", format!("k{i}").as_bytes()]),
4425 "$1\r\nv\r\n",
4426 "it took a key that had no deadline"
4427 );
4428 }
4429 }
4430
4431 #[test]
4432 fn a_sweep_of_a_server_with_no_deadlines_anywhere_costs_nothing() {
4433 let mut f = Fixture::new();
4434 for i in 0..2_000u32 {
4435 f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
4436 }
4437 assert_eq!(f.server.expire_step(4096), 0);
4438 f.run(&[b"SELECT", b"3"]);
4440 f.run(&[b"SET", b"x", b"v", b"PX", b"50"]);
4441 f.advance(100);
4442 for _ in 0..64 {
4443 f.server.expire_step(4096);
4444 }
4445 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4446 f.run(&[b"SELECT", b"0"]);
4447 assert_eq!(f.run(&[b"DBSIZE"]), ":2000\r\n");
4448 assert_eq!(f.server.expire_step(4096), 0, "and it is quiet again");
4449 }
4450
4451 #[test]
4454 fn the_sweep_the_loop_calls_runs_at_most_once_a_millisecond() {
4455 let mut f = Fixture::new();
4456 for i in 0..500u32 {
4457 f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
4458 }
4459 f.advance(100);
4460 let at = f.server.striped(0).now_ms();
4461 f.server.set_clock_ms(at);
4462 assert!(f.server.expire_slice(8) > 0, "the first one works");
4466 for _ in 0..1_000 {
4467 assert_eq!(
4468 f.server.expire_slice(8),
4469 0,
4470 "the millisecond has not moved and neither should this"
4471 );
4472 }
4473 assert!(
4474 f.server.striped(0).expires() > 400,
4475 "there is plenty left to take"
4476 );
4477 f.server.set_clock_ms(at + 1);
4478 assert!(f.server.expire_slice(8) > 0, "and then it goes again");
4479 }
4480
4481 #[test]
4484 fn info_keyspace_counts_the_keys_that_have_a_deadline() {
4485 let mut f = Fixture::new();
4486 f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
4487 assert!(
4488 f.run(&[b"INFO", b"keyspace"])
4489 .contains("db0:keys=3,expires=0"),
4490 "none of them has one yet"
4491 );
4492 f.run(&[b"EXPIRE", b"a", b"1000"]);
4493 f.run(&[b"EXPIRE", b"b", b"1000"]);
4494 let two = f.run(&[b"INFO", b"keyspace"]);
4495 assert!(two.contains("db0:keys=3,expires=2"), "{two}");
4496 f.run(&[b"PERSIST", b"a"]);
4497 f.run(&[b"DEL", b"b"]);
4498 let none = f.run(&[b"INFO", b"keyspace"]);
4499 assert!(none.contains("db0:keys=2,expires=0"), "{none}");
4500
4501 f.run(&[b"SELECT", b"1"]);
4503 f.run(&[b"SET", b"x", b"1", b"EX", b"1000"]);
4504 let both = f.run(&[b"INFO", b"keyspace"]);
4505 assert!(both.contains("db0:keys=2,expires=0"), "{both}");
4506 assert!(both.contains("db1:keys=1,expires=1"), "{both}");
4507 }
4508
4509 #[cfg(unix)]
4510 #[test]
4511 fn info_cpu_reports_processor_time_that_was_really_measured() {
4512 let mut f = Fixture::new();
4513 let cpu = f.run(&[b"INFO", b"cpu"]);
4514 assert!(cpu.contains("# CPU"), "{cpu}");
4515 assert!(cpu.contains("used_cpu_user:"), "{cpu}");
4517 assert!(cpu.contains("used_cpu_sys:"), "{cpu}");
4518 assert!(cpu.contains("used_cpu_user_children:0.000000"), "{cpu}");
4519 assert!(!cpu.contains("redis_version"), "{cpu}");
4520
4521 let before = used_cpu_user(&cpu);
4525 let mut n = 0u64;
4526 let mut rounds = 0;
4527 while used_cpu_user(&f.run(&[b"INFO", b"cpu"])) <= before {
4528 for i in 0..1_000_000u64 {
4529 n = n.wrapping_add(i.wrapping_mul(i));
4530 }
4531 rounds += 1;
4532 assert!(rounds < 1_000, "cpu time never moved, n is {n}");
4536 }
4537 }
4538
4539 #[cfg(unix)]
4541 fn used_cpu_user(info: &str) -> f64 {
4542 info.lines()
4543 .find_map(|l| l.strip_prefix("used_cpu_user:"))
4544 .expect("no used_cpu_user in the reply")
4545 .trim()
4546 .parse()
4547 .expect("used_cpu_user is not a number")
4548 }
4549
4550 #[test]
4556 fn a_command_that_fails_leaves_nothing_half_written() {
4557 let mut f = Fixture::new();
4558 let reply = f.run(&[b"SETRANGE", b"k", b"-1", b"x"]);
4559 assert_eq!(reply, "-ERR offset is out of range\r\n");
4560 assert!(!reply.contains(':'), "no integer went out in front of it");
4561 }
4562
4563 #[test]
4564 fn quit_answers_first_and_closes_after() {
4565 let mut f = Fixture::new();
4566 let (flow, reply) = f.flow(&[b"QUIT"]);
4567 assert_eq!(reply, "+OK\r\n");
4568 assert_eq!(flow, Flow::Close);
4569 }
4570
4571 #[test]
4578 fn shutdown_writes_nothing_and_sets_the_flag() {
4579 let mut f = Fixture::new();
4580 assert!(!f.server.stopping(), "nobody has asked yet");
4581
4582 let (flow, reply) = f.flow(&[b"SHUTDOWN"]);
4583 assert_eq!(reply, "");
4584 assert_eq!(flow, Flow::Close);
4585 assert!(f.server.stopping());
4586 }
4587
4588 #[test]
4596 fn shutdown_takes_the_flags_redis_takes() {
4597 for flags in [
4598 &[b"NOSAVE".as_slice()][..],
4599 &[b"SAVE"],
4600 &[b"NOW"],
4601 &[b"FORCE"],
4602 &[b"nosave"],
4603 &[b"NOW", b"NOW"],
4604 &[b"SAVE", b"SAVE"],
4605 &[b"NOSAVE", b"NOW", b"FORCE"],
4606 ] {
4607 let mut f = Fixture::new();
4608 let mut parts = vec![b"SHUTDOWN".as_slice()];
4609 parts.extend_from_slice(flags);
4610 let (flow, reply) = f.flow(&parts);
4611 assert_eq!(reply, "", "SHUTDOWN {flags:?} answered something");
4612 assert_eq!(flow, Flow::Close, "SHUTDOWN {flags:?} did not close");
4613 assert!(f.server.stopping(), "SHUTDOWN {flags:?} did not stop");
4614 }
4615
4616 for flags in [
4617 &[b"BOGUS".as_slice()][..],
4618 &[b"SAVE", b"NOSAVE"],
4619 &[b"NOSAVE", b"SAVE"],
4620 &[b"ABORT", b"NOW"],
4621 &[b"NOSAVE", b"ABORT"],
4622 &[b"NOW", b"FORCE", b"ABORT"],
4623 ] {
4624 let mut f = Fixture::new();
4625 let mut parts = vec![b"SHUTDOWN".as_slice()];
4626 parts.extend_from_slice(flags);
4627 assert_eq!(
4628 f.run(&parts),
4629 "-ERR syntax error\r\n",
4630 "SHUTDOWN {flags:?} was accepted"
4631 );
4632 assert!(!f.server.stopping(), "SHUTDOWN {flags:?} stopped anyway");
4633 }
4634 }
4635
4636 #[test]
4644 fn shutdown_abort_never_has_anything_to_abort() {
4645 let mut f = Fixture::new();
4646 for parts in [
4647 &[b"SHUTDOWN".as_slice(), b"ABORT"][..],
4648 &[b"SHUTDOWN", b"ABORT", b"ABORT"],
4649 ] {
4650 assert_eq!(f.run(parts), "-ERR No shutdown in progress.\r\n");
4651 assert!(!f.server.stopping(), "an abort stopped the server");
4652 }
4653 }
4654
4655 struct Backups {
4663 f: Fixture,
4664 dir: PathBuf,
4665 }
4666
4667 impl Backups {
4668 fn new(name: &str) -> Backups {
4669 let dir = std::env::temp_dir().join(format!("yo-backup-{name}-{}", std::process::id()));
4670 let _ = std::fs::remove_dir_all(&dir);
4671 std::fs::create_dir_all(&dir).expect("could not make a temporary directory");
4672 let mut f = Fixture::new();
4673 f.server.set_dir(dir.clone());
4674 Backups { f, dir }
4675 }
4676
4677 fn run(&mut self, parts: &[&[u8]]) -> String {
4678 self.f.run(parts)
4679 }
4680
4681 fn files(&self) -> Vec<String> {
4683 let mut names: Vec<String> = match std::fs::read_dir(self.dir.join("backupdir")) {
4684 Ok(entries) => entries
4685 .filter_map(|e| e.ok())
4686 .map(|e| e.file_name().to_string_lossy().into_owned())
4687 .collect(),
4688 Err(_) => Vec::new(),
4689 };
4690 names.sort();
4691 names
4692 }
4693
4694 fn read(&self, name: &str) -> Vec<u8> {
4695 std::fs::read(self.dir.join("backupdir").join(name)).expect("could not read")
4696 }
4697 }
4698
4699 impl Drop for Backups {
4700 fn drop(&mut self) {
4701 let _ = std::fs::remove_dir_all(&self.dir);
4702 }
4703 }
4704
4705 #[test]
4708 fn backup_walks_the_states_the_reference_walks() {
4709 let mut b = Backups::new("states");
4710 let status = |b: &mut Backups| b.run(&[b"BACKUP", b"STATUS"]);
4711
4712 assert!(status(&mut b).contains("idle"));
4713 assert!(b.files().is_empty(), "an idle server has written a backup");
4714
4715 assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
4716 assert!(status(&mut b).contains("incrementing"));
4717 assert_eq!(b.files(), ["appendonly.aof.1.base.rdb"]);
4718
4719 assert_eq!(b.run(&[b"BACKUP", b"SEAL"]), "+OK\r\n");
4720 assert!(status(&mut b).contains("sealed"));
4721 assert_eq!(
4722 b.files(),
4723 [
4724 "appendonly.aof.1.base.rdb",
4725 "appendonly.aof.1.incr.aof",
4726 "appendonly.aof.manifest",
4727 ]
4728 );
4729
4730 assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
4731 assert!(status(&mut b).contains("idle"));
4732 assert!(b.files().is_empty(), "cleanup left something behind");
4733 }
4734
4735 #[test]
4737 fn backup_refuses_the_moves_the_reference_refuses() {
4738 let mut b = Backups::new("refusals");
4739
4740 assert_eq!(
4741 b.run(&[b"BACKUP", b"SEAL"]),
4742 "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
4743 );
4744 assert_eq!(
4745 b.run(&[b"BACKUP", b"ABORT"]),
4746 "-ERR No backup in progress\r\n"
4747 );
4748 assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
4751
4752 b.run(&[b"BACKUP", b"START"]);
4753 assert_eq!(
4754 b.run(&[b"BACKUP", b"START"]),
4755 "-ERR A backup is already in progress, ABORT it first\r\n"
4756 );
4757 assert_eq!(
4758 b.run(&[b"BACKUP", b"CLEANUP"]),
4759 "-ERR Backup is in progress\r\n"
4760 );
4761
4762 b.run(&[b"BACKUP", b"SEAL"]);
4763 assert_eq!(
4764 b.run(&[b"BACKUP", b"START"]),
4765 "-ERR A sealed backup exists, CLEANUP it first\r\n"
4766 );
4767 assert_eq!(
4768 b.run(&[b"BACKUP", b"SEAL"]),
4769 "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
4770 );
4771 assert_eq!(
4772 b.run(&[b"BACKUP", b"ABORT"]),
4773 "-ERR No backup in progress\r\n"
4774 );
4775 }
4776
4777 #[test]
4784 fn backup_abort_removes_the_file_and_says_who_did_it() {
4785 let mut b = Backups::new("abort");
4786 b.run(&[b"BACKUP", b"START"]);
4787 assert_eq!(b.run(&[b"BACKUP", b"ABORT"]), "+OK\r\n");
4788
4789 let status = b.run(&[b"BACKUP", b"STATUS"]);
4790 assert!(status.contains("failed"), "{status}");
4791 assert!(status.contains("aborted by user"), "{status}");
4792 assert!(b.files().is_empty(), "abort left the base file behind");
4793 assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
4794
4795 assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
4797 assert_eq!(b.files(), ["appendonly.aof.2.base.rdb"]);
4798 let status = b.run(&[b"BACKUP", b"STATUS"]);
4799 assert!(status.contains("incrementing"), "{status}");
4800 assert!(!status.contains("aborted"), "the old error was kept");
4801 }
4802
4803 #[test]
4805 fn backup_list_names_the_files_that_are_pinned_so_far() {
4806 let mut b = Backups::new("list");
4807 assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
4808
4809 b.run(&[b"BACKUP", b"START"]);
4810 let base = b.dir.join("backupdir").join("appendonly.aof.1.base.rdb");
4811 let base = base.to_string_lossy().into_owned();
4812 assert_eq!(
4813 b.run(&[b"BACKUP", b"LIST"]),
4814 format!("*1\r\n${}\r\n{base}\r\n", base.len())
4815 );
4816
4817 b.run(&[b"BACKUP", b"SEAL"]);
4818 let listed = b.run(&[b"BACKUP", b"LIST"]);
4819 assert!(listed.starts_with("*3\r\n"), "{listed}");
4820 let names: Vec<&str> = listed
4823 .lines()
4824 .filter(|l| l.starts_with('/') || l.contains(":\\"))
4825 .collect();
4826 assert_eq!(names.len(), 3, "{listed}");
4827 assert!(names[0].ends_with("appendonly.aof.1.base.rdb"), "{listed}");
4828 assert!(names[1].ends_with("appendonly.aof.1.incr.aof"), "{listed}");
4829 assert!(names[2].ends_with("appendonly.aof.manifest"), "{listed}");
4830 }
4831
4832 #[test]
4840 fn a_backup_holds_the_dataset_as_it_was_at_start() {
4841 let mut b = Backups::new("contents");
4842 b.run(&[b"SET", b"bk", b"v1"]);
4843 b.run(&[b"BACKUP", b"START"]);
4844 b.run(&[b"SET", b"bk", b"v2"]);
4845 b.run(&[b"BACKUP", b"SEAL"]);
4846
4847 let base = b.read("appendonly.aof.1.base.rdb");
4848 assert!(base.starts_with(b"REDIS"), "not an RDB file");
4849 assert!(base.windows(2).any(|w| w == b"v1"), "the value is missing");
4850 assert!(
4851 !base.windows(2).any(|w| w == b"v2"),
4852 "the base file moved on after START"
4853 );
4854 let at = base
4859 .windows(8)
4860 .position(|w| w == b"aof-base")
4861 .expect("no aof-base aux field");
4862 assert_eq!(&base[at + 8..at + 10], b"\xc0\x01", "{:?}", &base[at..]);
4863
4864 assert!(b.read("appendonly.aof.1.incr.aof").is_empty());
4865 assert_eq!(
4866 String::from_utf8(b.read("appendonly.aof.manifest")).expect("the manifest is text"),
4867 "file appendonly.aof.1.base.rdb seq 1 type b\n\
4868 file appendonly.aof.1.incr.aof seq 1 type i startoffset 0 endoffset 0\n"
4869 );
4870 }
4871
4872 #[test]
4875 fn backup_status_is_a_map_on_resp3_and_a_flat_array_on_resp2() {
4876 let mut b = Backups::new("status");
4877 b.f.server.set_clock_ms(1_700_000_000_000);
4878
4879 assert_eq!(
4880 b.run(&[b"BACKUP", b"STATUS"]),
4881 "*8\r\n$5\r\nstate\r\n$4\r\nidle\r\n$5\r\nerror\r\n$0\r\n\r\n\
4882 $10\r\nstart_time\r\n:0\r\n$8\r\nend_time\r\n:0\r\n"
4883 );
4884
4885 b.f.out = Out::new(Proto::Resp3);
4886 b.run(&[b"BACKUP", b"START"]);
4887 assert_eq!(
4888 b.run(&[b"BACKUP", b"STATUS"]),
4889 "%4\r\n$5\r\nstate\r\n$12\r\nincrementing\r\n$5\r\nerror\r\n$0\r\n\r\n\
4890 $10\r\nstart_time\r\n:1700000000\r\n$8\r\nend_time\r\n:0\r\n"
4891 );
4892
4893 b.run(&[b"BACKUP", b"SEAL"]);
4894 let sealed = b.run(&[b"BACKUP", b"STATUS"]);
4895 assert!(sealed.contains("end_time\r\n:1700000000"), "{sealed}");
4896 }
4897
4898 #[test]
4901 fn a_sealed_backup_is_swept_away_after_the_timeout() {
4902 let mut b = Backups::new("ttl");
4903 b.f.server.set_clock_ms(1_000_000);
4904 assert_eq!(
4905 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"60"]),
4906 "+OK\r\n"
4907 );
4908 b.run(&[b"BACKUP", b"START"]);
4909 b.run(&[b"BACKUP", b"SEAL"]);
4910
4911 b.f.server.set_clock_ms(1_000_000 + 59_000);
4913 b.f.server.backup_expire();
4914 assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
4915 assert_eq!(b.files().len(), 3);
4916
4917 b.f.server.set_clock_ms(1_000_000 + 60_000);
4918 b.f.server.backup_expire();
4919 let status = b.run(&[b"BACKUP", b"STATUS"]);
4920 assert!(status.contains("idle"), "{status}");
4921 assert!(b.files().is_empty(), "the timeout left the files behind");
4922
4923 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"0"]);
4925 b.run(&[b"BACKUP", b"START"]);
4926 b.run(&[b"BACKUP", b"SEAL"]);
4927 b.f.server.set_clock_ms(9_000_000_000);
4928 b.f.server.backup_expire();
4929 assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
4930 }
4931
4932 #[test]
4935 fn the_backup_settings_behave_the_way_the_reference_does() {
4936 let mut b = Backups::new("config");
4937 let dir = b.dir.to_string_lossy().into_owned();
4938
4939 assert_eq!(
4940 b.run(&[b"CONFIG", b"GET", b"dir"]),
4941 format!("*2\r\n$3\r\ndir\r\n${}\r\n{dir}\r\n", dir.len())
4942 );
4943 assert_eq!(
4944 b.run(&[b"CONFIG", b"GET", b"backupdirname"]),
4945 "*2\r\n$13\r\nbackupdirname\r\n$9\r\nbackupdir\r\n"
4946 );
4947 assert_eq!(
4948 b.run(&[b"CONFIG", b"GET", b"backup-sealed-ttl"]),
4949 "*2\r\n$17\r\nbackup-sealed-ttl\r\n$1\r\n0\r\n"
4950 );
4951
4952 assert_eq!(
4955 b.run(&[b"CONFIG", b"SET", b"dir", dir.as_bytes()]),
4956 "-ERR CONFIG SET failed (possibly related to argument 'dir') - can't set protected config\r\n"
4957 );
4958 assert_eq!(
4959 b.run(&[b"CONFIG", b"SET", b"backupdirname", b"other"]),
4960 "-ERR CONFIG SET failed (possibly related to argument 'backupdirname') - can't set immutable config\r\n"
4961 );
4962 assert!(
4963 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"abc"])
4964 .contains("argument couldn't be parsed into an integer")
4965 );
4966 assert!(
4967 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"-1"])
4968 .contains("argument must be between 0 and 9223372036854775807 inclusive")
4969 );
4970 }
4971
4972 #[test]
4974 fn backup_help_is_the_text_the_reference_sends() {
4975 let mut f = Fixture::new();
4976 let help = f.run(&[b"BACKUP", b"HELP"]);
4977 assert!(help.starts_with("*17\r\n"), "{help}");
4978 assert!(
4979 help.contains("+BACKUP <subcommand> [<arg> [value] [opt] ...]. Subcommands are:\r\n")
4980 );
4981 assert!(help.contains("+ Start a new backup into the configured 'backupdirname'.\r\n"));
4982 assert!(help.contains("+ Freeze the current backup (BASE + INCR + manifest).\r\n"));
4983 assert!(help.contains("+ Return this help.\r\n+HELP\r\n+ Print this help.\r\n"));
4984 }
4985
4986 #[test]
4994 fn backup_refuses_what_it_cannot_read() {
4995 let mut f = Fixture::new();
4996 assert_eq!(
4997 f.run(&[b"BACKUP"]),
4998 "-ERR wrong number of arguments for 'backup' command\r\n"
4999 );
5000 assert_eq!(
5001 f.run(&[b"BACKUP", b"START", b"x"]),
5002 "-ERR wrong number of arguments for 'backup' command\r\n"
5003 );
5004 assert_eq!(
5005 f.run(&[b"BACKUP", b"NOPE"]),
5006 "-ERR unknown subcommand 'NOPE'. Try BACKUP HELP.\r\n"
5007 );
5008 }
5009
5010 #[test]
5011 fn the_command_counter_counts_every_command_including_the_bad_ones() {
5012 let mut f = Fixture::new();
5013 f.run(&[b"PING"]);
5014 f.run(&[b"NOPE"]);
5015 f.run(&[b"GET"]);
5016 assert_eq!(f.server.totals().commands, 3);
5017 }
5018
5019 #[test]
5020 fn what_a_thread_marked_is_taken_by_the_maintenance_turn() {
5021 let mut server = Server::new();
5022 server.set_threads(2);
5023 server.mine().turn.store(0, Relaxed);
5026 server.locals[1].mark(1 << 9);
5027 server.collect_marks();
5028 assert!(server.mine().wanted(9));
5029 assert_eq!(server.locals[1].dirty.load(Relaxed), 0);
5031 }
5032
5033 #[test]
5034 fn what_two_threads_counted_is_added_up_when_info_asks() {
5035 let mut server = Server::new();
5036 server.set_threads(2);
5037 let ping = lookup(b"PING").expect("PING is a command");
5041 for (at, calls) in [(0, 2), (1, 3)] {
5042 let counters = &server.locals[at];
5043 for _ in 0..calls {
5044 counters.stats.commands.bump();
5045 counters.cmdstats.at(ping).calls.bump();
5046 }
5047 counters.stats.opened();
5048 }
5049 assert_eq!(server.totals().commands, 5);
5050 assert_eq!(server.totals().clients, 2);
5051 assert_eq!(server.totals().connections, 2);
5052 let rows: Vec<_> = server.command_stats().collect();
5053 assert_eq!(rows.len(), 1);
5054 assert_eq!(rows[0].0, "ping");
5055 assert_eq!(rows[0].1.calls, 5);
5056 server.reset_stats();
5059 assert_eq!(server.totals().commands, 0);
5060 assert_eq!(server.totals().connections, 0);
5061 assert_eq!(server.totals().clients, 2);
5062 }
5063
5064 #[test]
5065 fn the_parked_count_says_what_the_waiter_list_says() {
5066 let mut f = Fixture::new();
5067 assert_eq!(f.server.parked(), 0);
5068 for client in 1..=3u64 {
5069 f.session = Session::new(client);
5070 assert_eq!(f.flow(&[b"BLPOP", b"q", b"0"]).0, Flow::Block);
5071 }
5072 assert_eq!(f.server.parked(), 3);
5073 assert_eq!(f.server.waiters().len(), 3);
5074
5075 f.server.forget_waiters(2);
5079 assert_eq!(f.server.parked(), f.server.waiters().len());
5080 f.server.forget_waiters(1);
5081 assert_eq!(f.server.parked(), f.server.waiters().len());
5082 f.run(&[b"RPUSH", b"q", b"v"]);
5083 let mut out = Out::new(Proto::Resp2);
5084 assert!(f.server.serve_waiter(3, 0, &mut out));
5085 f.server.forget_waiters(3);
5086 assert_eq!(f.server.parked(), 0);
5087 assert!(f.server.waiters().is_empty());
5088 }
5089
5090 #[test]
5091 fn a_set_goes_from_bytes_to_bytes() {
5092 let mut f = Fixture::new();
5093 assert_eq!(f.run(&[b"SADD", b"s", b"a", b"b", b"c"]), ":3\r\n");
5094 assert_eq!(f.run(&[b"SADD", b"s", b"b", b"d"]), ":1\r\n");
5095 assert_eq!(f.run(&[b"SCARD", b"s"]), ":4\r\n");
5096 assert_eq!(f.run(&[b"SISMEMBER", b"s", b"a"]), ":1\r\n");
5097 assert_eq!(f.run(&[b"SISMEMBER", b"s", b"z"]), ":0\r\n");
5098 assert_eq!(f.run(&[b"TYPE", b"s"]), "+set\r\n");
5099 assert_eq!(
5100 f.run(&[b"SMISMEMBER", b"s", b"a", b"z", b"d"]),
5101 "*3\r\n:1\r\n:0\r\n:1\r\n"
5102 );
5103 assert_eq!(f.run(&[b"SREM", b"s", b"a", b"z"]), ":1\r\n");
5104 assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
5105 }
5106
5107 #[test]
5108 fn a_set_command_at_a_key_that_is_not_there_answers_empty() {
5109 let mut f = Fixture::new();
5110 assert_eq!(f.run(&[b"SCARD", b"nope"]), ":0\r\n");
5111 assert_eq!(f.run(&[b"SISMEMBER", b"nope", b"a"]), ":0\r\n");
5112 assert_eq!(f.run(&[b"SREM", b"nope", b"a"]), ":0\r\n");
5113 assert_eq!(f.run(&[b"SMEMBERS", b"nope"]), "*0\r\n");
5114 assert_eq!(
5115 f.run(&[b"SMISMEMBER", b"nope", b"a", b"b"]),
5116 "*2\r\n:0\r\n:0\r\n"
5117 );
5118 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n", "and made nothing");
5119 }
5120
5121 #[test]
5122 fn smembers_answers_a_set_on_resp3_and_an_array_on_resp2() {
5123 let mut f = Fixture::new();
5127 f.run(&[b"SADD", b"s", b"one"]);
5128 assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$3\r\none\r\n");
5129
5130 f.run(&[b"HELLO", b"3"]);
5131 assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "~1\r\n$3\r\none\r\n");
5132 }
5133
5134 #[test]
5135 fn an_integer_member_comes_back_as_the_digits_it_never_stored() {
5136 let mut f = Fixture::new();
5139 f.run(&[b"SADD", b"s", b"42"]);
5140 assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$2\r\n42\r\n");
5141 assert_eq!(f.run(&[b"SISMEMBER", b"s", b"42"]), ":1\r\n");
5142 assert_eq!(
5143 f.run(&[b"SISMEMBER", b"s", b"042"]),
5144 ":0\r\n",
5145 "the member is the bytes and not the number they parse to"
5146 );
5147 }
5148
5149 #[test]
5150 fn the_wrong_command_at_the_wrong_type_says_so_both_ways() {
5151 let mut f = Fixture::new();
5152 f.run(&[b"SET", b"str", b"v"]);
5153 f.run(&[b"SADD", b"set", b"a"]);
5154
5155 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
5156 assert_eq!(f.run(&[b"SADD", b"str", b"a"]), wrong);
5157 assert_eq!(f.run(&[b"SCARD", b"str"]), wrong);
5158 assert_eq!(f.run(&[b"SMEMBERS", b"str"]), wrong);
5159 assert_eq!(f.run(&[b"SMISMEMBER", b"str", b"a"]), wrong);
5160 assert_eq!(f.run(&[b"GET", b"set"]), wrong);
5161 assert_eq!(f.run(&[b"APPEND", b"set", b"x"]), wrong);
5162 assert_eq!(f.run(&[b"INCR", b"set"]), wrong);
5163 assert_eq!(f.run(&[b"STRLEN", b"set"]), wrong);
5164
5165 assert_eq!(
5168 f.run(&[b"MGET", b"str", b"set", b"nope"]),
5169 "*3\r\n$1\r\nv\r\n$-1\r\n$-1\r\n"
5170 );
5171 assert_eq!(f.run(&[b"SET", b"set", b"now a string"]), "+OK\r\n");
5173 assert_eq!(f.run(&[b"TYPE", b"set"]), "+string\r\n");
5174 }
5175
5176 #[test]
5177 fn a_wrongtype_leaves_nothing_half_written() {
5178 let mut f = Fixture::new();
5182 f.run(&[b"SET", b"k", b"v"]);
5183 let reply = f.run(&[b"SMISMEMBER", b"k", b"a", b"b"]);
5184 assert!(reply.starts_with("-WRONGTYPE"), "got {reply}");
5185 assert!(!reply.contains('*'), "an array header went out in front");
5186 }
5187
5188 #[test]
5189 fn emptying_a_set_takes_the_key_with_it() {
5190 let mut f = Fixture::new();
5191 f.run(&[b"SADD", b"s", b"a", b"b"]);
5192 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
5193 assert_eq!(f.run(&[b"SREM", b"s", b"a", b"b"]), ":2\r\n");
5194 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
5195 assert_eq!(f.run(&[b"TYPE", b"s"]), "+none\r\n");
5196 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
5197 }
5198
5199 fn split_scan(reply: &str) -> (String, Vec<String>) {
5205 let mut lines = reply.split("\r\n");
5206 assert_eq!(lines.next(), Some("*2"), "got {reply}");
5207 lines.next().expect("the cursor header");
5208 let cursor = lines.next().expect("the cursor").to_owned();
5209 let header = lines.next().expect("the member header");
5210 let n: usize = header[1..].parse().expect("a member count");
5211 let mut members = Vec::with_capacity(n);
5212 for _ in 0..n {
5213 lines.next().expect("a member header");
5214 members.push(lines.next().expect("a member").to_owned());
5215 }
5216 (cursor, members)
5217 }
5218
5219 #[test]
5220 fn popping_takes_a_member_off_the_set_and_hands_it_back() {
5221 let mut f = Fixture::new();
5222 f.run(&[b"SADD", b"s", b"a", b"b", b"c", b"d"]);
5223
5224 let one = f.run(&[b"SPOP", b"s"]);
5225 assert!(
5226 ["$1\r\na\r\n", "$1\r\nb\r\n", "$1\r\nc\r\n", "$1\r\nd\r\n"].contains(&one.as_str()),
5227 "got {one}"
5228 );
5229 assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
5230
5231 let (_, rest) = ("", f.run(&[b"SPOP", b"s", b"3"]));
5233 assert!(rest.starts_with("*3\r\n"), "got {rest}");
5234 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
5235 assert_eq!(f.run(&[b"SPOP", b"s"]), "$-1\r\n");
5237 assert_eq!(f.run(&[b"SPOP", b"s", b"2"]), "*0\r\n");
5238 }
5239
5240 #[test]
5241 fn the_two_draws_disagree_about_the_reply_type_and_they_are_right_to() {
5242 let mut f = Fixture::new();
5247 f.run(&[b"HELLO", b"3"]);
5248 f.run(&[b"SADD", b"s", b"a", b"b", b"c"]);
5249
5250 assert!(f.run(&[b"SPOP", b"s", b"2"]).starts_with("~2\r\n"));
5251 assert!(f.run(&[b"SRANDMEMBER", b"s", b"1"]).starts_with("*1\r\n"));
5253
5254 f.run(&[b"SADD", b"one", b"z"]);
5258 assert_eq!(
5259 f.run(&[b"SRANDMEMBER", b"one", b"-3"]),
5260 "*3\r\n$1\r\nz\r\n$1\r\nz\r\n$1\r\nz\r\n"
5261 );
5262 }
5263
5264 #[test]
5265 fn drawing_a_member_removes_nothing_and_says_nil_at_a_missing_key() {
5266 let mut f = Fixture::new();
5267 f.run(&[b"SADD", b"s", b"only"]);
5268 assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
5269 assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
5270 assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n");
5271
5272 assert_eq!(f.run(&[b"SRANDMEMBER", b"nope"]), "$-1\r\n");
5273 assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"3"]), "*0\r\n");
5276 assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"-3"]), "*0\r\n");
5277 assert_eq!(f.run(&[b"SRANDMEMBER", b"s", b"9"]), "*1\r\n$4\r\nonly\r\n");
5279 }
5280
5281 #[test]
5282 fn a_pop_count_that_is_not_a_positive_number_says_so() {
5283 let mut f = Fixture::new();
5284 f.run(&[b"SADD", b"s", b"a"]);
5285 let bad = "-ERR value is out of range, must be positive\r\n";
5286 assert_eq!(f.run(&[b"SPOP", b"s", b"-1"]), bad);
5287 assert_eq!(f.run(&[b"SPOP", b"s", b"abc"]), bad);
5288 assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n", "and took nothing");
5289 assert_eq!(f.run(&[b"SPOP", b"s", b"0"]), "*0\r\n");
5291 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
5292 }
5293
5294 #[test]
5295 fn a_scan_walks_a_set_of_any_size_exactly_once() {
5296 let mut f = Fixture::new();
5297 let members: Vec<Vec<u8>> = (0..300).map(|i| format!("m{i}").into_bytes()).collect();
5298 let args: Vec<&[u8]> = [&b"SADD"[..], &b"s"[..]]
5299 .into_iter()
5300 .chain(members.iter().map(Vec::as_slice))
5301 .collect();
5302 f.run(&args);
5303
5304 let mut seen = Vec::new();
5305 let mut cursor = "0".to_owned();
5306 loop {
5307 let reply = f.run(&[b"SSCAN", b"s", cursor.as_bytes()]);
5308 let (next, got) = split_scan(&reply);
5309 seen.extend(got);
5310 cursor = next;
5311 if cursor == "0" {
5312 break;
5313 }
5314 }
5315 seen.sort();
5316 seen.dedup();
5317 assert_eq!(seen.len(), 300, "a walk saw a member twice or missed one");
5318
5319 f.run(&[b"SADD", b"small", b"a", b"b", b"c"]);
5322 let (cursor, got) = split_scan(&f.run(&[b"SSCAN", b"small", b"0", b"COUNT", b"1"]));
5323 assert_eq!(cursor, "0");
5324 assert_eq!(got.len(), 3);
5325 assert_eq!(f.run(&[b"SSCAN", b"nope", b"0"]), "*2\r\n$1\r\n0\r\n*0\r\n");
5327 }
5328
5329 #[test]
5330 fn a_scan_takes_match_and_count_and_refuses_anything_else() {
5331 let mut f = Fixture::new();
5332 f.run(&[b"SADD", b"s", b"aa", b"ab", b"ba", b"12", b"13"]);
5333
5334 let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"a*"]));
5335 let mut got = got;
5336 got.sort();
5337 assert_eq!(got, ["aa", "ab"]);
5338
5339 let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"1?"]));
5342 let mut got = got;
5343 got.sort();
5344 assert_eq!(got, ["12", "13"]);
5345
5346 assert_eq!(f.run(&[b"SSCAN", b"s", b"abc"]), "-ERR invalid cursor\r\n");
5347 assert_eq!(f.run(&[b"SSCAN", b"s", b"-1"]), "-ERR invalid cursor\r\n");
5348 assert_eq!(
5349 f.run(&[b"SSCAN", b"s", b"0", b"NOPE", b"1"]),
5350 "-ERR syntax error\r\n"
5351 );
5352 assert_eq!(
5355 f.run(&[b"SSCAN", b"s", b"0", b"COUNT", b"0"]),
5356 "-ERR syntax error\r\n"
5357 );
5358 }
5359
5360 #[test]
5361 fn moving_a_member_takes_it_off_one_set_and_puts_it_on_another() {
5362 let mut f = Fixture::new();
5363 f.run(&[b"SADD", b"src", b"a", b"b"]);
5364 f.run(&[b"SADD", b"dst", b"c"]);
5365
5366 assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"a"]), ":1\r\n");
5367 assert_eq!(f.run(&[b"SISMEMBER", b"src", b"a"]), ":0\r\n");
5368 assert_eq!(f.run(&[b"SISMEMBER", b"dst", b"a"]), ":1\r\n");
5369 assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"zz"]), ":0\r\n");
5371 assert_eq!(f.run(&[b"SCARD", b"dst"]), ":2\r\n");
5372
5373 assert_eq!(f.run(&[b"SMOVE", b"src", b"fresh", b"b"]), ":1\r\n");
5376 assert_eq!(f.run(&[b"EXISTS", b"src"]), ":0\r\n");
5377 assert_eq!(f.run(&[b"SMEMBERS", b"fresh"]), "*1\r\n$1\r\nb\r\n");
5378 }
5379
5380 #[test]
5381 fn moving_checks_the_types_in_the_order_redis_checks_them() {
5382 let mut f = Fixture::new();
5386 f.run(&[b"SET", b"str", b"v"]);
5387 f.run(&[b"SADD", b"set", b"a"]);
5388
5389 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
5390 assert_eq!(f.run(&[b"SMOVE", b"nope", b"str", b"a"]), ":0\r\n");
5391 assert_eq!(f.run(&[b"SMOVE", b"str", b"set", b"a"]), wrong);
5392 assert_eq!(f.run(&[b"SMOVE", b"set", b"str", b"a"]), wrong);
5393 assert_eq!(f.run(&[b"SPOP", b"str"]), wrong);
5394 assert_eq!(f.run(&[b"SRANDMEMBER", b"str"]), wrong);
5395 assert_eq!(f.run(&[b"SSCAN", b"str", b"0"]), wrong);
5396 assert_eq!(
5397 f.run(&[b"SISMEMBER", b"set", b"a"]),
5398 ":1\r\n",
5399 "and none of that moved anything"
5400 );
5401 }
5402
5403 #[test]
5404 fn a_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
5405 let mut f = Fixture::new();
5408 f.run(&[b"SADD", b"s", b"a"]);
5409 for bad in [
5410 &[b"SSCAN".as_slice(), b"s", b"abc"][..],
5411 &[b"SSCAN".as_slice(), b"s", b"0", b"COUNT", b"nope"][..],
5412 &[b"SSCAN".as_slice(), b"s", b"0", b"MATCH"][..],
5413 ] {
5414 let reply = f.run(bad);
5415 assert!(reply.starts_with("-ERR"), "got {reply}");
5416 assert!(!reply.contains('*'), "an array header went out in front");
5417 }
5418 }
5419
5420 #[test]
5421 fn a_hash_writes_reads_and_deletes_its_fields() {
5422 let mut f = Fixture::new();
5423 assert_eq!(f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]), ":2\r\n");
5424 assert_eq!(f.run(&[b"HSET", b"h", b"a", b"9"]), ":0\r\n", "a was there");
5425 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
5426 assert_eq!(f.run(&[b"HGET", b"h", b"nope"]), "$-1\r\n");
5427 assert_eq!(f.run(&[b"HGET", b"nokey", b"a"]), "$-1\r\n");
5428 assert_eq!(f.run(&[b"HLEN", b"h"]), ":2\r\n");
5429 assert_eq!(f.run(&[b"HEXISTS", b"h", b"a"]), ":1\r\n");
5430 assert_eq!(f.run(&[b"HEXISTS", b"h", b"nope"]), ":0\r\n");
5431 assert_eq!(f.run(&[b"HSTRLEN", b"h", b"a"]), ":1\r\n");
5432 assert_eq!(f.run(&[b"HSTRLEN", b"h", b"nope"]), ":0\r\n");
5433
5434 assert_eq!(f.run(&[b"HGET", b"h", b"2"]), "$-1\r\n");
5437
5438 assert_eq!(f.run(&[b"HDEL", b"h", b"a", b"nope"]), ":1\r\n");
5439 assert_eq!(f.run(&[b"HDEL", b"h", b"b"]), ":1\r\n");
5440 assert_eq!(
5441 f.run(&[b"EXISTS", b"h"]),
5442 ":0\r\n",
5443 "and losing the last field lost the key"
5444 );
5445 }
5446
5447 #[test]
5448 fn hgetall_answers_a_map_on_resp3_and_the_same_pairs_flat_on_resp2() {
5449 let mut f = Fixture::new();
5450 f.run(&[b"HSET", b"h", b"a", b"1"]);
5451 assert_eq!(f.run(&[b"HGETALL", b"h"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
5452 assert_eq!(f.run(&[b"HGETALL", b"nokey"]), "*0\r\n");
5453 assert_eq!(f.run(&[b"HKEYS", b"h"]), "*1\r\n$1\r\na\r\n");
5454 assert_eq!(f.run(&[b"HVALS", b"h"]), "*1\r\n$1\r\n1\r\n");
5455 assert_eq!(f.run(&[b"HKEYS", b"nokey"]), "*0\r\n");
5456
5457 f.run(&[b"HELLO", b"3"]);
5458 assert_eq!(f.run(&[b"HGETALL", b"h"]), "%1\r\n$1\r\na\r\n$1\r\n1\r\n");
5459 assert_eq!(
5460 f.run(&[b"HGETALL", b"nokey"]),
5461 "%0\r\n",
5462 "a missing key is the empty hash and never a nil"
5463 );
5464 assert_eq!(
5465 f.run(&[b"HKEYS", b"h"]),
5466 "*1\r\n$1\r\na\r\n",
5467 "and the two that answer one side stay arrays"
5468 );
5469 }
5470
5471 #[test]
5472 fn hmget_answers_once_per_field_and_hmset_answers_ok() {
5473 let mut f = Fixture::new();
5474 assert_eq!(f.run(&[b"HMSET", b"h", b"a", b"1", b"c", b"3"]), "+OK\r\n");
5475 assert_eq!(
5476 f.run(&[b"HMGET", b"h", b"a", b"b", b"c"]),
5477 "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n",
5478 "the reply is positional, so b is a nil and not a gap"
5479 );
5480 assert_eq!(
5481 f.run(&[b"HMGET", b"nokey", b"a", b"b"]),
5482 "*2\r\n$-1\r\n$-1\r\n",
5483 "and a missing key is all nils rather than an empty array"
5484 );
5485
5486 assert_eq!(f.run(&[b"HSETNX", b"h", b"a", b"9"]), ":0\r\n");
5487 assert_eq!(f.run(&[b"HSETNX", b"h", b"z", b"9"]), ":1\r\n");
5488 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
5489 }
5490
5491 #[test]
5492 fn a_hash_counts_up_and_says_so_when_it_cannot() {
5493 let mut f = Fixture::new();
5494 assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"5"]), ":5\r\n");
5495 assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"-7"]), ":-2\r\n");
5496 assert_eq!(f.run(&[b"HGET", b"h", b"n"]), "$2\r\n-2\r\n");
5497 assert_eq!(
5498 f.run(&[b"HINCRBYFLOAT", b"h", b"f", b"10.5"]),
5499 "$4\r\n10.5\r\n",
5500 "a bulk string and not a double, on both protocols"
5501 );
5502
5503 f.run(&[b"HSET", b"h", b"s", b"words"]);
5504 let bad = f.run(&[b"HINCRBY", b"h", b"s", b"1"]);
5505 assert!(
5506 bad.starts_with("-ERR hash value is not an integer"),
5507 "{bad}"
5508 );
5509 let bad = f.run(&[b"HINCRBY", b"h", b"n", b"nope"]);
5510 assert!(
5511 bad.starts_with("-ERR value is not an integer"),
5512 "a bad argument is not yet a hash value, {bad}"
5513 );
5514 assert_eq!(
5515 f.run(&[b"HGET", b"h", b"s"]),
5516 "$5\r\nwords\r\n",
5517 "and neither of them wrote anything"
5518 );
5519 }
5520
5521 #[test]
5522 fn a_hash_scan_walks_every_pair_once_and_novalues_drops_half_of_it() {
5523 let mut f = Fixture::new();
5524 for i in 0..500 {
5525 let field = format!("field-{i}");
5526 let value = format!("value-{i}");
5527 f.run(&[b"HSET", b"h", field.as_bytes(), value.as_bytes()]);
5528 }
5529
5530 let mut seen: Vec<String> = Vec::new();
5531 let mut cursor = "0".to_owned();
5532 loop {
5533 let reply = f.run(&[b"HSCAN", b"h", cursor.as_bytes(), b"COUNT", b"32"]);
5534 let (next, items) = scan_reply(&reply);
5535 assert_eq!(items.len() % 2, 0, "a pair went out half written");
5536 for pair in items.chunks(2) {
5537 assert_eq!(
5538 pair[0].strip_prefix("field-"),
5539 pair[1].strip_prefix("value-"),
5540 "a field came back with someone else's value"
5541 );
5542 seen.push(pair[0].clone());
5543 }
5544 cursor = next;
5545 if cursor == "0" {
5546 break;
5547 }
5548 }
5549 seen.sort();
5550 seen.dedup();
5551 assert_eq!(seen.len(), 500, "every field once and only once");
5552
5553 let (_, items) = scan_reply(&f.run(&[b"HSCAN", b"h", b"0", b"NOVALUES", b"COUNT", b"32"]));
5554 assert!(
5555 items.iter().all(|s| s.starts_with("field-")),
5556 "NOVALUES still sent the values"
5557 );
5558
5559 let (_, one) = scan_reply(&f.run(&[
5560 b"HSCAN",
5561 b"h",
5562 b"0",
5563 b"MATCH",
5564 b"field-499",
5565 b"COUNT",
5566 b"1000",
5567 ]));
5568 assert_eq!(one, ["field-499", "value-499"], "MATCH is on the field");
5569 }
5570
5571 #[test]
5572 fn hrandfield_draws_what_it_was_asked_for_and_nests_values_on_resp3() {
5573 let mut f = Fixture::new();
5574 f.run(&[b"HSET", b"h", b"a", b"1"]);
5575 assert_eq!(f.run(&[b"HRANDFIELD", b"h"]), "$1\r\na\r\n");
5576 assert_eq!(f.run(&[b"HRANDFIELD", b"nokey"]), "$-1\r\n");
5577 assert_eq!(f.run(&[b"HRANDFIELD", b"nokey", b"3"]), "*0\r\n");
5578 assert_eq!(
5579 f.run(&[b"HRANDFIELD", b"h", b"3"]),
5580 "*1\r\n$1\r\na\r\n",
5581 "a positive count is capped at the size of the hash"
5582 );
5583 assert_eq!(
5584 f.run(&[b"HRANDFIELD", b"h", b"-3"]),
5585 "*3\r\n$1\r\na\r\n$1\r\na\r\n$1\r\na\r\n",
5586 "and a negative one repeats itself"
5587 );
5588 assert_eq!(
5589 f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
5590 "*2\r\n$1\r\na\r\n$1\r\n1\r\n",
5591 "flat on RESP2"
5592 );
5593
5594 f.run(&[b"HELLO", b"3"]);
5595 assert_eq!(
5596 f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
5597 "*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n",
5598 "and nested on RESP3, but still an array and never a map"
5599 );
5600 }
5601
5602 #[test]
5603 fn every_hash_command_says_wrongtype_and_writes_nothing() {
5604 let mut f = Fixture::new();
5605 f.run(&[b"SET", b"str", b"v"]);
5606 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
5607
5608 for cmd in [
5609 &[b"HSET".as_slice(), b"str", b"f", b"v"][..],
5610 &[b"HMSET".as_slice(), b"str", b"f", b"v"][..],
5611 &[b"HSETNX".as_slice(), b"str", b"f", b"v"][..],
5612 &[b"HGET".as_slice(), b"str", b"f"][..],
5613 &[b"HMGET".as_slice(), b"str", b"f"][..],
5614 &[b"HDEL".as_slice(), b"str", b"f"][..],
5615 &[b"HLEN".as_slice(), b"str"][..],
5616 &[b"HEXISTS".as_slice(), b"str", b"f"][..],
5617 &[b"HSTRLEN".as_slice(), b"str", b"f"][..],
5618 &[b"HGETALL".as_slice(), b"str"][..],
5619 &[b"HKEYS".as_slice(), b"str"][..],
5620 &[b"HVALS".as_slice(), b"str"][..],
5621 &[b"HINCRBY".as_slice(), b"str", b"f", b"1"][..],
5622 &[b"HINCRBYFLOAT".as_slice(), b"str", b"f", b"1"][..],
5623 &[b"HRANDFIELD".as_slice(), b"str"][..],
5624 &[b"HRANDFIELD".as_slice(), b"str", b"2"][..],
5625 &[b"HSCAN".as_slice(), b"str", b"0"][..],
5626 ] {
5627 let reply = f.run(cmd);
5628 assert_eq!(reply, wrong, "{:?}", cmd[0]);
5629 }
5630 assert_eq!(
5631 f.run(&[b"GET", b"str"]),
5632 "$1\r\nv\r\n",
5633 "and none of them touched the value"
5634 );
5635 }
5636
5637 #[test]
5638 fn a_hash_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
5639 let mut f = Fixture::new();
5640 f.run(&[b"HSET", b"h", b"f", b"v"]);
5641 for bad in [
5642 &[b"HSCAN".as_slice(), b"h", b"abc"][..],
5643 &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"nope"][..],
5644 &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"0"][..],
5645 &[b"HSCAN".as_slice(), b"h", b"0", b"MATCH"][..],
5646 ] {
5647 let reply = f.run(bad);
5648 assert!(reply.starts_with("-ERR"), "got {reply}");
5649 assert!(!reply.contains('*'), "an array header went out in front");
5650 }
5651 }
5652
5653 #[test]
5654 fn a_field_deadline_goes_on_and_comes_back_in_all_four_units() {
5655 let mut f = Fixture::new();
5656 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
5657 assert_eq!(
5658 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]),
5659 "*1\r\n:1\r\n"
5660 );
5661 assert_eq!(
5662 f.run(&[b"HTTL", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
5663 "*3\r\n:100\r\n:-1\r\n:-2\r\n",
5664 "one answer per field, and the two sentinels are TTL's own"
5665 );
5666
5667 let ms = int_reply(&f.run(&[b"HPTTL", b"h", b"FIELDS", b"1", b"a"]));
5670 assert!((99_000..=100_000).contains(&ms), "got {ms}");
5671 let at = int_reply(&f.run(&[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
5672 let at_ms = int_reply(&f.run(&[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
5673 assert_eq!(at, at_ms.div_euclid(1000) + i64::from(at_ms % 1000 != 0));
5674 assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
5675
5676 assert_eq!(
5677 f.run(&[b"HPERSIST", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
5678 "*3\r\n:1\r\n:-1\r\n:-2\r\n",
5679 "one for the deadline taken off, and it does not say what it was"
5680 );
5681 assert_eq!(
5682 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
5683 "*1\r\n:-1\r\n"
5684 );
5685 assert_eq!(
5686 f.run(&[b"HGET", b"h", b"a"]),
5687 "$1\r\n1\r\n",
5688 "and the field is still there with the value it had"
5689 );
5690 }
5691
5692 #[test]
5693 fn a_deadline_that_has_already_gone_deletes_the_field_now() {
5694 let mut f = Fixture::new();
5695 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
5696 assert_eq!(
5697 f.run(&[b"HEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"a"]),
5698 "*1\r\n:2\r\n",
5699 "two, and not one, because nothing was stored"
5700 );
5701 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
5702 assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
5703
5704 assert_eq!(
5705 f.run(&[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"]),
5706 "*1\r\n:2\r\n"
5707 );
5708 assert_eq!(
5709 f.run(&[b"EXISTS", b"h"]),
5710 ":0\r\n",
5711 "and the last field going took the key with it"
5712 );
5713
5714 f.run(&[b"HSET", b"h", b"a", b"1"]);
5717 assert_eq!(
5718 f.run(&[b"HEXPIRE", b"h", b"0", b"FIELDS", b"1", b"a"]),
5719 "*1\r\n:2\r\n"
5720 );
5721 }
5722
5723 #[test]
5724 fn a_field_is_gone_once_its_moment_passes() {
5725 let mut f = Fixture::new();
5726 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
5727 assert_eq!(
5728 f.run(&[b"HPEXPIRE", b"h", b"20", b"FIELDS", b"1", b"a"]),
5729 "*1\r\n:1\r\n"
5730 );
5731 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n", "not yet");
5732
5733 f.server.advance_clock_ms(60);
5737 assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
5738 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
5739 assert_eq!(
5740 f.run(&[b"HGETALL", b"h"]),
5741 "*2\r\n$1\r\nb\r\n$1\r\n2\r\n",
5742 "and the walks do not hand back a field that has expired"
5743 );
5744 }
5745
5746 #[test]
5747 fn a_missing_key_answers_the_no_field_sentinel_for_every_field() {
5748 let mut f = Fixture::new();
5749 for cmd in [
5750 &[
5751 b"HEXPIRE".as_slice(),
5752 b"nokey",
5753 b"100",
5754 b"FIELDS",
5755 b"2",
5756 b"a",
5757 b"b",
5758 ][..],
5759 &[b"HTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
5760 &[b"HPTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
5761 &[
5762 b"HEXPIRETIME".as_slice(),
5763 b"nokey",
5764 b"FIELDS",
5765 b"2",
5766 b"a",
5767 b"b",
5768 ][..],
5769 &[
5770 b"HPERSIST".as_slice(),
5771 b"nokey",
5772 b"FIELDS",
5773 b"2",
5774 b"a",
5775 b"b",
5776 ][..],
5777 ] {
5778 assert_eq!(f.run(cmd), "*2\r\n:-2\r\n:-2\r\n", "{:?}", cmd[0]);
5779 }
5780 }
5781
5782 #[test]
5783 fn writing_a_field_clears_the_deadline_that_was_on_it() {
5784 let mut f = Fixture::new();
5785 f.run(&[b"HSET", b"h", b"a", b"1"]);
5786 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]);
5787 f.run(&[b"HSET", b"h", b"a", b"2"]);
5788 assert_eq!(
5789 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
5790 "*1\r\n:-1\r\n",
5791 "Redis has done this since 7.4, and it is why HGETEX exists"
5792 );
5793 }
5794
5795 #[test]
5796 fn the_four_conditions_reach_the_store_the_way_they_were_written() {
5797 let mut f = Fixture::new();
5798 f.run(&[b"HSET", b"h", b"a", b"1"]);
5799 assert_eq!(
5800 f.run(&[b"HEXPIRE", b"h", b"100", b"XX", b"FIELDS", b"1", b"a"]),
5801 "*1\r\n:0\r\n",
5802 "XX on a field with no deadline changes nothing"
5803 );
5804 assert_eq!(
5805 f.run(&[b"HEXPIRE", b"h", b"100", b"NX", b"FIELDS", b"1", b"a"]),
5806 "*1\r\n:1\r\n"
5807 );
5808 assert_eq!(
5809 f.run(&[b"HEXPIRE", b"h", b"200", b"NX", b"FIELDS", b"1", b"a"]),
5810 "*1\r\n:0\r\n",
5811 "and NX will not move one that is already there"
5812 );
5813 assert_eq!(
5814 f.run(&[b"HEXPIRE", b"h", b"50", b"GT", b"FIELDS", b"1", b"a"]),
5815 "*1\r\n:0\r\n"
5816 );
5817 assert_eq!(
5818 f.run(&[b"HEXPIRE", b"h", b"500", b"GT", b"FIELDS", b"1", b"a"]),
5819 "*1\r\n:1\r\n"
5820 );
5821 assert_eq!(
5822 f.run(&[b"HEXPIRE", b"h", b"50", b"LT", b"FIELDS", b"1", b"a"]),
5823 "*1\r\n:1\r\n"
5824 );
5825 assert_eq!(
5826 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
5827 "*1\r\n:50\r\n"
5828 );
5829 }
5830
5831 #[test]
5832 fn the_field_ttl_family_leaves_nothing_half_written_on_a_bad_argument() {
5833 let mut f = Fixture::new();
5834 f.run(&[b"HSET", b"h", b"a", b"1"]);
5835 for (bad, want) in [
5836 (
5837 &[b"HEXPIRE".as_slice(), b"h", b"-1", b"FIELDS", b"1", b"a"][..],
5838 "-ERR invalid expire time, must be >= 0",
5839 ),
5840 (
5841 &[
5842 b"HEXPIRE".as_slice(),
5843 b"h",
5844 b"9999999999999999",
5845 b"FIELDS",
5846 b"1",
5847 b"a",
5848 ][..],
5849 "-ERR invalid expire time in 'hexpire' command",
5850 ),
5851 (
5852 &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELD", b"1", b"a"][..],
5853 "-ERR wrong number of arguments for 'hexpire' command",
5854 ),
5855 (
5856 &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"0", b"a"][..],
5857 "-ERR Parameter `numFields` should be greater than 0",
5858 ),
5859 (
5860 &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"2", b"a"][..],
5861 "-ERR wrong number of arguments",
5862 ),
5863 (
5864 &[b"HTTL".as_slice(), b"h", b"FIELDS", b"3", b"a", b"b"][..],
5865 "-ERR wrong number of arguments",
5866 ),
5867 ] {
5868 let reply = f.run(bad);
5869 assert!(reply.starts_with(want), "wanted {want}, got {reply}");
5870 assert!(!reply.contains('*'), "an array header went out in front");
5871 }
5872 assert_eq!(
5873 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
5874 "*1\r\n:-1\r\n",
5875 "and not one of them put a deadline on anything"
5876 );
5877 }
5878
5879 #[test]
5880 fn every_field_ttl_command_says_wrongtype_and_writes_nothing() {
5881 let mut f = Fixture::new();
5882 f.run(&[b"SET", b"str", b"v"]);
5883 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
5884
5885 for cmd in [
5886 &[b"HEXPIRE".as_slice(), b"str", b"100", b"FIELDS", b"1", b"f"][..],
5887 &[
5888 b"HPEXPIRE".as_slice(),
5889 b"str",
5890 b"100",
5891 b"FIELDS",
5892 b"1",
5893 b"f",
5894 ][..],
5895 &[
5896 b"HEXPIREAT".as_slice(),
5897 b"str",
5898 b"9999999999",
5899 b"FIELDS",
5900 b"1",
5901 b"f",
5902 ][..],
5903 &[
5904 b"HPEXPIREAT".as_slice(),
5905 b"str",
5906 b"9999999999999",
5907 b"FIELDS",
5908 b"1",
5909 b"f",
5910 ][..],
5911 &[b"HTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
5912 &[b"HPTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
5913 &[b"HEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
5914 &[b"HPEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
5915 &[b"HPERSIST".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
5916 ] {
5917 assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
5918 }
5919 assert_eq!(
5920 f.run(&[b"GET", b"str"]),
5921 "$1\r\nv\r\n",
5922 "and none of them touched the value"
5923 );
5924 }
5925
5926 #[test]
5927 fn hgetdel_hands_the_value_out_and_then_takes_the_field() {
5928 let mut f = Fixture::new();
5929 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
5930 assert_eq!(
5931 f.run(&[b"HGETDEL", b"h", b"FIELDS", b"2", b"a", b"nope"]),
5932 "*2\r\n$1\r\n1\r\n$-1\r\n",
5933 "positional, so the field that was not there is a nil in its place"
5934 );
5935 assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
5936 assert_eq!(
5937 f.run(&[b"HGETDEL", b"nokey", b"FIELDS", b"1", b"a"]),
5938 "*1\r\n$-1\r\n"
5939 );
5940 assert_eq!(
5941 f.run(&[b"HGETDEL", b"h", b"FIELDS", b"1", b"b"]),
5942 "*1\r\n$1\r\n2\r\n"
5943 );
5944 assert_eq!(
5945 f.run(&[b"EXISTS", b"h"]),
5946 ":0\r\n",
5947 "and the last field took the key"
5948 );
5949 }
5950
5951 #[test]
5952 fn hgetex_reads_and_moves_the_deadline_in_one_command() {
5953 let mut f = Fixture::new();
5954 f.run(&[b"HSET", b"h", b"a", b"1"]);
5955 assert_eq!(
5956 f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]),
5957 "*1\r\n$1\r\n1\r\n"
5958 );
5959 assert_eq!(
5960 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
5961 "*1\r\n:-1\r\n",
5962 "no option means leave it alone, which is the one place this is not GETEX"
5963 );
5964
5965 f.run(&[b"HGETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a"]);
5966 assert_eq!(
5967 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
5968 "*1\r\n:100\r\n"
5969 );
5970 f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]);
5971 assert_eq!(
5972 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
5973 "*1\r\n:100\r\n",
5974 "and a plain read really does leave it alone"
5975 );
5976 assert_eq!(
5977 f.run(&[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"a"]),
5978 "*1\r\n$1\r\n1\r\n"
5979 );
5980 assert_eq!(
5981 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
5982 "*1\r\n:-1\r\n"
5983 );
5984
5985 assert_eq!(
5986 f.run(&[b"HGETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a"]),
5987 "*1\r\n$1\r\n1\r\n",
5988 "the value goes out before the deadline that has already gone is applied"
5989 );
5990 assert_eq!(f.run(&[b"EXISTS", b"h"]), ":0\r\n");
5991 assert_eq!(
5992 f.run(&[b"HGETEX", b"nokey", b"EX", b"100", b"FIELDS", b"1", b"a"]),
5993 "*1\r\n$-1\r\n"
5994 );
5995 }
5996
5997 #[test]
5998 fn hsetex_writes_all_of_it_or_none_of_it() {
5999 let mut f = Fixture::new();
6000 assert_eq!(
6001 f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"1"]),
6002 ":1\r\n"
6003 );
6004 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
6005 assert_eq!(
6006 f.run(&[
6007 b"HSETEX", b"h", b"FNX", b"FIELDS", b"2", b"a", b"9", b"new", b"9"
6008 ]),
6009 ":0\r\n",
6010 "FNX wants every field named to be missing"
6011 );
6012 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
6013 assert_eq!(
6014 f.run(&[b"HEXISTS", b"h", b"new"]),
6015 ":0\r\n",
6016 "and none of the list was written"
6017 );
6018 assert_eq!(
6019 f.run(&[
6020 b"HSETEX", b"h", b"FXX", b"FIELDS", b"2", b"a", b"9", b"nope", b"9"
6021 ]),
6022 ":0\r\n",
6023 "and FXX wants every one of them to be there"
6024 );
6025 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
6026 assert_eq!(
6027 f.run(&[b"HSETEX", b"h", b"FXX", b"FIELDS", b"1", b"a", b"9"]),
6028 ":1\r\n"
6029 );
6030 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
6031
6032 assert_eq!(
6033 f.run(&[b"HSETEX", b"gone", b"FXX", b"FIELDS", b"1", b"a", b"1"]),
6034 ":0\r\n"
6035 );
6036 assert_eq!(
6037 f.run(&[b"EXISTS", b"gone"]),
6038 ":0\r\n",
6039 "a key with no fields cannot meet FXX and is not created trying"
6040 );
6041 }
6042
6043 #[test]
6044 fn hsetex_clears_the_deadline_unless_it_is_told_to_keep_it() {
6045 let mut f = Fixture::new();
6046 f.run(&[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a", b"1"]);
6047 assert_eq!(
6048 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
6049 "*1\r\n:100\r\n"
6050 );
6051
6052 f.run(&[b"HSETEX", b"h", b"KEEPTTL", b"FIELDS", b"1", b"a", b"2"]);
6053 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n2\r\n");
6054 assert_eq!(
6055 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
6056 "*1\r\n:100\r\n",
6057 "KEEPTTL put back what the write cleared"
6058 );
6059
6060 f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"3"]);
6061 assert_eq!(
6062 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
6063 "*1\r\n:-1\r\n",
6064 "and without it a write clears the deadline the way HSET does"
6065 );
6066
6067 assert_eq!(
6070 f.run(&[
6071 b"HSETEX", b"h", b"PX", b"100000", b"FXX", b"FIELDS", b"1", b"a", b"4"
6072 ]),
6073 ":1\r\n"
6074 );
6075 assert_eq!(
6076 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
6077 "*1\r\n:100\r\n"
6078 );
6079
6080 assert_eq!(
6081 f.run(&[b"HSETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a", b"5"]),
6082 ":1\r\n",
6083 "written, and not the separate code the HEXPIRE family has for this"
6084 );
6085 assert_eq!(
6086 f.run(&[b"EXISTS", b"h"]),
6087 ":0\r\n",
6088 "and storing it and then removing it emptied the hash"
6089 );
6090 }
6091
6092 #[test]
6093 fn the_last_three_hash_commands_word_their_mistakes_their_own_way() {
6094 let mut f = Fixture::new();
6095 f.run(&[b"HSET", b"h", b"a", b"1"]);
6096 for (bad, want) in [
6097 (
6099 &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
6100 "-ERR Number of fields must be a positive integer",
6101 ),
6102 (
6103 &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
6104 "-ERR The `numfields` parameter must match the number of arguments",
6105 ),
6106 (
6107 &[b"HGETDEL".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
6108 "-ERR Mandatory argument FIELDS is missing or not at the right position",
6109 ),
6110 (
6112 &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
6113 "-ERR invalid number of fields",
6114 ),
6115 (
6116 &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
6117 "-ERR wrong number of arguments",
6118 ),
6119 (
6120 &[b"HGETEX".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
6121 "-ERR unknown argument: FIELD",
6122 ),
6123 (
6124 &[
6125 b"HGETEX".as_slice(),
6126 b"h",
6127 b"KEEPTTL",
6128 b"FIELDS",
6129 b"1",
6130 b"a",
6131 ][..],
6132 "-ERR unknown argument: KEEPTTL",
6133 ),
6134 (
6135 &[
6136 b"HGETEX".as_slice(),
6137 b"h",
6138 b"EX",
6139 b"100",
6140 b"PERSIST",
6141 b"FIELDS",
6142 b"1",
6143 b"a",
6144 ][..],
6145 "-ERR Only one of EX, PX, EXAT, PXAT or PERSIST arguments can be specified",
6146 ),
6147 (
6148 &[
6149 b"HSETEX".as_slice(),
6150 b"h",
6151 b"EX",
6152 b"1",
6153 b"KEEPTTL",
6154 b"FIELDS",
6155 b"1",
6156 b"a",
6157 b"1",
6158 ][..],
6159 "-ERR Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments can be specified",
6160 ),
6161 (
6162 &[
6163 b"HSETEX".as_slice(),
6164 b"h",
6165 b"FNX",
6166 b"FXX",
6167 b"FIELDS",
6168 b"1",
6169 b"a",
6170 b"1",
6171 ][..],
6172 "-ERR Only one of FXX or FNX arguments can be specified",
6173 ),
6174 (
6175 &[
6176 b"HSETEX".as_slice(),
6177 b"h",
6178 b"FIELDS",
6179 b"2",
6180 b"a",
6181 b"1",
6182 b"b",
6183 ][..],
6184 "-ERR wrong number of arguments",
6185 ),
6186 (
6187 &[
6188 b"HGETEX".as_slice(),
6189 b"h",
6190 b"EX",
6191 b"-1",
6192 b"FIELDS",
6193 b"1",
6194 b"a",
6195 ][..],
6196 "-ERR invalid expire time, must be >= 0",
6197 ),
6198 (
6199 &[
6200 b"HGETEX".as_slice(),
6201 b"h",
6202 b"PXAT",
6203 b"99999999999999",
6204 b"FIELDS",
6205 b"1",
6206 b"a",
6207 ][..],
6208 "-ERR invalid expire time in 'hgetex' command",
6209 ),
6210 (
6211 &[
6212 b"HSETEX".as_slice(),
6213 b"h",
6214 b"EX",
6215 b"abc",
6216 b"FIELDS",
6217 b"1",
6218 b"a",
6219 b"1",
6220 ][..],
6221 "-ERR value is not an integer or out of range",
6222 ),
6223 ] {
6224 let reply = f.run(bad);
6225 assert!(reply.starts_with(want), "wanted {want}, got {reply}");
6226 assert!(!reply.contains('*'), "an array header went out in front");
6227 }
6228 assert_eq!(
6229 f.run(&[b"HGET", b"h", b"a"]),
6230 "$1\r\n1\r\n",
6231 "and not one of them wrote anything"
6232 );
6233 assert_eq!(
6234 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
6235 "*1\r\n:-1\r\n"
6236 );
6237 }
6238
6239 #[test]
6240 fn the_last_three_hash_commands_say_wrongtype_and_write_nothing() {
6241 let mut f = Fixture::new();
6242 f.run(&[b"SET", b"str", b"v"]);
6243 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
6244 for cmd in [
6245 &[b"HGETDEL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
6246 &[b"HGETEX".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
6247 &[
6248 b"HGETEX".as_slice(),
6249 b"str",
6250 b"EX",
6251 b"100",
6252 b"FIELDS",
6253 b"1",
6254 b"f",
6255 ][..],
6256 &[b"HSETEX".as_slice(), b"str", b"FIELDS", b"1", b"f", b"v"][..],
6257 ] {
6258 assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
6259 }
6260 assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
6261 }
6262
6263 #[test]
6271 fn himport_writes_declared_values_into_sorted_fields() {
6272 let mut f = Fixture::new();
6273 assert_eq!(
6274 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"b", b"aa", b"a"]),
6275 "+OK\r\n"
6276 );
6277 assert_eq!(
6278 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2", b"3"]),
6279 "+OK\r\n"
6280 );
6281 assert_eq!(f.run(&[b"HKEYS", b"k"]), bulks(&["a", "b", "aa"]));
6282 assert_eq!(
6283 f.run(&[b"HGETALL", b"k"]),
6284 bulks(&["a", "3", "b", "1", "aa", "2"])
6285 );
6286 }
6287
6288 #[test]
6291 fn himport_set_replaces_the_whole_key() {
6292 let mut f = Fixture::new();
6293 f.run(&[b"HSET", b"k", b"gone", b"old", b"a", b"old"]);
6294 f.run(&[b"EXPIRE", b"k", b"100"]);
6295 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
6296 assert_eq!(
6297 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
6298 "+OK\r\n"
6299 );
6300 assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
6301 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
6302 }
6303
6304 #[test]
6307 fn himport_fieldsets_belong_to_the_connection_and_not_to_the_keyspace() {
6308 let mut f = Fixture::new();
6309 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a"]);
6310 f.run(&[b"SELECT", b"1"]);
6311 assert_eq!(
6312 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
6313 "+OK\r\n"
6314 );
6315 f.run(&[b"SELECT", b"0"]);
6316 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
6317 assert_eq!(
6318 f.run(&[b"HIMPORT", b"SET", b"k2", b"shape", b"1"]),
6319 "-ERR no such fieldset\r\n"
6320 );
6321 }
6322
6323 #[test]
6330 fn himport_complains_in_the_order_a_real_server_does() {
6331 let mut f = Fixture::new();
6332 f.run(&[b"SET", b"str", b"v"]);
6333 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
6334 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
6335 assert_eq!(
6336 f.run(&[b"HIMPORT", b"SET", b"str", b"nope", b"1"]),
6337 wrong,
6338 "the type beats a missing fieldset"
6339 );
6340 assert_eq!(
6341 f.run(&[b"HIMPORT", b"SET", b"str", b"shape", b"1"]),
6342 wrong,
6343 "and it beats a value count that does not fit"
6344 );
6345 assert_eq!(
6346 f.run(&[b"HIMPORT", b"SET", b"k", b"nope", b"1"]),
6347 "-ERR no such fieldset\r\n"
6348 );
6349 for values in [&[b"1".as_slice()][..], &[b"1".as_slice(), b"2", b"3"][..]] {
6351 let mut line: Vec<&[u8]> = vec![b"HIMPORT", b"SET", b"k", b"shape"];
6352 line.extend_from_slice(values);
6353 assert_eq!(
6354 f.run(&line),
6355 "-ERR value count does not match fieldset field count\r\n",
6356 "{} values into two fields",
6357 values.len()
6358 );
6359 }
6360 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
6361 }
6362
6363 #[test]
6365 fn himport_checks_each_subcommand_count_under_its_own_name() {
6366 let mut f = Fixture::new();
6367 assert_eq!(
6368 f.run(&[b"HIMPORT"]),
6369 "-ERR wrong number of arguments for 'himport' command\r\n"
6370 );
6371 for (rest, name) in [
6372 (&["PREPARE"][..], "prepare"),
6373 (&["PREPARE", "fs"][..], "prepare"),
6374 (&["SET"][..], "set"),
6375 (&["SET", "k"][..], "set"),
6376 (&["SET", "k", "fs"][..], "set"),
6377 (&["DISCARD"][..], "discard"),
6378 (&["DISCARD", "a", "b"][..], "discard"),
6379 (&["DISCARDALL", "x"][..], "discardall"),
6380 ] {
6381 let mut line: Vec<&[u8]> = vec![b"HIMPORT"];
6382 line.extend(rest.iter().map(|a| a.as_bytes()));
6383 assert_eq!(
6384 f.run(&line),
6385 format!("-ERR wrong number of arguments for 'himport|{name}' command\r\n"),
6386 "HIMPORT {}",
6387 rest.join(" ")
6388 );
6389 }
6390 assert_eq!(
6391 f.run(&[b"HIMPORT", b"NOPE", b"x"]),
6392 "-ERR unknown subcommand 'NOPE'. Try HIMPORT HELP.\r\n"
6393 );
6394 }
6395
6396 #[test]
6399 fn a_failed_himport_prepare_leaves_the_old_fieldset_alone() {
6400 let mut f = Fixture::new();
6401 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
6402 assert_eq!(
6403 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"c", b"c"]),
6404 "-ERR duplicate field name in fieldset\r\n"
6405 );
6406 assert_eq!(
6407 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
6408 "+OK\r\n"
6409 );
6410 assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
6411 }
6412
6413 #[test]
6416 fn himport_prepare_replaces_and_the_discards_count() {
6417 let mut f = Fixture::new();
6418 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
6419 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"z"]);
6420 assert_eq!(
6421 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
6422 "+OK\r\n"
6423 );
6424 assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["z", "1"]));
6425
6426 assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":1\r\n");
6427 assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":0\r\n");
6428 f.run(&[b"HIMPORT", b"PREPARE", b"one", b"a"]);
6429 f.run(&[b"HIMPORT", b"PREPARE", b"two", b"a"]);
6430 assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":2\r\n");
6431 assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":0\r\n");
6432 }
6433
6434 fn int(reply: &str) -> i64 {
6440 let body = reply
6441 .strip_prefix(':')
6442 .and_then(|s| s.strip_suffix("\r\n"))
6443 .unwrap_or_else(|| panic!("wanted an integer, got {reply}"));
6444 body.parse().expect("an integer")
6445 }
6446
6447 fn int_reply(reply: &str) -> i64 {
6448 let body = reply
6449 .strip_prefix("*1\r\n:")
6450 .and_then(|s| s.strip_suffix("\r\n"))
6451 .unwrap_or_else(|| panic!("wanted one integer, got {reply}"));
6452 body.parse().expect("an integer")
6453 }
6454
6455 fn scan_reply(reply: &str) -> (String, Vec<String>) {
6457 let mut lines = reply.split("\r\n");
6458 assert_eq!(lines.next(), Some("*2"), "got {reply}");
6459 lines.next().expect("the cursor header");
6460 let cursor = lines.next().expect("a cursor").to_owned();
6461 let header = lines.next().expect("an item count");
6462 let n: usize = header[1..].parse().expect("a count");
6463 let mut items = Vec::with_capacity(n);
6464 for _ in 0..n {
6465 lines.next().expect("an item header");
6466 items.push(lines.next().expect("an item").to_owned());
6467 }
6468 (cursor, items)
6469 }
6470
6471 fn sorted(reply: &str) -> Vec<String> {
6474 let mut lines = reply.split("\r\n");
6475 let header = lines.next().expect("a header");
6476 assert!(
6477 header.starts_with('*') || header.starts_with('~'),
6478 "got {reply}"
6479 );
6480 let n: usize = header[1..].parse().expect("a member count");
6481 let mut got = Vec::with_capacity(n);
6482 for _ in 0..n {
6483 lines.next().expect("a member header");
6484 got.push(lines.next().expect("a member").to_owned());
6485 }
6486 got.sort();
6487 got
6488 }
6489
6490 #[test]
6491 fn the_algebra_answers_what_the_sets_share_and_do_not() {
6492 let mut f = Fixture::new();
6493 f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
6494 f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
6495 f.run(&[b"SADD", b"c", b"3", b"4", b"5"]);
6496
6497 assert_eq!(sorted(&f.run(&[b"SINTER", b"a", b"b", b"c"])), ["3"]);
6498 assert_eq!(
6499 sorted(&f.run(&[b"SUNION", b"a", b"b", b"c"])),
6500 ["1", "2", "3", "4", "5"]
6501 );
6502 assert_eq!(sorted(&f.run(&[b"SDIFF", b"a", b"b"])), ["1"]);
6503 assert_eq!(sorted(&f.run(&[b"SINTER", b"a"])), ["1", "2", "3"]);
6504
6505 assert_eq!(f.run(&[b"SINTER", b"a", b"nope"]), "*0\r\n");
6508 assert_eq!(sorted(&f.run(&[b"SUNION", b"a", b"nope"])), ["1", "2", "3"]);
6509 assert_eq!(f.run(&[b"SDIFF", b"nope", b"a"]), "*0\r\n");
6510 assert_eq!(f.run(&[b"DBSIZE"]), ":3\r\n", "and none of it made a key");
6511 }
6512
6513 #[test]
6514 fn the_algebra_answers_a_set_on_resp3_and_an_array_on_resp2() {
6515 let mut f = Fixture::new();
6516 f.run(&[b"SADD", b"a", b"x"]);
6517 assert_eq!(f.run(&[b"SINTER", b"a"]), "*1\r\n$1\r\nx\r\n");
6518 assert_eq!(f.run(&[b"SUNION", b"a"]), "*1\r\n$1\r\nx\r\n");
6519 assert_eq!(f.run(&[b"SDIFF", b"a"]), "*1\r\n$1\r\nx\r\n");
6520
6521 f.run(&[b"HELLO", b"3"]);
6522 assert_eq!(f.run(&[b"SINTER", b"a"]), "~1\r\n$1\r\nx\r\n");
6523 assert_eq!(f.run(&[b"SUNION", b"a"]), "~1\r\n$1\r\nx\r\n");
6524 assert_eq!(f.run(&[b"SDIFF", b"a"]), "~1\r\n$1\r\nx\r\n");
6525 assert_eq!(f.run(&[b"SINTER", b"nope"]), "~0\r\n");
6526 }
6527
6528 #[test]
6529 fn a_store_form_writes_a_key_and_answers_how_big_it_is() {
6530 let mut f = Fixture::new();
6531 f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
6532 f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
6533
6534 assert_eq!(f.run(&[b"SINTERSTORE", b"d", b"a", b"b"]), ":2\r\n");
6535 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["2", "3"]);
6536 assert_eq!(f.run(&[b"SUNIONSTORE", b"d", b"a", b"b"]), ":4\r\n");
6537 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["1", "2", "3", "4"]);
6538 assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"b"]), ":1\r\n");
6539 assert_eq!(f.run(&[b"SMEMBERS", b"d"]), "*1\r\n$1\r\n1\r\n");
6540
6541 assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"a"]), ":0\r\n");
6544 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
6545 assert_eq!(f.run(&[b"SINTERSTORE", b"a", b"a", b"b"]), ":2\r\n");
6546 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"a"])), ["2", "3"]);
6547
6548 f.run(&[b"SET", b"str", b"v"]);
6551 assert_eq!(f.run(&[b"SUNIONSTORE", b"str", b"b"]), ":3\r\n");
6552 assert_eq!(f.run(&[b"TYPE", b"str"]), "+set\r\n");
6553 }
6554
6555 #[test]
6556 fn sintercard_counts_without_building_and_stops_at_a_limit() {
6557 let mut f = Fixture::new();
6558 f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
6559 f.run(&[b"SADD", b"b", b"2", b"3", b"4", b"5"]);
6560
6561 assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"b"]), ":3\r\n");
6562 assert_eq!(
6563 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
6564 ":2\r\n"
6565 );
6566 assert_eq!(
6567 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
6568 ":3\r\n",
6569 "a limit of zero is no limit"
6570 );
6571 assert_eq!(f.run(&[b"SINTERCARD", b"1", b"a"]), ":4\r\n");
6572 assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"nope"]), ":0\r\n");
6573
6574 assert_eq!(
6576 f.run(&[b"SINTERCARD", b"0", b"a"]),
6577 "-ERR numkeys should be greater than 0\r\n"
6578 );
6579 assert_eq!(
6580 f.run(&[b"SINTERCARD", b"abc", b"a"]),
6581 "-ERR numkeys should be greater than 0\r\n"
6582 );
6583 assert_eq!(
6584 f.run(&[b"SINTERCARD", b"3", b"a", b"b"]),
6585 "-ERR Number of keys can't be greater than number of args\r\n"
6586 );
6587 assert_eq!(
6588 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"-1"]),
6589 "-ERR LIMIT can't be negative\r\n"
6590 );
6591 assert_eq!(
6592 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"NOPE", b"1"]),
6593 "-ERR syntax error\r\n"
6594 );
6595 f.run(&[b"SADD", b"LIMIT", b"2"]);
6597 assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"LIMIT"]), ":1\r\n");
6598 }
6599
6600 #[test]
6603 fn sunioncard_and_sdiffcard_count_without_building() {
6604 let mut f = Fixture::new();
6605 f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
6606 f.run(&[b"SADD", b"b", b"3", b"4", b"5", b"6"]);
6607
6608 assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"b"]), ":6\r\n");
6609 assert_eq!(
6610 f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
6611 ":2\r\n"
6612 );
6613 assert_eq!(
6614 f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
6615 ":6\r\n",
6616 "a limit of zero is no limit"
6617 );
6618 assert_eq!(f.run(&[b"SUNIONCARD", b"1", b"a"]), ":4\r\n");
6619 assert_eq!(
6620 f.run(&[b"SUNIONCARD", b"2", b"a", b"nope"]),
6621 ":4\r\n",
6622 "a missing key adds nothing to a union"
6623 );
6624
6625 assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"b"]), ":2\r\n");
6626 assert_eq!(
6627 f.run(&[b"SDIFFCARD", b"2", b"a", b"b", b"LIMIT", b"1"]),
6628 ":1\r\n"
6629 );
6630 assert_eq!(
6631 f.run(&[b"SDIFFCARD", b"2", b"b", b"a"]),
6632 ":2\r\n",
6633 "a difference is not symmetric"
6634 );
6635 assert_eq!(f.run(&[b"SDIFFCARD", b"1", b"a"]), ":4\r\n");
6636 assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"nope"]), ":4\r\n");
6637 assert_eq!(
6638 f.run(&[b"SDIFFCARD", b"2", b"nope", b"a"]),
6639 ":0\r\n",
6640 "nothing taken away from nothing"
6641 );
6642
6643 for name in [b"SUNIONCARD".as_slice(), b"SDIFFCARD".as_slice()] {
6646 assert_eq!(
6647 f.run(&[name, b"0", b"a"]),
6648 "-ERR numkeys should be greater than 0\r\n"
6649 );
6650 assert_eq!(
6651 f.run(&[name, b"abc", b"a"]),
6652 "-ERR numkeys should be greater than 0\r\n"
6653 );
6654 assert_eq!(
6655 f.run(&[name, b"-1", b"a"]),
6656 "-ERR numkeys should be greater than 0\r\n"
6657 );
6658 assert_eq!(
6659 f.run(&[name, b"3", b"a", b"b"]),
6660 "-ERR Number of keys can't be greater than number of args\r\n"
6661 );
6662 assert_eq!(
6663 f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"-1"]),
6664 "-ERR LIMIT can't be negative\r\n"
6665 );
6666 assert_eq!(
6667 f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"abc"]),
6668 "-ERR LIMIT can't be negative\r\n",
6669 "a LIMIT that is not a number gets the negative message too"
6670 );
6671 assert_eq!(
6672 f.run(&[name, b"2", b"a", b"b", b"NOPE", b"1"]),
6673 "-ERR syntax error\r\n"
6674 );
6675 assert_eq!(
6676 f.run(&[name, b"2", b"a", b"b", b"LIMIT"]),
6677 "-ERR syntax error\r\n"
6678 );
6679 assert_eq!(
6680 f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"1", b"X"]),
6681 "-ERR syntax error\r\n"
6682 );
6683 }
6684
6685 f.run(&[b"SADD", b"LIMIT", b"2"]);
6687 assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"LIMIT"]), ":4\r\n");
6688 assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"LIMIT"]), ":3\r\n");
6689 }
6690
6691 #[test]
6692 fn the_algebra_answers_wrongtype_before_it_writes_anything() {
6693 let mut f = Fixture::new();
6694 f.run(&[b"SADD", b"a", b"1"]);
6695 f.run(&[b"SADD", b"d", b"old"]);
6696 f.run(&[b"SET", b"str", b"v"]);
6697
6698 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
6699 for bad in [
6700 &[b"SINTER".as_slice(), b"a", b"str"][..],
6701 &[b"SUNION".as_slice(), b"str"][..],
6702 &[b"SDIFF".as_slice(), b"a", b"str"][..],
6703 &[b"SINTERCARD".as_slice(), b"2", b"a", b"str"][..],
6704 &[b"SINTERSTORE".as_slice(), b"d", b"a", b"str"][..],
6705 &[b"SUNIONSTORE".as_slice(), b"d", b"str"][..],
6706 &[b"SDIFFSTORE".as_slice(), b"d", b"a", b"str"][..],
6707 ] {
6708 let reply = f.run(bad);
6709 assert_eq!(reply, wrong, "for {:?}", bad[0]);
6710 }
6711 assert_eq!(
6712 f.run(&[b"SMEMBERS", b"d"]),
6713 "*1\r\n$3\r\nold\r\n",
6714 "and the destination was left alone every time"
6715 );
6716 }
6717
6718 #[test]
6721 fn churning_sets_does_not_grow_the_server() {
6722 let mut f = Fixture::new();
6723 let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
6724 let args: Vec<&[u8]> = std::iter::once(&b"SADD"[..])
6725 .chain(std::iter::once(&b"s"[..]))
6726 .chain(members.iter().map(Vec::as_slice))
6727 .collect();
6728
6729 f.run(&args);
6730 f.run(&[b"DEL", b"s"]);
6731 f.server.compact_step();
6732 let after_first = f.server.memory_bytes();
6733
6734 for _ in 0..200 {
6735 f.run(&args);
6736 f.run(&[b"DEL", b"s"]);
6737 f.server.compact_step();
6738 }
6739 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
6740 assert!(
6741 f.server.memory_bytes() <= after_first * 2,
6742 "held {} after two hundred passes against {after_first} after one",
6743 f.server.memory_bytes()
6744 );
6745 }
6746
6747 #[test]
6755 fn a_bit_is_written_and_read_back_and_a_write_unpacks_an_int() {
6756 let mut f = Fixture::new();
6757 assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"1"]), ":0\r\n");
6758 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n\u{1}\r\n");
6759 assert_eq!(f.run(&[b"GETBIT", b"k", b"7"]), ":1\r\n");
6760 assert_eq!(f.run(&[b"GETBIT", b"k", b"6"]), ":0\r\n");
6761 assert_eq!(f.run(&[b"GETBIT", b"k", b"100"]), ":0\r\n");
6762 assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"0"]), ":1\r\n");
6763
6764 assert_eq!(f.run(&[b"SETBIT", b"nk", b"0", b"0"]), ":0\r\n");
6766 assert_eq!(f.run(&[b"STRLEN", b"nk"]), ":1\r\n");
6767 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"nk"]), "$3\r\nraw\r\n");
6768
6769 f.run(&[b"SET", b"num", b"12345"]);
6770 assert_eq!(f.run(&[b"GETBIT", b"num", b"1"]), ":0\r\n");
6771 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nint\r\n");
6772 assert_eq!(f.run(&[b"SETBIT", b"num", b"1", b"1"]), ":0\r\n");
6773 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nraw\r\n");
6774 assert_eq!(f.run(&[b"GET", b"num"]), "$5\r\nq2345\r\n");
6775 }
6776
6777 #[test]
6782 fn bits_are_counted_over_a_range_of_bytes_or_of_bits() {
6783 let mut f = Fixture::new();
6784 f.run(&[b"SET", b"mykey", b"foobar"]);
6785 assert_eq!(f.run(&[b"BITCOUNT", b"mykey"]), ":26\r\n");
6786 assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"0", b"0"]), ":4\r\n");
6787 assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"1", b"1"]), ":6\r\n");
6788 assert_eq!(
6789 f.run(&[b"BITCOUNT", b"mykey", b"1", b"1", b"BYTE"]),
6790 ":6\r\n"
6791 );
6792 assert_eq!(
6793 f.run(&[b"BITCOUNT", b"mykey", b"0", b"-5", b"BIT"]),
6794 ":25\r\n"
6795 );
6796 assert_eq!(
6797 f.run(&[b"BITCOUNT", b"mykey", b"5", b"30", b"BIT"]),
6798 ":17\r\n"
6799 );
6800 assert_eq!(f.run(&[b"BITCOUNT", b"nokey"]), ":0\r\n");
6801
6802 assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"10", b"20"]), ":0\r\n");
6805
6806 assert_eq!(
6808 f.run(&[b"BITCOUNT", b"mykey", b"0"]),
6809 "-ERR syntax error\r\n"
6810 );
6811 assert_eq!(
6812 f.run(&[b"BITCOUNT", b"mykey", b"0", b"1", b"NIB"]),
6813 "-ERR syntax error\r\n"
6814 );
6815 }
6816
6817 #[test]
6823 fn a_search_for_a_nought_past_the_end_answers_the_length_in_bits() {
6824 let mut f = Fixture::new();
6825 f.run(&[b"SET", b"ones", b"\xff\xff\xff"]);
6826 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0"]), ":24\r\n");
6827 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0"]), ":24\r\n");
6828 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"-1"]), ":-1\r\n");
6829 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"3"]), ":-1\r\n");
6830 assert_eq!(f.run(&[b"BITPOS", b"ones", b"1"]), ":0\r\n");
6831
6832 f.run(&[b"SET", b"mid", b"\x00\xff\xf0"]);
6833 assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"0"]), ":8\r\n");
6834 assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"2"]), ":16\r\n");
6835 assert_eq!(
6836 f.run(&[b"BITPOS", b"mid", b"1", b"0", b"-1", b"BIT"]),
6837 ":8\r\n"
6838 );
6839
6840 assert_eq!(f.run(&[b"BITPOS", b"gone", b"1"]), ":-1\r\n");
6843 assert_eq!(f.run(&[b"BITPOS", b"gone", b"0"]), ":0\r\n");
6844 }
6845
6846 #[test]
6848 fn the_eight_combinations_write_what_a_real_server_writes() {
6849 let mut f = Fixture::new();
6850 f.run(&[b"SET", b"a", b"abc"]);
6851 f.run(&[b"SET", b"b", b"abd"]);
6852 let cases: &[(&[u8], &str)] = &[
6853 (b"AND", "ab`"),
6854 (b"OR", "abg"),
6855 (b"XOR", "\u{0}\u{0}\u{7}"),
6856 (b"DIFF", "\u{0}\u{0}\u{3}"),
6857 (b"DIFF1", "\u{0}\u{0}\u{4}"),
6858 (b"ANDOR", "ab`"),
6859 (b"ONE", "\u{0}\u{0}\u{7}"),
6860 ];
6861 for (op, want) in cases {
6862 assert_eq!(f.run(&[b"BITOP", op, b"d", b"a", b"b"]), ":3\r\n", "{op:?}");
6863 assert_eq!(
6864 f.run(&[b"GET", b"d"]),
6865 format!("$3\r\n{want}\r\n"),
6866 "{op:?}"
6867 );
6868 }
6869 assert_eq!(f.run(&[b"BITOP", b"NOT", b"d", b"a"]), ":3\r\n");
6871 assert_eq!(f.raw(&[b"GET", b"d"]), b"$3\r\n\x9e\x9d\x9c\r\n".to_vec());
6872
6873 assert_eq!(f.run(&[b"BITOP", b"AND", b"d", b"a", b"gone"]), ":3\r\n");
6876 assert_eq!(f.run(&[b"GET", b"d"]), "$3\r\n\u{0}\u{0}\u{0}\r\n");
6877
6878 f.run(&[b"SET", b"dest", b"x"]);
6881 assert_eq!(f.run(&[b"BITOP", b"AND", b"dest", b"g1", b"g2"]), ":0\r\n");
6882 assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
6883 }
6884
6885 #[test]
6887 fn bitop_names_the_operation_in_its_own_complaints() {
6888 let mut f = Fixture::new();
6889 f.run(&[b"SET", b"a", b"abc"]);
6890 assert_eq!(
6891 f.run(&[b"BITOP", b"nope", b"d", b"a"]),
6892 "-ERR syntax error\r\n"
6893 );
6894 assert_eq!(
6895 f.run(&[b"BITOP", b"NOT", b"d", b"a", b"a"]),
6896 "-ERR BITOP NOT must be called with a single source key.\r\n"
6897 );
6898 for op in [&b"DIFF"[..], b"DIFF1", b"ANDOR"] {
6899 assert_eq!(
6900 f.run(&[b"BITOP", op, b"d", b"a"]),
6901 format!(
6902 "-ERR BITOP {} must be called with at least two source keys.\r\n",
6903 String::from_utf8_lossy(op)
6904 )
6905 );
6906 }
6907 f.run(&[b"LPUSH", b"l", b"x"]);
6908 assert_eq!(
6909 f.run(&[b"BITOP", b"AND", b"d", b"a", b"l"]),
6910 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
6911 );
6912 }
6913
6914 #[test]
6916 fn bitfield_reads_and_writes_packed_fields() {
6917 let mut f = Fixture::new();
6918 assert_eq!(f.run(&[b"BITFIELD", b"bf"]), "*0\r\n");
6919 assert_eq!(f.run(&[b"EXISTS", b"bf"]), ":0\r\n");
6920
6921 assert_eq!(
6922 f.run(&[
6923 b"BITFIELD",
6924 b"bf",
6925 b"INCRBY",
6926 b"u2",
6927 b"100",
6928 b"1",
6929 b"GET",
6930 b"u4",
6931 b"0"
6932 ]),
6933 "*2\r\n:1\r\n:0\r\n"
6934 );
6935 assert_eq!(f.run(&[b"STRLEN", b"bf"]), ":13\r\n");
6938
6939 assert_eq!(
6941 f.run(&[
6942 b"BITFIELD",
6943 b"bf",
6944 b"SET",
6945 b"u8",
6946 b"#0",
6947 b"255",
6948 b"GET",
6949 b"u8",
6950 b"#0"
6951 ]),
6952 "*2\r\n:0\r\n:255\r\n"
6953 );
6954
6955 assert_eq!(
6956 f.run(&[
6957 b"BITFIELD",
6958 b"bf",
6959 b"OVERFLOW",
6960 b"SAT",
6961 b"INCRBY",
6962 b"i8",
6963 b"0",
6964 b"120",
6965 b"INCRBY",
6966 b"i8",
6967 b"0",
6968 b"120"
6969 ]),
6970 "*2\r\n:119\r\n:127\r\n"
6971 );
6972 assert_eq!(
6973 f.run(&[
6974 b"BITFIELD",
6975 b"bf2",
6976 b"OVERFLOW",
6977 b"FAIL",
6978 b"INCRBY",
6979 b"u2",
6980 b"0",
6981 b"5"
6982 ]),
6983 "*1\r\n$-1\r\n"
6984 );
6985 assert_eq!(
6986 f.run(&[
6987 b"BITFIELD",
6988 b"bf3",
6989 b"OVERFLOW",
6990 b"WRAP",
6991 b"INCRBY",
6992 b"u2",
6993 b"0",
6994 b"5"
6995 ]),
6996 "*1\r\n:1\r\n"
6997 );
6998 assert_eq!(
6999 f.run(&[b"BITFIELD", b"bf3", b"GET", b"i64", b"0"]),
7000 "*1\r\n:4611686018427387904\r\n"
7001 );
7002 }
7003
7004 #[test]
7010 fn a_bad_bitfield_subcommand_leaves_the_key_alone() {
7011 let mut f = Fixture::new();
7012 let bad_type = "-ERR Invalid bitfield type. Use something like i16 u8. Note that u64 is not supported but i64 is.\r\n";
7013 assert_eq!(
7014 f.run(&[
7015 b"BITFIELD",
7016 b"bad",
7017 b"SET",
7018 b"u8",
7019 b"0",
7020 b"1",
7021 b"GET",
7022 b"u99",
7023 b"0"
7024 ]),
7025 bad_type
7026 );
7027 assert_eq!(f.run(&[b"EXISTS", b"bad"]), ":0\r\n");
7028 assert_eq!(
7029 f.run(&[b"BITFIELD", b"bad", b"GET", b"u64", b"0"]),
7030 bad_type
7031 );
7032 assert_eq!(
7033 f.run(&[b"BITFIELD", b"bad", b"GET"]),
7034 "-ERR syntax error\r\n"
7035 );
7036 assert_eq!(
7037 f.run(&[b"BITFIELD", b"bad", b"NOPE", b"u8", b"0"]),
7038 "-ERR syntax error\r\n"
7039 );
7040 assert_eq!(
7041 f.run(&[b"BITFIELD", b"bad", b"OVERFLOW"]),
7042 "-ERR syntax error\r\n"
7043 );
7044 assert_eq!(
7045 f.run(&[
7046 b"BITFIELD",
7047 b"bad",
7048 b"OVERFLOW",
7049 b"NOPE",
7050 b"GET",
7051 b"u8",
7052 b"0"
7053 ]),
7054 "-ERR Invalid OVERFLOW type specified\r\n"
7055 );
7056 assert_eq!(
7057 f.run(&[b"BITFIELD", b"bad", b"SET", b"u8", b"0", b"notanum"]),
7058 "-ERR value is not an integer or out of range\r\n"
7059 );
7060 for at in [&b"#-1"[..], b"abc"] {
7061 assert_eq!(
7062 f.run(&[b"BITFIELD", b"bad", b"GET", b"u8", at]),
7063 "-ERR bit offset is not an integer or out of range\r\n"
7064 );
7065 }
7066 }
7067
7068 #[test]
7070 fn bitfield_ro_answers_gets_and_refuses_the_rest() {
7071 let mut f = Fixture::new();
7072 f.run(&[b"SET", b"n", b"123"]);
7073 assert_eq!(
7074 f.run(&[b"BITFIELD_RO", b"n", b"GET", b"u8", b"0"]),
7075 "*1\r\n:49\r\n"
7076 );
7077 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"n"]), "$3\r\nint\r\n");
7079
7080 assert_eq!(
7082 f.run(&[
7083 b"BITFIELD_RO",
7084 b"n",
7085 b"OVERFLOW",
7086 b"SAT",
7087 b"GET",
7088 b"u8",
7089 b"0"
7090 ]),
7091 "*1\r\n:49\r\n"
7092 );
7093 for sub in [&b"SET"[..], b"INCRBY"] {
7094 assert_eq!(
7095 f.run(&[b"BITFIELD_RO", b"n", sub, b"u8", b"0", b"1"]),
7096 "-ERR BITFIELD_RO only supports the GET subcommand\r\n"
7097 );
7098 }
7099
7100 assert_eq!(
7101 f.run(&[b"BITFIELD_RO", b"gone", b"GET", b"u8", b"100"]),
7102 "*1\r\n:0\r\n"
7103 );
7104 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
7105 }
7106
7107 #[test]
7109 fn an_offset_off_the_end_of_the_world_is_refused() {
7110 let mut f = Fixture::new();
7111 let bad = "-ERR bit offset is not an integer or out of range\r\n";
7112 for arg in [&b"abc"[..], b"-1", b"4294967296"] {
7113 assert_eq!(f.run(&[b"SETBIT", b"k", arg, b"1"]), bad);
7114 assert_eq!(f.run(&[b"GETBIT", b"k", arg]), bad);
7115 }
7116 for arg in [&b"2"[..], b"-1"] {
7117 assert_eq!(
7118 f.run(&[b"BITPOS", b"k", arg]),
7119 "-ERR The bit argument must be 1 or 0.\r\n"
7120 );
7121 }
7122 assert_eq!(
7123 f.run(&[b"BITPOS", b"k", b"abc"]),
7124 "-ERR value is not an integer or out of range\r\n"
7125 );
7126 assert_eq!(
7127 f.run(&[b"BITPOS", b"k", b"0", b"5", b"BIT"]),
7128 "-ERR value is not an integer or out of range\r\n"
7129 );
7130 let bad_bit = "-ERR bit is not an integer or out of range\r\n";
7131 assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"2"]), bad_bit);
7132 assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"abc"]), bad_bit);
7133 }
7134
7135 #[test]
7137 fn every_bitmap_command_says_wrongtype() {
7138 let mut f = Fixture::new();
7139 f.run(&[b"LPUSH", b"l", b"x"]);
7140 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
7141 let cases: &[&[&[u8]]] = &[
7142 &[b"SETBIT", b"l", b"0", b"1"],
7143 &[b"GETBIT", b"l", b"0"],
7144 &[b"BITCOUNT", b"l"],
7145 &[b"BITPOS", b"l", b"1"],
7146 &[b"BITOP", b"AND", b"d", b"l"],
7147 &[b"BITFIELD", b"l", b"GET", b"u8", b"0"],
7148 &[b"BITFIELD_RO", b"l", b"GET", b"u8", b"0"],
7149 ];
7150 for case in cases {
7151 assert_eq!(f.run(case), wrong, "{:?}", case[0]);
7152 }
7153 }
7154
7155 #[test]
7158 fn a_sketch_is_added_to_and_counted() {
7159 let mut f = Fixture::new();
7160 assert_eq!(f.run(&[b"PFADD", b"h"]), ":1\r\n");
7162 assert_eq!(f.run(&[b"PFADD", b"h"]), ":0\r\n");
7163 assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":0\r\n");
7164 assert_eq!(f.run(&[b"STRLEN", b"h"]), ":18\r\n");
7165 assert_eq!(f.run(&[b"TYPE", b"h"]), "+string\r\n");
7168 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$3\r\nraw\r\n");
7169
7170 assert_eq!(f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]), ":1\r\n");
7171 assert_eq!(f.run(&[b"PFADD", b"h", b"a"]), ":0\r\n");
7172 assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
7173 }
7174
7175 #[test]
7176 fn the_bytes_of_a_sketch_are_the_ones_a_real_server_writes() {
7177 let mut f = Fixture::new();
7178 f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
7179 let want = b"HYLL\x01\0\0\0\0\0\0\0\0\0\0\x80\x60\xf3\x80\x50\xb1\x84\x4b\xfb\x80\x42\x5a";
7181 let mut reply = b"$27\r\n".to_vec();
7182 reply.extend_from_slice(want);
7183 reply.extend_from_slice(b"\r\n");
7184 assert_eq!(f.raw(&[b"GET", b"h"]), reply);
7185 }
7186
7187 #[test]
7188 fn counting_several_keys_counts_their_union() {
7189 let mut f = Fixture::new();
7190 f.run(&[b"PFADD", b"a", b"x", b"y"]);
7191 f.run(&[b"PFADD", b"b", b"y", b"z"]);
7192 assert_eq!(f.run(&[b"PFCOUNT", b"a"]), ":2\r\n");
7193 assert_eq!(f.run(&[b"PFCOUNT", b"a", b"b"]), ":3\r\n");
7194 assert_eq!(f.run(&[b"PFCOUNT", b"gone"]), ":0\r\n");
7197 assert_eq!(f.run(&[b"PFCOUNT", b"a", b"gone"]), ":2\r\n");
7198 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
7199 }
7200
7201 #[test]
7202 fn a_merge_keeps_what_the_destination_had() {
7203 let mut f = Fixture::new();
7204 f.run(&[b"PFADD", b"a", b"x", b"y"]);
7205 f.run(&[b"PFADD", b"b", b"z"]);
7206 assert_eq!(f.run(&[b"PFMERGE", b"d", b"a", b"b"]), "+OK\r\n");
7207 assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":3\r\n");
7208 f.run(&[b"PFADD", b"c", b"w"]);
7210 assert_eq!(f.run(&[b"PFMERGE", b"d", b"c"]), "+OK\r\n");
7211 assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":4\r\n");
7212 assert_eq!(f.run(&[b"PFMERGE", b"fresh"]), "+OK\r\n");
7215 assert_eq!(f.run(&[b"PFCOUNT", b"fresh"]), ":0\r\n");
7216 }
7217
7218 #[test]
7219 fn the_debug_forms_answer_four_different_shapes() {
7220 let mut f = Fixture::new();
7221 f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
7222 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+sparse\r\n");
7223 assert_eq!(
7224 f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
7225 "$44\r\nZ:8436 v:1,1 Z:4274 v:2,1 Z:3068 v:1,1 Z:603\r\n"
7226 );
7227 assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":1\r\n");
7228 assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":0\r\n");
7229 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+dense\r\n");
7230 assert_eq!(f.run(&[b"STRLEN", b"h"]), ":12304\r\n");
7231 assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
7232 assert_eq!(
7234 f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
7235 "-ERR HLL encoding is not sparse\r\n"
7236 );
7237
7238 let reply = f.run(&[b"PFDEBUG", b"GETREG", b"h"]);
7240 assert!(reply.starts_with("*16384\r\n"), "{}", &reply[..16]);
7241 assert_eq!(reply.matches(":0\r\n").count(), 16381);
7242 assert_eq!(reply.matches(":1\r\n").count(), 2);
7243 assert_eq!(reply.matches(":2\r\n").count(), 1);
7244
7245 assert_eq!(f.run(&[b"PFSELFTEST"]), "+OK\r\n");
7246 }
7247
7248 #[test]
7249 fn a_string_that_is_not_a_sketch_is_refused_with_its_own_sentence() {
7250 let mut f = Fixture::new();
7251 f.run(&[b"SET", b"plain", b"not a sketch"]);
7252 let not_hll = "-WRONGTYPE Key is not a valid HyperLogLog string value.\r\n";
7253 assert_eq!(f.run(&[b"PFADD", b"plain", b"a"]), not_hll);
7254 assert_eq!(f.run(&[b"PFCOUNT", b"plain"]), not_hll);
7255 assert_eq!(f.run(&[b"PFMERGE", b"plain"]), not_hll);
7256 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"plain"]), not_hll);
7257
7258 f.run(&[b"RPUSH", b"l", b"x"]);
7261 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
7262 assert_eq!(f.run(&[b"PFADD", b"l", b"a"]), wrong);
7263 assert_eq!(f.run(&[b"PFCOUNT", b"l"]), wrong);
7264 assert_eq!(f.run(&[b"PFMERGE", b"dest", b"l"]), wrong);
7265 assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
7266 assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"l"]), wrong);
7267 }
7268
7269 #[test]
7270 fn pfdebug_has_its_own_complaints() {
7271 let mut f = Fixture::new();
7272 f.run(&[b"PFADD", b"h", b"a"]);
7273 assert_eq!(
7276 f.run(&[b"PFDEBUG", b"NOPE", b"h"]),
7277 "-ERR Unknown PFDEBUG subcommand 'NOPE'\r\n"
7278 );
7279 let gone = "-ERR The specified key does not exist\r\n";
7281 assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"missing"]), gone);
7282 assert_eq!(f.run(&[b"PFDEBUG", b"DECODE", b"missing"]), gone);
7283 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"missing"]), gone);
7284 assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"missing"]), gone);
7285 assert_eq!(
7286 f.run(&[b"PFDEBUG"]),
7287 "-ERR wrong number of arguments for 'pfdebug' command\r\n"
7288 );
7289 assert_eq!(
7290 f.run(&[b"PFSELFTEST", b"x"]),
7291 "-ERR wrong number of arguments for 'pfselftest' command\r\n"
7292 );
7293 }
7294
7295 #[test]
7296 fn a_sketch_whose_opcodes_do_not_add_up_says_so() {
7297 let mut f = Fixture::new();
7298 f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
7299 let reply = f.raw(&[b"GET", b"h"]);
7302 let short = reply[5..reply.len() - 3].to_vec();
7303 f.run(&[b"SET", b"h", &short]);
7304 assert_eq!(
7305 f.run(&[b"PFCOUNT", b"h"]),
7306 "-INVALIDOBJ Corrupted HLL object detected\r\n"
7307 );
7308 }
7309
7310 #[test]
7311 fn a_sketch_survives_a_dump_and_a_restore_in_both_encodings() {
7312 let mut f = Fixture::new();
7313 f.run(&[b"PFADD", b"small", b"a", b"b", b"c"]);
7316 for i in 0..10_000u32 {
7317 let ele = format!("e{i}");
7318 f.run(&[b"PFADD", b"big", ele.as_bytes()]);
7319 }
7320 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"small"]), "+sparse\r\n");
7321 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"big"]), "+dense\r\n");
7322
7323 for key in [&b"small"[..], b"big"] {
7324 let mut copy = key.to_vec();
7325 copy.push(b'2');
7326 let bytes = payload(&f.raw(&[b"DUMP", key]));
7327 assert_eq!(f.run(&[b"RESTORE", ©, b"0", &bytes]), "+OK\r\n");
7328 assert_eq!(f.raw(&[b"GET", ©]), f.raw(&[b"GET", key]));
7331 assert_eq!(
7332 f.run(&[b"PFDEBUG", b"ENCODING", ©]),
7333 f.run(&[b"PFDEBUG", b"ENCODING", key])
7334 );
7335 assert_eq!(f.run(&[b"PFCOUNT", ©]), f.run(&[b"PFCOUNT", key]));
7336 }
7337 assert_eq!(f.run(&[b"PFCOUNT", b"small2"]), ":3\r\n");
7338 assert_eq!(f.run(&[b"STRLEN", b"big2"]), ":12304\r\n");
7339 }
7340
7341 fn bulk(s: &str) -> String {
7345 format!("${}\r\n{s}\r\n", s.len())
7346 }
7347
7348 fn bulks(parts: &[&str]) -> String {
7351 let mut s = format!("*{}\r\n", parts.len());
7352 for p in parts {
7353 s.push_str(&format!("${}\r\n{p}\r\n", p.len()));
7354 }
7355 s
7356 }
7357
7358 #[test]
7359 fn a_list_is_pushed_from_both_ends_and_the_left_one_reverses() {
7360 let mut f = Fixture::new();
7361 assert_eq!(f.run(&[b"LPUSH", b"k", b"a", b"b", b"c"]), ":3\r\n");
7365 assert_eq!(
7366 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
7367 bulks(&["c", "b", "a"])
7368 );
7369 assert_eq!(f.run(&[b"RPUSH", b"k", b"d"]), ":4\r\n");
7370 assert_eq!(f.run(&[b"LLEN", b"k"]), ":4\r\n");
7371 assert_eq!(f.run(&[b"LPOP", b"k"]), "$1\r\nc\r\n");
7372 assert_eq!(f.run(&[b"RPOP", b"k"]), "$1\r\nd\r\n");
7373 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "a"]));
7374 assert_eq!(f.run(&[b"TYPE", b"k"]), "+list\r\n");
7375 }
7376
7377 #[test]
7378 fn the_x_pushes_refuse_to_bring_a_list_back_to_life() {
7379 let mut f = Fixture::new();
7380 assert_eq!(f.run(&[b"LPUSHX", b"k", b"a"]), ":0\r\n");
7381 assert_eq!(f.run(&[b"RPUSHX", b"k", b"a"]), ":0\r\n");
7382 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
7383 f.run(&[b"RPUSH", b"k", b"a"]);
7384 assert_eq!(f.run(&[b"LPUSHX", b"k", b"z"]), ":2\r\n");
7385 assert_eq!(f.run(&[b"RPUSHX", b"k", b"y"]), ":3\r\n");
7386 assert_eq!(
7387 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
7388 bulks(&["z", "a", "y"])
7389 );
7390 }
7391
7392 #[test]
7395 fn an_empty_pop_is_a_different_nothing_with_a_count_and_without() {
7396 let mut f = Fixture::new();
7397 assert_eq!(f.run(&[b"LPOP", b"nope"]), "$-1\r\n");
7398 assert_eq!(f.run(&[b"LPOP", b"nope", b"2"]), "*-1\r\n");
7399 assert_eq!(f.run(&[b"RPOP", b"nope"]), "$-1\r\n");
7400 assert_eq!(f.run(&[b"RPOP", b"nope", b"2"]), "*-1\r\n");
7401 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
7402 assert_eq!(f.run(&[b"LPOP", b"k", b"0"]), "*0\r\n");
7405 assert_eq!(f.run(&[b"LPOP", b"k", b"1"]), bulks(&["a"]));
7406 assert_eq!(f.run(&[b"RPOP", b"k", b"9"]), bulks(&["c", "b"]));
7408 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
7409 }
7410
7411 #[test]
7412 fn a_pop_count_has_its_own_sentence_and_a_third_argument_is_an_arity_error() {
7413 let mut f = Fixture::new();
7414 f.run(&[b"RPUSH", b"k", b"a"]);
7415 let range = "-ERR value is out of range, must be positive\r\n";
7416 assert_eq!(f.run(&[b"LPOP", b"k", b"-1"]), range);
7417 assert_eq!(f.run(&[b"LPOP", b"k", b"abc"]), range);
7418 assert_eq!(f.run(&[b"RPOP", b"k", b"-1"]), range);
7419 assert_eq!(
7422 f.run(&[b"LPOP", b"k", b"1", b"2"]),
7423 "-ERR wrong number of arguments for 'lpop' command\r\n"
7424 );
7425 assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
7426 }
7427
7428 #[test]
7429 fn a_range_takes_negative_ends_and_clamps_the_ones_that_run_off() {
7430 let mut f = Fixture::new();
7431 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
7432 assert_eq!(
7433 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
7434 bulks(&["a", "b", "c"])
7435 );
7436 assert_eq!(f.run(&[b"LRANGE", b"k", b"-2", b"-1"]), bulks(&["b", "c"]));
7437 assert_eq!(f.run(&[b"LRANGE", b"k", b"1", b"1"]), bulks(&["b"]));
7438 assert_eq!(f.run(&[b"LRANGE", b"k", b"5", b"10"]), "*0\r\n");
7439 assert_eq!(f.run(&[b"LRANGE", b"k", b"2", b"1"]), "*0\r\n");
7440 assert_eq!(
7441 f.run(&[b"LRANGE", b"k", b"-100", b"100"]),
7442 bulks(&["a", "b", "c"])
7443 );
7444 assert_eq!(f.run(&[b"LRANGE", b"nope", b"0", b"-1"]), "*0\r\n");
7447 assert_eq!(
7448 f.run(&[b"LRANGE", b"k", b"a", b"b"]),
7449 "-ERR value is not an integer or out of range\r\n"
7450 );
7451 }
7452
7453 #[test]
7454 fn an_index_reads_and_writes_from_whichever_end_is_nearer() {
7455 let mut f = Fixture::new();
7456 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
7457 assert_eq!(f.run(&[b"LINDEX", b"k", b"0"]), "$1\r\na\r\n");
7458 assert_eq!(f.run(&[b"LINDEX", b"k", b"-1"]), "$1\r\nc\r\n");
7459 assert_eq!(f.run(&[b"LINDEX", b"k", b"99"]), "$-1\r\n");
7460 assert_eq!(f.run(&[b"LINDEX", b"nope", b"0"]), "$-1\r\n");
7461 assert_eq!(f.run(&[b"LSET", b"k", b"-1", b"z"]), "+OK\r\n");
7462 assert_eq!(
7463 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
7464 bulks(&["a", "b", "z"])
7465 );
7466 assert_eq!(
7469 f.run(&[b"LSET", b"k", b"99", b"z"]),
7470 "-ERR index out of range\r\n"
7471 );
7472 assert_eq!(
7473 f.run(&[b"LSET", b"nope", b"0", b"z"]),
7474 "-ERR no such key\r\n"
7475 );
7476 }
7477
7478 #[test]
7479 fn linsert_says_three_things_with_one_signed_number() {
7480 let mut f = Fixture::new();
7481 assert_eq!(
7484 f.run(&[b"LINSERT", b"nope", b"BEFORE", b"a", b"x"]),
7485 ":0\r\n"
7486 );
7487 f.run(&[b"RPUSH", b"k", b"a", b"b"]);
7488 assert_eq!(f.run(&[b"LINSERT", b"k", b"before", b"a", b"X"]), ":3\r\n");
7489 assert_eq!(f.run(&[b"LINSERT", b"k", b"AFTER", b"b", b"Y"]), ":4\r\n");
7490 assert_eq!(
7491 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
7492 bulks(&["X", "a", "b", "Y"])
7493 );
7494 assert_eq!(
7495 f.run(&[b"LINSERT", b"k", b"BEFORE", b"zz", b"x"]),
7496 ":-1\r\n"
7497 );
7498 assert_eq!(
7499 f.run(&[b"LINSERT", b"k", b"SIDEWAYS", b"a", b"x"]),
7500 "-ERR syntax error\r\n"
7501 );
7502 }
7503
7504 #[test]
7505 fn lrem_counts_in_three_directions_and_takes_the_key_when_it_empties() {
7506 let mut f = Fixture::new();
7507 f.run(&[b"RPUSH", b"k", b"a", b"b", b"a", b"c", b"a"]);
7508 assert_eq!(f.run(&[b"LREM", b"k", b"2", b"a"]), ":2\r\n");
7509 assert_eq!(
7510 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
7511 bulks(&["b", "c", "a"])
7512 );
7513 assert_eq!(f.run(&[b"LREM", b"k", b"-1", b"a"]), ":1\r\n");
7514 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
7515 assert_eq!(f.run(&[b"LREM", b"k", b"0", b"b"]), ":1\r\n");
7516 assert_eq!(f.run(&[b"LREM", b"k", b"0", b"c"]), ":1\r\n");
7517 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
7518 assert_eq!(f.run(&[b"LREM", b"nope", b"0", b"a"]), ":0\r\n");
7519 }
7520
7521 #[test]
7522 fn ltrim_keeps_a_window_and_an_empty_one_deletes_the_key() {
7523 let mut f = Fixture::new();
7524 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c", b"d"]);
7525 assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"-2"]), "+OK\r\n");
7526 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
7527 assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"0"]), "+OK\r\n");
7530 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
7531 assert_eq!(f.run(&[b"LTRIM", b"nope", b"0", b"-1"]), "+OK\r\n");
7532 }
7533
7534 #[test]
7535 fn lpos_walks_from_either_end_and_stops_where_it_is_told() {
7536 let mut f = Fixture::new();
7537 f.run(&[b"RPUSH", b"p", b"a", b"b", b"c", b"a", b"b", b"c", b"a"]);
7538 assert_eq!(f.run(&[b"LPOS", b"p", b"a"]), ":0\r\n");
7539 assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1"]), ":6\r\n");
7540 assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"2"]), ":3\r\n");
7541 assert_eq!(
7542 f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"2"]),
7543 "*2\r\n:0\r\n:3\r\n"
7544 );
7545 assert_eq!(
7546 f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1", b"COUNT", b"0"]),
7547 "*3\r\n:6\r\n:3\r\n:0\r\n"
7548 );
7549 assert_eq!(
7552 f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"0", b"MAXLEN", b"3"]),
7553 "*1\r\n:0\r\n"
7554 );
7555 assert_eq!(f.run(&[b"LPOS", b"p", b"zz"]), "$-1\r\n");
7558 assert_eq!(f.run(&[b"LPOS", b"p", b"zz", b"COUNT", b"0"]), "*0\r\n");
7559 assert_eq!(f.run(&[b"LPOS", b"nope", b"a"]), "$-1\r\n");
7560 assert_eq!(f.run(&[b"LPOS", b"nope", b"a", b"COUNT", b"2"]), "*0\r\n");
7561 }
7562
7563 #[test]
7564 fn lpos_words_its_three_mistakes_the_way_redis_does() {
7565 let mut f = Fixture::new();
7566 f.run(&[b"RPUSH", b"p", b"a"]);
7567 assert_eq!(
7570 f.run(&[b"LPOS", b"p", b"a", b"RANK", b"0"]),
7571 "-ERR RANK can't be zero: use 1 to start from the first match, 2 from the second ... or use negative to start from the end of the list\r\n"
7572 );
7573 assert_eq!(
7574 f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"-1"]),
7575 "-ERR COUNT can't be negative\r\n"
7576 );
7577 assert_eq!(
7578 f.run(&[b"LPOS", b"p", b"a", b"MAXLEN", b"-1"]),
7579 "-ERR MAXLEN can't be negative\r\n"
7580 );
7581 assert_eq!(
7582 f.run(&[b"LPOS", b"p", b"a", b"RANK"]),
7583 "-ERR syntax error\r\n"
7584 );
7585 assert_eq!(
7586 f.run(&[b"LPOS", b"p", b"a", b"FOO", b"1"]),
7587 "-ERR syntax error\r\n"
7588 );
7589 }
7590
7591 #[test]
7592 fn a_move_takes_from_one_end_and_gives_to_another_even_on_one_key() {
7593 let mut f = Fixture::new();
7594 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
7595 assert_eq!(f.run(&[b"RPOPLPUSH", b"k", b"d"]), "$1\r\nc\r\n");
7596 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
7597 assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c"]));
7598 assert_eq!(
7599 f.run(&[b"LMOVE", b"k", b"d", b"LEFT", b"RIGHT"]),
7600 "$1\r\na\r\n"
7601 );
7602 assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c", "a"]));
7603 f.run(&[b"DEL", b"r"]);
7606 f.run(&[b"RPUSH", b"r", b"1", b"2", b"3"]);
7607 assert_eq!(f.run(&[b"RPOPLPUSH", b"r", b"r"]), "$1\r\n3\r\n");
7608 assert_eq!(
7609 f.run(&[b"LRANGE", b"r", b"0", b"-1"]),
7610 bulks(&["3", "1", "2"])
7611 );
7612 assert_eq!(
7613 f.run(&[b"LMOVE", b"nope", b"d", b"LEFT", b"LEFT"]),
7614 "$-1\r\n"
7615 );
7616 assert_eq!(
7617 f.run(&[b"LMOVE", b"r", b"d", b"LEFT", b"SIDEWAYS"]),
7618 "-ERR syntax error\r\n"
7619 );
7620 }
7621
7622 #[test]
7623 fn a_move_checks_the_destination_before_it_takes_anything() {
7624 let mut f = Fixture::new();
7625 f.run(&[b"RPUSH", b"k", b"a", b"b"]);
7626 f.run(&[b"SET", b"str", b"v"]);
7627 assert_eq!(
7628 f.run(&[b"LMOVE", b"k", b"str", b"LEFT", b"LEFT"]),
7629 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
7630 );
7631 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
7633 }
7634
7635 #[test]
7636 fn a_block_move_orders_the_block_by_the_ends_and_the_ordering_word() {
7637 for (from, to, order, want) in [
7641 ("LEFT", "RIGHT", "OBO", ["a", "b"]),
7642 ("LEFT", "RIGHT", "BULK", ["a", "b"]),
7643 ("LEFT", "LEFT", "OBO", ["b", "a"]),
7644 ("LEFT", "LEFT", "BULK", ["a", "b"]),
7645 ("RIGHT", "LEFT", "OBO", ["d", "e"]),
7646 ("RIGHT", "LEFT", "BULK", ["d", "e"]),
7647 ("RIGHT", "RIGHT", "OBO", ["e", "d"]),
7648 ("RIGHT", "RIGHT", "BULK", ["d", "e"]),
7649 ] {
7650 let mut f = Fixture::new();
7651 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c", b"d", b"e"]);
7652 let how = format!("{from} {to} {order}");
7653 let reply = f.run(&[
7654 b"LMOVEM",
7655 b"s",
7656 b"d",
7657 from.as_bytes(),
7658 to.as_bytes(),
7659 b"COUNT",
7660 b"2",
7661 order.as_bytes(),
7662 ]);
7663 assert_eq!(reply, bulks(&want), "the reply for {how}");
7664 assert_eq!(
7665 f.run(&[b"LRANGE", b"d", b"0", b"-1"]),
7666 bulks(&want),
7667 "the destination for {how}"
7668 );
7669 }
7670 }
7671
7672 #[test]
7673 fn a_block_move_of_one_needs_no_count_at_all() {
7674 let mut f = Fixture::new();
7675 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
7676 assert_eq!(
7677 f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT"]),
7678 bulks(&["a"])
7679 );
7680 assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["b", "c"]));
7681 assert_eq!(
7684 f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT"]),
7685 "-ERR syntax error\r\n"
7686 );
7687 assert_eq!(
7688 f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"2"]),
7689 "-ERR syntax error\r\n"
7690 );
7691 }
7692
7693 #[test]
7694 fn a_block_move_with_exactly_takes_all_of_them_or_none() {
7695 let mut f = Fixture::new();
7696 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
7697 assert_eq!(
7701 f.run(&[
7702 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"EXACTLY", b"99", b"BULK"
7703 ]),
7704 "*-1\r\n"
7705 );
7706 assert_eq!(
7707 f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
7708 bulks(&["a", "b", "c"])
7709 );
7710 assert_eq!(
7712 f.run(&[
7713 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"99", b"BULK"
7714 ]),
7715 bulks(&["a", "b", "c"])
7716 );
7717 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
7718 assert_eq!(
7719 f.run(&[
7720 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
7721 ]),
7722 "*-1\r\n"
7723 );
7724 }
7725
7726 #[test]
7727 fn a_block_move_onto_itself_rotates_by_the_count() {
7728 let mut f = Fixture::new();
7729 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
7730 assert_eq!(
7731 f.run(&[
7732 b"LMOVEM", b"s", b"s", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
7733 ]),
7734 bulks(&["a", "b"])
7735 );
7736 assert_eq!(
7737 f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
7738 bulks(&["c", "a", "b"])
7739 );
7740 }
7741
7742 #[test]
7743 fn a_block_move_reads_the_count_before_the_ordering_word() {
7744 let mut f = Fixture::new();
7745 f.run(&[b"RPUSH", b"s", b"a", b"b"]);
7746 f.run(&[b"SET", b"str", b"v"]);
7747 let count = "-ERR count should be greater than 0\r\n";
7748 assert_eq!(
7749 f.run(&[
7750 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"NOPE"
7751 ]),
7752 count
7753 );
7754 assert_eq!(
7755 f.run(&[
7756 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"0", b"BULK"
7757 ]),
7758 count
7759 );
7760 assert_eq!(
7761 f.run(&[
7762 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"NOPE"
7763 ]),
7764 "-ERR syntax error\r\n"
7765 );
7766 assert_eq!(
7767 f.run(&[
7768 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"NOPE", b"abc", b"BULK"
7769 ]),
7770 "-ERR syntax error\r\n"
7771 );
7772 assert_eq!(
7775 f.run(&[
7776 b"LMOVEM", b"str", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"BULK"
7777 ]),
7778 count
7779 );
7780 assert_eq!(
7781 f.run(&[
7782 b"LMOVEM", b"s", b"str", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
7783 ]),
7784 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
7785 );
7786 assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["a", "b"]));
7787 }
7788
7789 #[test]
7790 fn lmpop_answers_from_the_first_key_that_has_anything() {
7791 let mut f = Fixture::new();
7792 f.run(&[b"RPUSH", b"b", b"1", b"2", b"3"]);
7793 assert_eq!(
7796 f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT", b"COUNT", b"2"]),
7797 "*2\r\n$1\r\nb\r\n*2\r\n$1\r\n1\r\n$1\r\n2\r\n"
7798 );
7799 assert_eq!(
7800 f.run(&[b"LMPOP", b"2", b"a", b"b", b"RIGHT"]),
7801 "*2\r\n$1\r\nb\r\n*1\r\n$1\r\n3\r\n"
7802 );
7803 assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
7804 assert_eq!(f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT"]), "*-1\r\n");
7807 }
7808
7809 #[test]
7810 fn lmpop_has_its_own_words_for_a_count_and_for_a_key_count() {
7811 let mut f = Fixture::new();
7812 f.run(&[b"RPUSH", b"k", b"a"]);
7813 assert_eq!(
7814 f.run(&[b"LMPOP", b"0", b"k", b"LEFT"]),
7815 "-ERR numkeys should be greater than 0\r\n"
7816 );
7817 assert_eq!(
7818 f.run(&[b"LMPOP", b"-1", b"k", b"LEFT"]),
7819 "-ERR numkeys should be greater than 0\r\n"
7820 );
7821 assert_eq!(
7822 f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"0"]),
7823 "-ERR count should be greater than 0\r\n"
7824 );
7825 assert_eq!(
7828 f.run(&[b"LMPOP", b"3", b"k", b"LEFT"]),
7829 "-ERR syntax error\r\n"
7830 );
7831 assert_eq!(
7832 f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"1", b"x"]),
7833 "-ERR syntax error\r\n"
7834 );
7835 assert_eq!(
7836 f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"FOO", b"1"]),
7837 "-ERR syntax error\r\n"
7838 );
7839 assert_eq!(
7840 f.run(&[b"LMPOP", b"1", b"k", b"SIDEWAYS"]),
7841 "-ERR syntax error\r\n"
7842 );
7843 assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
7844 }
7845
7846 #[test]
7847 fn every_list_command_says_wrongtype_and_writes_nothing() {
7848 let mut f = Fixture::new();
7849 f.run(&[b"SET", b"str", b"v"]);
7850 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
7851 for cmd in [
7852 &[b"LPUSH".as_slice(), b"str", b"a"][..],
7853 &[b"RPUSH", b"str", b"a"],
7854 &[b"LPUSHX", b"str", b"a"],
7855 &[b"RPUSHX", b"str", b"a"],
7856 &[b"LPOP", b"str"],
7857 &[b"LPOP", b"str", b"2"],
7858 &[b"RPOP", b"str"],
7859 &[b"LLEN", b"str"],
7860 &[b"LRANGE", b"str", b"0", b"-1"],
7861 &[b"LINDEX", b"str", b"0"],
7862 &[b"LSET", b"str", b"0", b"a"],
7863 &[b"LINSERT", b"str", b"BEFORE", b"a", b"b"],
7864 &[b"LREM", b"str", b"0", b"a"],
7865 &[b"LTRIM", b"str", b"0", b"-1"],
7866 &[b"LPOS", b"str", b"a"],
7867 &[b"LPOS", b"str", b"a", b"COUNT", b"0"],
7868 &[b"RPOPLPUSH", b"str", b"d"],
7869 &[b"LMOVE", b"str", b"d", b"LEFT", b"LEFT"],
7870 &[b"LMPOP", b"1", b"str", b"LEFT"],
7871 ] {
7872 assert_eq!(f.run(cmd), wrong, "{:?}", String::from_utf8_lossy(cmd[0]));
7873 }
7874 assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
7875 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
7876 }
7877
7878 #[test]
7882 fn a_timeout_has_three_ways_of_being_wrong() {
7883 let mut f = Fixture::new();
7884 let not_float = "-ERR timeout is not a float or out of range\r\n";
7885 let range = "-ERR timeout is out of range\r\n";
7886 for (bad, want) in [
7887 (&[b"BLPOP".as_slice(), b"k", b"abc"][..], not_float),
7888 (&[b"BLPOP", b"k", b"nan"], not_float),
7889 (&[b"BLPOP", b"k", b""], not_float),
7890 (&[b"BLPOP", b"k", b" 1"], not_float),
7893 (&[b"BLPOP", b"k", b"1 "], not_float),
7894 (&[b"BLPOP", b"k", b"-1"], "-ERR timeout is negative\r\n"),
7895 (&[b"BLPOP", b"k", b"-0.1"], "-ERR timeout is negative\r\n"),
7896 (&[b"BLPOP", b"k", b"1e400"], range),
7899 (&[b"BLPOP", b"k", b"inf"], range),
7900 (&[b"BLPOP", b"k", b"9999999999999999"], range),
7901 (&[b"BRPOP", b"k", b"abc"], not_float),
7902 (
7903 &[b"BLMOVE", b"a", b"b", b"LEFT", b"RIGHT", b"abc"],
7904 not_float,
7905 ),
7906 (
7907 &[b"BRPOPLPUSH", b"a", b"b", b"-1"],
7908 "-ERR timeout is negative\r\n",
7909 ),
7910 (&[b"BLMPOP", b"abc", b"1", b"k", b"LEFT"], not_float),
7911 ] {
7912 assert_eq!(f.run(bad), want, "for {bad:?}");
7913 }
7914 }
7915
7916 #[test]
7919 fn a_zero_timeout_waits_and_the_smallest_positive_one_does_not() {
7920 let mut f = Fixture::new();
7921 for timeout in [b"0".as_slice(), b"0.0", b"-0.0"] {
7922 let (flow, out) = f.flow(&[b"BLPOP", b"k", timeout]);
7923 assert_eq!(flow, Flow::Block, "for {timeout:?}");
7924 assert!(out.is_empty(), "for {timeout:?}");
7925 }
7926 let (flow, out) = f.flow(&[b"BLPOP", b"k", b"0.0000001"]);
7930 assert_eq!(flow, Flow::Block);
7931 assert!(out.is_empty());
7932 }
7933
7934 #[test]
7935 fn a_blocking_command_that_can_be_answered_answers_like_the_one_it_wraps() {
7936 let mut f = Fixture::new();
7937 f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
7938
7939 assert_eq!(
7942 f.flow(&[b"BLPOP", b"nope", b"L", b"0"]),
7943 (Flow::Continue, "*2\r\n$1\r\nL\r\n$1\r\na\r\n".to_owned())
7944 );
7945 assert_eq!(
7946 f.run(&[b"BRPOP", b"L", b"0"]),
7947 "*2\r\n$1\r\nL\r\n$1\r\ne\r\n"
7948 );
7949 assert_eq!(
7950 f.run(&[
7951 b"BLMPOP", b"0", b"2", b"nope", b"L", b"LEFT", b"COUNT", b"2"
7952 ]),
7953 "*2\r\n$1\r\nL\r\n*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
7954 );
7955 assert_eq!(
7956 f.run(&[b"BLMOVE", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
7957 "$1\r\nd\r\n"
7958 );
7959 assert_eq!(
7960 f.run(&[b"EXISTS", b"L"]),
7961 ":0\r\n",
7962 "and the key went with it"
7963 );
7964 assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nd\r\n");
7965 f.run(&[b"RPUSH", b"D", b"x"]);
7968 assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"D", b"0"]), "$1\r\nx\r\n");
7969 assert_eq!(
7970 f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
7971 "*2\r\n$1\r\nx\r\n$1\r\nd\r\n"
7972 );
7973 }
7974
7975 #[test]
7976 fn blmpop_reads_its_count_and_its_key_count_the_way_lmpop_does() {
7977 let mut f = Fixture::new();
7978 f.run(&[b"RPUSH", b"k", b"a"]);
7979 for (bad, want) in [
7980 (
7981 &[b"BLMPOP".as_slice(), b"0", b"0", b"k", b"LEFT"][..],
7982 "-ERR numkeys should be greater than 0\r\n",
7983 ),
7984 (
7985 &[b"BLMPOP", b"0", b"-1", b"k", b"LEFT"],
7986 "-ERR numkeys should be greater than 0\r\n",
7987 ),
7988 (
7991 &[b"BLMPOP", b"0", b"2", b"k", b"LEFT"],
7992 "-ERR syntax error\r\n",
7993 ),
7994 (
7995 &[b"BLMPOP", b"0", b"1", b"k", b"SIDEWAYS"],
7996 "-ERR syntax error\r\n",
7997 ),
7998 (
7999 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT"],
8000 "-ERR syntax error\r\n",
8001 ),
8002 (
8003 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"2", b"x"],
8004 "-ERR syntax error\r\n",
8005 ),
8006 (
8009 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"0"],
8010 "-ERR count should be greater than 0\r\n",
8011 ),
8012 (
8013 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"abc"],
8014 "-ERR count should be greater than 0\r\n",
8015 ),
8016 ] {
8017 assert_eq!(f.run(bad), want, "for {bad:?}");
8018 }
8019 assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n", "and none of them popped");
8020 }
8021
8022 #[test]
8023 fn a_blocking_move_reads_its_directions_before_its_timeout() {
8024 let mut f = Fixture::new();
8025 assert_eq!(
8028 f.run(&[b"BLMOVE", b"a", b"b", b"UP", b"DOWN", b"abc"]),
8029 "-ERR syntax error\r\n"
8030 );
8031 assert_eq!(
8032 f.run(&[b"BLMOVE", b"a", b"b", b"LEFT", b"DOWN", b"0.05"]),
8033 "-ERR syntax error\r\n"
8034 );
8035 }
8036
8037 #[test]
8041 fn a_blocking_block_move_that_can_be_answered_answers_like_lmovem() {
8042 let mut f = Fixture::new();
8043 f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
8044 assert_eq!(
8045 f.flow(&[b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
8046 (Flow::Continue, "*1\r\n$1\r\na\r\n".to_owned())
8047 );
8048 assert_eq!(
8049 f.run(&[
8050 b"BLMOVEM", b"L", b"D", b"RIGHT", b"RIGHT", b"0", b"COUNT", b"2", b"OBO"
8051 ]),
8052 bulks(&["e", "d"])
8053 );
8054 assert_eq!(
8055 f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
8056 bulks(&["a", "e", "d"])
8057 );
8058 assert_eq!(
8060 f.run(&[
8061 b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"2", b"BULK"
8062 ]),
8063 bulks(&["b", "c"])
8064 );
8065 assert_eq!(f.run(&[b"EXISTS", b"L"]), ":0\r\n", "and the key went");
8066 }
8067
8068 #[test]
8072 fn a_blocking_block_move_waits_for_the_whole_block_only_under_exactly() {
8073 let mut f = Fixture::new();
8074 f.run(&[b"RPUSH", b"L", b"a", b"b"]);
8075 assert_eq!(
8077 f.flow(&[
8078 b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"COUNT", b"3", b"BULK"
8079 ]),
8080 (Flow::Continue, bulks(&["a", "b"]))
8081 );
8082
8083 f.run(&[b"RPUSH", b"L", b"a", b"b"]);
8084 assert_eq!(
8087 f.flow(&[
8088 b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"3", b"BULK"
8089 ])
8090 .0,
8091 Flow::Block
8092 );
8093 assert_eq!(f.run(&[b"LRANGE", b"L", b"0", b"-1"]), bulks(&["a", "b"]));
8094 }
8095
8096 #[test]
8097 fn a_blocking_block_move_reads_its_directions_then_its_timeout_then_its_count() {
8098 let mut f = Fixture::new();
8099 let syntax = "-ERR syntax error\r\n";
8100 assert_eq!(
8102 f.run(&[
8103 b"BLMOVEM", b"a", b"b", b"UP", b"DOWN", b"abc", b"NOPE", b"x", b"y"
8104 ]),
8105 syntax
8106 );
8107 assert_eq!(
8109 f.run(&[
8110 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"abc", b"COUNT", b"abc", b"BULK"
8111 ]),
8112 "-ERR timeout is not a float or out of range\r\n"
8113 );
8114 assert_eq!(
8115 f.run(&[
8116 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"-1", b"COUNT", b"1", b"BULK"
8117 ]),
8118 "-ERR timeout is negative\r\n"
8119 );
8120 assert_eq!(
8122 f.run(&[
8123 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"abc", b"NOPE"
8124 ]),
8125 "-ERR count should be greater than 0\r\n"
8126 );
8127 assert_eq!(
8128 f.run(&[
8129 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"1", b"NOPE"
8130 ]),
8131 syntax
8132 );
8133 assert_eq!(
8136 f.run(&[b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT"]),
8137 syntax
8138 );
8139 assert_eq!(
8140 f.run(&[
8141 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"2"
8142 ]),
8143 syntax
8144 );
8145 }
8146
8147 #[test]
8150 fn a_blocking_command_errors_on_a_wrong_type_rather_than_waiting_on_it() {
8151 let mut f = Fixture::new();
8152 f.run(&[b"SET", b"S", b"v"]);
8153 f.run(&[b"RPUSH", b"D", b"x"]);
8154 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8155
8156 assert_eq!(f.run(&[b"BLPOP", b"S", b"0"]), wrong);
8157 assert_eq!(f.run(&[b"BLPOP", b"E", b"S", b"0"]), wrong);
8160 assert_eq!(f.run(&[b"BRPOP", b"S", b"0"]), wrong);
8161 assert_eq!(f.run(&[b"BLMPOP", b"0", b"1", b"S", b"LEFT"]), wrong);
8162 assert_eq!(f.run(&[b"BRPOPLPUSH", b"S", b"D", b"0"]), wrong);
8163 assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"S", b"0"]), wrong);
8166 assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nx\r\n");
8167 assert_eq!(
8168 f.run(&[b"BLMOVEM", b"S", b"D", b"LEFT", b"RIGHT", b"0"]),
8169 wrong
8170 );
8171 assert_eq!(
8172 f.run(&[b"BLMOVEM", b"D", b"S", b"LEFT", b"RIGHT", b"0"]),
8173 wrong
8174 );
8175
8176 assert_eq!(
8180 f.flow(&[b"BLMOVE", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
8181 .0,
8182 Flow::Block
8183 );
8184 assert_eq!(
8188 f.flow(&[b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
8189 .0,
8190 Flow::Block
8191 );
8192 f.run(&[b"RPUSH", b"E", b"1", b"2"]);
8193 assert_eq!(
8194 f.flow(&[
8195 b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1", b"EXACTLY", b"3", b"BULK"
8196 ])
8197 .0,
8198 Flow::Block
8199 );
8200 }
8201
8202 #[test]
8206 fn churning_lists_does_not_grow_the_server() {
8207 let mut f = Fixture::new();
8208 let vals: Vec<Vec<u8>> = (0..200).map(|i| format!("v{i}").into_bytes()).collect();
8209 let args: Vec<&[u8]> = [&b"RPUSH"[..], &b"k"[..]]
8210 .into_iter()
8211 .chain(vals.iter().map(Vec::as_slice))
8212 .collect();
8213
8214 f.run(&args);
8215 f.run(&[b"DEL", b"k"]);
8216 f.server.compact_step();
8217 let after_first = f.server.memory_bytes();
8218
8219 for _ in 0..200 {
8220 f.run(&args);
8221 f.run(&[b"LTRIM", b"k", b"1", b"0"]);
8222 f.server.compact_step();
8223 }
8224 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
8225 assert!(
8226 f.server.memory_bytes() <= after_first * 2,
8227 "held {} after two hundred passes against {after_first} after one",
8228 f.server.memory_bytes()
8229 );
8230 }
8231
8232 #[test]
8235 fn a_sorted_set_takes_scores_and_gives_them_back() {
8236 let mut f = Fixture::new();
8237 assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]), ":2\r\n");
8238 assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]), ":1\r\n");
8239 assert_eq!(f.run(&[b"ZCARD", b"z"]), ":3\r\n");
8240 assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$1\r\n2\r\n");
8241 assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "$-1\r\n");
8242 assert_eq!(f.run(&[b"ZSCORE", b"nokey", b"b"]), "$-1\r\n");
8243 assert_eq!(
8244 f.run(&[b"ZMSCORE", b"z", b"a", b"nope", b"c"]),
8245 "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n"
8246 );
8247 assert_eq!(f.run(&[b"ZREM", b"z", b"a", b"nope"]), ":1\r\n");
8248 assert_eq!(f.run(&[b"ZCARD", b"z"]), ":2\r\n");
8249 assert_eq!(f.run(&[b"ZREM", b"z", b"b", b"c"]), ":2\r\n");
8251 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
8252 }
8253
8254 #[test]
8255 fn a_score_is_a_double_on_resp3_and_digits_on_resp2() {
8256 let mut f = Fixture::new();
8257 f.run(&[b"ZADD", b"z", b"1.5", b"a", b"inf", b"b", b"-inf", b"c"]);
8258 assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$3\r\n1.5\r\n");
8259 assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$3\r\ninf\r\n");
8260 assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), "$4\r\n-inf\r\n");
8261
8262 f.out = Out::new(Proto::Resp3);
8263 assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), ",1.5\r\n");
8264 assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), ",inf\r\n");
8265 assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), ",-inf\r\n");
8266 assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "_\r\n");
8267 }
8268
8269 #[test]
8270 fn the_zadd_options_gate_what_gets_written() {
8271 let mut f = Fixture::new();
8272 f.run(&[b"ZADD", b"z", b"5", b"a"]);
8273 assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"9", b"a"]), ":0\r\n");
8275 assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$1\r\n5\r\n");
8276 assert_eq!(f.run(&[b"ZADD", b"z", b"XX", b"9", b"new"]), ":0\r\n");
8277 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":1\r\n");
8278 assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"3", b"a"]), ":0\r\n");
8280 assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"7", b"a"]), ":1\r\n");
8281 assert_eq!(f.run(&[b"ZADD", b"z", b"LT", b"CH", b"9", b"a"]), ":0\r\n");
8282 assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"1", b"b"]), ":1\r\n");
8284 assert_eq!(
8285 f.run(&[b"ZADD", b"z", b"CH", b"2", b"a", b"2", b"c"]),
8286 ":2\r\n"
8287 );
8288 }
8289
8290 #[test]
8291 fn zadd_incr_answers_a_score_or_nothing_at_all() {
8292 let mut f = Fixture::new();
8293 assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"5", b"m"]), "$1\r\n5\r\n");
8294 assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"2", b"m"]), "$1\r\n7\r\n");
8295 assert_eq!(
8298 f.run(&[b"ZADD", b"z", b"NX", b"INCR", b"2", b"m"]),
8299 "$-1\r\n"
8300 );
8301 assert_eq!(
8302 f.run(&[b"ZADD", b"z", b"XX", b"INCR", b"2", b"gone"]),
8303 "$-1\r\n"
8304 );
8305 assert_eq!(
8306 f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"-1", b"m"]),
8307 "$-1\r\n"
8308 );
8309 assert_eq!(
8310 f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"1", b"m"]),
8311 "$1\r\n8\r\n"
8312 );
8313 assert_eq!(f.run(&[b"ZINCRBY", b"z", b"2", b"m"]), "$2\r\n10\r\n");
8314 assert_eq!(f.run(&[b"ZINCRBY", b"z", b"1", b"fresh"]), "$1\r\n1\r\n");
8315 }
8316
8317 #[test]
8318 fn the_two_infinities_will_not_be_added_together() {
8319 let mut f = Fixture::new();
8320 f.run(&[b"ZADD", b"z", b"inf", b"m"]);
8321 let nan = "-ERR resulting score is not a number (NaN)\r\n";
8322 assert_eq!(f.run(&[b"ZINCRBY", b"z", b"-inf", b"m"]), nan);
8323 assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"-inf", b"m"]), nan);
8324 assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\ninf\r\n");
8325 assert_eq!(f.run(&[b"ZINCRBY", b"gone", b"1", b"m"]), "$1\r\n1\r\n");
8327 }
8328
8329 #[test]
8330 fn zadd_says_its_mistakes_the_way_redis_says_them() {
8331 let mut f = Fixture::new();
8332 assert_eq!(
8335 f.run(&[b"ZADD", b"z", b"NX", b"XX"]),
8336 "-ERR syntax error\r\n"
8337 );
8338 assert_eq!(
8339 f.run(&[b"ZADD", b"z", b"NX", b"XX", b"1", b"a"]),
8340 "-ERR XX and NX options at the same time are not compatible\r\n"
8341 );
8342 let gtlt = "-ERR GT, LT, and/or NX options at the same time are not compatible\r\n";
8343 assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"GT", b"1", b"a"]), gtlt);
8344 assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"LT", b"1", b"a"]), gtlt);
8345 assert_eq!(
8346 f.run(&[b"ZADD", b"z", b"INCR", b"1", b"a", b"2", b"b"]),
8347 "-ERR INCR option supports a single increment-element pair\r\n"
8348 );
8349 assert_eq!(
8351 f.run(&[b"ZADD", b"z", b"1", b"a", b"2"]),
8352 "-ERR syntax error\r\n"
8353 );
8354 assert_eq!(
8356 f.run(&[b"ZADD", b"z", b"1", b"a", b"nonsense", b"b"]),
8357 "-ERR value is not a valid float\r\n"
8358 );
8359 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
8360 }
8361
8362 #[test]
8363 fn a_rank_says_where_a_member_sits_from_either_end() {
8364 let mut f = Fixture::new();
8365 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8366 assert_eq!(f.run(&[b"ZRANK", b"z", b"a"]), ":0\r\n");
8367 assert_eq!(f.run(&[b"ZRANK", b"z", b"c"]), ":2\r\n");
8368 assert_eq!(f.run(&[b"ZREVRANK", b"z", b"c"]), ":0\r\n");
8369 assert_eq!(f.run(&[b"ZREVRANK", b"z", b"a"]), ":2\r\n");
8370 assert_eq!(
8372 f.run(&[b"ZRANK", b"z", b"b", b"WITHSCORE"]),
8373 "*2\r\n:1\r\n$1\r\n2\r\n"
8374 );
8375 assert_eq!(f.run(&[b"ZRANK", b"z", b"nope"]), "$-1\r\n");
8376 assert_eq!(f.run(&[b"ZRANK", b"z", b"nope", b"WITHSCORE"]), "*-1\r\n");
8377 assert_eq!(f.run(&[b"ZRANK", b"nokey", b"a", b"WITHSCORE"]), "*-1\r\n");
8378 assert_eq!(
8381 f.run(&[b"ZRANK", b"z", b"b", b"bogus"]),
8382 "-ERR syntax error\r\n"
8383 );
8384 assert_eq!(
8385 f.run(&[b"ZREVRANK", b"z", b"b", b"WITHSCORE", b"more"]),
8386 "-ERR wrong number of arguments for 'zrevrank' command\r\n"
8387 );
8388 }
8389
8390 #[test]
8391 fn the_two_counts_read_their_two_kinds_of_bound() {
8392 let mut f = Fixture::new();
8393 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8394 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"-inf", b"+inf"]), ":3\r\n");
8395 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"2", b"3"]), ":2\r\n");
8396 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"3"]), ":2\r\n");
8397 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"(3"]), ":1\r\n");
8398 assert_eq!(f.run(&[b"ZCOUNT", b"nokey", b"-inf", b"+inf"]), ":0\r\n");
8399 assert_eq!(
8400 f.run(&[b"ZCOUNT", b"z", b"bogus", b"3"]),
8401 "-ERR min or max is not a float\r\n"
8402 );
8403
8404 f.run(&[b"ZADD", b"l", b"0", b"a", b"0", b"b", b"0", b"c"]);
8405 assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"-", b"+"]), ":3\r\n");
8406 assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"[a", b"(c"]), ":2\r\n");
8407 assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"(a", b"+"]), ":2\r\n");
8408 assert_eq!(
8411 f.run(&[b"ZLEXCOUNT", b"l", b"a", b"c"]),
8412 "-ERR min or max not valid string range item\r\n"
8413 );
8414 }
8415
8416 #[test]
8422 fn one_range_command_selects_by_rank_or_score_or_name() {
8423 let mut f = Fixture::new();
8424 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8425 assert_eq!(
8426 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
8427 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
8428 );
8429 assert_eq!(
8430 f.run(&[b"ZRANGE", b"z", b"-2", b"-1"]),
8431 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
8432 );
8433 assert_eq!(f.run(&[b"ZRANGE", b"z", b"5", b"9"]), "*0\r\n");
8434 assert_eq!(f.run(&[b"ZRANGE", b"nokey", b"0", b"-1"]), "*0\r\n");
8435 assert_eq!(
8438 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"REV"]),
8439 "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
8440 );
8441 assert_eq!(
8442 f.run(&[b"ZRANGE", b"z", b"(1", b"+inf", b"BYSCORE"]),
8443 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
8444 );
8445 assert_eq!(
8448 f.run(&[b"ZRANGE", b"z", b"+inf", b"(1", b"BYSCORE", b"REV"]),
8449 "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
8450 );
8451 assert_eq!(
8452 f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX"]),
8453 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
8454 );
8455 assert_eq!(
8456 f.run(&[b"ZRANGE", b"z", b"+", b"-", b"BYLEX", b"REV"]),
8457 "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
8458 );
8459 }
8460
8461 #[test]
8464 fn the_older_range_spellings_name_their_high_end_first() {
8465 let mut f = Fixture::new();
8466 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8467 assert_eq!(
8468 f.run(&[b"ZREVRANGE", b"z", b"0", b"-1"]),
8469 "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
8470 );
8471 assert_eq!(
8472 f.run(&[b"ZREVRANGE", b"z", b"0", b"0", b"WITHSCORES"]),
8473 "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
8474 );
8475 assert_eq!(
8476 f.run(&[b"ZRANGEBYSCORE", b"z", b"(1", b"3"]),
8477 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
8478 );
8479 assert_eq!(
8480 f.run(&[b"ZREVRANGEBYSCORE", b"z", b"3", b"(1"]),
8481 "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
8482 );
8483 assert_eq!(f.run(&[b"ZREVRANGEBYSCORE", b"z", b"(1", b"3"]), "*0\r\n");
8487 assert_eq!(
8488 f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"(c"]),
8489 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
8490 );
8491 assert_eq!(
8492 f.run(&[b"ZREVRANGEBYLEX", b"z", b"(c", b"[a"]),
8493 "*2\r\n$1\r\nb\r\n$1\r\na\r\n"
8494 );
8495 for cmd in [
8498 &[b"ZREVRANGE".as_slice(), b"z", b"0", b"-1", b"BYSCORE"][..],
8499 &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"REV"],
8500 &[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"BYLEX"],
8501 ] {
8502 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{:?}", cmd[0]);
8503 }
8504 }
8505
8506 #[test]
8509 fn limit_and_withscores_are_read_by_all_of_them_and_refused_afterwards() {
8510 let mut f = Fixture::new();
8511 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8512 assert_eq!(
8513 f.run(&[
8514 b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"1", b"1"
8515 ]),
8516 "*1\r\n$1\r\nb\r\n"
8517 );
8518 assert_eq!(
8520 f.run(&[
8521 b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"-1", b"2"
8522 ]),
8523 "*0\r\n"
8524 );
8525 assert_eq!(
8526 f.run(&[
8527 b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"0", b"-1"
8528 ]),
8529 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
8530 );
8531 let both = "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n";
8533 assert_eq!(
8534 f.run(&[
8535 b"ZRANGEBYSCORE",
8536 b"z",
8537 b"1",
8538 b"3",
8539 b"WITHSCORES",
8540 b"LIMIT",
8541 b"0",
8542 b"2"
8543 ]),
8544 both
8545 );
8546 assert_eq!(
8547 f.run(&[
8548 b"ZRANGEBYSCORE",
8549 b"z",
8550 b"1",
8551 b"3",
8552 b"LIMIT",
8553 b"0",
8554 b"2",
8555 b"WITHSCORES"
8556 ]),
8557 both
8558 );
8559 let needs_by = "-ERR syntax error, LIMIT is only supported in combination with either BYSCORE or BYLEX\r\n";
8562 assert_eq!(
8563 f.run(&[
8564 b"ZREVRANGE",
8565 b"z",
8566 b"0",
8567 b"-1",
8568 b"WITHSCORES",
8569 b"LIMIT",
8570 b"0",
8571 b"1"
8572 ]),
8573 needs_by
8574 );
8575 assert_eq!(
8576 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"LIMIT", b"0", b"1"]),
8577 needs_by
8578 );
8579 let not_bylex = "-ERR syntax error, WITHSCORES not supported in combination with BYLEX\r\n";
8580 assert_eq!(
8581 f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX", b"WITHSCORES"]),
8582 not_bylex
8583 );
8584 assert_eq!(
8585 f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"WITHSCORES"]),
8586 not_bylex
8587 );
8588 for cmd in [
8591 &[
8592 b"ZRANGE".as_slice(),
8593 b"z",
8594 b"0",
8595 b"-1",
8596 b"BYSCORE",
8597 b"BYLEX",
8598 ][..],
8599 &[b"ZRANGE", b"z", b"0", b"-1", b"junk"],
8600 &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"0"],
8601 ] {
8602 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
8603 }
8604 assert_eq!(
8605 f.run(&[b"ZRANGEBYSCORE", b"z", b"bad", b"3"]),
8606 "-ERR min or max is not a float\r\n"
8607 );
8608 assert_eq!(
8609 f.run(&[b"ZRANGEBYLEX", b"z", b"a", b"[c"]),
8610 "-ERR min or max not valid string range item\r\n"
8611 );
8612 assert_eq!(
8613 f.run(&[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"a", b"2"]),
8614 "-ERR value is not an integer or out of range\r\n"
8615 );
8616 }
8617
8618 #[test]
8621 fn withscores_nests_on_resp3_and_flattens_on_resp2() {
8622 let mut f = Fixture::new();
8623 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8624 assert_eq!(
8625 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
8626 "*6\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
8627 );
8628 f.out = Out::new(Proto::Resp3);
8629 assert_eq!(
8630 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
8631 "*3\r\n*2\r\n$1\r\na\r\n,1\r\n*2\r\n$1\r\nb\r\n,2\r\n*2\r\n$1\r\nc\r\n,3\r\n"
8632 );
8633 assert_eq!(
8634 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
8635 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
8636 );
8637 }
8638
8639 #[test]
8641 fn a_range_store_writes_the_window_into_another_key() {
8642 let mut f = Fixture::new();
8643 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8644 assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1"]), ":3\r\n");
8645 assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"5", b"9"]), ":0\r\n");
8648 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
8649 assert_eq!(
8650 f.run(&[b"ZRANGESTORE", b"d", b"z", b"(1", b"+inf", b"BYSCORE"]),
8651 ":2\r\n"
8652 );
8653 assert_eq!(
8654 f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
8655 "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
8656 );
8657 assert_eq!(f.run(&[b"ZRANGESTORE", b"z", b"z", b"1", b"2"]), ":2\r\n");
8660 assert_eq!(
8661 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
8662 "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
8663 );
8664 assert_eq!(
8667 f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1", b"WITHSCORES"]),
8668 "-ERR syntax error\r\n"
8669 );
8670 }
8671
8672 #[test]
8675 fn the_three_removals_share_their_window_with_the_reads() {
8676 let mut f = Fixture::new();
8677 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8678 assert_eq!(f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"0"]), ":1\r\n");
8679 assert_eq!(
8680 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
8681 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
8682 );
8683 assert_eq!(
8684 f.run(&[b"ZREMRANGEBYSCORE", b"z", b"(2", b"+inf"]),
8685 ":1\r\n"
8686 );
8687 assert_eq!(f.run(&[b"ZRANGE", b"z", b"0", b"-1"]), "*1\r\n$1\r\nb\r\n");
8688 assert_eq!(f.run(&[b"ZREMRANGEBYLEX", b"z", b"-", b"+"]), ":1\r\n");
8690 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
8691 assert_eq!(
8692 f.run(&[b"ZREMRANGEBYRANK", b"nokey", b"0", b"-1"]),
8693 ":0\r\n"
8694 );
8695 assert_eq!(
8696 f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"x"]),
8697 "-ERR value is not an integer or out of range\r\n"
8698 );
8699 }
8700
8701 #[test]
8703 fn the_three_algebra_commands_combine_scores_and_order_the_answer_once() {
8704 let mut f = Fixture::new();
8705 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8706 f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
8707 assert_eq!(
8708 f.run(&[b"ZUNION", b"2", b"z", b"y"]),
8709 "*4\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\nd\r\n"
8710 );
8711 assert_eq!(
8714 f.run(&[b"ZUNION", b"2", b"z", b"y", b"WITHSCORES"]),
8715 "*8\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nc\r\n$1\r\n3\r\n$1\r\nb\r\n$2\r\n12\r\n$1\r\nd\r\n$2\r\n20\r\n"
8716 );
8717 assert_eq!(
8718 f.run(&[
8719 b"ZUNION",
8720 b"2",
8721 b"z",
8722 b"y",
8723 b"WEIGHTS",
8724 b"2",
8725 b"3",
8726 b"WITHSCORES"
8727 ]),
8728 "*8\r\n$1\r\na\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n6\r\n$1\r\nb\r\n$2\r\n34\r\n$1\r\nd\r\n$2\r\n60\r\n"
8729 );
8730 assert_eq!(
8731 f.run(&[
8732 b"ZUNION",
8733 b"2",
8734 b"z",
8735 b"y",
8736 b"AGGREGATE",
8737 b"MIN",
8738 b"WITHSCORES"
8739 ]),
8740 "*8\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n$1\r\nd\r\n$2\r\n20\r\n"
8741 );
8742 assert_eq!(
8743 f.run(&[
8744 b"ZUNION",
8745 b"2",
8746 b"z",
8747 b"y",
8748 b"AGGREGATE",
8749 b"MAX",
8750 b"WITHSCORES"
8751 ]),
8752 "*8\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nc\r\n$1\r\n3\r\n$1\r\nb\r\n$2\r\n10\r\n$1\r\nd\r\n$2\r\n20\r\n"
8753 );
8754 assert_eq!(
8755 f.run(&[b"ZINTER", b"2", b"z", b"y", b"WITHSCORES"]),
8756 "*2\r\n$1\r\nb\r\n$2\r\n12\r\n"
8757 );
8758 assert_eq!(
8759 f.run(&[b"ZDIFF", b"2", b"z", b"y", b"WITHSCORES"]),
8760 "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nc\r\n$1\r\n3\r\n"
8761 );
8762 assert_eq!(f.run(&[b"ZUNION", b"1", b"nokey"]), "*0\r\n");
8763 f.run(&[b"SADD", b"p", b"a", b"d"]);
8766 assert_eq!(
8767 f.run(&[b"ZUNION", b"2", b"z", b"p", b"WITHSCORES"]),
8768 "*8\r\n$1\r\nd\r\n$1\r\n1\r\n$1\r\na\r\n$1\r\n2\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
8769 );
8770 for cmd in [
8773 &[
8774 b"ZDIFF".as_slice(),
8775 b"2",
8776 b"z",
8777 b"y",
8778 b"WEIGHTS",
8779 b"1",
8780 b"1",
8781 ][..],
8782 &[b"ZDIFF", b"2", b"z", b"y", b"AGGREGATE", b"MIN"],
8783 ] {
8784 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
8785 }
8786 }
8787
8788 #[test]
8790 fn the_algebra_counts_its_keys_and_says_so_when_the_count_is_wrong() {
8791 let mut f = Fixture::new();
8792 f.run(&[b"ZADD", b"z", b"1", b"a"]);
8793 f.run(&[b"ZADD", b"y", b"2", b"b"]);
8794 assert_eq!(
8796 f.run(&[b"ZUNION", b"0", b"z"]),
8797 "-ERR at least 1 input key is needed for 'zunion' command\r\n"
8798 );
8799 assert_eq!(
8800 f.run(&[b"ZUNION", b"-1", b"z"]),
8801 "-ERR at least 1 input key is needed for 'zunion' command\r\n"
8802 );
8803 assert_eq!(
8804 f.run(&[b"ZINTERCARD", b"0", b"z"]),
8805 "-ERR at least 1 input key is needed for 'zintercard' command\r\n"
8806 );
8807 assert_eq!(
8810 f.run(&[b"ZUNION", b"3", b"z", b"y"]),
8811 "-ERR syntax error\r\n"
8812 );
8813 assert_eq!(
8814 f.run(&[b"ZUNION", b"x", b"z"]),
8815 "-ERR value is not an integer or out of range\r\n"
8816 );
8817 assert_eq!(
8820 f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"1"]),
8821 "-ERR syntax error\r\n"
8822 );
8823 assert_eq!(
8824 f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"a", b"b"]),
8825 "-ERR weight value is not a float\r\n"
8826 );
8827 assert_eq!(
8828 f.run(&[b"ZUNION", b"2", b"z", b"y", b"AGGREGATE", b"NOPE"]),
8829 "-ERR syntax error\r\n"
8830 );
8831 }
8832
8833 #[test]
8835 fn the_algebra_stores_answer_a_count_and_delete_an_empty_destination() {
8836 let mut f = Fixture::new();
8837 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8838 f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
8839 assert_eq!(f.run(&[b"ZUNIONSTORE", b"d", b"2", b"z", b"y"]), ":4\r\n");
8840 assert_eq!(
8841 f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
8842 "*8\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nc\r\n$1\r\n3\r\n$1\r\nb\r\n$2\r\n12\r\n$1\r\nd\r\n$2\r\n20\r\n"
8843 );
8844 assert_eq!(f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"y"]), ":1\r\n");
8845 assert_eq!(f.run(&[b"ZDIFFSTORE", b"d", b"2", b"z", b"y"]), ":2\r\n");
8846 assert_eq!(
8849 f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"nokey"]),
8850 ":0\r\n"
8851 );
8852 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
8853 assert_eq!(f.run(&[b"ZUNIONSTORE", b"z", b"2", b"z", b"y"]), ":4\r\n");
8855 assert_eq!(f.run(&[b"ZCARD", b"z"]), ":4\r\n");
8856 for cmd in [
8857 &[
8858 b"ZUNIONSTORE".as_slice(),
8859 b"d",
8860 b"2",
8861 b"z",
8862 b"y",
8863 b"WITHSCORES",
8864 ][..],
8865 &[
8866 b"ZDIFFSTORE",
8867 b"d",
8868 b"2",
8869 b"z",
8870 b"y",
8871 b"WEIGHTS",
8872 b"1",
8873 b"1",
8874 ],
8875 ] {
8876 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
8877 }
8878 }
8879
8880 #[test]
8882 fn intercard_counts_and_stops_at_its_limit() {
8883 let mut f = Fixture::new();
8884 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8885 f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"c", b"30", b"d"]);
8886 assert_eq!(f.run(&[b"ZINTERCARD", b"2", b"z", b"y"]), ":2\r\n");
8887 assert_eq!(
8889 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"0"]),
8890 ":2\r\n"
8891 );
8892 assert_eq!(
8893 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"1"]),
8894 ":1\r\n"
8895 );
8896 let bad = "-ERR LIMIT can't be negative\r\n";
8899 assert_eq!(
8900 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"-1"]),
8901 bad
8902 );
8903 assert_eq!(
8904 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"x"]),
8905 bad
8906 );
8907 for cmd in [
8908 &[b"ZINTERCARD".as_slice(), b"3", b"z", b"y"][..],
8909 &[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT"],
8910 &[b"ZINTERCARD", b"2", b"z", b"y", b"junk", b"1"],
8911 ] {
8912 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
8913 }
8914 }
8915
8916 #[test]
8918 fn a_draw_answers_one_member_or_an_array_of_them() {
8919 let mut f = Fixture::new();
8920 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8921 assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "$-1\r\n");
8924 assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
8925 assert_eq!(f.run(&[b"ZRANDMEMBER", b"z", b"0"]), "*0\r\n");
8926 assert!(f.run(&[b"ZRANDMEMBER", b"z"]).starts_with("$1\r\n"));
8927 let all = f.run(&[b"ZRANDMEMBER", b"z", b"10"]);
8930 assert!(all.starts_with("*3\r\n"), "{all}");
8931 for m in ["a", "b", "c"] {
8932 assert!(all.contains(m), "{all}");
8933 }
8934 assert!(
8937 f.run(&[b"ZRANDMEMBER", b"z", b"-5"]).starts_with("*5\r\n"),
8938 "five draws with replacement"
8939 );
8940 assert!(
8941 f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"])
8942 .starts_with("*4\r\n"),
8943 "two pairs, flat on RESP2"
8944 );
8945 f.out = Out::new(Proto::Resp3);
8946 let got = f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"]);
8947 assert!(got.starts_with("*2\r\n*2\r\n"), "{got}");
8948 assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "_\r\n");
8949 f.out = Out::new(Proto::Resp2);
8950 assert_eq!(
8951 f.run(&[b"ZRANDMEMBER", b"z", b"2", b"junk"]),
8952 "-ERR syntax error\r\n"
8953 );
8954 assert_eq!(
8955 f.run(&[b"ZRANDMEMBER", b"z", b"x"]),
8956 "-ERR value is not an integer or out of range\r\n"
8957 );
8958 }
8959
8960 #[test]
8962 fn a_sorted_set_scan_answers_pairs_of_strings_on_both_protocols() {
8963 let mut f = Fixture::new();
8964 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8965 let all = "*2\r\n$1\r\n0\r\n*6\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n";
8966 assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
8967 assert_eq!(f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"10"]), all);
8968 assert_eq!(
8969 f.run(&[b"ZSCAN", b"z", b"0", b"MATCH", b"a*"]),
8970 "*2\r\n$1\r\n0\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
8971 );
8972 assert_eq!(
8973 f.run(&[b"ZSCAN", b"nokey", b"0"]),
8974 "*2\r\n$1\r\n0\r\n*0\r\n"
8975 );
8976 f.out = Out::new(Proto::Resp3);
8979 assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
8980 f.out = Out::new(Proto::Resp2);
8981 assert_eq!(
8982 f.run(&[b"ZSCAN", b"z", b"0", b"NOVALUES"]),
8983 "-ERR NOVALUES option can only be used in HSCAN\r\n"
8984 );
8985 assert_eq!(f.run(&[b"ZSCAN", b"z", b"-1"]), "-ERR invalid cursor\r\n");
8986 assert_eq!(
8987 f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"0"]),
8988 "-ERR syntax error\r\n"
8989 );
8990 }
8991
8992 #[test]
8994 fn a_sorted_set_pop_changes_shape_when_it_is_given_a_count() {
8995 let mut f = Fixture::new();
8996 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8997 assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
8999 assert_eq!(f.run(&[b"ZPOPMAX", b"z"]), "*2\r\n$1\r\nc\r\n$1\r\n3\r\n");
9000 f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
9001 assert_eq!(
9003 f.run(&[b"ZPOPMIN", b"z", b"2"]),
9004 "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
9005 );
9006 assert_eq!(f.run(&[b"ZPOPMIN", b"nokey"]), "*0\r\n");
9009 assert_eq!(f.run(&[b"ZPOPMIN", b"nokey", b"2"]), "*0\r\n");
9010 assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"0"]), "*0\r\n");
9011 assert_eq!(
9013 f.run(&[b"ZPOPMIN", b"z", b"9"]),
9014 "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
9015 );
9016 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
9017
9018 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]);
9019 f.out = Out::new(Proto::Resp3);
9020 assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n,1\r\n");
9021 assert_eq!(
9022 f.run(&[b"ZPOPMIN", b"z", b"1"]),
9023 "*1\r\n*2\r\n$1\r\nb\r\n,2\r\n"
9024 );
9025 f.out = Out::new(Proto::Resp2);
9026 let bad = "-ERR value is out of range, must be positive\r\n";
9029 assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"x"]), bad);
9030 assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"-1"]), bad);
9031 assert_eq!(
9032 f.run(&[b"ZPOPMIN", b"z", b"1", b"2"]),
9033 "-ERR syntax error\r\n"
9034 );
9035 }
9036
9037 #[test]
9039 fn a_multi_key_pop_names_the_key_that_answered_and_nests_its_pairs() {
9040 let mut f = Fixture::new();
9041 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
9042 assert_eq!(
9043 f.run(&[b"ZMPOP", b"2", b"nokey", b"z", b"MIN"]),
9044 "*2\r\n$1\r\nz\r\n*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
9045 );
9046 assert_eq!(
9049 f.run(&[b"ZMPOP", b"1", b"z", b"MAX", b"COUNT", b"2"]),
9050 "*2\r\n$1\r\nz\r\n*2\r\n*2\r\n$1\r\nc\r\n$1\r\n3\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
9051 );
9052 assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "*-1\r\n");
9054 f.out = Out::new(Proto::Resp3);
9055 assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "_\r\n");
9056 f.out = Out::new(Proto::Resp2);
9057 let numkeys = "-ERR numkeys should be greater than 0\r\n";
9058 for bad in [
9059 &[b"ZMPOP".as_slice(), b"0", b"z", b"MIN"][..],
9060 &[b"ZMPOP", b"-1", b"z", b"MIN"],
9061 &[b"ZMPOP", b"x", b"z", b"MIN"],
9062 ] {
9063 assert_eq!(f.run(bad), numkeys, "{:?}", bad[1]);
9064 }
9065 let count = "-ERR count should be greater than 0\r\n";
9066 for bad in [
9067 &[b"ZMPOP".as_slice(), b"1", b"z", b"MIN", b"COUNT", b"0"][..],
9068 &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"-1"],
9069 &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"x"],
9070 ] {
9071 assert_eq!(f.run(bad), count, "{:?}", bad[5]);
9072 }
9073 let syntax = "-ERR syntax error\r\n";
9074 for bad in [
9075 &[b"ZMPOP".as_slice(), b"2", b"z", b"MIN"][..],
9078 &[b"ZMPOP", b"1", b"z", b"SIDEWAYS"],
9079 &[b"ZMPOP", b"1", b"z", b"MIN", b"junk"],
9080 &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"1", b"junk"],
9081 ] {
9082 assert_eq!(f.run(bad), syntax, "{bad:?}");
9083 }
9084 }
9085
9086 #[test]
9089 fn the_sorted_set_pops_that_wait_answer_like_the_ones_they_wrap() {
9090 let mut f = Fixture::new();
9091 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
9092 assert_eq!(
9093 f.flow(&[b"BZPOPMIN", b"nokey", b"z", b"0"]),
9094 (
9095 Flow::Continue,
9096 "*3\r\n$1\r\nz\r\n$1\r\na\r\n$1\r\n1\r\n".to_owned()
9097 )
9098 );
9099 assert_eq!(
9100 f.run(&[b"BZPOPMAX", b"z", b"0"]),
9101 "*3\r\n$1\r\nz\r\n$1\r\nc\r\n$1\r\n3\r\n"
9102 );
9103 f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
9104 assert_eq!(
9105 f.run(&[
9106 b"BZMPOP", b"0", b"2", b"nokey", b"z", b"MIN", b"COUNT", b"2"
9107 ]),
9108 "*2\r\n$1\r\nz\r\n*2\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
9109 );
9110 f.out = Out::new(Proto::Resp3);
9111 assert_eq!(
9112 f.run(&[b"BZPOPMIN", b"z", b"0"]),
9113 "*3\r\n$1\r\nz\r\n$1\r\nc\r\n,3\r\n"
9114 );
9115 f.out = Out::new(Proto::Resp2);
9116 assert_eq!(
9118 f.flow(&[b"BZPOPMIN", b"z", b"0"]),
9119 (Flow::Block, String::new())
9120 );
9121 assert_eq!(
9122 f.flow(&[b"BZMPOP", b"0", b"1", b"z", b"MIN"]),
9123 (Flow::Block, String::new())
9124 );
9125 assert_eq!(
9128 f.run(&[b"BZMPOP", b"abc", b"0", b"z", b"MIN"]),
9129 "-ERR timeout is not a float or out of range\r\n"
9130 );
9131 assert_eq!(
9132 f.run(&[b"BZMPOP", b"0", b"0", b"z", b"MIN"]),
9133 "-ERR numkeys should be greater than 0\r\n"
9134 );
9135 assert_eq!(
9136 f.run(&[b"BZPOPMIN", b"z", b"-1"]),
9137 "-ERR timeout is negative\r\n"
9138 );
9139 }
9140
9141 #[test]
9145 fn a_parked_sorted_set_client_waits_for_a_member_and_not_for_a_key() {
9146 let mut f = Fixture::new();
9147 assert_eq!(f.flow(&[b"BZPOPMIN", b"z", b"0"]).0, Flow::Block);
9148 assert_eq!(f.server.parked(), 1);
9149 f.run(&[b"SET", b"z", b"v"]);
9152 let mut out = Out::new(Proto::Resp2);
9153 assert!(!f.server.serve_waiter(7, 0, &mut out));
9154 assert!(out.as_slice().is_empty());
9155 f.run(&[b"DEL", b"z"]);
9156 f.run(&[b"ZADD", b"z", b"5", b"m"]);
9157 assert!(f.server.serve_waiter(7, 0, &mut out));
9158 assert_eq!(
9159 core::str::from_utf8(out.as_slice()).expect("ascii"),
9160 "*3\r\n$1\r\nz\r\n$1\r\nm\r\n$1\r\n5\r\n"
9161 );
9162 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
9165 }
9166
9167 #[test]
9168 fn every_sorted_set_command_says_wrongtype_and_writes_nothing() {
9169 let mut f = Fixture::new();
9170 f.run(&[b"SET", b"s", b"v"]);
9171 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9172 for cmd in [
9173 &[b"ZADD".as_slice(), b"s", b"1", b"a"][..],
9174 &[b"ZINCRBY", b"s", b"1", b"a"],
9175 &[b"ZCARD", b"s"],
9176 &[b"ZSCORE", b"s", b"a"],
9177 &[b"ZMSCORE", b"s", b"a"],
9178 &[b"ZREM", b"s", b"a"],
9179 &[b"ZRANK", b"s", b"a"],
9180 &[b"ZREVRANK", b"s", b"a"],
9181 &[b"ZCOUNT", b"s", b"1", b"2"],
9182 &[b"ZLEXCOUNT", b"s", b"-", b"+"],
9183 &[b"ZRANGE", b"s", b"0", b"-1"],
9184 &[b"ZREVRANGE", b"s", b"0", b"-1"],
9185 &[b"ZRANGEBYSCORE", b"s", b"1", b"2"],
9186 &[b"ZREVRANGEBYSCORE", b"s", b"2", b"1"],
9187 &[b"ZRANGEBYLEX", b"s", b"-", b"+"],
9188 &[b"ZREVRANGEBYLEX", b"s", b"+", b"-"],
9189 &[b"ZRANGESTORE", b"d", b"s", b"0", b"-1"],
9190 &[b"ZREMRANGEBYRANK", b"s", b"0", b"-1"],
9191 &[b"ZREMRANGEBYSCORE", b"s", b"1", b"2"],
9192 &[b"ZREMRANGEBYLEX", b"s", b"-", b"+"],
9193 &[b"ZUNION", b"1", b"s"],
9194 &[b"ZINTER", b"1", b"s"],
9195 &[b"ZDIFF", b"1", b"s"],
9196 &[b"ZUNIONSTORE", b"d", b"1", b"s"],
9197 &[b"ZINTERSTORE", b"d", b"1", b"s"],
9198 &[b"ZDIFFSTORE", b"d", b"1", b"s"],
9199 &[b"ZINTERCARD", b"1", b"s"],
9200 &[b"ZRANDMEMBER", b"s"],
9201 &[b"ZSCAN", b"s", b"0"],
9202 &[b"ZPOPMIN", b"s"],
9203 &[b"ZPOPMAX", b"s", b"2"],
9204 &[b"ZMPOP", b"1", b"s", b"MIN"],
9205 &[b"BZPOPMIN", b"s", b"0"],
9206 &[b"BZPOPMAX", b"s", b"0"],
9207 &[b"BZMPOP", b"0", b"1", b"s", b"MIN"],
9208 ] {
9209 assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
9210 }
9211 assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nv\r\n");
9212 }
9213
9214 #[test]
9218 fn churning_sorted_sets_does_not_grow_the_server() {
9219 let mut f = Fixture::new();
9220 let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
9221 let scores: Vec<Vec<u8>> = (0..200).map(|i| format!("{i}").into_bytes()).collect();
9222 let mut args: Vec<&[u8]> = vec![b"ZADD", b"z"];
9223 for i in 0..200 {
9224 args.push(&scores[i]);
9225 args.push(&members[i]);
9226 }
9227
9228 f.run(&args);
9229 f.run(&[b"DEL", b"z"]);
9230 f.server.compact_step();
9231 let after_first = f.server.memory_bytes();
9232
9233 for _ in 0..200 {
9234 f.run(&args);
9235 f.run(&[b"DEL", b"z"]);
9236 f.server.compact_step();
9237 }
9238 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
9239 assert!(
9240 f.server.memory_bytes() <= after_first * 2,
9241 "held {} after two hundred passes against {after_first} after one",
9242 f.server.memory_bytes()
9243 );
9244 }
9245
9246 fn sicily(f: &mut Fixture) {
9254 f.run(&[
9255 b"GEOADD",
9256 b"Sicily",
9257 b"13.361389",
9258 b"38.115556",
9259 b"Palermo",
9260 b"15.087269",
9261 b"37.502669",
9262 b"Catania",
9263 ]);
9264 f.run(&[
9265 b"GEOADD",
9266 b"Sicily",
9267 b"13.583333",
9268 b"37.316667",
9269 b"Agrigento",
9270 ]);
9271 }
9272
9273 #[test]
9274 fn places_go_in_as_scores_and_come_back_as_positions() {
9275 let mut f = Fixture::new();
9276 assert_eq!(
9277 f.run(&[
9278 b"GEOADD",
9279 b"Sicily",
9280 b"13.361389",
9281 b"38.115556",
9282 b"Palermo",
9283 b"15.087269",
9284 b"37.502669",
9285 b"Catania"
9286 ]),
9287 ":2\r\n"
9288 );
9289 assert_eq!(f.run(&[b"TYPE", b"Sicily"]), "+zset\r\n");
9293 assert_eq!(
9294 f.run(&[b"ZSCORE", b"Sicily", b"Palermo"]),
9295 "$16\r\n3479099956230698\r\n"
9296 );
9297 assert_eq!(
9298 f.run(&[b"GEOPOS", b"Sicily", b"Palermo", b"NonExisting"]),
9299 "*2\r\n*2\r\n$18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n*-1\r\n"
9300 );
9301 assert_eq!(
9302 f.run(&[
9303 b"GEOHASH",
9304 b"Sicily",
9305 b"Palermo",
9306 b"Catania",
9307 b"NonExisting"
9308 ]),
9309 "*3\r\n$11\r\nsqc8b49rny0\r\n$11\r\nsqdtr74hyu0\r\n$-1\r\n"
9310 );
9311 assert_eq!(f.run(&[b"GEOPOS", b"nokey", b"a"]), "*1\r\n*-1\r\n");
9315 assert_eq!(f.run(&[b"GEOHASH", b"nokey", b"a"]), "*1\r\n$-1\r\n");
9316 }
9317
9318 #[test]
9319 fn a_distance_comes_back_with_four_places_in_whatever_unit_was_asked_for() {
9320 let mut f = Fixture::new();
9321 sicily(&mut f);
9322 assert_eq!(
9323 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania"]),
9324 "$11\r\n166274.1516\r\n"
9325 );
9326 assert_eq!(
9327 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"km"]),
9328 "$8\r\n166.2742\r\n"
9329 );
9330 assert_eq!(
9331 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"mi"]),
9332 "$8\r\n103.3182\r\n"
9333 );
9334 assert_eq!(
9338 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Foo"]),
9339 "$-1\r\n"
9340 );
9341 assert_eq!(f.run(&[b"GEODIST", b"nokey", b"a", b"b"]), "$-1\r\n");
9342 assert_eq!(
9343 f.run(&[b"GEODIST", b"nokey", b"a", b"b", b"parsecs"]),
9344 "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n"
9345 );
9346 assert_eq!(
9347 f.run(&[b"GEODIST", b"Sicily", b"a", b"b", b"km", b"extra"]),
9348 "-ERR syntax error\r\n"
9349 );
9350 }
9351
9352 #[test]
9353 fn a_search_finds_what_is_inside_it_nearest_first() {
9354 let mut f = Fixture::new();
9355 sicily(&mut f);
9356 let all = "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n";
9357 assert_eq!(
9358 f.run(&[
9359 b"GEOSEARCH",
9360 b"Sicily",
9361 b"FROMLONLAT",
9362 b"15",
9363 b"37",
9364 b"BYRADIUS",
9365 b"200",
9366 b"km",
9367 b"ASC"
9368 ]),
9369 all
9370 );
9371 assert_eq!(
9374 f.run(&[b"GEORADIUS", b"Sicily", b"15", b"37", b"200", b"km", b"ASC"]),
9375 all
9376 );
9377 assert_eq!(
9378 f.run(&[
9379 b"GEORADIUS_RO",
9380 b"Sicily",
9381 b"15",
9382 b"37",
9383 b"200",
9384 b"km",
9385 b"ASC"
9386 ]),
9387 all
9388 );
9389 assert_eq!(
9392 f.run(&[
9393 b"GEORADIUS",
9394 b"Sicily",
9395 b"15",
9396 b"37",
9397 b"200",
9398 b"km",
9399 b"DESC",
9400 b"COUNT",
9401 b"1"
9402 ]),
9403 "*1\r\n$7\r\nPalermo\r\n"
9404 );
9405 assert_eq!(
9406 f.run(&[
9407 b"GEORADIUS",
9408 b"Sicily",
9409 b"15",
9410 b"37",
9411 b"200",
9412 b"km",
9413 b"COUNT",
9414 b"1"
9415 ]),
9416 "*1\r\n$7\r\nCatania\r\n"
9417 );
9418 let empty = "*0\r\n";
9421 assert_eq!(
9422 f.run(&[
9423 b"GEOSEARCH",
9424 b"Sicily",
9425 b"FROMLONLAT",
9426 b"15",
9427 b"37",
9428 b"BYRADIUS",
9429 b"1",
9430 b"km"
9431 ]),
9432 empty
9433 );
9434 assert_eq!(
9435 f.run(&[
9436 b"GEOSEARCH",
9437 b"nokey",
9438 b"FROMLONLAT",
9439 b"15",
9440 b"37",
9441 b"BYRADIUS",
9442 b"1",
9443 b"km"
9444 ]),
9445 empty
9446 );
9447 assert_eq!(
9448 f.run(&[b"GEORADIUSBYMEMBER", b"nokey", b"m", b"1", b"km"]),
9449 empty
9450 );
9451 }
9452
9453 #[test]
9454 fn a_search_centred_on_a_member_starts_from_where_that_member_is() {
9455 let mut f = Fixture::new();
9456 sicily(&mut f);
9457 assert_eq!(
9458 f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Agrigento", b"100", b"km"]),
9459 "*2\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
9460 );
9461 let with_dist = "*2\r\n*2\r\n$9\r\nAgrigento\r\n$6\r\n0.0000\r\n*2\r\n$7\r\nPalermo\r\n$7\r\n90.9778\r\n";
9464 assert_eq!(
9465 f.run(&[
9466 b"GEORADIUSBYMEMBER_RO",
9467 b"Sicily",
9468 b"Agrigento",
9469 b"100",
9470 b"km",
9471 b"WITHDIST"
9472 ]),
9473 with_dist
9474 );
9475 assert_eq!(
9476 f.run(&[
9477 b"GEOSEARCH",
9478 b"Sicily",
9479 b"FROMMEMBER",
9480 b"Agrigento",
9481 b"BYRADIUS",
9482 b"100",
9483 b"km",
9484 b"ASC",
9485 b"WITHDIST"
9486 ]),
9487 with_dist
9488 );
9489 assert_eq!(
9490 f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Nowhere", b"100", b"km"]),
9491 "-ERR could not decode requested zset member\r\n"
9492 );
9493 }
9494
9495 #[test]
9496 fn a_box_search_reports_the_distance_the_hash_and_the_coordinates() {
9497 let mut f = Fixture::new();
9498 sicily(&mut f);
9499 assert_eq!(
9503 f.run(&[
9504 b"GEOSEARCH",
9505 b"Sicily",
9506 b"FROMLONLAT",
9507 b"15",
9508 b"37",
9509 b"BYBOX",
9510 b"400",
9511 b"400",
9512 b"km",
9513 b"ASC",
9514 b"WITHCOORD",
9515 b"WITHDIST",
9516 b"WITHHASH"
9517 ]),
9518 "*3\r\n*4\r\n$7\r\nCatania\r\n$7\r\n56.4413\r\n:3479447370796909\r\n*2\r\n\
9519 $18\r\n15.087267458438873\r\n$17\r\n37.50266842333162\r\n\
9520 *4\r\n$9\r\nAgrigento\r\n$8\r\n130.4235\r\n:3479030013248308\r\n*2\r\n\
9521 $18\r\n13.583331406116486\r\n$18\r\n37.316668049938166\r\n\
9522 *4\r\n$7\r\nPalermo\r\n$8\r\n190.4424\r\n:3479099956230698\r\n*2\r\n\
9523 $18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n"
9524 );
9525 }
9526
9527 #[test]
9528 fn a_store_writes_the_hashes_and_a_storedist_writes_the_distances() {
9529 let mut f = Fixture::new();
9530 sicily(&mut f);
9531 let hashes = "*6\r\n$9\r\nAgrigento\r\n$16\r\n3479030013248308\r\n\
9532 $7\r\nPalermo\r\n$16\r\n3479099956230698\r\n\
9533 $7\r\nCatania\r\n$16\r\n3479447370796909\r\n";
9534 assert_eq!(
9535 f.run(&[
9536 b"GEOSEARCHSTORE",
9537 b"dst",
9538 b"Sicily",
9539 b"FROMLONLAT",
9540 b"15",
9541 b"37",
9542 b"BYRADIUS",
9543 b"200",
9544 b"km",
9545 b"ASC"
9546 ]),
9547 ":3\r\n"
9548 );
9549 assert_eq!(
9550 f.run(&[b"ZRANGE", b"dst", b"0", b"-1", b"WITHSCORES"]),
9551 hashes
9552 );
9553 assert_eq!(
9556 f.run(&[
9557 b"GEORADIUS",
9558 b"Sicily",
9559 b"15",
9560 b"37",
9561 b"200",
9562 b"km",
9563 b"STORE",
9564 b"dst3"
9565 ]),
9566 ":3\r\n"
9567 );
9568 assert_eq!(
9569 f.run(&[b"ZRANGE", b"dst3", b"0", b"-1", b"WITHSCORES"]),
9570 hashes
9571 );
9572 assert_eq!(
9581 f.run(&[
9582 b"GEOSEARCHSTORE",
9583 b"dst2",
9584 b"Sicily",
9585 b"FROMLONLAT",
9586 b"15",
9587 b"37",
9588 b"BYRADIUS",
9589 b"200",
9590 b"km",
9591 b"ASC",
9592 b"STOREDIST"
9593 ]),
9594 ":3\r\n"
9595 );
9596 for (member, want) in [
9597 ("Catania", 56.441_257_870_158_19),
9598 ("Agrigento", 130.423_487_067_147_14),
9599 ("Palermo", 190.442_429_847_757_92),
9600 ] {
9601 let reply = f.run(&[b"ZSCORE", b"dst2", member.as_bytes()]);
9602 let got: f64 = reply
9603 .trim_start_matches(|c: char| c != '\n')
9604 .trim()
9605 .parse()
9606 .unwrap_or_else(|_| panic!("{member} scored {reply:?}"));
9607 assert!(
9608 (got - want).abs() < 1e-9,
9609 "{member} scored {got} not {want}"
9610 );
9611 }
9612 assert_eq!(
9615 f.run(&[b"ZRANGE", b"dst2", b"0", b"-1"]),
9616 "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
9617 );
9618 assert_eq!(
9622 f.run(&[
9623 b"GEOSEARCHSTORE",
9624 b"dst",
9625 b"nokey",
9626 b"FROMLONLAT",
9627 b"15",
9628 b"37",
9629 b"BYRADIUS",
9630 b"200",
9631 b"km"
9632 ]),
9633 ":0\r\n"
9634 );
9635 assert_eq!(f.run(&[b"EXISTS", b"dst"]), ":0\r\n");
9636 }
9637
9638 #[test]
9639 fn the_gates_on_geoadd_are_the_ones_zadd_has() {
9640 let mut f = Fixture::new();
9641 sicily(&mut f);
9642 assert_eq!(
9645 f.run(&[
9646 b"GEOADD",
9647 b"Sicily",
9648 b"XX",
9649 b"CH",
9650 b"13.361389",
9651 b"38.115556",
9652 b"Palermo"
9653 ]),
9654 ":0\r\n"
9655 );
9656 assert_eq!(
9657 f.run(&[
9658 b"GEOADD",
9659 b"Sicily",
9660 b"NX",
9661 b"13.361389",
9662 b"38.9",
9663 b"Palermo"
9664 ]),
9665 ":0\r\n"
9666 );
9667 assert_eq!(
9668 f.run(&[
9669 b"GEOADD",
9670 b"Sicily",
9671 b"CH",
9672 b"13.361389",
9673 b"38.9",
9674 b"Palermo"
9675 ]),
9676 ":1\r\n"
9677 );
9678 assert_eq!(
9681 f.run(&[
9682 b"GEOADD",
9683 b"new",
9684 b"13.361389",
9685 b"38.115556",
9686 b"here",
9687 b"181",
9688 b"38",
9689 b"there"
9690 ]),
9691 "-ERR invalid longitude,latitude pair 181.000000,38.000000\r\n"
9692 );
9693 assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
9694 assert_eq!(
9695 f.run(&[b"GEOADD", b"new", b"x", b"38", b"here"]),
9696 "-ERR value is not a valid float\r\n"
9697 );
9698 assert_eq!(
9701 f.run(&[b"GEOADD", b"new", b"13", b"38", b"here", b"and"]),
9702 "-ERR syntax error\r\n"
9703 );
9704 assert_eq!(
9705 f.run(&[b"GEOADD", b"new", b"NX", b"XX", b"CH"]),
9706 "-ERR syntax error\r\n"
9707 );
9708 assert_eq!(
9709 f.run(&[b"GEOADD", b"new", b"CH", b"CH", b"CH", b"CH"]),
9710 "-ERR syntax error\r\n"
9711 );
9712 assert_eq!(
9713 f.run(&[b"GEOADD", b"new", b"NX", b"CH"]),
9714 "-ERR wrong number of arguments for 'geoadd' command\r\n"
9715 );
9716 }
9717
9718 #[test]
9721 fn every_way_a_search_can_be_written_wrong_has_its_own_sentence() {
9722 let mut f = Fixture::new();
9723 sicily(&mut f);
9724 let cases: &[(&[&[u8]], &str)] = &[
9725 (
9726 &[b"GEORADIUS", b"Sicily", b"15", b"37", b"x", b"km"],
9727 "-ERR need numeric radius\r\n",
9728 ),
9729 (
9730 &[b"GEORADIUS", b"Sicily", b"15", b"37", b"-1", b"km"],
9731 "-ERR radius cannot be negative\r\n",
9732 ),
9733 (
9734 &[b"GEORADIUS", b"Sicily", b"15", b"37", b"1", b"parsecs"],
9735 "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n",
9736 ),
9737 (
9738 &[b"GEORADIUS", b"Sicily", b"181", b"37", b"1", b"km"],
9739 "-ERR invalid longitude,latitude pair 181.000000,37.000000\r\n",
9740 ),
9741 (
9742 &[
9743 b"GEOSEARCH",
9744 b"Sicily",
9745 b"FROMLONLAT",
9746 b"15",
9747 b"37",
9748 b"BYBOX",
9749 b"x",
9750 b"1",
9751 b"km",
9752 ],
9753 "-ERR need numeric width\r\n",
9754 ),
9755 (
9756 &[
9757 b"GEOSEARCH",
9758 b"Sicily",
9759 b"FROMLONLAT",
9760 b"15",
9761 b"37",
9762 b"BYBOX",
9763 b"1",
9764 b"y",
9765 b"km",
9766 ],
9767 "-ERR need numeric height\r\n",
9768 ),
9769 (
9770 &[
9771 b"GEOSEARCH",
9772 b"Sicily",
9773 b"FROMLONLAT",
9774 b"15",
9775 b"37",
9776 b"BYBOX",
9777 b"-1",
9778 b"1",
9779 b"km",
9780 ],
9781 "-ERR height or width cannot be negative\r\n",
9782 ),
9783 (
9784 &[
9785 b"GEOSEARCH",
9786 b"Sicily",
9787 b"FROMLONLAT",
9788 b"15",
9789 b"37",
9790 b"BYRADIUS",
9791 b"1",
9792 b"km",
9793 b"ANY",
9794 ],
9795 "-ERR the ANY argument requires COUNT argument\r\n",
9796 ),
9797 (
9798 &[
9799 b"GEOSEARCH",
9800 b"Sicily",
9801 b"FROMLONLAT",
9802 b"15",
9803 b"37",
9804 b"BYRADIUS",
9805 b"1",
9806 b"km",
9807 b"COUNT",
9808 b"0",
9809 ],
9810 "-ERR COUNT must be > 0\r\n",
9811 ),
9812 (
9813 &[
9814 b"GEOSEARCH",
9815 b"Sicily",
9816 b"BYRADIUS",
9817 b"1",
9818 b"km",
9819 b"BYBOX",
9820 b"1",
9821 b"1",
9822 b"km",
9823 ],
9824 "-ERR syntax error\r\n",
9825 ),
9826 (
9827 &[
9828 b"GEOSEARCH",
9829 b"Sicily",
9830 b"FROMMEMBER",
9831 b"Palermo",
9832 b"FROMLONLAT",
9833 b"1",
9834 b"2",
9835 b"BYRADIUS",
9836 b"1",
9837 b"km",
9838 ],
9839 "-ERR syntax error\r\n",
9840 ),
9841 (
9844 &[
9845 b"geosearch",
9846 b"Sicily",
9847 b"BYRADIUS",
9848 b"1",
9849 b"km",
9850 b"ASC",
9851 b"WITHDIST",
9852 ],
9853 "-ERR exactly one of FROMMEMBER or FROMLONLAT can be specified for geosearch\r\n",
9854 ),
9855 (
9856 &[
9857 b"GEOSEARCH",
9858 b"Sicily",
9859 b"FROMLONLAT",
9860 b"15",
9861 b"37",
9862 b"ASC",
9863 b"WITHDIST",
9864 ],
9865 "-ERR exactly one of BYRADIUS and BYBOX can be specified for GEOSEARCH\r\n",
9866 ),
9867 (
9870 &[
9871 b"GEOSEARCHSTORE",
9872 b"d",
9873 b"Sicily",
9874 b"FROMLONLAT",
9875 b"15",
9876 b"37",
9877 b"BYRADIUS",
9878 b"1",
9879 b"km",
9880 b"WITHCOORD",
9881 ],
9882 "-ERR GEOSEARCHSTORE is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
9883 ),
9884 (
9885 &[
9886 b"GEORADIUS",
9887 b"Sicily",
9888 b"15",
9889 b"37",
9890 b"1",
9891 b"km",
9892 b"WITHDIST",
9893 b"STORE",
9894 b"d",
9895 ],
9896 "-ERR STORE option in GEORADIUS is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
9897 ),
9898 (
9901 &[
9902 b"GEORADIUS_RO",
9903 b"Sicily",
9904 b"15",
9905 b"37",
9906 b"1",
9907 b"km",
9908 b"STORE",
9909 b"d",
9910 ],
9911 "-ERR syntax error\r\n",
9912 ),
9913 (
9914 &[
9915 b"GEOSEARCH",
9916 b"Sicily",
9917 b"FROMLONLAT",
9918 b"15",
9919 b"37",
9920 b"BYRADIUS",
9921 b"1",
9922 b"km",
9923 b"STOREDIST",
9924 ],
9925 "-ERR syntax error\r\n",
9926 ),
9927 ];
9928 for (parts, want) in cases {
9929 assert_eq!(&f.run(parts), want, "{:?}", parts[0]);
9930 }
9931 }
9932
9933 #[test]
9936 fn every_geo_command_says_wrongtype() {
9937 let mut f = Fixture::new();
9938 f.run(&[b"SET", b"s", b"v"]);
9939 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9940 let cases: &[&[&[u8]]] = &[
9941 &[b"GEOADD", b"s", b"13", b"38", b"m"],
9942 &[b"GEOPOS", b"s", b"m"],
9943 &[b"GEOHASH", b"s", b"m"],
9944 &[b"GEODIST", b"s", b"a", b"b"],
9945 &[
9946 b"GEOSEARCH",
9947 b"s",
9948 b"FROMLONLAT",
9949 b"15",
9950 b"37",
9951 b"BYRADIUS",
9952 b"1",
9953 b"km",
9954 ],
9955 &[
9956 b"GEOSEARCHSTORE",
9957 b"d",
9958 b"s",
9959 b"FROMLONLAT",
9960 b"15",
9961 b"37",
9962 b"BYRADIUS",
9963 b"1",
9964 b"km",
9965 ],
9966 &[b"GEORADIUS", b"s", b"15", b"37", b"1", b"km"],
9967 &[b"GEORADIUS_RO", b"s", b"15", b"37", b"1", b"km"],
9968 &[b"GEORADIUSBYMEMBER", b"s", b"m", b"1", b"km"],
9969 &[b"GEORADIUSBYMEMBER_RO", b"s", b"m", b"1", b"km"],
9970 ];
9971 for case in cases {
9972 assert_eq!(f.run(case), wrong, "{:?}", case[0]);
9973 }
9974 assert_eq!(
9977 f.run(&[b"GEORADIUS", b"s", b"15", b"37", b"x", b"km"]),
9978 wrong
9979 );
9980 }
9981
9982 #[test]
9985 fn an_array_writes_at_any_index_and_reads_back_what_it_sent() {
9986 let mut f = Fixture::new();
9987 assert_eq!(
9990 f.run(&[b"ARSET", b"a", b"1000", b"x", b"y", b"z"]),
9991 ":3\r\n"
9992 );
9993 assert_eq!(f.run(&[b"ARSET", b"a", b"1000", b"X", b"Y"]), ":0\r\n");
9994 assert_eq!(f.run(&[b"ARGET", b"a", b"1000"]), "$1\r\nX\r\n");
9995 assert_eq!(f.run(&[b"ARGET", b"a", b"1002"]), "$1\r\nz\r\n");
9996 assert_eq!(f.run(&[b"ARGET", b"a", b"999"]), "$-1\r\n");
9998 assert_eq!(f.run(&[b"ARGET", b"nope", b"0"]), "$-1\r\n");
9999 assert_eq!(
10000 f.run(&[b"ARMGET", b"a", b"1002", b"999", b"1000"]),
10001 "*3\r\n$1\r\nz\r\n$-1\r\n$1\r\nX\r\n"
10002 );
10003 assert_eq!(f.run(&[b"ARMSET", b"a", b"5", b"p", b"5", b"q"]), ":1\r\n");
10005 assert_eq!(f.run(&[b"ARGET", b"a", b"5"]), "$1\r\nq\r\n");
10006 }
10007
10008 #[test]
10011 fn the_length_is_the_high_water_mark_and_the_count_is_the_population() {
10012 let mut f = Fixture::new();
10013 assert_eq!(f.run(&[b"ARLEN", b"nope"]), ":0\r\n");
10014 assert_eq!(f.run(&[b"ARCOUNT", b"nope"]), ":0\r\n");
10015 f.run(&[b"ARMSET", b"a", b"0", b"x", b"9", b"y"]);
10016 assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
10017 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
10018 assert_eq!(f.run(&[b"ARDEL", b"a", b"0"]), ":1\r\n");
10020 assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
10021 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":1\r\n");
10022
10023 f.run(&[b"ARSET", b"top", b"18446744073709551614", b"z"]);
10027 assert_eq!(f.run(&[b"ARLEN", b"top"]), ":18446744073709551615\r\n");
10028 assert_eq!(f.run(&[b"ARCOUNT", b"top"]), ":1\r\n");
10029 assert_eq!(
10032 f.run(&[b"ARSET", b"over", b"18446744073709551614", b"a", b"b"]),
10033 "-ERR array index overflow\r\n"
10034 );
10035 assert_eq!(f.run(&[b"EXISTS", b"over"]), ":0\r\n");
10036 }
10037
10038 #[test]
10041 fn a_range_read_answers_for_the_holes_too_and_is_capped_at_a_million() {
10042 let mut f = Fixture::new();
10043 f.run(&[b"ARSET", b"a", b"1", b"x"]);
10044 assert_eq!(
10045 f.run(&[b"ARGETRANGE", b"a", b"0", b"3"]),
10046 "*4\r\n$-1\r\n$1\r\nx\r\n$-1\r\n$-1\r\n"
10047 );
10048 assert_eq!(
10051 f.run(&[b"ARGETRANGE", b"a", b"3", b"0"]),
10052 "*4\r\n$-1\r\n$-1\r\n$1\r\nx\r\n$-1\r\n"
10053 );
10054 assert_eq!(
10056 f.run(&[b"ARGETRANGE", b"nope", b"0", b"1"]),
10057 "*2\r\n$-1\r\n$-1\r\n"
10058 );
10059 assert_eq!(
10063 f.run(&[b"ARGETRANGE", b"nope", b"0", b"18446744073709551614"]),
10064 "-ERR range exceeds maximum of 1000000 items\r\n"
10065 );
10066 }
10067
10068 #[test]
10071 fn a_bad_index_late_in_the_line_writes_none_of_the_earlier_ones() {
10072 let mut f = Fixture::new();
10073 assert_eq!(
10074 f.run(&[b"ARMSET", b"a", b"0", b"x", b"-1", b"y"]),
10075 "-ERR invalid array index\r\n"
10076 );
10077 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
10078 f.run(&[b"ARSET", b"a", b"0", b"x", b"y", b"z"]);
10079 assert_eq!(
10080 f.run(&[b"ARDEL", b"a", b"0", b"01"]),
10081 "-ERR invalid array index\r\n"
10082 );
10083 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":3\r\n");
10084 assert_eq!(
10087 f.run(&[b"ARGET", b"a", b"-1"]),
10088 "-ERR invalid array index\r\n"
10089 );
10090 assert_eq!(
10093 f.run(&[b"ARMSET", b"a", b"0", b"x", b"1"]),
10094 "-ERR wrong number of arguments for 'armset' command\r\n"
10095 );
10096 assert_eq!(
10097 f.run(&[b"ARDELRANGE", b"a", b"0", b"1", b"2"]),
10098 "-ERR wrong number of arguments for 'ardelrange' command\r\n"
10099 );
10100 }
10101
10102 #[test]
10103 fn a_range_delete_costs_the_elements_and_takes_the_key_when_it_empties() {
10104 let mut f = Fixture::new();
10105 f.run(&[b"ARSET", b"a", b"0", b"0", b"1", b"2", b"3", b"4"]);
10106 assert_eq!(f.run(&[b"ARDELRANGE", b"a", b"3", b"1"]), ":3\r\n");
10107 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
10108 assert_eq!(
10111 f.run(&[
10112 b"ARDELRANGE",
10113 b"a",
10114 b"100",
10115 b"200",
10116 b"0",
10117 b"18446744073709551614"
10118 ]),
10119 ":2\r\n"
10120 );
10121 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
10122 assert_eq!(f.run(&[b"ARDELRANGE", b"nope", b"0", b"1"]), ":0\r\n");
10123 assert_eq!(f.run(&[b"ARDEL", b"nope", b"0"]), ":0\r\n");
10124 }
10125
10126 #[test]
10129 fn a_value_comes_back_byte_for_byte_however_it_was_packed() {
10130 let mut f = Fixture::new();
10131 let long = vec![b'v'; 200];
10132 f.run(&[
10133 b"ARMSET", b"a", b"0", b"42", b"1", b"007", b"2", b"3.5", b"3", b"3.14", b"4",
10134 b"short", b"5", &long, b"6", b"-0",
10135 ]);
10136 assert_eq!(
10140 f.run(&[b"ARGETRANGE", b"a", b"0", b"6"]),
10141 format!(
10142 "*7\r\n$2\r\n42\r\n$3\r\n007\r\n$3\r\n3.5\r\n$4\r\n3.14\r\n$5\r\nshort\r\n$200\r\n{}\r\n$2\r\n-0\r\n",
10143 String::from_utf8_lossy(&long)
10144 )
10145 );
10146 }
10147
10148 #[test]
10149 fn an_array_is_a_type_and_an_encoding_a_client_can_see() {
10150 let mut f = Fixture::new();
10151 f.run(&[b"ARSET", b"a", b"0", b"x"]);
10152 assert_eq!(f.run(&[b"TYPE", b"a"]), "+array\r\n");
10153 assert_eq!(
10154 f.run(&[b"OBJECT", b"ENCODING", b"a"]),
10155 "$12\r\nsliced-array\r\n"
10156 );
10157 assert_eq!(f.run(&[b"EXPIRE", b"a", b"100"]), ":1\r\n");
10159 assert_eq!(f.run(&[b"PERSIST", b"a"]), ":1\r\n");
10160 assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":1\r\n");
10161 assert_eq!(f.run(&[b"ARGET", b"b", b"0"]), "$1\r\nx\r\n");
10162 assert_eq!(f.run(&[b"RENAME", b"a", b"c"]), "+OK\r\n");
10163 assert_eq!(f.run(&[b"ARCOUNT", b"c"]), ":1\r\n");
10164 }
10165
10166 #[test]
10167 fn every_array_command_refuses_a_key_holding_something_else() {
10168 let mut f = Fixture::new();
10169 f.run(&[b"SET", b"s", b"v"]);
10170 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10171 for cmd in [
10172 &[b"ARSET".as_ref(), b"s", b"0", b"x"][..],
10173 &[b"ARMSET".as_ref(), b"s", b"0", b"x"][..],
10174 &[b"ARGET".as_ref(), b"s", b"0"][..],
10175 &[b"ARMGET".as_ref(), b"s", b"0"][..],
10176 &[b"ARGETRANGE".as_ref(), b"s", b"0", b"1"][..],
10177 &[b"ARLEN".as_ref(), b"s"][..],
10178 &[b"ARCOUNT".as_ref(), b"s"][..],
10179 &[b"ARDEL".as_ref(), b"s", b"0"][..],
10180 &[b"ARDELRANGE".as_ref(), b"s", b"0", b"1"][..],
10181 &[b"ARINSERT".as_ref(), b"s", b"x"][..],
10182 &[b"ARRING".as_ref(), b"s", b"4", b"x"][..],
10183 &[b"ARNEXT".as_ref(), b"s"][..],
10184 &[b"ARSEEK".as_ref(), b"s", b"1"][..],
10185 &[b"ARLASTITEMS".as_ref(), b"s", b"1"][..],
10186 &[b"ARSCAN".as_ref(), b"s", b"0", b"1"][..],
10187 &[b"ARGREP".as_ref(), b"s", b"0", b"1", b"EXACT", b"v"][..],
10188 &[b"AROP".as_ref(), b"s", b"0", b"1", b"SUM"][..],
10189 &[b"ARINFO".as_ref(), b"s"][..],
10190 ] {
10191 assert_eq!(f.run(cmd), wrong, "{}", String::from_utf8_lossy(cmd[0]));
10192 }
10193 }
10194
10195 #[test]
10199 fn a_bad_index_reports_the_type_only_where_redis_reports_it() {
10200 let mut f = Fixture::new();
10201 f.run(&[b"SET", b"s", b"v"]);
10202 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10203 let bad = "-ERR invalid array index\r\n";
10204 assert_eq!(f.run(&[b"ARGET", b"s", b"-1"]), wrong);
10205 assert_eq!(f.run(&[b"ARMGET", b"s", b"0", b"-1"]), wrong);
10206 assert_eq!(f.run(&[b"ARSET", b"s", b"-1", b"x"]), bad);
10207 assert_eq!(f.run(&[b"ARDEL", b"s", b"-1"]), bad);
10208 assert_eq!(f.run(&[b"ARSCAN", b"s", b"-1", b"0"]), bad);
10209 assert_eq!(f.run(&[b"ARGREP", b"s", b"-1", b"0", b"EXACT", b"v"]), bad);
10210 f.run(&[b"ARSET", b"a", b"0", b"x"]);
10212 assert_eq!(f.run(&[b"ARGET", b"a", b"-1"]), bad);
10213 assert_eq!(f.run(&[b"ARGET", b"nope", b"-1"]), bad);
10214 }
10215
10216 #[test]
10217 fn an_append_follows_a_cursor_the_client_can_move() {
10218 let mut f = Fixture::new();
10219 assert_eq!(f.run(&[b"ARNEXT", b"nope"]), ":0\r\n");
10220 assert_eq!(f.run(&[b"ARINSERT", b"a", b"x", b"y"]), ":1\r\n");
10221 assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":2\r\n");
10222 assert_eq!(f.run(&[b"ARINSERT", b"a", b"z"]), ":2\r\n");
10223 assert_eq!(f.run(&[b"ARGET", b"a", b"2"]), "$1\r\nz\r\n");
10224
10225 assert_eq!(f.run(&[b"ARSEEK", b"nope", b"5"]), ":0\r\n");
10228 assert_eq!(f.run(&[b"EXISTS", b"nope"]), ":0\r\n");
10229 assert_eq!(f.run(&[b"ARSEEK", b"a", b"100"]), ":1\r\n");
10230 assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":100\r\n");
10231 assert_eq!(f.run(&[b"ARINSERT", b"a", b"far"]), ":100\r\n");
10232 assert_eq!(f.run(&[b"ARSEEK", b"a", b"0"]), ":1\r\n");
10233 assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":0\r\n");
10234
10235 assert_eq!(f.run(&[b"ARSEEK", b"a", b"18446744073709551615"]), ":1\r\n");
10238 assert_eq!(f.run(&[b"ARNEXT", b"a"]), "$-1\r\n");
10239 assert_eq!(
10240 f.run(&[b"ARINSERT", b"a", b"x"]),
10241 "-ERR insert index overflow\r\n"
10242 );
10243 assert_eq!(
10244 f.run(&[b"ARSET", b"a", b"18446744073709551615", b"x"]),
10245 "-ERR invalid array index\r\n"
10246 );
10247 }
10248
10249 #[test]
10250 fn a_ring_keeps_the_newest_and_renumbers_them_when_it_is_resized() {
10251 let mut f = Fixture::new();
10252 assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"a", b"b", b"c"]), ":2\r\n");
10253 assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"d", b"e"]), ":1\r\n");
10254 assert_eq!(f.run(&[b"ARLEN", b"r"]), ":3\r\n");
10255 assert_eq!(
10256 f.run(&[b"ARGETRANGE", b"r", b"0", b"2"]),
10257 "*3\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nc\r\n"
10258 );
10259 assert_eq!(f.run(&[b"ARRING", b"r", b"5", b"f"]), ":3\r\n");
10262 assert_eq!(
10263 f.run(&[b"ARGETRANGE", b"r", b"0", b"3"]),
10264 "*4\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nf\r\n"
10265 );
10266 assert_eq!(
10269 f.run(&[b"ARRING", b"r", b"0", b"x"]),
10270 "-ERR size must be positive\r\n"
10271 );
10272 assert_eq!(
10273 f.run(&[b"ARRING", b"r", b"big", b"x"]),
10274 "-ERR invalid size\r\n"
10275 );
10276 }
10277
10278 #[test]
10279 fn the_last_items_walk_back_from_the_cursor_and_report_the_holes() {
10280 let mut f = Fixture::new();
10281 assert_eq!(f.run(&[b"ARLASTITEMS", b"nope", b"5"]), "*0\r\n");
10282 f.run(&[b"ARRING", b"r", b"4", b"a", b"b", b"c", b"d", b"e"]);
10283 assert_eq!(
10284 f.run(&[b"ARLASTITEMS", b"r", b"3"]),
10285 "*3\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n"
10286 );
10287 assert_eq!(
10288 f.run(&[b"ARLASTITEMS", b"r", b"3", b"rev"]),
10289 "*3\r\n$1\r\ne\r\n$1\r\nd\r\n$1\r\nc\r\n"
10290 );
10291 assert_eq!(
10292 f.run(&[b"ARLASTITEMS", b"r", b"99"]),
10293 "*4\r\n$1\r\nb\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n",
10294 "more than there is gets what there is"
10295 );
10296 assert_eq!(f.run(&[b"ARLASTITEMS", b"r", b"0", b"junk"]), "*0\r\n");
10299 assert_eq!(
10300 f.run(&[b"ARLASTITEMS", b"r", b"1", b"junk"]),
10301 "-ERR syntax error\r\n"
10302 );
10303 assert_eq!(
10304 f.run(&[b"ARLASTITEMS", b"r", b"nine"]),
10305 "-ERR invalid COUNT\r\n"
10306 );
10307
10308 f.run(&[b"ARMSET", b"h", b"0", b"x", b"2", b"z"]);
10311 assert_eq!(
10312 f.run(&[b"ARLASTITEMS", b"h", b"5"]),
10313 "*2\r\n$-1\r\n$1\r\nz\r\n"
10314 );
10315 }
10316
10317 #[test]
10318 fn a_scan_answers_pairs_for_what_is_there_and_skips_what_is_not() {
10319 let mut f = Fixture::new();
10320 assert_eq!(f.run(&[b"ARSCAN", b"nope", b"0", b"10"]), "*0\r\n");
10321 f.run(&[b"ARMSET", b"a", b"0", b"x", b"7", b"y", b"1000000", b"z"]);
10322 assert_eq!(
10325 f.run(&[b"ARSCAN", b"a", b"0", b"18446744073709551614"]),
10326 "*3\r\n*2\r\n:0\r\n$1\r\nx\r\n*2\r\n:7\r\n$1\r\ny\r\n*2\r\n:1000000\r\n$1\r\nz\r\n"
10327 );
10328 assert_eq!(
10329 f.run(&[
10330 b"ARSCAN",
10331 b"a",
10332 b"18446744073709551614",
10333 b"0",
10334 b"LIMIT",
10335 b"1"
10336 ]),
10337 "*1\r\n*2\r\n:1000000\r\n$1\r\nz\r\n"
10338 );
10339 assert_eq!(f.run(&[b"ARSCAN", b"a", b"1", b"6"]), "*0\r\n");
10340 assert_eq!(
10341 f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT", b"0"]),
10342 "-ERR LIMIT must be positive\r\n"
10343 );
10344 assert_eq!(
10345 f.run(&[b"ARSCAN", b"a", b"0", b"10", b"NOPE", b"1"]),
10346 "-ERR syntax error\r\n"
10347 );
10348 assert_eq!(
10349 f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT"]),
10350 "-ERR wrong number of arguments for 'arscan' command\r\n"
10351 );
10352 }
10353
10354 #[test]
10355 fn a_grep_answers_the_indexes_whose_elements_match() {
10356 let mut f = Fixture::new();
10357 assert_eq!(
10358 f.run(&[b"ARGREP", b"nope", b"0", b"10", b"EXACT", b"x"]),
10359 "*0\r\n"
10360 );
10361 f.run(&[b"ARSET", b"a", b"0", b"alpha", b"beta", b"gamma", b"ALPHA"]);
10362
10363 assert_eq!(
10366 f.run(&[b"ARGREP", b"a", b"-", b"+", b"GLOB", b"*a"]),
10367 "*3\r\n:0\r\n:1\r\n:2\r\n"
10368 );
10369 assert_eq!(
10370 f.run(&[b"ARGREP", b"a", b"+", b"-", b"GLOB", b"*a"]),
10371 "*3\r\n:2\r\n:1\r\n:0\r\n"
10372 );
10373 assert_eq!(
10374 f.run(&[b"ARGREP", b"a", b"1", b"2", b"GLOB", b"*a"]),
10375 "*2\r\n:1\r\n:2\r\n"
10376 );
10377
10378 assert_eq!(
10381 f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha"]),
10382 "*1\r\n:0\r\n"
10383 );
10384 assert_eq!(
10385 f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha", b"NOCASE"]),
10386 "*2\r\n:0\r\n:3\r\n"
10387 );
10388 assert_eq!(
10389 f.run(&[b"ARGREP", b"a", b"-", b"+", b"MATCH", b"mm"]),
10390 "*1\r\n:2\r\n"
10391 );
10392 assert_eq!(
10393 f.run(&[b"ARGREP", b"a", b"-", b"+", b"RE", b"^[bg]"]),
10394 "*2\r\n:1\r\n:2\r\n"
10395 );
10396
10397 let both: &[&[u8]] = &[
10400 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al",
10401 ];
10402 assert_eq!(f.run(both), "*2\r\n:0\r\n:1\r\n");
10403 assert_eq!(
10404 f.run(&[
10405 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND"
10406 ]),
10407 "*0\r\n"
10408 );
10409 assert_eq!(
10410 f.run(&[
10411 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND", b"OR"
10412 ]),
10413 "*2\r\n:0\r\n:1\r\n"
10414 );
10415
10416 assert_eq!(
10419 f.run(&[
10420 b"ARGREP",
10421 b"a",
10422 b"-",
10423 b"+",
10424 b"MATCH",
10425 b"a",
10426 b"WITHVALUES",
10427 b"LIMIT",
10428 b"2"
10429 ]),
10430 "*2\r\n*2\r\n:0\r\n$5\r\nalpha\r\n*2\r\n:1\r\n$4\r\nbeta\r\n"
10431 );
10432 assert_eq!(
10433 f.run(&[
10434 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"ALPHA", b"LIMIT", b"1"
10435 ]),
10436 "*1\r\n:3\r\n"
10437 );
10438 }
10439
10440 #[test]
10442 fn a_grep_reports_a_broken_command_the_way_redis_does() {
10443 let mut f = Fixture::new();
10444 f.run(&[b"ARSET", b"a", b"0", b"alpha"]);
10445 let syntax = "-ERR syntax error\r\n";
10446
10447 assert_eq!(
10450 f.run(&[b"ARGREP", b"a", b"-1", b"0", b"NOPE", b"x"]),
10451 "-ERR invalid array index\r\n"
10452 );
10453 assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOPE", b"x"]), syntax);
10454 assert_eq!(
10456 f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"EXACT"]),
10457 syntax
10458 );
10459 assert_eq!(
10460 f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT"]),
10461 syntax
10462 );
10463 assert_eq!(
10464 f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"WITHVALUES"]),
10465 syntax,
10466 "a command with no predicate in it at all"
10467 );
10468 assert_eq!(
10469 f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"0"]),
10470 "-ERR LIMIT must be positive\r\n"
10471 );
10472 assert_eq!(
10473 f.run(&[
10474 b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"nine"
10475 ]),
10476 "-ERR value is not an integer or out of range\r\n"
10477 );
10478 assert_eq!(
10479 f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b""]),
10480 "-ERR regular expression is empty\r\n"
10481 );
10482 assert_eq!(
10483 f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b"(a"]),
10484 "-ERR invalid regular expression: Missing ')'\r\n"
10485 );
10486 assert_eq!(
10487 f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", br"(a)\1"]),
10488 "-ERR regular expression backreferences are not supported\r\n"
10489 );
10490 let arity = "-ERR wrong number of arguments for 'argrep' command\r\n";
10493 assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT"]), arity);
10494 assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1"]), arity);
10495 }
10496
10497 #[test]
10498 fn an_op_reduces_a_range_to_one_number() {
10499 let mut f = Fixture::new();
10500 f.run(&[b"ARSET", b"a", b"0", b"1", b"2.5", b"word", b"-4"]);
10501 assert_eq!(
10502 f.run(&[b"AROP", b"a", b"0", b"10", b"SUM"]),
10503 "$4\r\n-0.5\r\n"
10504 );
10505 assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"min"]), "$2\r\n-4\r\n");
10506 assert_eq!(
10507 f.run(&[b"AROP", b"a", b"0", b"10", b"MAX"]),
10508 "$3\r\n2.5\r\n"
10509 );
10510 assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"USED"]), ":4\r\n");
10511 assert_eq!(
10512 f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH", b"word"]),
10513 ":1\r\n"
10514 );
10515 f.run(&[b"ARSET", b"t", b"0", b"0.1", b"0.2"]);
10518 assert_eq!(
10519 f.run(&[b"AROP", b"t", b"0", b"10", b"SUM"]),
10520 "$19\r\n0.30000000000000004\r\n"
10521 );
10522 assert_eq!(f.run(&[b"ZADD", b"z", b"0.3", b"m"]), ":1\r\n");
10523 assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\n0.3\r\n");
10524
10525 f.run(&[b"ARSET", b"w", b"0", b"word"]);
10528 assert_eq!(f.run(&[b"AROP", b"w", b"0", b"10", b"SUM"]), "$-1\r\n");
10529 assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"SUM"]), "$-1\r\n");
10530 assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"USED"]), ":0\r\n");
10531
10532 assert_eq!(
10533 f.run(&[b"AROP", b"a", b"0", b"10", b"NOPE"]),
10534 "-ERR unknown operation\r\n"
10535 );
10536 assert_eq!(
10537 f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH"]),
10538 "-ERR MATCH requires a value argument\r\n"
10539 );
10540 assert_eq!(
10541 f.run(&[b"AROP", b"a", b"0", b"10", b"SUM", b"extra"]),
10542 "-ERR wrong number of arguments for 'arop' command\r\n"
10543 );
10544 }
10545
10546 #[test]
10547 fn the_info_is_a_map_and_a_missing_key_is_an_error() {
10548 let mut f = Fixture::new();
10549 assert_eq!(f.run(&[b"ARINFO", b"nope"]), "-ERR no such key\r\n");
10550 f.run(&[b"ARINSERT", b"a", b"x", b"y"]);
10551 let short = f.run(&[b"ARINFO", b"a"]);
10552 assert!(
10553 short.starts_with("*14\r\n"),
10554 "seven pairs on RESP2: {short}"
10555 );
10556 assert!(short.contains("$5\r\ncount\r\n:2\r\n"), "{short}");
10557 assert!(
10558 short.contains("$17\r\nnext-insert-index\r\n:2\r\n"),
10559 "{short}"
10560 );
10561 assert!(short.contains("$10\r\nslice-size\r\n:4096\r\n"), "{short}");
10562 let full = f.run(&[b"ARINFO", b"a", b"full"]);
10563 assert!(full.starts_with("*24\r\n"), "twelve pairs: {full}");
10564 assert!(full.contains("$12\r\ndense-slices\r\n:0\r\n"), "{full}");
10567 assert!(full.contains("$13\r\nsparse-slices\r\n:1\r\n"), "{full}");
10568 assert!(
10569 full.contains("$14\r\navg-dense-size\r\n$1\r\n0\r\n"),
10570 "{full}"
10571 );
10572 assert_eq!(f.run(&[b"ARINFO", b"a", b"nope"]), "-ERR syntax error\r\n");
10573
10574 let mut g = Fixture::new();
10576 g.run(&[b"HELLO", b"3"]);
10577 g.run(&[b"ARINSERT", b"a", b"x"]);
10578 let map = g.run(&[b"ARINFO", b"a", b"FULL"]);
10579 assert!(map.starts_with("%12\r\n"), "{map}");
10580 assert!(map.contains("$5\r\ncount\r\n:1\r\n"), "{map}");
10581 assert!(map.contains("$14\r\navg-dense-size\r\n,0\r\n"), "{map}");
10582 }
10583
10584 #[test]
10585 fn a_double_on_the_wire_is_written_the_way_redis_writes_one() {
10586 let mut f = Fixture::new();
10587 for (score, want) in [
10590 ("3", "3"),
10591 ("3.5", "3.5"),
10592 ("0.3", "0.3"),
10593 ("1e30", "1e+30"),
10594 ("1e19", "1e+19"),
10595 ("1e-7", "1e-7"),
10596 ("0.000001", "0.000001"),
10597 ("4611686018427387904", "4611686018427387904"),
10598 ("-0", "-0"),
10599 ] {
10600 f.run(&[b"ZADD", b"z", score.as_bytes(), b"m"]);
10601 assert_eq!(
10602 f.run(&[b"ZSCORE", b"z", b"m"]),
10603 format!("${}\r\n{want}\r\n", want.len()),
10604 "score {score}"
10605 );
10606 }
10607
10608 let mut g = Fixture::new();
10611 g.run(&[b"HELLO", b"3"]);
10612 g.run(&[b"ZADD", b"z", b"1e30", b"m"]);
10613 assert_eq!(g.run(&[b"ZSCORE", b"z", b"m"]), ",1e+30\r\n");
10614 assert_eq!(
10619 g.run(&[b"INCRBYFLOAT", b"s", b"1e30"]),
10620 "$31\r\n1000000000000000000000000000000\r\n"
10621 );
10622 assert_eq!(g.run(&[b"INCRBYFLOAT", b"t", b"0.1"]), "$3\r\n0.1\r\n");
10623 assert_eq!(
10624 g.run(&[b"HINCRBYFLOAT", b"h", b"f", b"1e19"]),
10625 "$20\r\n10000000000000000000\r\n"
10626 );
10627 }
10628
10629 #[test]
10632 fn a_node_comes_back_with_the_fields_it_went_in_with() {
10633 let mut f = Fixture::new();
10634 assert_eq!(
10635 f.run(&[
10636 b"G.NADD", b"social", b"ada", b"name", b"Ada", b"born", b"1815"
10637 ]),
10638 ":1\r\n"
10639 );
10640 assert_eq!(
10646 f.run(&[b"G.NGET", b"social", b"ada"]),
10647 "*4\r\n$4\r\nborn\r\n$4\r\n1815\r\n$4\r\nname\r\n$3\r\nAda\r\n"
10648 );
10649 assert_eq!(
10652 f.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada Lovelace"]),
10653 ":0\r\n"
10654 );
10655 assert_eq!(
10656 f.run(&[b"G.NGET", b"social", b"ada"]),
10657 "*2\r\n$4\r\nname\r\n$12\r\nAda Lovelace\r\n"
10658 );
10659 assert_eq!(f.run(&[b"G.NADD", b"social", b"grace"]), ":1\r\n");
10662 assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
10663 assert_eq!(f.run(&[b"G.NGET", b"social", b"nobody"]), "$-1\r\n");
10664 assert_eq!(f.run(&[b"G.NGET", b"nokey", b"ada"]), "$-1\r\n");
10665
10666 assert_eq!(
10669 f.run(&[b"G.NADD", b"fresh", b"n", b"lonely"]),
10670 "-ERR syntax error\r\n"
10671 );
10672 assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
10673
10674 let mut g = Fixture::new();
10676 g.run(&[b"HELLO", b"3"]);
10677 g.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada"]);
10678 assert_eq!(
10679 g.run(&[b"G.NGET", b"social", b"ada"]),
10680 "%1\r\n$4\r\nname\r\n$3\r\nAda\r\n"
10681 );
10682 }
10683
10684 #[test]
10685 fn an_edge_creates_the_ends_it_needs() {
10686 let mut f = Fixture::new();
10687 assert_eq!(
10688 f.run(&[
10689 b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1843"
10690 ]),
10691 ":1\r\n"
10692 );
10693 assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "*0\r\n");
10695 assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
10696 assert_eq!(
10697 f.run(&[b"G.OUT", b"social", b"ada", b"FOLLOWS"]),
10698 "*2\r\n$1\r\n0\r\n*1\r\n$5\r\ngrace\r\n"
10699 );
10700 assert_eq!(
10701 f.run(&[b"G.IN", b"social", b"grace", b"FOLLOWS"]),
10702 "*2\r\n$1\r\n0\r\n*1\r\n$3\r\nada\r\n"
10703 );
10704 assert_eq!(
10707 f.run(&[
10708 b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1844"
10709 ]),
10710 ":0\r\n"
10711 );
10712 assert_eq!(f.run(&[b"G.DEG", b"social", b"ada", b"FOLLOWS"]), ":1\r\n");
10713 assert_eq!(
10715 f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"WORKS_WITH"]),
10716 ":1\r\n"
10717 );
10718 assert_eq!(
10719 f.run(&[b"G.DEG", b"social", b"ada", b"WORKS_WITH"]),
10720 ":1\r\n"
10721 );
10722
10723 assert_eq!(
10724 f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
10725 ":1\r\n"
10726 );
10727 assert_eq!(
10728 f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
10729 ":0\r\n"
10730 );
10731 assert_eq!(
10734 f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"NEVER"]),
10735 ":0\r\n"
10736 );
10737 assert_eq!(
10738 f.run(&[b"G.EDEL", b"social", b"ada", b"nobody", b"FOLLOWS"]),
10739 ":0\r\n"
10740 );
10741 assert_eq!(
10742 f.run(&[b"G.EDEL", b"nokey", b"ada", b"grace", b"FOLLOWS"]),
10743 ":0\r\n"
10744 );
10745 }
10746
10747 #[test]
10750 fn a_hop_answers_a_cursor_and_a_page() {
10751 let mut f = Fixture::new();
10752 for i in 0..25u32 {
10753 let dst = format!("n{i}");
10754 f.run(&[b"G.EADD", b"social", b"hub", dst.as_bytes(), b"FOLLOWS"]);
10755 }
10756 let first = f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS"]);
10758 assert!(first.starts_with("*2\r\n$2\r\n10\r\n*10\r\n"), "{first}");
10759
10760 let mut seen = 0;
10761 let mut cursor = String::from("0");
10762 loop {
10763 let page = f.run(&[
10764 b"G.OUT",
10765 b"social",
10766 b"hub",
10767 b"FOLLOWS",
10768 b"COUNT",
10769 b"7",
10770 b"CURSOR",
10771 cursor.as_bytes(),
10772 ]);
10773 let (head, rest) = page.split_once("\r\n*").expect("a cursor and a page");
10774 cursor = head
10775 .rsplit("\r\n")
10776 .next()
10777 .expect("the cursor line")
10778 .to_string();
10779 seen += rest
10780 .split_once("\r\n")
10781 .expect("the page length")
10782 .0
10783 .parse::<usize>()
10784 .expect("a length");
10785 if cursor == "0" {
10786 break;
10787 }
10788 }
10789 assert_eq!(seen, 25, "every neighbour once across the pages");
10790
10791 assert_eq!(
10794 f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"CURSOR", b"900"]),
10795 "*2\r\n$1\r\n0\r\n*0\r\n"
10796 );
10797 assert_eq!(
10798 f.run(&[b"G.OUT", b"social", b"hub", b"NEVER"]),
10799 "*2\r\n$1\r\n0\r\n*0\r\n"
10800 );
10801 assert_eq!(
10802 f.run(&[b"G.OUT", b"nokey", b"hub", b"FOLLOWS"]),
10803 "*2\r\n$1\r\n0\r\n*0\r\n"
10804 );
10805 assert_eq!(
10806 f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"COUNT", b"0"]),
10807 "-ERR COUNT must be a positive integer\r\n"
10808 );
10809 assert_eq!(
10810 f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"NOPE", b"1"]),
10811 "-ERR syntax error\r\n"
10812 );
10813 }
10814
10815 #[test]
10816 fn a_degree_counts_one_way_or_both() {
10817 let mut f = Fixture::new();
10818 f.run(&[b"G.EADD", b"social", b"a", b"b", b"F"]);
10819 f.run(&[b"G.EADD", b"social", b"a", b"c", b"F"]);
10820 f.run(&[b"G.EADD", b"social", b"d", b"a", b"F"]);
10821 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F"]), ":2\r\n");
10822 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"OUT"]), ":2\r\n");
10823 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"IN"]), ":1\r\n");
10824 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"BOTH"]), ":3\r\n");
10825 assert_eq!(f.run(&[b"G.DEG", b"social", b"nobody", b"F"]), ":0\r\n");
10826 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"NEVER"]), ":0\r\n");
10827 assert_eq!(f.run(&[b"G.DEG", b"nokey", b"a", b"F"]), ":0\r\n");
10828 assert_eq!(
10829 f.run(&[b"G.DEG", b"social", b"a", b"F", b"SIDEWAYS"]),
10830 "-ERR syntax error\r\n"
10831 );
10832 }
10833
10834 #[test]
10837 fn a_walk_reaches_each_node_once_however_many_ways_there_are() {
10838 let mut f = Fixture::new();
10839 for (src, dst) in [
10840 ("ada", "grace"),
10841 ("ada", "alan"),
10842 ("grace", "edsger"),
10843 ("alan", "edsger"),
10844 ("edsger", "barbara"),
10845 ] {
10846 f.run(&[b"G.EADD", b"social", src.as_bytes(), dst.as_bytes(), b"F"]);
10847 }
10848 assert_eq!(
10851 f.run(&[b"G.NEIGH", b"social", b"ada", b"F"]),
10852 "*3\r\n$5\r\ngrace\r\n$4\r\nalan\r\n$6\r\nedsger\r\n"
10853 );
10854 assert_eq!(
10855 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"1"]),
10856 "*2\r\n$5\r\ngrace\r\n$4\r\nalan\r\n"
10857 );
10858 let deep = f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"9"]);
10859 assert!(deep.starts_with("*4\r\n"), "the whole component: {deep}");
10860 assert!(deep.contains("$7\r\nbarbara\r\n"), "{deep}");
10861 assert_eq!(
10863 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"COUNT", b"1"]),
10864 "*1\r\n$5\r\ngrace\r\n"
10865 );
10866 assert_eq!(f.run(&[b"G.NEIGH", b"social", b"barbara", b"F"]), "*0\r\n");
10868 assert_eq!(f.run(&[b"G.NEIGH", b"social", b"ada", b"NEVER"]), "*0\r\n");
10869 assert_eq!(f.run(&[b"G.NEIGH", b"nokey", b"ada", b"F"]), "*0\r\n");
10870 assert_eq!(
10871 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"0"]),
10872 "-ERR DEPTH must be a positive integer\r\n"
10873 );
10874 assert_eq!(
10875 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"NOPE", b"1"]),
10876 "-ERR syntax error\r\n"
10877 );
10878 }
10879
10880 #[test]
10883 fn a_path_is_the_shortest_one_and_goes_over_any_label() {
10884 let mut f = Fixture::new();
10885 for i in 0..6u32 {
10888 let src = format!("n{i}");
10889 let dst = format!("n{}", i + 1);
10890 f.run(&[b"G.EADD", b"road", src.as_bytes(), dst.as_bytes(), b"STEP"]);
10891 }
10892 assert_eq!(
10893 f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
10894 "*7\r\n$2\r\nn0\r\n$2\r\nn1\r\n$2\r\nn2\r\n$2\r\nn3\r\n$2\r\nn4\r\n$2\r\nn5\r\n$2\r\nn6\r\n"
10895 );
10896 f.run(&[b"G.EADD", b"road", b"n0", b"n5", b"JUMP"]);
10897 assert_eq!(
10898 f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
10899 "*3\r\n$2\r\nn0\r\n$2\r\nn5\r\n$2\r\nn6\r\n"
10900 );
10901 assert_eq!(
10904 f.run(&[b"G.PATH", b"road", b"n2", b"n2"]),
10905 "*1\r\n$2\r\nn2\r\n"
10906 );
10907 assert_eq!(
10908 f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"MAXDEPTH", b"1"]),
10909 "*0\r\n"
10910 );
10911 assert_eq!(f.run(&[b"G.PATH", b"road", b"n6", b"n0"]), "*0\r\n");
10913 f.run(&[b"G.NADD", b"road", b"island"]);
10916 assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"island"]), "*0\r\n");
10917 assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"nobody"]), "*0\r\n");
10918 assert_eq!(f.run(&[b"G.PATH", b"nokey", b"n0", b"n6"]), "*0\r\n");
10919 assert_eq!(
10920 f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"NOPE", b"3"]),
10921 "-ERR syntax error\r\n"
10922 );
10923 }
10924
10925 #[test]
10929 fn the_keyspace_sees_a_graph_key_like_any_other() {
10930 let mut f = Fixture::new();
10931 f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"F"]);
10932 assert_eq!(f.run(&[b"TYPE", b"social"]), "+graph\r\n");
10933 assert_eq!(
10934 f.run(&[b"OBJECT", b"ENCODING", b"social"]),
10935 "$9\r\nadjacency\r\n"
10936 );
10937 assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
10938 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
10939 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$6\r\nsocial\r\n");
10940 let held = f.server.memory_bytes();
10944 for i in 0..200u32 {
10945 let dst = format!("n{i}");
10946 f.run(&[b"G.EADD", b"big", b"hub", dst.as_bytes(), b"F"]);
10947 }
10948 assert!(
10949 f.server.memory_bytes() > held,
10950 "two hundred edges cost something: {held} then {}",
10951 f.server.memory_bytes()
10952 );
10953 f.run(&[b"DEL", b"big"]);
10954
10955 assert_eq!(f.run(&[b"EXPIRE", b"social", b"100"]), ":1\r\n");
10958 assert_eq!(f.run(&[b"PERSIST", b"social"]), ":1\r\n");
10959 assert_eq!(f.run(&[b"RENAME", b"social", b"net"]), "+OK\r\n");
10960 assert_eq!(f.run(&[b"MOVE", b"net", b"1"]), ":1\r\n");
10961 assert_eq!(f.run(&[b"EXISTS", b"net"]), ":0\r\n");
10962 f.run(&[b"SELECT", b"1"]);
10963 assert_eq!(f.run(&[b"G.DEG", b"net", b"ada", b"F"]), ":1\r\n");
10964
10965 assert_eq!(f.run(&[b"DEL", b"net"]), ":1\r\n");
10966 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
10967 f.run(&[b"G.NADD", b"g", b"n"]);
10968 assert_eq!(f.run(&[b"FLUSHDB"]), "+OK\r\n");
10969 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
10970 }
10971
10972 #[test]
10975 fn a_graph_cannot_be_copied_or_dumped() {
10976 let mut f = Fixture::new();
10977 f.run(&[b"G.NADD", b"social", b"ada"]);
10978 assert_eq!(
10979 f.run(&[b"COPY", b"social", b"other"]),
10980 "-ERR COPY is not supported for a graph\r\n"
10981 );
10982 assert_eq!(
10983 f.run(&[b"COPY", b"social", b"other", b"DB", b"1"]),
10984 "-ERR COPY is not supported for a graph\r\n"
10985 );
10986 assert_eq!(
10987 f.run(&[b"DUMP", b"social"]),
10988 "-ERR DUMP is not supported for a graph\r\n"
10989 );
10990 assert_eq!(f.run(&[b"EXISTS", b"social", b"other"]), ":1\r\n");
10992 }
10993
10994 #[test]
10997 fn a_graph_and_a_string_are_the_wrong_type_for_each_other() {
10998 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10999 let mut f = Fixture::new();
11000 f.run(&[b"G.NADD", b"social", b"ada"]);
11001 assert_eq!(f.run(&[b"GET", b"social"]), wrong);
11002 assert_eq!(f.run(&[b"LPUSH", b"social", b"x"]), wrong);
11003 assert_eq!(f.run(&[b"SADD", b"social", b"x"]), wrong);
11004
11005 f.run(&[b"SET", b"str", b"v"]);
11006 for cmd in [
11007 vec![b"G.NADD".as_ref(), b"str", b"n"],
11008 vec![b"G.NGET".as_ref(), b"str", b"n"],
11009 vec![b"G.NDEL".as_ref(), b"str", b"n"],
11010 vec![b"G.EADD".as_ref(), b"str", b"a", b"b", b"F"],
11011 vec![b"G.EDEL".as_ref(), b"str", b"a", b"b", b"F"],
11012 vec![b"G.OUT".as_ref(), b"str", b"a", b"F"],
11013 vec![b"G.IN".as_ref(), b"str", b"a", b"F"],
11014 vec![b"G.DEG".as_ref(), b"str", b"a", b"F"],
11015 vec![b"G.NEIGH".as_ref(), b"str", b"a", b"F"],
11016 vec![b"G.PATH".as_ref(), b"str", b"a", b"b"],
11017 ] {
11018 assert_eq!(f.run(&cmd), wrong, "{:?}", cmd[0]);
11019 }
11020 }
11021
11022 #[test]
11025 fn a_graph_goes_when_its_last_node_does() {
11026 let mut f = Fixture::new();
11027 f.run(&[
11028 b"G.EADD", b"social", b"ada", b"grace", b"F", b"since", b"1843",
11029 ]);
11030 assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":1\r\n");
11031 assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "$-1\r\n");
11033 assert_eq!(
11034 f.run(&[b"G.DEG", b"social", b"grace", b"F", b"IN"]),
11035 ":0\r\n"
11036 );
11037 assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":0\r\n");
11038 assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
11039
11040 assert_eq!(f.run(&[b"G.NDEL", b"social", b"grace"]), ":1\r\n");
11041 assert_eq!(f.run(&[b"EXISTS", b"social"]), ":0\r\n");
11042 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
11043 assert_eq!(f.run(&[b"G.NDEL", b"nokey", b"ada"]), ":0\r\n");
11044
11045 f.run(&[b"G.NADD", b"social", b"first"]);
11048 f.run(&[b"G.NADD", b"social", b"second"]);
11049 f.run(&[b"G.NDEL", b"social", b"first"]);
11050 f.run(&[b"G.EADD", b"social", b"third", b"second", b"F"]);
11051 assert_eq!(
11052 f.run(&[b"G.OUT", b"social", b"third", b"F"]),
11053 "*2\r\n$1\r\n0\r\n*1\r\n$6\r\nsecond\r\n"
11054 );
11055 }
11056
11057 #[test]
11062 fn a_json_path_answers_a_set_and_a_legacy_path_answers_a_value() {
11063 let mut f = Fixture::new();
11064 let doc = br#"{"a":1,"b":{"c":true}}"#;
11065 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$", doc]), "+OK\r\n");
11066 assert_eq!(
11069 f.run(&[b"JSON.GET", b"doc"]),
11070 bulk(r#"{"a":1,"b":{"c":true}}"#)
11071 );
11072 assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.a"]), bulk("[1]"));
11073 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("1"));
11074 assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$..c"]), bulk("[true]"));
11075 assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.nope"]), bulk("[]"));
11078 assert_eq!(
11079 f.run(&[b"JSON.GET", b"doc", b".nope"]),
11080 "-ERR Path does not exist\r\n"
11081 );
11082 assert_eq!(f.run(&[b"JSON.GET", b"nokey"]), "$-1\r\n");
11083 assert_eq!(f.run(&[b"TYPE", b"doc"]), "+ReJSON-RL\r\n");
11086 assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":1\r\n");
11087 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"doc"]), bulk("raw"));
11088 assert_eq!(f.run(&[b"DEL", b"doc"]), ":1\r\n");
11089 assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
11090 }
11091
11092 #[test]
11098 fn the_two_json_errors_that_carry_no_prefix() {
11099 let mut f = Fixture::new();
11100 f.run(&[b"SET", b"plain", b"x"]);
11101 let wrong = "-Existing key has wrong Redis type\r\n";
11102 assert_eq!(f.run(&[b"JSON.GET", b"plain"]), wrong);
11103 assert_eq!(f.run(&[b"JSON.SET", b"plain", b"$", b"1"]), wrong);
11104 assert_eq!(f.run(&[b"JSON.DEL", b"plain"]), wrong);
11105 assert_eq!(f.run(&[b"JSON.TYPE", b"plain"]), wrong);
11106 assert_eq!(f.run(&[b"JSON.CLEAR", b"plain"]), wrong);
11107
11108 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"z":1},"b":{"z":2}}"#]);
11109 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.*.z", b"9"]), "+OK\r\n");
11113 assert_eq!(
11114 f.run(&[b"JSON.GET", b"doc"]),
11115 bulk(r#"{"a":{"z":9},"b":{"z":9}}"#)
11116 );
11117 assert_eq!(
11118 f.run(&[b"JSON.SET", b"doc", b"$.*.y", b"9"]),
11119 "-Err wrong static path\r\n"
11120 );
11121 }
11122
11123 #[test]
11125 fn json_set_creates_one_field_and_refuses_to_invent_the_rest() {
11126 let mut f = Fixture::new();
11127 assert_eq!(
11129 f.run(&[b"JSON.SET", b"new", b".a", b"1"]),
11130 "-ERR new objects must be created at the root\r\n"
11131 );
11132 assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
11133 assert_eq!(
11136 f.run(&[b"JSON.SET", b"new", b".a", b"1", b"NX"]),
11137 "-ERR new objects must be created at the root\r\n"
11138 );
11139 assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"XX"]), "$-1\r\n");
11140 assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"NX"]), "+OK\r\n");
11141
11142 f.run(&[
11143 b"JSON.SET",
11144 b"doc",
11145 b"$",
11146 br#"{"o":{},"arr":[1,2],"s":"x"}"#,
11147 ]);
11148 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"1"]), "+OK\r\n");
11150 assert_eq!(
11153 f.run(&[b"JSON.SET", b"doc", b"$.nope.made", b"1"]),
11154 "$-1\r\n"
11155 );
11156 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.s.made", b"1"]), "$-1\r\n");
11157 assert_eq!(
11159 f.run(&[b"JSON.SET", b"doc", b"$.arr[5]", b"9"]),
11160 "-ERR array index out of range\r\n"
11161 );
11162 assert_eq!(
11163 f.run(&[b"JSON.SET", b"doc", b"$.arr[2]", b"9"]),
11164 "-ERR array index out of range\r\n"
11165 );
11166 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.arr[1]", b"9"]), "+OK\r\n");
11167 assert_eq!(
11170 f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"2", b"NX"]),
11171 "$-1\r\n"
11172 );
11173 assert_eq!(
11174 f.run(&[b"JSON.SET", b"doc", b"$.gone", b"2", b"XX"]),
11175 "$-1\r\n"
11176 );
11177 assert_eq!(
11178 f.run(&[b"JSON.GET", b"doc"]),
11179 bulk(r#"{"o":{"made":1},"s":"x","arr":[1,9]}"#)
11180 );
11181 assert!(
11185 f.run(&[b"JSON.SET", b"doc", b"$.s", b"nope"])
11186 .starts_with("-this is not the start of a value")
11187 );
11188 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".s"]), bulk("\"x\""));
11189 }
11190
11191 #[test]
11194 fn the_json_commands_that_do_not_answer_text() {
11195 let mut f = Fixture::new();
11196 let doc = br#"{"a":1,"t":true,"o":{"x":1},"arr":[1,2],"f":1.5,"s":"x","n":null}"#;
11197 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
11198
11199 assert_eq!(f.run(&[b"JSON.TYPE", b"doc"]), bulk("object"));
11200 assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".a"]), bulk("integer"));
11201 assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".f"]), bulk("number"));
11202 assert_eq!(
11203 f.run(&[b"JSON.TYPE", b"doc", b"$.a"]),
11204 format!("*1\r\n{}", bulk("integer"))
11205 );
11206 assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".nope"]), "$-1\r\n");
11209 assert_eq!(f.run(&[b"JSON.TYPE", b"nokey"]), "$-1\r\n");
11210
11211 assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.t"]), "*1\r\n:0\r\n");
11214 assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b".t"]), bulk("true"));
11215 assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.a"]), "*1\r\n$-1\r\n");
11218 assert_eq!(
11219 f.run(&[b"JSON.TOGGLE", b"doc", b".a"]),
11220 "-ERR Path does not exist or not a bool\r\n"
11221 );
11222 assert_eq!(
11223 f.run(&[b"JSON.TOGGLE", b"doc", b".nope"]),
11224 "-ERR Path does not exist or not a bool\r\n"
11225 );
11226 assert_eq!(
11227 f.run(&[b"JSON.TOGGLE", b"nokey", b"$.a"]),
11228 "-ERR could not perform this operation on a key that doesn't exist\r\n"
11229 );
11230
11231 assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.s"]), ":0\r\n");
11234 assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":4\r\n");
11235 assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":0\r\n");
11236 assert_eq!(
11237 f.run(&[b"JSON.GET", b"doc"]),
11238 bulk(r#"{"a":0,"f":0,"n":null,"o":{},"s":"x","t":true,"arr":[]}"#)
11239 );
11240
11241 assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.nope"]), ":0\r\n");
11244 assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.a"]), ":1\r\n");
11245 assert_eq!(f.run(&[b"JSON.FORGET", b"doc", b"$.*"]), ":6\r\n");
11250 assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":0\r\n");
11251 assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
11252 assert_eq!(f.run(&[b"JSON.DEL", b"doc"]), ":0\r\n");
11253 assert_eq!(f.run(&[b"JSON.SET", b"empty", b"$", b"{}"]), "+OK\r\n");
11254 assert_eq!(f.run(&[b"EXISTS", b"empty"]), ":1\r\n");
11255 assert_eq!(f.run(&[b"JSON.GET", b"empty"]), bulk("{}"));
11256 assert_eq!(f.run(&[b"JSON.DEL", b"nokey"]), ":0\r\n");
11257 }
11258
11259 #[test]
11266 fn json_get_lays_out_the_wrapper_it_builds() {
11267 let mut f = Fixture::new();
11268 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[1,{"c":2}]}"#]);
11269
11270 assert_eq!(
11271 f.run(&[b"JSON.GET", b"doc", b"$.a", b"$.b"]),
11272 bulk(r#"{"$.a":[1],"$.b":[[1,{"c":2}]]}"#)
11273 );
11274 assert_eq!(
11276 f.run(&[b"JSON.GET", b"doc", b".a", b".b"]),
11277 bulk(r#"{".a":1,".b":[1,{"c":2}]}"#)
11278 );
11279 let fmt: &[&[u8]] = &[b"INDENT", b" ", b"NEWLINE", b"\n", b"SPACE", b" "];
11280 let mut one = vec![b"JSON.GET".as_slice(), b"doc"];
11281 one.extend_from_slice(fmt);
11282 one.push(b"$.b");
11283 assert_eq!(
11284 f.run(&one),
11285 bulk("[\n [\n 1,\n {\n \"c\": 2\n }\n ]\n]")
11286 );
11287 let mut two = vec![b"JSON.GET".as_slice(), b"doc"];
11288 two.extend_from_slice(fmt);
11289 two.push(b"$.a");
11290 two.push(b"$.nope");
11291 assert_eq!(
11292 f.run(&two),
11293 bulk("{\n \"$.a\": [\n 1\n ],\n \"$.nope\": []\n}")
11294 );
11295 let mut root = vec![b"JSON.GET".as_slice(), b"doc", b"SPACE", b" "];
11298 root.push(b".a");
11299 assert_eq!(f.run(&root), bulk("1"));
11300 }
11301
11302 #[test]
11305 fn json_mget_answers_once_per_key_whatever_is_under_them() {
11306 let mut f = Fixture::new();
11307 f.run(&[b"JSON.SET", b"one", b"$", br#"{"a":1}"#]);
11308 f.run(&[b"JSON.SET", b"two", b"$", br#"{"a":2}"#]);
11309 f.run(&[b"SET", b"plain", b"x"]);
11310 assert_eq!(
11311 f.run(&[b"JSON.MGET", b"one", b"two", b"$.a"]),
11312 format!("*2\r\n{}{}", bulk("[1]"), bulk("[2]"))
11313 );
11314 assert_eq!(
11317 f.run(&[b"JSON.MGET", b"one", b"nokey", b"plain", b".a"]),
11318 format!("*3\r\n{}$-1\r\n$-1\r\n", bulk("1"))
11319 );
11320 assert_eq!(f.run(&[b"JSON.MGET", b"one", b".nope"]), "*1\r\n$-1\r\n");
11323 }
11324
11325 #[test]
11333 fn the_json_commands_that_answer_a_size_disagree_about_every_failure() {
11334 let mut f = Fixture::new();
11335 let doc = br#"{"a":[1,2,3],"o":{"x":1,"y":2},"s":"hello","n":7}"#;
11336 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
11337
11338 assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b".a"]), ":3\r\n");
11339 assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b"$.a"]), "*1\r\n:3\r\n");
11340 assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".o"]), ":2\r\n");
11341 assert_eq!(f.run(&[b"JSON.STRLEN", b"doc", b".s"]), ":5\r\n");
11342 assert_eq!(
11343 f.run(&[b"JSON.OBJKEYS", b"doc", b".o"]),
11344 format!("*2\r\n{}{}", bulk("x"), bulk("y"))
11345 );
11346 assert_eq!(
11349 f.run(&[b"JSON.ARRLEN", b"doc", b"$.*"]),
11350 "*4\r\n:3\r\n$-1\r\n$-1\r\n$-1\r\n"
11351 );
11352
11353 assert_eq!(
11356 f.run(&[b"JSON.ARRLEN", b"doc", b".nope"]),
11357 "-ERR Path does not exist\r\n"
11358 );
11359 assert_eq!(
11360 f.run(&[b"JSON.STRLEN", b"doc", b".nope"]),
11361 "-ERR Path does not exist\r\n"
11362 );
11363 assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".nope"]), "$-1\r\n");
11364 assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b".nope"]), "$-1\r\n");
11367 assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b"$.nope"]), "*0\r\n");
11370
11371 assert_eq!(
11375 f.run(&[b"JSON.ARRLEN", b"doc", b".n"]),
11376 "-ERR Path does not exist or not an array\r\n"
11377 );
11378 assert_eq!(
11379 f.run(&[b"JSON.OBJKEYS", b"doc", b".n"]),
11380 "-ERR Path does not exist or not an object\r\n"
11381 );
11382 assert_eq!(
11383 f.run(&[b"JSON.OBJLEN", b"doc", b".n"]),
11384 "-WRONGTYPE wrong type of path value - expected object\r\n"
11385 );
11386 assert_eq!(
11387 f.run(&[b"JSON.STRLEN", b"doc", b".n"]),
11388 "-WRONGTYPE wrong type of path value - expected string\r\n"
11389 );
11390
11391 assert_eq!(f.run(&[b"JSON.ARRLEN", b"nokey", b".a"]), "$-1\r\n");
11394 assert_eq!(f.run(&[b"JSON.OBJLEN", b"nokey", b".a"]), "$-1\r\n");
11395 assert_eq!(f.run(&[b"JSON.STRLEN", b"nokey", b".a"]), "$-1\r\n");
11396 assert_eq!(f.run(&[b"JSON.OBJKEYS", b"nokey", b".a"]), "$-1\r\n");
11397 assert_eq!(
11398 f.run(&[b"JSON.ARRLEN", b"nokey", b"$.a"]),
11399 "-ERR could not perform this operation on a key that doesn't exist\r\n"
11400 );
11401 assert_eq!(
11403 f.run(&[b"JSON.OBJLEN", b"nokey", b"$.a"]),
11404 "-ERR Path does not exist or not an object\r\n"
11405 );
11406 }
11407
11408 #[test]
11414 fn the_json_array_writes_agree_on_the_errors_and_not_on_the_indexes() {
11415 let mut f = Fixture::new();
11416 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3],"n":7}"#]);
11417
11418 assert_eq!(f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"4"]), ":4\r\n");
11419 assert_eq!(
11420 f.run(&[b"JSON.ARRAPPEND", b"doc", b"$.a", b"5", b"6"]),
11421 "*1\r\n:6\r\n"
11422 );
11423 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[1,2,3,4,5,6]"));
11424
11425 assert_eq!(
11428 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-1", b"0"]),
11429 ":7\r\n"
11430 );
11431 assert_eq!(
11432 f.run(&[b"JSON.GET", b"doc", b".a"]),
11433 bulk("[1,2,3,4,5,0,6]")
11434 );
11435 assert_eq!(
11436 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"7", b"9"]),
11437 ":8\r\n"
11438 );
11439 assert_eq!(
11441 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"9", b"9"]),
11442 "-ERR index out of bounds\r\n"
11443 );
11444 assert_eq!(
11445 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-9", b"9"]),
11446 "-ERR index out of bounds\r\n"
11447 );
11448
11449 f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3,4,5]"]);
11452 assert_eq!(
11453 f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"1", b"3"]),
11454 ":3\r\n"
11455 );
11456 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[2,3,4]"));
11457 assert_eq!(
11458 f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"-2", b"99"]),
11459 ":2\r\n"
11460 );
11461 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[3,4]"));
11462 assert_eq!(
11463 f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"9", b"9"]),
11464 ":0\r\n"
11465 );
11466 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
11467
11468 f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3]"]);
11471 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), bulk("3"));
11472 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"0"]), bulk("1"));
11473 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"99"]), bulk("2"));
11474 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), "$-1\r\n");
11475
11476 for call in [
11479 &[&b"JSON.ARRAPPEND"[..], b"doc", b"PATH", b"1"][..],
11480 &[&b"JSON.ARRTRIM"[..], b"doc", b"PATH", b"1", b"1"][..],
11481 &[&b"JSON.ARRPOP"[..], b"doc", b"PATH", b"1"][..],
11482 &[&b"JSON.ARRINSERT"[..], b"doc", b"PATH", b"0", b"1"][..],
11483 ] {
11484 for path in [&b".n"[..], &b".nope"[..]] {
11485 let args: Vec<&[u8]> = call
11486 .iter()
11487 .map(|a| if *a == b"PATH" { path } else { *a })
11488 .collect();
11489 assert_eq!(
11490 f.run(&args),
11491 "-ERR Path does not exist or not an array\r\n",
11492 "{} {}",
11493 String::from_utf8_lossy(call[0]),
11494 String::from_utf8_lossy(path)
11495 );
11496 }
11497 }
11498
11499 assert_eq!(
11502 f.run(&[b"JSON.ARRAPPEND", b"nokey", b".a", b"1"]),
11503 "-ERR could not perform this operation on a key that doesn't exist\r\n"
11504 );
11505 assert_eq!(
11506 f.run(&[b"JSON.ARRPOP", b"nokey", b"$.a"]),
11507 "-ERR could not perform this operation on a key that doesn't exist\r\n"
11508 );
11509
11510 assert!(
11515 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"nope"])
11516 .starts_with("-this is not the start of a value")
11517 );
11518 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
11519 }
11520
11521 #[test]
11529 fn json_arrinsert_is_all_or_nothing_across_the_matches() {
11530 let mut f = Fixture::new();
11531 let doc = br#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#;
11532 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
11533 assert_eq!(
11534 f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"-2", b"0"]),
11535 "-ERR index out of bounds\r\n"
11536 );
11537 assert_eq!(
11538 f.run(&[b"JSON.GET", b"doc"]),
11539 bulk(r#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#)
11540 );
11541 assert_eq!(
11543 f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"0", b"0"]),
11544 "*3\r\n:4\r\n:3\r\n:2\r\n"
11545 );
11546 assert_eq!(
11547 f.run(&[b"JSON.GET", b"doc"]),
11548 bulk(r#"{"a":[0,1,2,3],"n":{"a":[0,9,8],"in":{"a":[0,1]}}}"#)
11549 );
11550 }
11551
11552 #[test]
11560 fn json_arrindex_has_an_exclusive_stop_and_a_start_that_cannot_run_off_the_end() {
11561 let mut f = Fixture::new();
11562 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3,1],"n":7}"#]);
11563
11564 assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"2"]), ":1\r\n");
11565 assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"9"]), ":-1\r\n");
11566 assert_eq!(
11567 f.run(&[b"JSON.ARRINDEX", b"doc", b"$.a", b"2"]),
11568 "*1\r\n:1\r\n"
11569 );
11570
11571 assert_eq!(
11574 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"0"]),
11575 ":3\r\n"
11576 );
11577 assert_eq!(
11580 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"3"]),
11581 ":-1\r\n"
11582 );
11583
11584 for start in [&b"4"[..], &b"5"[..], &b"-1"[..]] {
11587 assert_eq!(
11588 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", start]),
11589 ":3\r\n",
11590 "{}",
11591 String::from_utf8_lossy(start)
11592 );
11593 }
11594 assert_eq!(
11595 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"-100"]),
11596 ":0\r\n"
11597 );
11598 f.run(&[b"JSON.SET", b"doc", b"$.a", b"[]"]);
11601 assert_eq!(
11602 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1"]),
11603 ":-1\r\n"
11604 );
11605
11606 f.run(&[b"JSON.SET", b"doc", b"$.a", br#"[{"k":1},[1,2],"s"]"#]);
11610 assert_eq!(
11611 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", br#"{"k":1}"#]),
11612 ":0\r\n"
11613 );
11614 assert_eq!(
11615 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[1,2]"]),
11616 ":1\r\n"
11617 );
11618 assert_eq!(
11619 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[2,1]"]),
11620 ":-1\r\n"
11621 );
11622
11623 assert_eq!(
11627 f.run(&[b"JSON.ARRINDEX", b"doc", b".nope", b"1"]),
11628 "-ERR Path does not exist\r\n"
11629 );
11630 assert_eq!(
11631 f.run(&[b"JSON.ARRINDEX", b"doc", b".n", b"1"]),
11632 "-WRONGTYPE wrong type of path value - expected array\r\n"
11633 );
11634 assert_eq!(
11635 f.run(&[b"JSON.ARRINDEX", b"nokey", b".a", b"1"]),
11636 "-ERR Path does not exist\r\n"
11637 );
11638 assert_eq!(
11639 f.run(&[b"JSON.ARRINDEX", b"nokey", b"$.a", b"1"]),
11640 "-ERR Path does not exist\r\n"
11641 );
11642 }
11643
11644 #[test]
11647 fn the_json_number_family_answers_json_text_and_keeps_its_integers() {
11648 let mut f = Fixture::new();
11649 let doc = br#"{"i":7,"f":1.5,"neg":-2,"s":"ab"}"#;
11650 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
11651
11652 assert_eq!(
11655 f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2"]),
11656 bulk("9").as_str()
11657 );
11658 assert_eq!(
11660 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.i", b"2"]),
11661 bulk("[11]").as_str()
11662 );
11663 assert_eq!(
11666 f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2.0"]),
11667 bulk("13.0").as_str()
11668 );
11669 assert_eq!(
11670 f.run(&[b"JSON.TYPE", b"doc", b".i"]),
11671 bulk("number").as_str()
11672 );
11673 assert_eq!(
11674 f.run(&[b"JSON.NUMMULTBY", b"doc", b".f", b"2"]),
11675 bulk("3.0").as_str()
11676 );
11677 assert_eq!(
11678 f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"3"]),
11679 bulk("-8").as_str()
11680 );
11681 f.run(&[b"JSON.SET", b"doc", b"$.f", b"1.5"]);
11684 assert_eq!(
11685 f.run(&[b"JSON.NUMPOWBY", b"doc", b".f", b"0.5"]),
11686 bulk("1.224744871391589").as_str()
11687 );
11688 assert_eq!(
11689 f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"0.5"]),
11690 "-ERR result is not a number\r\n"
11691 );
11692 f.run(&[b"JSON.SET", b"doc", b"$.big", b"9223372036854775807"]);
11696 assert_eq!(
11697 f.run(&[b"JSON.NUMINCRBY", b"doc", b".big", b"1"]),
11698 "-ERR numeric overflow\r\n"
11699 );
11700 f.run(&[b"JSON.SET", b"doc", b"$.p", b"2"]);
11701 assert_eq!(
11702 f.run(&[b"JSON.NUMPOWBY", b"doc", b".p", b"-1"]),
11703 "-ERR numeric overflow\r\n"
11704 );
11705 f.run(&[b"JSON.SET", b"doc", b"$.huge", b"1e308"]);
11707 assert_eq!(
11708 f.run(&[b"JSON.NUMMULTBY", b"doc", b".huge", b"1e10"]),
11709 "-ERR result is not a number\r\n"
11710 );
11711
11712 assert_eq!(
11716 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"1"]),
11717 bulk("[null]").as_str()
11718 );
11719 assert_eq!(
11720 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.nope", b"1"]),
11721 bulk("[]").as_str()
11722 );
11723 assert_eq!(
11724 f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", b"1"]),
11725 "-ERR Path does not exist or does not contains a number\r\n"
11726 );
11727 assert_eq!(
11728 f.run(&[b"JSON.NUMINCRBY", b"doc", b".nope", b"1"]),
11729 "-ERR Path does not exist or does not contains a number\r\n"
11730 );
11731 assert_eq!(
11734 f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"true"]),
11735 "-bad input number\r\n"
11736 );
11737 assert_eq!(
11738 f.run(&[b"JSON.NUMINCRBY", b"nokey", b".i", b"1"]),
11739 "-ERR could not perform this operation on a key that doesn't exist\r\n"
11740 );
11741 assert_eq!(
11742 f.run(&[b"JSON.NUMINCRBY", b"nokey", b"$.i", b"1"]),
11743 "-ERR could not perform this operation on a key that doesn't exist\r\n"
11744 );
11745 }
11746
11747 #[test]
11750 fn json_strappend_reads_its_shape_off_the_argument_count() {
11751 let mut f = Fixture::new();
11752 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"s":"ab","n":1}"#]);
11753
11754 assert_eq!(
11755 f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""c""#]),
11756 ":3\r\n"
11757 );
11758 assert_eq!(
11759 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", br#""d""#]),
11760 "*1\r\n:4\r\n"
11761 );
11762 assert_eq!(
11765 f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""\u00e9""#]),
11766 ":6\r\n"
11767 );
11768 f.run(&[b"JSON.SET", b"str", b"$", br#""ab""#]);
11771 assert_eq!(f.run(&[b"JSON.STRAPPEND", b"str", br#""c""#]), ":3\r\n");
11772 assert_eq!(f.run(&[b"JSON.GET", b"str"]), bulk("\"abc\"").as_str());
11773
11774 assert_eq!(
11778 f.run(&[b"JSON.STRAPPEND", b"doc", b".s", b"5"]),
11779 "-WRONGTYPE wrong type of path value - expected string\r\n"
11780 );
11781 assert_eq!(
11782 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", br#""c""#]),
11783 "*1\r\n$-1\r\n"
11784 );
11785 assert_eq!(
11786 f.run(&[b"JSON.STRAPPEND", b"doc", b".n", br#""c""#]),
11787 "-ERR Path does not exist or not a string\r\n"
11788 );
11789 assert_eq!(
11790 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.nope", br#""c""#]),
11791 "*0\r\n"
11792 );
11793 assert_eq!(
11794 f.run(&[b"JSON.STRAPPEND", b"nokey", br#""c""#]),
11795 "-ERR could not perform this operation on a key that doesn't exist\r\n"
11796 );
11797 }
11798
11799 #[test]
11802 fn a_legacy_wildcard_write_touches_every_match_and_answers_only_one() {
11803 let mut f = Fixture::new();
11804 let three = br#"{"a":[[7],[7,7],[7,7,7]]}"#;
11807
11808 f.run(&[b"JSON.SET", b"doc", b"$", three]);
11809 assert_eq!(
11810 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
11811 ":4\r\n"
11812 );
11813 f.run(&[b"JSON.SET", b"doc", b"$", three]);
11814 assert_eq!(
11815 f.run(&[b"JSON.ARRINSERT", b"doc", b".a[*]", b"0", b"9"]),
11816 ":2\r\n"
11817 );
11818 f.run(&[b"JSON.SET", b"doc", b"$", three]);
11819 assert_eq!(
11820 f.run(&[b"JSON.ARRTRIM", b"doc", b".a[*]", b"0", b"1"]),
11821 ":1\r\n"
11822 );
11823 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[1,2,3],[4,5,6]]}"#]);
11824 assert_eq!(
11825 f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]", b"0"]),
11826 bulk("1").as_str()
11827 );
11828 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3]}"#]);
11829 assert_eq!(
11830 f.run(&[b"JSON.NUMINCRBY", b"doc", b".a[*]", b"10"]),
11831 bulk("13").as_str()
11832 );
11833 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["p","qq","rrr"]}"#]);
11834 assert_eq!(
11835 f.run(&[b"JSON.STRAPPEND", b"doc", b".a[*]", br#""z""#]),
11836 ":4\r\n"
11837 );
11838 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[true,false,true]}"#]);
11839 assert_eq!(
11840 f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
11841 bulk("false").as_str()
11842 );
11843 assert_eq!(
11846 f.run(&[b"JSON.GET", b"doc", b".a"]),
11847 bulk("[false,true,false]").as_str()
11848 );
11849
11850 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x",[1],[1,2]]}"#]);
11853 assert_eq!(
11854 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
11855 ":3\r\n"
11856 );
11857 assert_eq!(
11858 f.run(&[b"JSON.GET", b"doc", b".a"]),
11859 bulk(r#"["x",[1,9],[1,2,9]]"#).as_str()
11860 );
11861 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x","y"]}"#]);
11864 assert_eq!(
11865 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
11866 "-ERR Path does not exist or not an array\r\n"
11867 );
11868 assert_eq!(
11869 f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
11870 "-ERR Path does not exist or not a bool\r\n"
11871 );
11872 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[],[2,3]]}"#]);
11876 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]"]), "$-1\r\n");
11877 assert_eq!(
11878 f.run(&[b"JSON.GET", b"doc", b".a"]),
11879 bulk("[[],[2]]").as_str()
11880 );
11881 }
11882
11883 #[test]
11886 fn a_write_reaches_a_match_that_sits_inside_another_match() {
11887 let mut f = Fixture::new();
11888 let nested = br#"{"a":[{"a":[7]},{"a":[7,7]}]}"#;
11889
11890 f.run(&[b"JSON.SET", b"doc", b"$", nested]);
11891 assert_eq!(
11892 f.run(&[b"JSON.ARRAPPEND", b"doc", b"$..a", b"9"]),
11893 "*3\r\n:3\r\n:2\r\n:3\r\n"
11894 );
11895 assert_eq!(
11896 f.run(&[b"JSON.GET", b"doc", b"$"]),
11897 bulk(r#"[{"a":[{"a":[7,9]},{"a":[7,7,9]},9]}]"#).as_str()
11898 );
11899
11900 f.run(&[b"JSON.SET", b"doc", b"$", nested]);
11903 assert_eq!(
11904 f.run(&[b"JSON.ARRTRIM", b"doc", b"$..a", b"0", b"0"]),
11905 "*3\r\n:1\r\n:1\r\n:1\r\n"
11906 );
11907 assert_eq!(
11908 f.run(&[b"JSON.GET", b"doc", b"$"]),
11909 bulk(r#"[{"a":[{"a":[7]}]}]"#).as_str()
11910 );
11911
11912 f.run(&[b"JSON.SET", b"doc", b"$", nested]);
11915 assert_eq!(
11916 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$..a[0]", b"1"]),
11917 bulk("[null,8,8]").as_str()
11918 );
11919 }
11920
11921 #[test]
11924 fn a_bad_operand_is_not_the_answer_when_the_path_found_nothing_to_use_it_on() {
11925 let mut f = Fixture::new();
11926 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"n":7,"s":"t"}"#]);
11927
11928 assert_eq!(
11931 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", br#""x""#]),
11932 bulk("[null]").as_str()
11933 );
11934 assert_eq!(
11935 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"notjson"]),
11936 bulk("[null]").as_str()
11937 );
11938 assert_eq!(
11939 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.missing", b"notjson"]),
11940 bulk("[]").as_str()
11941 );
11942 assert_eq!(
11943 f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", br#""x""#]),
11944 "-ERR Path does not exist or does not contains a number\r\n"
11945 );
11946 assert_eq!(
11948 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.n", br#""x""#]),
11949 "-bad input number\r\n"
11950 );
11951
11952 assert_eq!(
11954 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", b"1"]),
11955 "*1\r\n$-1\r\n"
11956 );
11957 assert_eq!(
11958 f.run(&[b"JSON.STRAPPEND", b"doc", b".n", b"1"]),
11959 "-ERR Path does not exist or not a string\r\n"
11960 );
11961 assert_eq!(
11962 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", b"1"]),
11963 "-WRONGTYPE wrong type of path value - expected string\r\n"
11964 );
11965
11966 assert_eq!(
11968 f.run(&[b"JSON.NUMINCRBY", b"nope", b"$.a", br#""x""#]),
11969 "-ERR could not perform this operation on a key that doesn't exist\r\n"
11970 );
11971 assert_eq!(
11972 f.run(&[b"JSON.STRAPPEND", b"nope", b"$.a", b"1"]),
11973 "-ERR could not perform this operation on a key that doesn't exist\r\n"
11974 );
11975 }
11976
11977 #[test]
11980 fn a_merge_patch_adds_replaces_and_deletes_in_one_write() {
11981 let mut f = Fixture::new();
11982
11983 assert_eq!(
11987 f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"x":null,"y":1}"#]),
11988 "+OK\r\n"
11989 );
11990 assert_eq!(
11991 f.run(&[b"JSON.GET", b"doc", b"$"]),
11992 bulk(r#"[{"x":null,"y":1}]"#).as_str()
11993 );
11994
11995 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1,"c":2},"d":3}"#]);
11998 assert_eq!(
11999 f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"a":{"b":null,"e":4}}"#]),
12000 "+OK\r\n"
12001 );
12002 assert_eq!(
12003 f.run(&[b"JSON.GET", b"doc", b"$"]),
12004 bulk(r#"[{"a":{"c":2,"e":4},"d":3}]"#).as_str()
12005 );
12006
12007 assert_eq!(f.run(&[b"JSON.MERGE", b"doc", b"$.a", b"[1,2]"]), "+OK\r\n");
12009 assert_eq!(
12010 f.run(&[b"JSON.GET", b"doc", b"$"]),
12011 bulk(r#"[{"a":[1,2],"d":3}]"#).as_str()
12012 );
12013
12014 assert_eq!(
12018 f.run(&[b"JSON.MERGE", b"doc", b"$.d", br#"{"p":null,"q":9}"#]),
12019 "+OK\r\n"
12020 );
12021 assert_eq!(
12022 f.run(&[b"JSON.GET", b"doc", b"$"]),
12023 bulk(r#"[{"a":[1,2],"d":{"q":9}}]"#).as_str()
12024 );
12025
12026 assert_eq!(
12030 f.run(&[b"JSON.MERGE", b"doc", b"$.new", br#"{"z":null}"#]),
12031 "+OK\r\n"
12032 );
12033 assert_eq!(
12034 f.run(&[b"JSON.GET", b"doc", b"$.new"]),
12035 bulk(r#"[{"z":null}]"#).as_str()
12036 );
12037 assert_eq!(
12038 f.run(&[b"JSON.MERGE", b"doc", b"$.no.deep", b"1"]),
12039 "$-1\r\n"
12040 );
12041 assert_eq!(
12042 f.run(&[b"JSON.MERGE", b"doc", b"$.no.*", b"1"]),
12043 "-Err wrong static path\r\n"
12044 );
12045
12046 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"n":1},"b":{"n":2}}"#]);
12048 assert_eq!(
12049 f.run(&[b"JSON.MERGE", b"doc", b"$.*", br#"{"m":0}"#]),
12050 "+OK\r\n"
12051 );
12052 assert_eq!(
12053 f.run(&[b"JSON.GET", b"doc", b"$"]),
12054 bulk(r#"[{"a":{"m":0,"n":1},"b":{"m":0,"n":2}}]"#).as_str()
12055 );
12056
12057 assert_eq!(
12059 f.run(&[b"JSON.MERGE", b"doc", b"$", b"{}", b"more"]),
12060 "-ERR syntax error\r\n"
12061 );
12062 assert_eq!(
12063 f.run(&[b"JSON.MERGE", b"gone", b"$.a", b"1"]),
12064 "-ERR new objects must be created at the root\r\n"
12065 );
12066 f.run(&[b"SET", b"str", b"x"]);
12067 assert_eq!(
12068 f.run(&[b"JSON.MERGE", b"str", b"$", b"1"]),
12069 "-Existing key has wrong Redis type\r\n"
12070 );
12071 }
12072
12073 #[test]
12076 fn a_merge_down_a_descent_keeps_what_the_inner_match_did() {
12077 let mut f = Fixture::new();
12078 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
12079 assert_eq!(
12080 f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"m":1}"#]),
12081 "+OK\r\n"
12082 );
12083 assert_eq!(
12087 f.run(&[b"JSON.GET", b"doc", b"$"]),
12088 bulk(r#"[{"a":{"b":{"m":1},"m":1},"c":{"m":1}}]"#).as_str()
12089 );
12090
12091 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
12094 assert_eq!(
12095 f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"a":null}"#]),
12096 "+OK\r\n"
12097 );
12098 assert_eq!(
12099 f.run(&[b"JSON.GET", b"doc", b"$"]),
12100 bulk(r#"[{"a":{"b":{}},"c":{}}]"#).as_str()
12101 );
12102 }
12103
12104 #[test]
12107 fn a_filter_path_reads_and_writes_the_members_it_keeps() {
12108 let mut f = Fixture::new();
12109 let doc = br#"{"book":[{"t":"a","p":8},{"t":"b","p":13},{"t":"c","p":9}],"cap":10}"#;
12110 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
12111
12112 assert_eq!(
12113 f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < 10)].t"]),
12114 bulk(r#"["a","c"]"#).as_str()
12115 );
12116 assert_eq!(
12119 f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < $.cap)].t"]),
12120 bulk(r#"["a","c"]"#).as_str()
12121 );
12122 assert_eq!(
12124 f.run(&[b"JSON.GET", b"doc", b"book[?(@.p < 10)].t"]),
12125 bulk(r#""a""#).as_str()
12126 );
12127 assert_eq!(
12128 f.run(&[b"JSON.TYPE", b"doc", b"$.book[?(@.p > 10)]"]),
12129 "*1\r\n$6\r\nobject\r\n"
12130 );
12131
12132 assert_eq!(
12136 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.book[?(@.p < 10)].p", b"1"]),
12137 bulk("[9,10]").as_str()
12138 );
12139 assert_eq!(
12140 f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].t", br#""B""#]),
12141 "+OK\r\n"
12142 );
12143 assert_eq!(
12144 f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].n", b"1"]),
12145 "-Err wrong static path\r\n"
12146 );
12147 assert_eq!(
12148 f.run(&[b"JSON.DEL", b"doc", b"$.book[?(@.p > 9)]"]),
12149 ":2\r\n"
12150 );
12151 assert_eq!(
12152 f.run(&[b"JSON.GET", b"doc", b"$"]),
12153 bulk(r#"[{"cap":10,"book":[{"p":9,"t":"a"}]}]"#).as_str()
12154 );
12155
12156 assert!(
12159 f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p <)]"])
12160 .starts_with("-ERR")
12161 );
12162 assert!(
12163 f.run(&[b"JSON.GET", b"nokey", b"$.book[?(@.p <)]"])
12164 .starts_with("-ERR")
12165 );
12166 }
12167
12168 #[test]
12171 fn a_filter_takes_the_membership_operators_and_the_methods_too() {
12172 let mut f = Fixture::new();
12173 let doc = br#"{"box":[{"t":"a","n":[1,2],"g":"x"},{"t":"b","n":[9],"g":"y"}]}"#;
12174 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
12175
12176 for (path, want) in [
12177 (&b"$.box[?(@.g in [\"x\"])].t"[..], r#"["a"]"#),
12178 (b"$.box[?(@.g nin [\"x\"])].t", r#"["b"]"#),
12179 (b"$.box[?(@.n anyof [2,3])].t", r#"["a"]"#),
12180 (b"$.box[?(@.n subsetof [1,2,3])].t", r#"["a"]"#),
12181 (b"$.box[?(@.n size 2)].t", r#"["a"]"#),
12182 (b"$.box[?(@.n empty false)].t", r#"["a","b"]"#),
12183 (b"$.box[?(@.n.length() == 1)].t", r#"["b"]"#),
12184 (b"$.box[?(@.n.sum() > 5)].t", r#"["b"]"#),
12185 (b"$.box[?(@.n[0] + 1 == 2)].t", r#"["a"]"#),
12186 (b"$.box[?(@~ size 3)].t", r#"["a","b"]"#),
12187 (b"$.box[?(@.n~)].t", "[]"),
12188 (b"$.box[?(@.n sizeof 2)].t", r#"["a"]"#),
12189 (b"$.box[?(-@.n[0] == -9)].t", r#"["b"]"#),
12190 (b"$.box[?(1 in @.n)].t", r#"["a"]"#),
12191 (b"$.box[?(\"g\" in @~)].t", r#"["a","b"]"#),
12192 ] {
12193 assert_eq!(f.run(&[b"JSON.GET", b"doc", path]), bulk(want).as_str());
12194 }
12195
12196 assert_eq!(
12199 f.run(&[b"JSON.SET", b"doc", b"$.box[?(@.n size 1)].g", br#""z""#]),
12200 "+OK\r\n"
12201 );
12202 assert_eq!(
12203 f.run(&[b"JSON.GET", b"doc", b"$.box[?(@.g == \"z\")].t"]),
12204 bulk(r#"["b"]"#).as_str()
12205 );
12206 }
12207
12208 #[test]
12211 fn a_merge_onto_a_number_inside_an_array_is_a_merge_and_not_an_error() {
12212 let mut f = Fixture::new();
12213 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2]}"#]);
12214 assert_eq!(
12215 f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
12216 "+OK\r\n"
12217 );
12218 assert_eq!(
12219 f.run(&[b"JSON.GET", b"doc", b"$"]),
12220 bulk(r#"[{"a":[{"x":1},2]}]"#).as_str()
12221 );
12222 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,"s"]}"#]);
12225 assert_eq!(
12226 f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
12227 "+OK\r\n"
12228 );
12229 assert_eq!(
12230 f.run(&[b"JSON.GET", b"doc", b"$"]),
12231 bulk(r#"[{"a":[{"x":1},"s"]}]"#).as_str()
12232 );
12233 }
12234
12235 #[test]
12238 fn an_mset_writes_every_triple_it_can_and_checks_the_rest_up_front() {
12239 let mut f = Fixture::new();
12240 assert_eq!(
12241 f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b", b"$", b"2"]),
12242 "+OK\r\n"
12243 );
12244 assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[1]").as_str());
12245 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[2]").as_str());
12246
12247 assert_eq!(
12249 f.run(&[b"JSON.MSET", b"a", b"$", b"3", b"a", b"$", b"4"]),
12250 "+OK\r\n"
12251 );
12252 assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[4]").as_str());
12253
12254 f.run(&[b"JSON.SET", b"a", b"$", br#"{"n":1}"#]);
12259 assert_eq!(
12260 f.run(&[b"JSON.MSET", b"a", b"$.no.deep", b"9", b"b", b"$", b"5"]),
12261 "$-1\r\n"
12262 );
12263 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[5]").as_str());
12264 assert_eq!(
12265 f.run(&[b"JSON.MSET", b"b", b"$", b"6", b"a", b"$.no.deep", b"9"]),
12266 "$-1\r\n"
12267 );
12268 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
12269
12270 f.run(&[b"SET", b"str", b"x"]);
12275 assert_eq!(
12276 f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"b", b"$", b"notjson"]),
12277 "-this is not the start of a value, at byte 0 of the JSON text\r\n"
12278 );
12279 assert_eq!(
12280 f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"str", b"$", b"1"]),
12281 "-Existing key has wrong Redis type\r\n"
12282 );
12283 assert_eq!(
12284 f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"gone", b"$.x", b"1"]),
12285 "-ERR new objects must be created at the root\r\n"
12286 );
12287 assert_eq!(f.run(&[b"JSON.GET", b"a", b"$.n"]), bulk("[1]").as_str());
12288
12289 assert_eq!(
12294 f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$.no.*", b"9"]),
12295 "-Err wrong static path\r\n"
12296 );
12297 assert_eq!(
12298 f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$[0]", b"9"]),
12299 "-ERR array index out of range\r\n"
12300 );
12301 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
12302
12303 f.run(&[b"JSON.SET", b"c", b"$", br#"{"n":1}"#]);
12307 assert_eq!(
12308 f.run(&[b"JSON.MSET", b"c", b"$", br#"{"n":2}"#, b"c", b"$.n", b"3"]),
12309 "+OK\r\n"
12310 );
12311 assert_eq!(
12312 f.run(&[b"JSON.GET", b"c", b"$"]),
12313 bulk(r#"[{"n":3}]"#).as_str()
12314 );
12315
12316 assert_eq!(
12319 f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b"]),
12320 "-ERR wrong number of arguments for 'json.mset' command\r\n"
12321 );
12322 }
12323
12324 #[test]
12327 fn json_resp_answers_the_document_as_resp_types() {
12328 let mut f = Fixture::new();
12329 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[2,"c"]}"#]);
12330 assert_eq!(
12331 f.run(&[b"JSON.RESP", b"doc"]),
12332 "*5\r\n+{\r\n$1\r\na\r\n:1\r\n$1\r\nb\r\n*3\r\n+[\r\n:2\r\n$1\r\nc\r\n"
12333 );
12334 assert_eq!(
12336 f.run(&[b"JSON.RESP", b"doc", b"$.b"]),
12337 "*1\r\n*3\r\n+[\r\n:2\r\n$1\r\nc\r\n"
12338 );
12339
12340 f.run(&[
12341 b"JSON.SET",
12342 b"doc",
12343 b"$",
12344 br#"{"f":2.5,"t":true,"z":null,"e":[],"o":{}}"#,
12345 ]);
12346 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".e"]), "*1\r\n+[\r\n");
12347 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".o"]), "*1\r\n+{\r\n");
12348 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".f"]), bulk("2.5").as_str());
12351 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".t"]), "+true\r\n");
12352 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".z"]), "$-1\r\n");
12353
12354 assert_eq!(
12357 f.run(&[b"JSON.RESP", b"doc", b".nope"]),
12358 "-ERR Path does not exist\r\n"
12359 );
12360 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b"$.nope"]), "*0\r\n");
12361 assert_eq!(f.run(&[b"JSON.RESP", b"gone"]), "$-1\r\n");
12362 assert_eq!(f.run(&[b"JSON.RESP", b"gone", b"$"]), "$-1\r\n");
12363 }
12364
12365 #[test]
12369 fn json_debug_answers_a_byte_count_and_its_own_help() {
12370 let mut f = Fixture::new();
12371 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2],"s":"hello"}"#]);
12372 let one = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".s"]);
12373 assert!(one.starts_with(':'), "{one}");
12374 assert_eq!(
12375 f.run(&[b"JSON.DEBUG", b"memory", b"doc", b"$.s"]),
12376 format!("*1\r\n{one}")
12377 );
12378 let whole = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc"]);
12379 assert!(whole.starts_with(':') && whole.len() > one.len(), "{whole}");
12380
12381 assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone"]), ":0\r\n");
12385 assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone", b"$"]), "*0\r\n");
12386 assert_eq!(
12387 f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".nope"]),
12388 "-ERR Path does not exist\r\n"
12389 );
12390 assert_eq!(
12391 f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b"$.nope"]),
12392 "*0\r\n"
12393 );
12394
12395 assert_eq!(
12396 f.run(&[b"JSON.DEBUG", b"HELP"]),
12397 "*2\r\n$42\r\nMEMORY <key> [path] - reports memory usage\r\n\
12398 $34\r\nHELP - this message\r\n"
12399 );
12400 assert_eq!(
12401 f.run(&[b"JSON.DEBUG", b"NOPE"]),
12402 "-ERR unknown subcommand - try `JSON.DEBUG HELP`\r\n"
12403 );
12404 assert_eq!(
12405 f.run(&[b"JSON.DEBUG", b"MEMORY"]),
12406 "-ERR wrong number of arguments for 'json.debug' command\r\n"
12407 );
12408 }
12409
12410 #[test]
12415 fn the_first_vadd_decides_how_wide_the_set_is() {
12416 let mut f = Fixture::new();
12417 assert_eq!(
12418 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]),
12419 ":1\r\n"
12420 );
12421 assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
12422 assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
12423 assert_eq!(
12426 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"east"]),
12427 ":0\r\n"
12428 );
12429 assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
12430 assert_eq!(
12433 f.run(&[b"VADD", b"v", b"VALUES", b"3", b"1", b"0", b"0", b"up"]),
12434 "-ERR Vector dimension mismatch - got 3 but set has 2\r\n"
12435 );
12436 assert_eq!(
12439 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"0", b"nowhere"]),
12440 ":1\r\n"
12441 );
12442 assert_eq!(
12443 f.run(&[b"VEMB", b"v", b"nowhere"]),
12444 "*2\r\n$1\r\n0\r\n$1\r\n0\r\n"
12445 );
12446 assert_eq!(
12450 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"other", b"BIN"]),
12451 "-ERR asked quantization mismatch with existing vector set\r\n"
12452 );
12453 assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
12456 assert_eq!(f.run(&[b"VDIM", b"fresh"]), "-ERR key does not exist\r\n");
12457 assert_eq!(f.run(&[b"VCARD", b"fresh"]), ":0\r\n");
12458 }
12459
12460 #[test]
12463 fn vemb_gives_back_the_vector_and_vsim_gives_back_a_similarity() {
12464 let mut f = Fixture::new();
12465 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"4", b"a"]);
12466 assert_eq!(
12472 f.run(&[b"VEMB", b"v", b"a"]),
12473 "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
12474 );
12475 f.run(&[b"VADD", b"n", b"VALUES", b"2", b"3", b"4", b"a", b"NOQUANT"]);
12477 assert_eq!(
12478 f.run(&[b"VEMB", b"n", b"a"]),
12479 "*2\r\n$1\r\n3\r\n$1\r\n4\r\n"
12480 );
12481 f.run(&[b"VADD", b"b", b"VALUES", b"2", b"3", b"-4", b"a", b"BIN"]);
12484 assert_eq!(
12485 f.run(&[b"VEMB", b"b", b"a"]),
12486 "*2\r\n$1\r\n1\r\n$2\r\n-1\r\n"
12487 );
12488 assert_eq!(f.run(&[b"VEMB", b"v", b"nobody"]), "*-1\r\n");
12489 assert_eq!(f.run(&[b"VEMB", b"nokey", b"a"]), "*-1\r\n");
12490
12491 let mut f = Fixture::new();
12495 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"0", b"a"]);
12496 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"-1", b"0", b"opposite"]);
12497 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"7", b"across"]);
12498 assert_eq!(
12499 f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"2", b"0", b"WITHSCORES"]),
12500 "*6\r\n$1\r\na\r\n$1\r\n1\r\n$6\r\nacross\r\n$3\r\n0.5\r\n\
12501 $8\r\nopposite\r\n$1\r\n0\r\n"
12502 );
12503 assert_eq!(
12506 f.run(&[b"VSIM", b"v", b"ELE", b"a"]),
12507 "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
12508 );
12509 assert_eq!(f.run(&[b"VSIM", b"v", b"ELE", b"nobody"]), "*0\r\n");
12512 assert_eq!(f.run(&[b"VSIM", b"nokey", b"ELE", b"a"]), "*0\r\n");
12513 assert_eq!(
12516 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1"]),
12517 "*1\r\n$6\r\nacross\r\n"
12518 );
12519 assert_eq!(
12520 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"TRUTH"]),
12521 "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
12522 );
12523 assert_eq!(
12527 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1", b"EF", b"500"]),
12528 "*1\r\n$6\r\nacross\r\n"
12529 );
12530
12531 let mut g = Fixture::new();
12534 g.run(&[b"HELLO", b"3"]);
12535 g.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
12536 assert_eq!(
12537 g.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHSCORES"]),
12538 "%1\r\n$4\r\neast\r\n,1\r\n"
12539 );
12540 }
12541
12542 #[test]
12544 fn an_attribute_is_bytes_and_an_empty_one_takes_it_off() {
12545 let mut f = Fixture::new();
12546 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
12547 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
12548 assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]), ":1\r\n");
12549 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$7\r\n{\"k\":1}\r\n");
12550 assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"not json"]), ":1\r\n");
12553 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$8\r\nnot json\r\n");
12554 assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b""]), ":1\r\n");
12556 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
12557 assert_eq!(f.run(&[b"VSETATTR", b"v", b"nobody", b"{}"]), ":0\r\n");
12560 assert_eq!(f.run(&[b"VSETATTR", b"nokey", b"east", b"{}"]), ":0\r\n");
12561 assert_eq!(f.run(&[b"EXISTS", b"nokey"]), ":0\r\n");
12562 assert_eq!(f.run(&[b"VGETATTR", b"v", b"nobody"]), "$-1\r\n");
12565 assert_eq!(f.run(&[b"VISMEMBER", b"v", b"east"]), ":1\r\n");
12566 assert_eq!(f.run(&[b"VISMEMBER", b"v", b"nobody"]), ":0\r\n");
12567 assert_eq!(f.run(&[b"VISMEMBER", b"nokey", b"east"]), ":0\r\n");
12568
12569 f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
12571 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
12572 assert_eq!(
12573 f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHATTRIBS"]),
12574 "*4\r\n$4\r\neast\r\n$7\r\n{\"k\":1}\r\n$5\r\nnorth\r\n$-1\r\n"
12575 );
12576 }
12577
12578 #[test]
12581 fn vrem_takes_the_attribute_with_it() {
12582 let mut f = Fixture::new();
12583 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
12584 f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
12585 assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":1\r\n");
12586 assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":0\r\n");
12587 assert_eq!(f.run(&[b"VREM", b"nokey", b"east"]), ":0\r\n");
12588 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
12591
12592 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
12595 f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
12596 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
12597 f.run(&[b"VREM", b"v", b"east"]);
12598 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"between"]);
12599 assert_eq!(f.run(&[b"VGETATTR", b"v", b"between"]), "$-1\r\n");
12600 }
12601
12602 #[test]
12605 fn vinfo_says_partition_first() {
12606 let mut f = Fixture::new();
12607 f.run(&[
12608 b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east", b"M", b"32",
12609 ]);
12610 f.run(&[b"VSETATTR", b"v", b"east", b"{}"]);
12611 let info = f.run(&[b"VINFO", b"v"]);
12612 assert!(info.starts_with("*24\r\n$10\r\nindex-type\r\n$9\r\npartition\r\n"));
12613 assert!(info.contains("$6\r\nhnsw-m\r\n:32\r\n"), "{info}");
12616 assert!(info.contains("$10\r\nvector-dim\r\n:2\r\n"), "{info}");
12617 assert!(info.contains("$16\r\nattributes-count\r\n:1\r\n"), "{info}");
12618 assert!(
12621 info.contains("$10\r\nquant-type\r\n$4\r\nint8\r\n"),
12622 "{info}"
12623 );
12624 let mut f = Fixture::new();
12625 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north", b"BIN"]);
12626 assert!(
12627 f.run(&[b"VINFO", b"v"])
12628 .contains("$10\r\nquant-type\r\n$3\r\nbin\r\n")
12629 );
12630 assert_eq!(f.run(&[b"VINFO", b"nokey"]), "$-1\r\n");
12631 }
12632
12633 fn named() -> Fixture {
12635 let mut f = Fixture::new();
12636 for (i, name) in ["alpha", "beta", "gamma", "delta", "epsilon"]
12637 .iter()
12638 .enumerate()
12639 {
12640 let x = (i + 1).to_string();
12641 f.run(&[
12642 b"VADD",
12643 b"r",
12644 b"VALUES",
12645 b"2",
12646 x.as_bytes(),
12647 b"1",
12648 name.as_bytes(),
12649 ]);
12650 }
12651 f
12652 }
12653
12654 #[test]
12657 fn vrange_walks_the_names_and_not_the_vectors() {
12658 let mut f = named();
12659 assert_eq!(
12660 f.run(&[b"VRANGE", b"r", b"-", b"+"]),
12661 "*5\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n$5\r\ndelta\r\n$7\r\nepsilon\r\n$5\r\ngamma\r\n"
12662 );
12663 assert_eq!(
12664 f.run(&[b"VRANGE", b"r", b"[a", b"[d"]),
12665 "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n",
12666 "the high end is a name and not a prefix, so delta is past it"
12667 );
12668 assert_eq!(
12669 f.run(&[b"VRANGE", b"r", b"(alpha", b"(gamma"]),
12670 "*3\r\n$4\r\nbeta\r\n$5\r\ndelta\r\n$7\r\nepsilon\r\n"
12671 );
12672 assert_eq!(
12673 f.run(&[b"VRANGE", b"r", b"[beta", b"[beta"]),
12674 "*1\r\n$4\r\nbeta\r\n"
12675 );
12676 assert_eq!(f.run(&[b"VRANGE", b"r", b"[z", b"+"]), "*0\r\n");
12677 f.run(&[b"VADD", b"r", b"VALUES", b"2", b"1", b"1", b"Beta"]);
12680 assert_eq!(
12681 f.run(&[b"VRANGE", b"r", b"-", b"[beta"]),
12682 "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
12683 );
12684 assert_eq!(f.run(&[b"VRANGE", b"nokey", b"-", b"+"]), "*0\r\n");
12685 }
12686
12687 #[test]
12690 fn a_vrange_count_of_zero_asks_for_nothing() {
12691 let mut f = named();
12692 assert_eq!(
12693 f.run(&[b"VRANGE", b"r", b"-", b"+", b"2"]),
12694 "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
12695 );
12696 assert_eq!(f.run(&[b"VRANGE", b"r", b"-", b"+", b"0"]), "*0\r\n");
12697 assert!(
12698 f.run(&[b"VRANGE", b"r", b"-", b"+", b"-1"])
12699 .starts_with("*5\r\n"),
12700 "a negative count is no limit at all"
12701 );
12702 }
12703
12704 #[test]
12707 fn vrange_says_which_end_it_could_not_read() {
12708 let mut f = named();
12709 assert_eq!(
12710 f.run(&[b"VRANGE", b"r", b"x", b"y"]),
12711 "-ERR invalid start range format\r\n"
12712 );
12713 assert_eq!(
12714 f.run(&[b"VRANGE", b"r", b"+", b"x"]),
12715 "-ERR invalid end range format\r\n",
12716 "the high end is spelled wrong, which is worth saying before the \
12717 low end being on the wrong side"
12718 );
12719 assert_eq!(
12720 f.run(&[b"VRANGE", b"r", b"+", b"-"]),
12721 "-ERR '-' can only be used as first argument, '+' only as second\r\n"
12722 );
12723 assert_eq!(
12726 f.run(&[b"VRANGE", b"r", b"[", b"+"]),
12727 "-ERR invalid start range format\r\n"
12728 );
12729 assert_eq!(
12730 f.run(&[b"VRANGE", b"r", b"x", b"+", b"z"]),
12731 "-ERR invalid COUNT value\r\n"
12732 );
12733 assert_eq!(
12734 f.run(&[b"VRANGE", b"r", b"-", b"+", b"2", b"extra"]),
12735 "-ERR wrong number of arguments for 'VRANGE' command\r\n"
12736 );
12737 f.run(&[b"SET", b"s", b"x"]);
12738 assert!(
12739 f.run(&[b"VRANGE", b"s", b"-", b"+"])
12740 .starts_with("-WRONGTYPE")
12741 );
12742 }
12743
12744 #[test]
12747 fn reduce_is_refused_and_not_ignored() {
12748 let mut f = Fixture::new();
12749 let reduce = f.run(&[
12750 b"VADD", b"v", b"REDUCE", b"1", b"VALUES", b"2", b"1", b"0", b"east",
12751 ]);
12752 assert!(
12753 reduce.starts_with("-ERR REDUCE is not supported."),
12754 "{reduce}"
12755 );
12756 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
12757 }
12758
12759 #[test]
12762 fn vsim_filter_reads_the_attributes() {
12763 let mut f = Fixture::new();
12764 for (name, x, y, attr) in [
12765 ("a", "1", "0", r#"{"lang":"en","year":1999}"#),
12766 ("b", "9", "1", r#"{"lang":"fr","year":2005}"#),
12767 ("c", "8", "2", r#"{"lang":"en","year":1970}"#),
12768 ("d", "7", "3", r#"{"lang":"en","year":2020}"#),
12769 ] {
12770 f.run(&[
12771 b"VADD",
12772 b"v",
12773 b"VALUES",
12774 b"2",
12775 x.as_bytes(),
12776 y.as_bytes(),
12777 name.as_bytes(),
12778 b"SETATTR",
12779 attr.as_bytes(),
12780 ]);
12781 }
12782 assert_eq!(
12785 f.run(&[
12786 b"VSIM",
12787 b"v",
12788 b"VALUES",
12789 b"2",
12790 b"9",
12791 b"1",
12792 b"COUNT",
12793 b"2",
12794 b"FILTER",
12795 b".lang == \"en\"",
12796 ]),
12797 "*2\r\n$1\r\na\r\n$1\r\nc\r\n"
12798 );
12799 assert_eq!(
12802 f.run(&[
12803 b"VSIM",
12804 b"v",
12805 b"VALUES",
12806 b"2",
12807 b"9",
12808 b"1",
12809 b"FILTER",
12810 b".lang == 'en' and .year > 1980",
12811 ]),
12812 "*2\r\n$1\r\na\r\n$1\r\nd\r\n"
12813 );
12814 assert_eq!(
12816 f.run(&[
12817 b"VSIM",
12818 b"v",
12819 b"VALUES",
12820 b"2",
12821 b"9",
12822 b"1",
12823 b"FILTER",
12824 b".lang in ['fr', 'de']",
12825 ]),
12826 "*1\r\n$1\r\nb\r\n"
12827 );
12828 assert_eq!(
12829 f.run(&[
12830 b"VSIM",
12831 b"v",
12832 b"VALUES",
12833 b"2",
12834 b"9",
12835 b"1",
12836 b"FILTER",
12837 b".rating > 3"
12838 ]),
12839 "*0\r\n"
12840 );
12841 assert_eq!(
12844 f.run(&[
12845 b"VSIM",
12846 b"v",
12847 b"VALUES",
12848 b"2",
12849 b"9",
12850 b"1",
12851 b"TRUTH",
12852 b"FILTER",
12853 b".year < 1980",
12854 ]),
12855 "*1\r\n$1\r\nc\r\n"
12856 );
12857 f.run(&[b"VSETATTR", b"v", b"b", r#"{"lang":"en"}"#.as_bytes()]);
12860 assert_eq!(
12861 f.run(&[
12862 b"VSIM",
12863 b"v",
12864 b"VALUES",
12865 b"2",
12866 b"9",
12867 b"1",
12868 b"COUNT",
12869 b"1",
12870 b"FILTER",
12871 b".lang == \"en\"",
12872 ]),
12873 "*1\r\n$1\r\nb\r\n"
12874 );
12875 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"9", b"2", b"b"]);
12878 assert_eq!(
12879 f.run(&[
12880 b"VSIM",
12881 b"v",
12882 b"VALUES",
12883 b"2",
12884 b"9",
12885 b"1",
12886 b"COUNT",
12887 b"1",
12888 b"FILTER",
12889 b".lang == \"en\"",
12890 ]),
12891 "*1\r\n$1\r\nb\r\n"
12892 );
12893
12894 let bad = f.run(&[b"VSIM", b"nokey", b"ELE", b"e", b"FILTER", b".k =="]);
12897 assert_eq!(bad, "-ERR invalid FILTER expression\r\n");
12898 assert_eq!(
12899 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"FILTER", b"junk"]),
12900 "-ERR invalid FILTER expression\r\n"
12901 );
12902 assert_eq!(
12905 f.run(&[
12906 b"VSIM",
12907 b"v",
12908 b"VALUES",
12909 b"2",
12910 b"9",
12911 b"1",
12912 b"COUNT",
12913 b"1",
12914 b"FILTER-EF",
12915 b"500",
12916 b"FILTER",
12917 b".lang == 'en'",
12918 ]),
12919 "*1\r\n$1\r\nb\r\n"
12920 );
12921 assert_eq!(
12922 f.run(&[
12923 b"VSIM",
12924 b"v",
12925 b"VALUES",
12926 b"2",
12927 b"9",
12928 b"1",
12929 b"COUNT",
12930 b"1",
12931 b"FILTER-EF",
12932 b"0"
12933 ]),
12934 "*1\r\n$1\r\nb\r\n"
12935 );
12936 assert_eq!(
12937 f.run(&[
12938 b"VSIM",
12939 b"v",
12940 b"VALUES",
12941 b"2",
12942 b"9",
12943 b"1",
12944 b"FILTER-EF",
12945 b"lots"
12946 ]),
12947 "-ERR EF must be a positive integer\r\n"
12948 );
12949 }
12950
12951 #[test]
12954 fn the_keyspace_sees_a_vector_set_key_like_any_other() {
12955 let mut f = Fixture::new();
12956 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
12957 assert_eq!(f.run(&[b"TYPE", b"v"]), "+vectorset\r\n");
12958 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":1\r\n");
12959 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"v"]), "$6\r\nrabitq\r\n");
12960 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\nv\r\n");
12961 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
12962 assert_eq!(f.run(&[b"EXPIRE", b"v", b"100"]), ":1\r\n");
12963 assert_eq!(f.run(&[b"TTL", b"v"]), ":100\r\n");
12964 assert_eq!(f.run(&[b"PERSIST", b"v"]), ":1\r\n");
12965 assert_eq!(f.run(&[b"DEL", b"v"]), ":1\r\n");
12966 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
12967
12968 f.run(&[b"SET", b"s", b"1"]);
12970 assert_eq!(
12971 f.run(&[b"VADD", b"s", b"VALUES", b"2", b"1", b"0", b"east"]),
12972 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
12973 );
12974 assert_eq!(
12975 f.run(&[b"VCARD", b"s"]),
12976 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
12977 );
12978 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
12979 assert_eq!(
12980 f.run(&[b"GET", b"v"]),
12981 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
12982 );
12983 f.run(&[b"G.NADD", b"social", b"ada"]);
12987 assert_eq!(
12988 f.run(&[b"VCARD", b"social"]),
12989 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
12990 );
12991 assert_eq!(
12992 f.run(&[b"G.NGET", b"v", b"ada"]),
12993 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
12994 );
12995 }
12996
12997 #[test]
13000 fn vrandmember_has_the_two_shapes_srandmember_has() {
13001 let mut f = Fixture::new();
13002 for (i, name) in [&b"a"[..], b"b", b"c"].iter().enumerate() {
13003 let x = (i + 1).to_string();
13004 f.run(&[b"VADD", b"v", b"VALUES", b"2", x.as_bytes(), b"1", name]);
13005 }
13006 let one = f.run(&[b"VRANDMEMBER", b"v"]);
13008 assert!(one.starts_with("$1\r\n"), "{one}");
13009 let mut all = f.run(&[b"VRANDMEMBER", b"v", b"9"]);
13011 assert!(all.starts_with("*3\r\n"), "{all}");
13012 for name in ["a", "b", "c"] {
13013 assert!(all.contains(name), "{all} is missing {name}");
13014 }
13015 all = f.run(&[b"VRANDMEMBER", b"v", b"2"]);
13016 assert!(all.starts_with("*2\r\n"), "{all}");
13017 let many = f.run(&[b"VRANDMEMBER", b"v", b"-5"]);
13019 assert!(many.starts_with("*5\r\n"), "{many}");
13020 assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey"]), "$-1\r\n");
13022 assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
13023 }
13024
13025 #[test]
13028 fn vlinks_reports_one_layer_of_partition_neighbours() {
13029 let mut f = Fixture::new();
13030 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
13031 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
13032 assert_eq!(
13035 f.run(&[b"VLINKS", b"v", b"east"]),
13036 "*1\r\n*1\r\n$5\r\nnorth\r\n"
13037 );
13038 assert_eq!(
13039 f.run(&[b"VLINKS", b"v", b"east", b"WITHSCORES"]),
13040 "*1\r\n*2\r\n$5\r\nnorth\r\n$3\r\n0.5\r\n"
13041 );
13042 assert_eq!(f.run(&[b"VLINKS", b"v", b"nobody"]), "*-1\r\n");
13043 assert_eq!(f.run(&[b"VLINKS", b"nokey", b"east"]), "*-1\r\n");
13044 }
13045
13046 #[test]
13049 fn fp32_and_values_are_the_same_vector() {
13050 let mut f = Fixture::new();
13051 let mut blob = Vec::new();
13052 for x in [3.0f32, 4.0] {
13053 blob.extend_from_slice(&x.to_le_bytes());
13054 }
13055 assert_eq!(f.run(&[b"VADD", b"v", b"FP32", &blob, b"a"]), ":1\r\n");
13056 assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
13057 assert_eq!(
13058 f.run(&[b"VEMB", b"v", b"a"]),
13059 "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
13060 );
13061 assert_eq!(
13067 f.run(&[b"VEMB", b"v", b"a", b"RAW"]),
13068 "*4\r\n+int8\r\n$2\r\n_\x7f\r\n$1\r\n5\r\n$17\r\n0.800000011920929\r\n"
13069 );
13070 assert_eq!(
13072 f.run(&[b"VADD", b"w", b"FP32", b"abc", b"a"]),
13073 "-ERR invalid vector specification\r\n"
13074 );
13075 assert_eq!(
13077 f.run(&[b"VADD", b"w", b"VALUES", b"4", b"1", b"0", b"a"]),
13078 "-ERR syntax error\r\n"
13079 );
13080 assert_eq!(f.run(&[b"EXISTS", b"w"]), ":0\r\n");
13081 }
13082
13083 #[test]
13088 fn bf_add_makes_the_filter_and_says_whether_it_was_new() {
13089 let mut f = Fixture::new();
13090 assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":1\r\n");
13091 assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":0\r\n");
13092 assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"hello"]), ":1\r\n");
13093 assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"never"]), ":0\r\n");
13094 assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":1\r\n");
13095 assert_eq!(
13098 f.run(&[b"BF.INFO", b"b"]),
13099 "*10\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
13100 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
13101 +Expansion rate\r\n:2\r\n"
13102 );
13103 assert_eq!(f.run(&[b"TYPE", b"b"]), "+MBbloom--\r\n");
13104 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"b"]), "$3\r\nraw\r\n");
13105 assert_eq!(f.run(&[b"BF.CARD", b"gone"]), ":0\r\n");
13108 assert_eq!(f.run(&[b"BF.INFO", b"gone"]), "-ERR not found\r\n");
13109 }
13110
13111 #[test]
13117 fn a_wrong_type_is_a_miss_to_the_two_that_only_read_bits() {
13118 let mut f = Fixture::new();
13119 f.run(&[b"SET", b"s", b"text"]);
13120 assert_eq!(f.run(&[b"BF.EXISTS", b"s", b"x"]), ":0\r\n");
13121 assert_eq!(f.run(&[b"BF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
13122 for cmd in [
13123 vec![&b"BF.ADD"[..], b"s", b"x"],
13124 vec![&b"BF.MADD"[..], b"s", b"x"],
13125 vec![&b"BF.CARD"[..], b"s"],
13126 vec![&b"BF.INFO"[..], b"s"],
13127 vec![&b"BF.DEBUG"[..], b"s"],
13128 vec![&b"BF.SCANDUMP"[..], b"s", b"0"],
13129 ] {
13130 let name = String::from_utf8_lossy(cmd[0]).into_owned();
13131 assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
13132 }
13133 assert_eq!(
13136 f.run(&[b"BF.RESERVE", b"s", b"abc", b"10"]),
13137 "-ERR bad error rate\r\n"
13138 );
13139 assert!(
13140 f.run(&[b"BF.RESERVE", b"s", b"0.01", b"10"])
13141 .starts_with("-WRONGTYPE")
13142 );
13143 }
13144
13145 #[test]
13148 fn a_full_filter_grows_a_link_and_a_fixed_one_says_no() {
13149 let mut f = Fixture::new();
13150 assert_eq!(f.run(&[b"BF.RESERVE", b"g", b"0.01", b"10"]), "+OK\r\n");
13151 for i in 0..10u32 {
13152 assert_eq!(
13153 f.run(&[b"BF.ADD", b"g", i.to_string().as_bytes()]),
13154 ":1\r\n"
13155 );
13156 }
13157 assert_eq!(f.run(&[b"BF.INFO", b"g", b"FILTERS"]), "*1\r\n:1\r\n");
13158 assert_eq!(f.run(&[b"BF.ADD", b"g", b"11"]), ":1\r\n");
13159 assert_eq!(f.run(&[b"BF.INFO", b"g", b"filters"]), "*1\r\n:2\r\n");
13160 assert_eq!(f.run(&[b"BF.INFO", b"g", b"CAPACITY"]), "*1\r\n:30\r\n");
13163 assert_eq!(
13164 f.run(&[b"BF.DEBUG", b"g"]),
13165 "*3\r\n$7\r\nsize:11\r\n\
13166 $71\r\nbytes:16 bits:128 hashes:8 hashwidth:64 capacity:10 size:10 ratio:0.005\r\n\
13167 $71\r\nbytes:32 bits:256 hashes:9 hashwidth:64 capacity:20 size:1 ratio:0.0025\r\n"
13168 );
13169
13170 assert_eq!(
13172 f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]),
13173 "+OK\r\n"
13174 );
13175 assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":1\r\n");
13176 assert_eq!(f.run(&[b"BF.ADD", b"n", b"b"]), ":1\r\n");
13177 assert_eq!(
13178 f.run(&[b"BF.ADD", b"n", b"c"]),
13179 "-ERR non scaling filter is full\r\n"
13180 );
13181 assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":0\r\n");
13184 assert_eq!(f.run(&[b"BF.INFO", b"n", b"EXPANSION"]), "*1\r\n$-1\r\n");
13187 f.run(&[b"BF.RESERVE", b"z", b"0.01", b"2", b"EXPANSION", b"0"]);
13188 assert_eq!(f.run(&[b"BF.INFO", b"z", b"EXPANSION"]), "*1\r\n$-1\r\n");
13189 assert_eq!(
13192 f.run(&[
13193 b"BF.RESERVE",
13194 b"q",
13195 b"0.01",
13196 b"2",
13197 b"NONSCALING",
13198 b"EXPANSION",
13199 b"2"
13200 ]),
13201 "-Nonscaling filters cannot expand\r\n"
13202 );
13203 }
13204
13205 #[test]
13208 fn madd_truncates_its_reply_at_the_item_that_did_not_fit() {
13209 let mut f = Fixture::new();
13210 f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]);
13211 assert_eq!(
13212 f.run(&[b"BF.MADD", b"n", b"a", b"b", b"c", b"d"]),
13213 "*3\r\n:1\r\n:1\r\n-ERR non scaling filter is full\r\n"
13214 );
13215 assert_eq!(
13216 f.run(&[b"BF.MEXISTS", b"n", b"a", b"c"]),
13217 "*2\r\n:1\r\n:0\r\n"
13218 );
13219 }
13220
13221 #[test]
13224 fn insert_is_a_reserve_and_a_madd_with_different_errors() {
13225 let mut f = Fixture::new();
13226 assert_eq!(
13227 f.run(&[
13228 b"BF.INSERT",
13229 b"i",
13230 b"CAPACITY",
13231 b"50",
13232 b"ERROR",
13233 b"0.001",
13234 b"ITEMS",
13235 b"a",
13236 b"b"
13237 ]),
13238 "*2\r\n:1\r\n:1\r\n"
13239 );
13240 assert_eq!(f.run(&[b"BF.INFO", b"i", b"CAPACITY"]), "*1\r\n:50\r\n");
13241 assert_eq!(
13243 f.run(&[b"BF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
13244 "-ERR not found\r\n"
13245 );
13246 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
13247 assert_eq!(
13250 f.run(&[b"BF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
13251 "-Bad capacity\r\n"
13252 );
13253 assert_eq!(
13254 f.run(&[b"BF.INSERT", b"i", b"ERROR", b"2", b"ITEMS", b"a"]),
13255 "-Bad error rate\r\n"
13256 );
13257 assert_eq!(
13258 f.run(&[b"BF.INSERT", b"i", b"EXPANSION", b"99999", b"ITEMS", b"a"]),
13259 "-Bad expansion\r\n"
13260 );
13261 assert_eq!(
13266 f.run(&[b"BF.INSERT", b"ns", b"NOSUCHTHING", b"ITEMS", b"a"]),
13267 "*1\r\n:1\r\n"
13268 );
13269 assert_eq!(f.run(&[b"BF.INFO", b"ns", b"EXPANSION"]), "*1\r\n$-1\r\n");
13270 assert_eq!(
13274 f.run(&[b"BF.INSERT", b"e1", b"E", b"4", b"ITEMS", b"a"]),
13275 "*1\r\n:1\r\n"
13276 );
13277 assert_eq!(f.run(&[b"BF.INFO", b"e1", b"EXPANSION"]), "*1\r\n:4\r\n");
13278 assert_eq!(
13279 f.run(&[b"BF.INSERT", b"e2", b"ER", b"0.5", b"ITEMS", b"a"]),
13280 "*1\r\n:1\r\n"
13281 );
13282 assert_eq!(
13283 f.run(&[b"BF.INSERT", b"gone", b"NOC", b"ITEMS", b"a"]),
13284 "-ERR not found\r\n"
13285 );
13286 assert_eq!(
13288 f.run(&[b"BF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
13289 "-Unknown argument received\r\n"
13290 );
13291 assert_eq!(
13293 f.run(&[b"BF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
13294 "*1\r\n:1\r\n"
13295 );
13296 assert!(
13298 f.run(&[b"BF.INSERT", b"i", b"ITEMS"])
13299 .contains("wrong number of arguments")
13300 );
13301 }
13302
13303 #[test]
13306 fn a_dump_replays_into_a_filter_that_answers_the_same() {
13307 let mut f = Fixture::new();
13308 f.run(&[b"BF.RESERVE", b"src", b"0.01", b"10"]);
13309 for i in 0..25u32 {
13310 f.run(&[b"BF.ADD", b"src", i.to_string().as_bytes()]);
13311 }
13312 assert_eq!(f.run(&[b"BF.INFO", b"src", b"FILTERS"]), "*1\r\n:2\r\n");
13313
13314 let mut iter = b"0".to_vec();
13317 let mut chunks = 0;
13318 loop {
13319 let raw = f.raw(&[b"BF.SCANDUMP", b"src", &iter]);
13320 let text = String::from_utf8_lossy(&raw).into_owned();
13321 let next = text
13322 .split("\r\n")
13323 .nth(1)
13324 .and_then(|n| n.strip_prefix(':'))
13325 .expect("a two element reply of an iterator and a chunk")
13326 .to_owned();
13327 let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
13328 let data = &body[body
13329 .windows(2)
13330 .position(|w| w == b"\r\n")
13331 .expect("a length line")
13332 + 2..body.len() - 2];
13333 if next == "0" {
13334 assert!(data.is_empty(), "the last chunk is empty");
13335 break;
13336 }
13337 let put = f.run(&[b"BF.LOADCHUNK", b"dst", next.as_bytes(), data]);
13338 assert_eq!(put, "+OK\r\n", "loading chunk {chunks}");
13339 iter = next.into_bytes();
13340 chunks += 1;
13341 }
13342 assert_eq!(chunks, 3, "a header and one chunk per link");
13343
13344 assert_eq!(f.run(&[b"BF.INFO", b"dst"]), f.run(&[b"BF.INFO", b"src"]));
13345 assert_eq!(f.run(&[b"BF.DEBUG", b"dst"]), f.run(&[b"BF.DEBUG", b"src"]));
13346 for i in 0..25u32 {
13347 assert_eq!(
13348 f.run(&[b"BF.EXISTS", b"dst", i.to_string().as_bytes()]),
13349 ":1\r\n"
13350 );
13351 }
13352
13353 assert_eq!(
13356 f.run(&[b"BF.LOADCHUNK", b"dst", b"1", b"anything"]),
13357 "-ERR received bad data\r\n"
13358 );
13359 assert_eq!(
13360 f.run(&[b"BF.LOADCHUNK", b"fresh", b"1", b"anything"]),
13361 "-ERR received bad data\r\n"
13362 );
13363 assert_eq!(
13365 f.run(&[b"BF.LOADCHUNK", b"dst", b"99999", b"x"]),
13366 "-ERR invalid offset - no link found\r\n"
13367 );
13368 assert_eq!(
13369 f.run(&[b"BF.LOADCHUNK", b"dst", b"nope", b"x"]),
13370 "-ERR Second argument must be numeric\r\n"
13371 );
13372 assert_eq!(
13375 f.run(&[b"BF.SCANDUMP", b"src", b"nope"]),
13376 "-Second argument must be numeric\r\n"
13377 );
13378 }
13379
13380 #[test]
13383 fn reserve_reads_its_numbers_the_way_string2ll_does() {
13384 let mut f = Fixture::new();
13385 for (args, want) in [
13386 (vec![&b"abc"[..], b"10"], "-ERR bad error rate\r\n"),
13387 (vec![&b"nan"[..], b"10"], "-ERR bad error rate\r\n"),
13388 (
13389 vec![&b"0"[..], b"10"],
13390 "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
13391 ),
13392 (
13393 vec![&b"1"[..], b"10"],
13394 "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
13395 ),
13396 (
13397 vec![&b"inf"[..], b"10"],
13398 "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
13399 ),
13400 (vec![&b"0.01"[..], b"+10"], "-ERR bad capacity\r\n"),
13401 (vec![&b"0.01"[..], b"1e2"], "-ERR bad capacity\r\n"),
13402 (vec![&b"0.01"[..], b"007"], "-ERR bad capacity\r\n"),
13403 (
13404 vec![&b"0.01"[..], b"0"],
13405 "-ERR capacity must be in the range [1, 1073741824]\r\n",
13406 ),
13407 (
13408 vec![&b"0.01"[..], b"1073741825"],
13409 "-ERR capacity must be in the range [1, 1073741824]\r\n",
13410 ),
13411 ] {
13412 let mut cmd = vec![&b"BF.RESERVE"[..], b"k"];
13413 cmd.extend(args.iter().copied());
13414 assert_eq!(f.run(&cmd), want, "{}", String::from_utf8_lossy(args[0]));
13415 }
13416 assert_eq!(
13417 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION"]),
13418 "-ERR no expansion\r\n"
13419 );
13420 assert_eq!(
13421 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"abc"]),
13422 "-ERR bad expansion\r\n"
13423 );
13424 assert_eq!(
13425 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"32769"]),
13426 "-ERR expansion must be in the range [0, 32768]\r\n"
13427 );
13428 assert_eq!(
13430 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"junk"]),
13431 "+OK\r\n"
13432 );
13433 assert_eq!(
13434 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10"]),
13435 "-ERR item exists\r\n"
13436 );
13437 assert_eq!(
13438 f.run(&[b"BF.INFO", b"k", b"nosuchfield"]),
13439 "-Invalid information value\r\n"
13440 );
13441 assert!(
13442 f.run(&[b"BF.INFO", b"k", b"CAPACITY", b"SIZE"])
13443 .contains("wrong number of arguments")
13444 );
13445 }
13446
13447 #[test]
13449 fn the_bloom_family_answers_in_resp3_spelling_too() {
13450 let mut f = Fixture::new();
13451 f.out.set_proto(Proto::Resp3);
13452 assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#t\r\n");
13453 assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#f\r\n");
13454 assert_eq!(f.run(&[b"BF.MADD", b"b", b"a", b"c"]), "*2\r\n#f\r\n#t\r\n");
13455 assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"a"]), "#t\r\n");
13456 assert_eq!(
13457 f.run(&[b"BF.MEXISTS", b"b", b"a", b"z"]),
13458 "*2\r\n#t\r\n#f\r\n"
13459 );
13460 assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":2\r\n");
13462 assert_eq!(
13463 f.run(&[b"BF.INFO", b"b"]),
13464 "%5\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
13465 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:2\r\n\
13466 +Expansion rate\r\n:2\r\n"
13467 );
13468 assert_eq!(
13471 f.run(&[b"BF.INFO", b"b", b"CAPACITY"]),
13472 "%1\r\n+Capacity\r\n:100\r\n"
13473 );
13474 }
13475
13476 fn cf_header(
13485 items: u64,
13486 buckets: u64,
13487 deletes: u64,
13488 filters: u64,
13489 geometry: [u16; 3],
13490 ) -> Vec<u8> {
13491 let mut out = Vec::with_capacity(38);
13492 for n in [items, buckets, deletes, filters] {
13493 out.extend_from_slice(&n.to_le_bytes());
13494 }
13495 for n in geometry {
13496 out.extend_from_slice(&n.to_le_bytes());
13497 }
13498 out
13499 }
13500
13501 #[test]
13505 fn cf_add_makes_the_filter_and_counts_the_copies() {
13506 let mut f = Fixture::new();
13507 assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
13508 assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
13509 assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":2\r\n");
13510 assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"hello"]), ":0\r\n");
13513 assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"other"]), ":1\r\n");
13514 assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"hello"]), ":1\r\n");
13515 assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"no"]), ":0\r\n");
13516 assert_eq!(
13517 f.run(&[b"CF.MEXISTS", b"d", b"hello", b"no"]),
13518 "*2\r\n:1\r\n:0\r\n"
13519 );
13520 assert_eq!(
13523 f.run(&[b"CF.INFO", b"d"]),
13524 "*16\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
13525 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:3\r\n\
13526 +Number of items deleted\r\n:0\r\n+Bucket size\r\n:2\r\n\
13527 +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
13528 );
13529 assert_eq!(
13530 f.run(&[b"CF.DEBUG", b"d"]),
13531 "$79\r\nbktsize:2 buckets:512 items:3 deletes:0 filters:1 \
13532 max_iterations:20 expansion:1\r\n"
13533 );
13534 assert_eq!(f.run(&[b"TYPE", b"d"]), "+MBbloomCF\r\n");
13535 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
13536
13537 assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
13539 assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":1\r\n");
13540 assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
13541 assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":0\r\n");
13542 assert_eq!(f.run(&[b"CF.COMPACT", b"d"]), "+OK\r\n");
13543
13544 assert_eq!(f.run(&[b"CF.INFO", b"gone"]), "-ERR not found\r\n");
13547 assert_eq!(f.run(&[b"CF.DEL", b"gone", b"x"]), "-Not found\r\n");
13548 assert_eq!(
13549 f.run(&[b"CF.COMPACT", b"gone"]),
13550 "-Cuckoo filter was not found\r\n"
13551 );
13552 assert_eq!(f.run(&[b"CF.EXISTS", b"gone", b"x"]), ":0\r\n");
13553 assert!(
13557 f.run(&[b"CF.COMPACT", b"a", b"b"])
13558 .contains("wrong number of arguments")
13559 );
13560 }
13561
13562 #[test]
13565 fn a_wrong_type_is_a_miss_to_the_four_that_only_read_fingerprints() {
13566 let mut f = Fixture::new();
13567 f.run(&[b"SET", b"s", b"text"]);
13568 assert_eq!(f.run(&[b"CF.EXISTS", b"s", b"x"]), ":0\r\n");
13569 assert_eq!(f.run(&[b"CF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
13570 assert_eq!(f.run(&[b"CF.COUNT", b"s", b"x"]), ":0\r\n");
13571 assert_eq!(f.run(&[b"CF.DEL", b"s", b"x"]), "-Not found\r\n");
13575 assert_eq!(
13576 f.run(&[b"CF.COMPACT", b"s"]),
13577 "-Cuckoo filter was not found\r\n"
13578 );
13579 for cmd in [
13580 vec![&b"CF.ADD"[..], b"s", b"x"],
13581 vec![&b"CF.ADDNX"[..], b"s", b"x"],
13582 vec![&b"CF.INSERT"[..], b"s", b"ITEMS", b"x"],
13583 vec![&b"CF.INSERTNX"[..], b"s", b"ITEMS", b"x"],
13584 vec![&b"CF.INFO"[..], b"s"],
13585 vec![&b"CF.DEBUG"[..], b"s"],
13586 vec![&b"CF.SCANDUMP"[..], b"s", b"0"],
13587 vec![&b"CF.LOADCHUNK"[..], b"s", b"2", b"x"],
13588 vec![&b"CF.RESERVE"[..], b"s", b"64"],
13589 ] {
13590 let name = String::from_utf8_lossy(cmd[0]).into_owned();
13591 assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
13592 }
13593 }
13594
13595 #[test]
13598 fn reserve_complains_about_its_options_in_the_order_it_looks_for_them() {
13599 let mut f = Fixture::new();
13600 assert_eq!(
13601 f.run(&[
13602 b"CF.RESERVE",
13603 b"r",
13604 b"64",
13605 b"BUCKETSIZE",
13606 b"1",
13607 b"MAXITERATIONS",
13608 b"7",
13609 b"EXPANSION",
13610 b"4"
13611 ]),
13612 "+OK\r\n"
13613 );
13614 assert_eq!(
13615 f.run(&[b"CF.DEBUG", b"r"]),
13616 "$77\r\nbktsize:1 buckets:64 items:0 deletes:0 filters:1 \
13617 max_iterations:7 expansion:4\r\n"
13618 );
13619 assert_eq!(f.run(&[b"CF.RESERVE", b"r", b"64"]), "-ERR item exists\r\n");
13620
13621 assert_eq!(f.run(&[b"CF.RESERVE", b"q", b"abc"]), "-Bad capacity\r\n");
13622 assert_eq!(
13623 f.run(&[b"CF.RESERVE", b"q", b"1"]),
13624 "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
13625 );
13626 assert_eq!(
13629 f.run(&[b"CF.RESERVE", b"q", b"7", b"BUCKETSIZE", b"4"]),
13630 "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
13631 );
13632 assert_eq!(
13633 f.run(&[b"CF.RESERVE", b"q", b"8", b"BUCKETSIZE", b"4"]),
13634 "+OK\r\n"
13635 );
13636
13637 assert_eq!(
13642 f.run(&[b"CF.RESERVE", b"q2", b"64", b"BUCKETSIZE", b"0"]),
13643 "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
13644 );
13645 assert_eq!(
13646 f.run(&[
13647 b"CF.RESERVE",
13648 b"q2",
13649 b"64",
13650 b"EXPANSION",
13651 b"xx",
13652 b"BUCKETSIZE",
13653 b"0"
13654 ]),
13655 "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
13656 );
13657 assert_eq!(
13658 f.run(&[
13659 b"CF.RESERVE",
13660 b"q2",
13661 b"64",
13662 b"MAXITERATIONS",
13663 b"0",
13664 b"BUCKETSIZE",
13665 b"0"
13666 ]),
13667 "-MAXITERATIONS: value must be in the range [1, 65535]\r\n"
13668 );
13669 assert_eq!(
13673 f.run(&[
13674 b"CF.RESERVE",
13675 b"q3",
13676 b"64",
13677 b"BUCKETSIZE",
13678 b"1",
13679 b"BUCKETSIZE",
13680 b"0"
13681 ]),
13682 "+OK\r\n"
13683 );
13684 assert_eq!(
13687 f.run(&[b"CF.RESERVE", b"q4", b"64", b"NOSUCH", b"9"]),
13688 "+OK\r\n"
13689 );
13690 assert_eq!(
13691 f.run(&[b"CF.DEBUG", b"q4"]),
13692 "$78\r\nbktsize:2 buckets:32 items:0 deletes:0 filters:1 \
13693 max_iterations:20 expansion:1\r\n"
13694 );
13695 assert!(
13698 f.run(&[b"CF.RESERVE", b"q5", b"64", b"BUCKETSIZE"])
13699 .contains("wrong number of arguments")
13700 );
13701 }
13702
13703 #[test]
13706 fn insert_checks_every_occurrence_and_matches_on_the_first_letter() {
13707 let mut f = Fixture::new();
13708 assert_eq!(
13709 f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"64", b"ITEMS", b"a", b"b"]),
13710 "*2\r\n:1\r\n:1\r\n"
13711 );
13712 assert_eq!(
13713 f.run(&[b"CF.DEBUG", b"i"]),
13714 "$78\r\nbktsize:2 buckets:32 items:2 deletes:0 filters:1 \
13715 max_iterations:20 expansion:1\r\n"
13716 );
13717 assert_eq!(
13720 f.run(&[b"CF.INSERTNX", b"i", b"ITEMS", b"a", b"c"]),
13721 "*2\r\n:0\r\n:1\r\n"
13722 );
13723 assert_eq!(
13724 f.run(&[b"CF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
13725 "-ERR not found\r\n"
13726 );
13727 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
13728
13729 assert_eq!(
13730 f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
13731 "-Bad capacity\r\n"
13732 );
13733 assert_eq!(
13736 f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"2", b"ITEMS", b"a"]),
13737 "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
13738 );
13739 assert_eq!(
13743 f.run(&[
13744 b"CF.INSERT",
13745 b"i",
13746 b"CAPACITY",
13747 b"8",
13748 b"CAPACITY",
13749 b"2",
13750 b"ITEMS",
13751 b"a"
13752 ]),
13753 "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
13754 );
13755 assert_eq!(
13759 f.run(&[b"CF.INSERT", b"i", b"NOSUCH", b"ITEMS", b"a"]),
13760 "*1\r\n:1\r\n"
13761 );
13762 assert_eq!(
13763 f.run(&[b"CF.INSERT", b"i", b"ITEMSXYZ", b"a"]),
13764 "*1\r\n:1\r\n"
13765 );
13766 assert_eq!(
13767 f.run(&[b"CF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
13768 "-Unknown argument received\r\n"
13769 );
13770 assert_eq!(
13772 f.run(&[b"CF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
13773 "*1\r\n:1\r\n"
13774 );
13775 assert!(
13777 f.run(&[b"CF.INSERT", b"i", b"ITEMS"])
13778 .contains("wrong number of arguments")
13779 );
13780 assert!(
13781 f.run(&[b"CF.INSERT", b"i", b"CAPACITY"])
13782 .contains("wrong number of arguments")
13783 );
13784 }
13785
13786 #[test]
13789 fn a_full_filter_and_one_that_ran_out_of_filters_answer_differently() {
13790 let mut f = Fixture::new();
13791 f.run(&[
13792 b"CF.RESERVE",
13793 b"s",
13794 b"4",
13795 b"BUCKETSIZE",
13796 b"1",
13797 b"EXPANSION",
13798 b"0",
13799 ]);
13800 for i in 0..4u32 {
13801 assert_eq!(
13802 f.run(&[b"CF.ADD", b"s", i.to_string().as_bytes()]),
13803 ":1\r\n"
13804 );
13805 }
13806 assert_eq!(f.run(&[b"CF.ADD", b"s", b"4"]), "-Filter is full\r\n");
13807 assert_eq!(f.run(&[b"CF.ADDNX", b"s", b"zz"]), "-Filter is full\r\n");
13808 assert_eq!(
13811 f.run(&[b"CF.INSERT", b"s", b"ITEMS", b"p", b"q"]),
13812 "*2\r\n:-1\r\n:-1\r\n"
13813 );
13814 assert_eq!(
13815 f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"0", b"q"]),
13816 "*2\r\n:0\r\n:-1\r\n"
13817 );
13818
13819 let full = cf_header(0, 4, 0, 32, [1, 20, 1]);
13825 assert_eq!(f.run(&[b"CF.LOADCHUNK", b"g", b"1", &full]), "+OK\r\n");
13826 assert_eq!(
13827 f.run(&[b"CF.ADD", b"g", b"q"]),
13828 "-Maximum expansions reached\r\n"
13829 );
13830 assert_eq!(
13831 f.run(&[b"CF.INFO", b"g"]),
13832 "*16\r\n+Size\r\n:680\r\n+Number of buckets\r\n:4\r\n\
13833 +Number of filters\r\n:32\r\n+Number of items inserted\r\n:0\r\n\
13834 +Number of items deleted\r\n:0\r\n+Bucket size\r\n:1\r\n\
13835 +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
13836 );
13837 }
13838
13839 #[test]
13843 fn a_cuckoo_dump_replays_into_a_filter_that_answers_the_same() {
13844 let mut f = Fixture::new();
13845 f.run(&[
13846 b"CF.RESERVE",
13847 b"src",
13848 b"8",
13849 b"BUCKETSIZE",
13850 b"2",
13851 b"EXPANSION",
13852 b"2",
13853 ]);
13854 for i in 0..40u32 {
13855 f.run(&[b"CF.ADD", b"src", i.to_string().as_bytes()]);
13856 }
13857 let mut pos = b"0".to_vec();
13861 let mut chunks = 0;
13862 loop {
13863 let raw = f.raw(&[b"CF.SCANDUMP", b"src", &pos]);
13864 let head = String::from_utf8_lossy(&raw[..raw.len().min(24)]).into_owned();
13865 let next = head
13866 .split("\r\n")
13867 .nth(1)
13868 .and_then(|n| n.strip_prefix(':'))
13869 .expect("a two element reply of a position and a chunk")
13870 .to_owned();
13871 if next == "0" {
13872 assert!(raw.ends_with(b"$-1\r\n"), "the walk ends on a nil");
13873 break;
13874 }
13875 let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
13876 let at = body
13877 .windows(2)
13878 .position(|w| w == b"\r\n")
13879 .expect("a length line")
13880 + 2;
13881 let data = &body[at..body.len() - 2];
13882 assert_eq!(
13883 f.run(&[b"CF.LOADCHUNK", b"dst", next.as_bytes(), data]),
13884 "+OK\r\n",
13885 "loading chunk {chunks}"
13886 );
13887 pos = next.into_bytes();
13888 chunks += 1;
13889 }
13890 assert!(chunks >= 2, "a header and at least one chunk");
13891
13892 assert_eq!(f.run(&[b"CF.INFO", b"dst"]), f.run(&[b"CF.INFO", b"src"]));
13893 assert_eq!(f.run(&[b"CF.DEBUG", b"dst"]), f.run(&[b"CF.DEBUG", b"src"]));
13894 for i in 0..40u32 {
13895 assert_eq!(
13896 f.run(&[b"CF.EXISTS", b"dst", i.to_string().as_bytes()]),
13897 ":1\r\n"
13898 );
13899 }
13900
13901 f.run(&[b"CF.RESERVE", b"empty", b"4", b"BUCKETSIZE", b"1"]);
13904 assert_eq!(
13905 f.run(&[b"CF.SCANDUMP", b"empty", b"0"]),
13906 "*2\r\n:0\r\n$-1\r\n"
13907 );
13908
13909 assert_eq!(
13913 f.run(&[b"CF.SCANDUMP", b"src", b"nope"]),
13914 "-Invalid position\r\n"
13915 );
13916 assert_eq!(
13917 f.run(&[b"CF.SCANDUMP", b"src", b"-1"]),
13918 "-Invalid position\r\n"
13919 );
13920 assert_eq!(
13921 f.run(&[b"CF.LOADCHUNK", b"dst", b"0", b"x"]),
13922 "-Invalid position\r\n"
13923 );
13924 assert_eq!(
13925 f.run(&[b"CF.LOADCHUNK", b"dst", b"99999", b"x"]),
13926 "-Couldn't load chunk!\r\n"
13927 );
13928 let good = cf_header(0, 8, 0, 1, [2, 20, 1]);
13930 assert_eq!(
13931 f.run(&[b"CF.LOADCHUNK", b"dst", b"1", &good]),
13932 "-ERR item exists\r\n"
13933 );
13934 assert_eq!(
13938 f.run(&[b"CF.LOADCHUNK", b"n1", b"1", b"short"]),
13939 "-Invalid header\r\n"
13940 );
13941 for (why, bad) in [
13942 ("no filters at all", cf_header(0, 8, 0, 0, [2, 20, 1])),
13943 ("no buckets", cf_header(0, 0, 0, 1, [2, 20, 1])),
13944 (
13945 "a bucket count that is not a power of two",
13946 cf_header(0, 3, 0, 1, [2, 20, 1]),
13947 ),
13948 ("an empty bucket", cf_header(0, 8, 0, 1, [0, 20, 1])),
13949 ("no kicks", cf_header(0, 8, 0, 1, [2, 0, 1])),
13950 (
13951 "a growth nobody could reach",
13952 cf_header(0, 8, 0, 1, [2, 20, 32769]),
13953 ),
13954 (
13955 "a chain that cannot grow and did",
13956 cf_header(0, 8, 0, 2, [2, 20, 0]),
13957 ),
13958 (
13961 "a filter count that wraps",
13962 cf_header(0, 8, 0, 65_536, [2, 20, 1]),
13963 ),
13964 ] {
13965 assert_eq!(
13966 f.run(&[b"CF.LOADCHUNK", b"bad", b"1", &bad]),
13967 "-Couldn't create filter!\r\n",
13968 "{why}"
13969 );
13970 }
13971 }
13972
13973 #[test]
13976 fn the_cuckoo_family_answers_in_resp3_spelling_too() {
13977 let mut f = Fixture::new();
13978 f.out.set_proto(Proto::Resp3);
13979 assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
13980 assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
13981 assert_eq!(f.run(&[b"CF.ADDNX", b"c", b"a"]), "#f\r\n");
13982 assert_eq!(f.run(&[b"CF.EXISTS", b"c", b"a"]), "#t\r\n");
13983 assert_eq!(
13984 f.run(&[b"CF.MEXISTS", b"c", b"a", b"z"]),
13985 "*2\r\n#t\r\n#f\r\n"
13986 );
13987 assert_eq!(f.run(&[b"CF.DEL", b"c", b"a"]), "#t\r\n");
13988 assert_eq!(f.run(&[b"CF.DEL", b"c", b"z"]), "#f\r\n");
13989 assert_eq!(f.run(&[b"CF.COUNT", b"c", b"a"]), ":1\r\n");
13991 assert_eq!(
13992 f.run(&[b"CF.INFO", b"c"]),
13993 "%8\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
13994 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
13995 +Number of items deleted\r\n:1\r\n+Bucket size\r\n:2\r\n\
13996 +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
13997 );
13998
13999 f.run(&[
14004 b"CF.RESERVE",
14005 b"s",
14006 b"4",
14007 b"BUCKETSIZE",
14008 b"1",
14009 b"EXPANSION",
14010 b"0",
14011 ]);
14012 assert_eq!(
14013 f.run(&[
14014 b"CF.INSERT",
14015 b"s",
14016 b"ITEMS",
14017 b"a",
14018 b"b",
14019 b"c",
14020 b"d",
14021 b"e",
14022 b"f"
14023 ]),
14024 "*6\r\n#t\r\n#t\r\n#t\r\n#f\r\n#f\r\n#f\r\n"
14025 );
14026 assert_eq!(
14027 f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"a", b"zz"]),
14028 "*2\r\n:0\r\n:-1\r\n"
14029 );
14030 assert_eq!(f.run(&[b"CF.ADD", b"s", b"zzz"]), "-Filter is full\r\n");
14031 assert_eq!(f.run(&[b"CF.SCANDUMP", b"c", b"9999"]), "*2\r\n:0\r\n_\r\n");
14034 }
14035
14036 #[test]
14041 fn a_sketch_is_made_from_a_size_or_from_an_error_rate() {
14042 let mut f = Fixture::new();
14043 assert_eq!(f.run(&[b"CMS.INITBYDIM", b"d", b"100", b"5"]), "+OK\r\n");
14044 assert_eq!(
14045 f.run(&[b"CMS.INFO", b"d"]),
14046 "*6\r\n+width\r\n:100\r\n+depth\r\n:5\r\n+count\r\n:0\r\n"
14047 );
14048 assert_eq!(f.run(&[b"TYPE", b"d"]), "+CMSk-TYPE\r\n");
14049 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
14050 assert_eq!(
14053 f.run(&[b"CMS.INITBYPROB", b"p", b"0.01", b"0.03"]),
14054 "+OK\r\n"
14055 );
14056 assert_eq!(
14057 f.run(&[b"CMS.INFO", b"p"]),
14058 "*6\r\n+width\r\n:200\r\n+depth\r\n:6\r\n+count\r\n:0\r\n"
14059 );
14060 assert_eq!(
14063 f.run(&[b"CMS.INITBYDIM", b"d", b"0", b"2"]),
14064 "-CMS: key already exists\r\n"
14065 );
14066 assert_eq!(
14067 f.run(&[b"CMS.INITBYDIM", b"new", b"0", b"2"]),
14068 "-CMS: invalid width\r\n"
14069 );
14070 assert_eq!(
14071 f.run(&[b"CMS.INITBYDIM", b"new", b"2", b"0"]),
14072 "-CMS: invalid depth\r\n"
14073 );
14074 assert_eq!(
14075 f.run(&[b"CMS.INITBYPROB", b"new", b"0", b"0.5"]),
14076 "-CMS: invalid overestimation value\r\n"
14077 );
14078 assert_eq!(
14079 f.run(&[b"CMS.INITBYPROB", b"new", b"0.1", b"1"]),
14080 "-CMS: invalid prob value\r\n"
14081 );
14082 assert_eq!(
14086 f.run(&[b"CMS.INITBYPROB", b"new", b"0.5", b"1e-46"]),
14087 "-CMS: invalid init arguments\r\n"
14088 );
14089 assert_eq!(
14093 f.run(&[b"CMS.INITBYDIM", b"new", b"268435457", b"1"]),
14094 "-CMS: Insufficient memory to create the key\r\n"
14095 );
14096 assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
14097 }
14098
14099 #[test]
14102 fn increments_are_parsed_whole_and_the_counters_saturate() {
14103 let mut f = Fixture::new();
14104 f.run(&[b"CMS.INITBYDIM", b"c", b"100", b"4"]);
14105 assert_eq!(
14106 f.run(&[b"CMS.INCRBY", b"c", b"a", b"3", b"b", b"4"]),
14107 "*2\r\n:3\r\n:4\r\n"
14108 );
14109 assert_eq!(
14112 f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"a", b"1"]),
14113 "*2\r\n:4\r\n:5\r\n"
14114 );
14115 assert_eq!(
14117 f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"x"]),
14118 "-CMS: Cannot parse number\r\n"
14119 );
14120 assert_eq!(
14121 f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"-1"]),
14122 "-CMS: Number cannot be negative\r\n"
14123 );
14124 assert_eq!(
14125 f.run(&[b"CMS.QUERY", b"c", b"a", b"b"]),
14126 "*2\r\n:5\r\n:4\r\n"
14127 );
14128 f.run(&[b"CMS.INCRBY", b"c", b"a", b"4294967295"]);
14131 assert_eq!(
14132 f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b", b"1"]),
14133 "*2\r\n-CMS: INCRBY overflow\r\n:5\r\n"
14134 );
14135 assert_eq!(f.run(&[b"CMS.QUERY", b"c", b"a"]), "*1\r\n:4294967295\r\n");
14136 f.run(&[b"CMS.INITBYDIM", b"w", b"4", b"1"]);
14139 f.run(&[b"CMS.INCRBY", b"w", b"x", b"9223372036854775807"]);
14140 f.run(&[b"CMS.INCRBY", b"w", b"x", b"1"]);
14141 assert_eq!(
14142 f.run(&[b"CMS.INFO", b"w"]),
14143 "*6\r\n+width\r\n:4\r\n+depth\r\n:1\r\n+count\r\n:-9223372036854775808\r\n"
14144 );
14145 assert!(
14148 f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b"])
14149 .contains("wrong number of arguments")
14150 );
14151 assert_eq!(
14152 f.run(&[b"CMS.INCRBY", b"nope", b"a", b"1"]),
14153 "-CMS: key does not exist\r\n"
14154 );
14155 assert_eq!(
14156 f.run(&[b"CMS.QUERY", b"nope", b"a"]),
14157 "-CMS: key does not exist\r\n"
14158 );
14159 }
14160
14161 #[test]
14164 fn a_merge_lands_whole_or_not_at_all() {
14165 let mut f = Fixture::new();
14166 for name in [&b"m1"[..], b"m2", b"dst"] {
14167 f.run(&[b"CMS.INITBYDIM", name, b"64", b"3"]);
14168 }
14169 f.run(&[b"CMS.INCRBY", b"m1", b"a", b"5"]);
14170 f.run(&[b"CMS.INCRBY", b"m2", b"a", b"7"]);
14171 assert_eq!(
14172 f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
14173 "+OK\r\n"
14174 );
14175 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
14176 assert_eq!(
14179 f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
14180 "+OK\r\n"
14181 );
14182 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
14183 assert_eq!(
14184 f.run(&[
14185 b"CMS.MERGE",
14186 b"dst",
14187 b"2",
14188 b"m1",
14189 b"m2",
14190 b"WEIGHTS",
14191 b"2",
14192 b"3"
14193 ]),
14194 "+OK\r\n"
14195 );
14196 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
14197 assert_eq!(
14200 f.run(&[
14201 b"CMS.MERGE",
14202 b"dst",
14203 b"1",
14204 b"m1",
14205 b"WEIGHTS",
14206 b"4611686018427387904"
14207 ]),
14208 "-CMS: MERGE overflow\r\n"
14209 );
14210 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
14211 f.run(&[b"CMS.INITBYDIM", b"wide", b"128", b"3"]);
14214 assert_eq!(
14215 f.run(&[b"CMS.MERGE", b"gone", b"1", b"m1"]),
14216 "-CMS: key does not exist\r\n"
14217 );
14218 assert_eq!(
14219 f.run(&[b"CMS.MERGE", b"dst", b"0", b"m1"]),
14220 "-CMS: Number of keys must be positive\r\n"
14221 );
14222 assert_eq!(
14223 f.run(&[b"CMS.MERGE", b"dst", b"3", b"m1"]),
14224 "-CMS: wrong number of keys\r\n"
14225 );
14226 assert_eq!(
14227 f.run(&[b"CMS.MERGE", b"dst", b"1", b"m1", b"WEIGHTS", b"1", b"2"]),
14228 "-CMS: wrong number of keys/weights\r\n"
14229 );
14230 assert_eq!(
14231 f.run(&[b"CMS.MERGE", b"dst", b"1", b"wide"]),
14232 "-CMS: width/depth is not equal\r\n"
14233 );
14234 assert_eq!(
14235 f.run(&[b"CMS.MERGE", b"dst", b"1", b"gone"]),
14236 "-CMS: key does not exist\r\n"
14237 );
14238 }
14239
14240 #[test]
14243 fn a_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
14244 let mut f = Fixture::new();
14245 f.run(&[b"SET", b"s", b"text"]);
14246 for cmd in [
14247 vec![&b"CMS.INITBYDIM"[..], b"s", b"8", b"2"],
14248 vec![&b"CMS.INCRBY"[..], b"s", b"a", b"1"],
14249 vec![&b"CMS.QUERY"[..], b"s", b"a"],
14250 vec![&b"CMS.INFO"[..], b"s"],
14251 vec![&b"CMS.MERGE"[..], b"s", b"1", b"s"],
14252 ] {
14253 let name = String::from_utf8_lossy(cmd[0]).into_owned();
14254 let reply = f.run(&cmd);
14255 assert!(
14258 reply.starts_with("-WRONGTYPE") || reply == "-CMS: key already exists\r\n",
14259 "{name}: {reply}"
14260 );
14261 }
14262 f.run(&[b"CMS.INITBYDIM", b"c", b"64", b"2"]);
14263 assert_eq!(
14267 f.run(&[b"COPY", b"c", b"c2"]),
14268 "-ERR not supported for this module key\r\n"
14269 );
14270 assert_eq!(
14271 f.run(&[b"DUMP", b"c"]),
14272 "-ERR DUMP is not supported for this module key\r\n"
14273 );
14274 f.run(&[b"G.NADD", b"g", b"a"]);
14276 assert_eq!(
14277 f.run(&[b"COPY", b"g", b"g2"]),
14278 "-ERR COPY is not supported for a graph\r\n"
14279 );
14280 assert_eq!(
14281 f.run(&[b"DUMP", b"g"]),
14282 "-ERR DUMP is not supported for a graph\r\n"
14283 );
14284 assert_eq!(f.run(&[b"EXPIRE", b"c", b"100"]), ":1\r\n");
14287 assert_eq!(f.run(&[b"PERSIST", b"c"]), ":1\r\n");
14288 assert_eq!(f.run(&[b"RENAME", b"c", b"c3"]), "+OK\r\n");
14289 assert_eq!(f.run(&[b"TYPE", b"c3"]), "+CMSk-TYPE\r\n");
14290 assert_eq!(f.run(&[b"DEL", b"c3"]), ":1\r\n");
14291 }
14292
14293 #[test]
14298 fn a_reserve_takes_three_arguments_or_six() {
14299 let mut f = Fixture::new();
14300 assert_eq!(f.run(&[b"TOPK.RESERVE", b"t", b"5"]), "+OK\r\n");
14301 assert_eq!(
14302 f.run(&[b"TOPK.INFO", b"t"]),
14303 "*8\r\n+k\r\n:5\r\n+width\r\n:8\r\n+depth\r\n:7\r\n+decay\r\n$3\r\n0.9\r\n"
14304 );
14305 for cmd in [
14308 vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8"],
14309 vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8", b"7"],
14310 ] {
14311 assert!(f.run(&cmd).contains("wrong number of arguments"));
14312 }
14313 assert_eq!(
14314 f.run(&[b"TOPK.RESERVE", b"u", b"5", b"8", b"7", b"0.5"]),
14315 "+OK\r\n"
14316 );
14317 assert_eq!(
14320 f.run(&[b"TOPK.RESERVE", b"u", b"0", b"0", b"0", b"9"]),
14321 "-TopK: key already exists\r\n"
14322 );
14323 assert_eq!(
14324 f.run(&[b"TOPK.RESERVE", b"v", b"0"]),
14325 "-TopK: invalid k\r\n"
14326 );
14327 assert_eq!(
14328 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"0", b"7", b"0.9"]),
14329 "-TopK: invalid width\r\n"
14330 );
14331 assert_eq!(
14332 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"x", b"0.9"]),
14333 "-TopK: invalid depth\r\n"
14334 );
14335 assert_eq!(
14337 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"0"]),
14338 "-TopK: invalid decay value. must be '<= 1' & '> 0'\r\n"
14339 );
14340 assert_eq!(
14341 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"1"]),
14342 "+OK\r\n"
14343 );
14344 assert_eq!(
14346 f.run(&[
14347 b"TOPK.RESERVE",
14348 b"w",
14349 b"1",
14350 b"4294967295",
14351 b"4294967295",
14352 b"0.9"
14353 ]),
14354 "-ERR Insufficient memory to create topk data structure\r\n"
14355 );
14356 }
14357
14358 #[test]
14360 fn the_kept_set_is_what_query_and_list_answer_from() {
14361 let mut f = Fixture::new();
14362 f.run(&[b"TOPK.RESERVE", b"t", b"2", b"1000", b"5", b"0.9"]);
14363 assert_eq!(
14366 f.run(&[b"TOPK.ADD", b"t", b"a", b"b"]),
14367 "*2\r\n$-1\r\n$-1\r\n"
14368 );
14369 assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"a", b"10"]), "*1\r\n$-1\r\n");
14370 assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"c"]), "*1\r\n$1\r\nb\r\n");
14373 assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"c", b"5"]), "*1\r\n$-1\r\n");
14374 assert_eq!(
14375 f.run(&[b"TOPK.QUERY", b"t", b"a", b"b", b"c"]),
14376 "*3\r\n:1\r\n:0\r\n:1\r\n"
14377 );
14378 assert_eq!(
14380 f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
14381 "*3\r\n:11\r\n:1\r\n:6\r\n"
14382 );
14383 assert_eq!(f.run(&[b"TOPK.LIST", b"t"]), "*2\r\n$1\r\na\r\n$1\r\nc\r\n");
14384 assert_eq!(
14385 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"]),
14386 "*4\r\n$1\r\na\r\n:11\r\n$1\r\nc\r\n:6\r\n"
14387 );
14388 assert_eq!(
14391 f.run(&[b"TOPK.LIST", b"t", b"w"]),
14392 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
14393 );
14394 assert_eq!(
14395 f.run(&[b"TOPK.LIST", b"t", b""]),
14396 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
14397 );
14398 assert_eq!(
14399 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNTS"]),
14400 "-WITHCOUNT keyword expected\r\n"
14401 );
14402 assert_eq!(
14405 f.run(&[b"TOPK.LIST", b"missing", b"nope"]),
14406 "-WITHCOUNT keyword expected\r\n"
14407 );
14408 assert_eq!(
14409 f.run(&[b"TOPK.LIST", b"missing"]),
14410 "-TopK: key does not exist\r\n"
14411 );
14412 f.run(&[b"TOPK.RESERVE", b"z", b"3"]);
14414 assert_eq!(
14415 f.run(&[b"TOPK.INCRBY", b"z", b"nothing", b"0"]),
14416 "*1\r\n$-1\r\n"
14417 );
14418 assert_eq!(f.run(&[b"TOPK.QUERY", b"z", b"nothing"]), "*1\r\n:1\r\n");
14419 assert_eq!(f.run(&[b"TOPK.LIST", b"z"]), "*0\r\n");
14420 }
14421
14422 #[test]
14425 fn an_increment_is_applied_as_it_goes_and_stops_at_a_bad_one() {
14426 let mut f = Fixture::new();
14427 f.run(&[b"TOPK.RESERVE", b"t", b"5", b"1000", b"5", b"0.9"]);
14428 assert_eq!(
14432 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"3", b"b", b"-1", b"c", b"4"]),
14433 format!(
14434 "*2\r\n$-1\r\n-{}\r\n",
14435 "TopK: increment must be an integer greater or equal to 0 and smaller or equal to 100,000"
14436 )
14437 );
14438 assert_eq!(
14439 f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
14440 "*3\r\n:3\r\n:0\r\n:0\r\n"
14441 );
14442 assert_eq!(
14444 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100000"]),
14445 "*1\r\n$-1\r\n"
14446 );
14447 assert!(
14448 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100001"])
14449 .contains("smaller or equal to 100,000")
14450 );
14451 assert!(
14453 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"1", b"b"])
14454 .contains("wrong number of arguments")
14455 );
14456 assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:100003\r\n");
14457 }
14458
14459 #[test]
14461 fn a_query_is_a_bool_and_info_is_a_map_on_resp3() {
14462 let mut f = Fixture::new();
14463 f.run(&[b"HELLO", b"3"]);
14464 f.run(&[b"TOPK.RESERVE", b"t", b"2", b"8", b"7", b"0.5"]);
14465 f.run(&[b"TOPK.ADD", b"t", b"a"]);
14466 assert_eq!(
14467 f.run(&[b"TOPK.QUERY", b"t", b"a", b"b"]),
14468 "*2\r\n#t\r\n#f\r\n"
14469 );
14470 assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:1\r\n");
14472 assert_eq!(
14473 f.run(&[b"TOPK.INFO", b"t"]),
14474 "%4\r\n+k\r\n:2\r\n+width\r\n:8\r\n+depth\r\n:7\r\n+decay\r\n,0.5\r\n"
14475 );
14476 assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"a"]), "*1\r\n_\r\n");
14477 }
14478
14479 #[test]
14482 fn a_top_k_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
14483 let mut f = Fixture::new();
14484 f.run(&[b"SET", b"s", b"text"]);
14485 for cmd in [
14486 vec![&b"TOPK.RESERVE"[..], b"s", b"5"],
14487 vec![&b"TOPK.ADD"[..], b"s", b"a"],
14488 vec![&b"TOPK.INCRBY"[..], b"s", b"a", b"1"],
14489 vec![&b"TOPK.QUERY"[..], b"s", b"a"],
14490 vec![&b"TOPK.COUNT"[..], b"s", b"a"],
14491 vec![&b"TOPK.LIST"[..], b"s"],
14492 vec![&b"TOPK.INFO"[..], b"s"],
14493 ] {
14494 let name = String::from_utf8_lossy(cmd[0]).into_owned();
14495 let reply = f.run(&cmd);
14496 assert!(
14497 reply.starts_with("-WRONGTYPE") || reply == "-TopK: key already exists\r\n",
14498 "{name}: {reply}"
14499 );
14500 }
14501 f.run(&[b"TOPK.RESERVE", b"t", b"5"]);
14502 assert_eq!(
14503 f.run(&[b"COPY", b"t", b"t2"]),
14504 "-ERR not supported for this module key\r\n"
14505 );
14506 assert_eq!(
14507 f.run(&[b"DUMP", b"t"]),
14508 "-ERR DUMP is not supported for this module key\r\n"
14509 );
14510 assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
14511 assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
14512 assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
14513 assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TopK-TYPE\r\n");
14514 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
14515 assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
14516 assert_eq!(
14519 f.run(&[b"TOPK.INFO", b"t3"]),
14520 "-TopK: key does not exist\r\n"
14521 );
14522 }
14523
14524 #[test]
14529 fn a_create_takes_two_arguments_or_four_and_reads_the_last_one() {
14530 let mut f = Fixture::new();
14531 assert_eq!(f.run(&[b"TDIGEST.CREATE", b"t"]), "+OK\r\n");
14532 assert_eq!(
14534 f.run(&[b"TDIGEST.INFO", b"t"]),
14535 "*18\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:0\r\n\
14536 +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:0\r\n+Unmerged weight\r\n:0\r\n\
14537 +Observations\r\n:0\r\n+Total compressions\r\n:0\r\n+Memory usage\r\n:9840\r\n"
14538 );
14539 assert_eq!(
14540 f.run(&[b"TDIGEST.CREATE", b"t"]),
14541 "-ERR T-Digest: key already exists\r\n"
14542 );
14543 assert!(
14545 f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION"])
14546 .contains("wrong number of arguments")
14547 );
14548 assert_eq!(
14549 f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION", b"1000"]),
14550 "+OK\r\n"
14551 );
14552 assert_eq!(
14553 f.run(&[b"TDIGEST.CREATE", b"v", b"compression", b"1"]),
14554 "+OK\r\n"
14555 );
14556 assert_eq!(
14560 f.run(&[b"TDIGEST.CREATE", b"w", b"100", b"COMPRESSION"]),
14561 "-ERR T-Digest: error parsing compression parameter\r\n"
14562 );
14563 assert_eq!(
14564 f.run(&[b"TDIGEST.CREATE", b"w", b"NOPE", b"100"]),
14565 "-ERR T-Digest: wrong keyword\r\n"
14566 );
14567 assert_eq!(
14568 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"1.5"]),
14569 "-ERR T-Digest: error parsing compression parameter\r\n"
14570 );
14571 assert_eq!(
14572 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"0"]),
14573 "-ERR T-Digest: compression parameter needs to be a positive integer\r\n"
14574 );
14575 assert_eq!(
14578 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"357913942"]),
14579 "-ERR T-Digest: allocation failed\r\n"
14580 );
14581 assert_eq!(
14583 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"100000000"]),
14584 "-ERR T-Digest: allocation failed\r\n"
14585 );
14586 assert_eq!(
14589 f.run(&[b"TDIGEST.CREATE", b"t", b"COMPRESSION", b"0"]),
14590 "-ERR T-Digest: key already exists\r\n"
14591 );
14592 }
14593
14594 #[test]
14597 fn the_quantile_family_answers_what_the_module_answers() {
14598 let mut f = Fixture::new();
14599 f.run(&[b"TDIGEST.CREATE", b"s"]);
14600 assert_eq!(
14601 f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]),
14602 "+OK\r\n"
14603 );
14604 assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), "$1\r\n1\r\n");
14605 assert_eq!(f.run(&[b"TDIGEST.MAX", b"s"]), "$1\r\n4\r\n");
14606 assert_eq!(
14608 f.run(&[b"TDIGEST.CDF", b"s", b"1", b"2", b"3", b"4"]),
14609 "*4\r\n$5\r\n0.125\r\n$5\r\n0.375\r\n$5\r\n0.625\r\n$5\r\n0.875\r\n"
14610 );
14611 assert_eq!(
14612 f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"0.5", b"1"]),
14613 "*3\r\n$1\r\n1\r\n$1\r\n3\r\n$1\r\n4\r\n"
14614 );
14615 assert_eq!(
14618 f.run(&[b"TDIGEST.QUANTILE", b"s", b"0.5", b"0.1", b"0.9"]),
14619 "*3\r\n$1\r\n3\r\n$1\r\n1\r\n$1\r\n4\r\n"
14620 );
14621 assert_eq!(
14622 f.run(&[b"TDIGEST.RANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
14623 "*5\r\n:-1\r\n:0\r\n:2\r\n:3\r\n:4\r\n"
14624 );
14625 assert_eq!(
14626 f.run(&[b"TDIGEST.REVRANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
14627 "*5\r\n:4\r\n:3\r\n:1\r\n:0\r\n:-1\r\n"
14628 );
14629 assert_eq!(
14630 f.run(&[b"TDIGEST.BYRANK", b"s", b"0", b"1", b"3", b"4"]),
14631 "*4\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n4\r\n$3\r\ninf\r\n"
14632 );
14633 assert_eq!(
14634 f.run(&[b"TDIGEST.BYREVRANK", b"s", b"0", b"1", b"3", b"4"]),
14635 "*4\r\n$1\r\n4\r\n$1\r\n3\r\n$1\r\n1\r\n$4\r\n-inf\r\n"
14636 );
14637 assert_eq!(
14638 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0", b"1"]),
14639 "$3\r\n2.5\r\n"
14640 );
14641 assert_eq!(
14642 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.25", b"0.75"]),
14643 "$3\r\n2.5\r\n"
14644 );
14645 assert_eq!(
14647 f.run(&[b"TDIGEST.QUANTILE", b"s", b"1.1"]),
14648 "-ERR T-Digest: quantile should be in [0,1]\r\n"
14649 );
14650 assert_eq!(
14651 f.run(&[b"TDIGEST.QUANTILE", b"s", b"zzz"]),
14652 "-ERR T-Digest: error parsing quantile\r\n"
14653 );
14654 assert_eq!(
14655 f.run(&[b"TDIGEST.CDF", b"s", b"zzz"]),
14656 "-ERR T-Digest: error parsing cdf\r\n"
14657 );
14658 assert_eq!(
14659 f.run(&[b"TDIGEST.RANK", b"s", b"zzz"]),
14660 "-ERR T-Digest: error parsing value\r\n"
14661 );
14662 assert_eq!(
14663 f.run(&[b"TDIGEST.BYRANK", b"s", b"-1"]),
14664 "-ERR T-Digest: rank needs to be non negative\r\n"
14665 );
14666 assert_eq!(
14667 f.run(&[b"TDIGEST.BYRANK", b"s", b"1.5"]),
14668 "-ERR T-Digest: error parsing rank\r\n"
14669 );
14670 assert_eq!(
14673 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"zzz", b"0.9"]),
14674 "-ERR T-Digest: error parsing low_cut_percentile\r\n"
14675 );
14676 assert_eq!(
14677 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"zzz"]),
14678 "-ERR T-Digest: error parsing high_cut_percentile\r\n"
14679 );
14680 assert_eq!(
14681 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"1.1"]),
14682 "-ERR T-Digest: low_cut_percentile and high_cut_percentile should be in [0,1]\r\n"
14683 );
14684 assert_eq!(
14685 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.5", b"0.5"]),
14686 "-ERR T-Digest: low_cut_percentile should be lower than high_cut_percentile\r\n"
14687 );
14688 }
14689
14690 #[test]
14693 fn an_empty_digest_has_an_answer_for_everything() {
14694 let mut f = Fixture::new();
14695 f.run(&[b"TDIGEST.CREATE", b"e"]);
14696 assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
14697 assert_eq!(f.run(&[b"TDIGEST.MAX", b"e"]), "$3\r\nnan\r\n");
14698 assert_eq!(
14699 f.run(&[b"TDIGEST.QUANTILE", b"e", b"0", b"1"]),
14700 "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
14701 );
14702 assert_eq!(f.run(&[b"TDIGEST.CDF", b"e", b"0"]), "*1\r\n$3\r\nnan\r\n");
14703 assert_eq!(
14704 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"e", b"0.1", b"0.9"]),
14705 "$3\r\nnan\r\n"
14706 );
14707 assert_eq!(
14710 f.run(&[b"TDIGEST.RANK", b"e", b"0", b"1"]),
14711 "*2\r\n:-2\r\n:-2\r\n"
14712 );
14713 assert_eq!(
14714 f.run(&[b"TDIGEST.REVRANK", b"e", b"0", b"1"]),
14715 "*2\r\n:-2\r\n:-2\r\n"
14716 );
14717 assert_eq!(
14718 f.run(&[b"TDIGEST.BYRANK", b"e", b"0", b"5"]),
14719 "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
14720 );
14721 f.run(&[b"TDIGEST.ADD", b"e", b"1", b"2", b"3"]);
14723 assert_eq!(f.run(&[b"TDIGEST.RESET", b"e"]), "+OK\r\n");
14724 assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
14725 f.run(&[b"TDIGEST.CREATE", b"e2"]);
14728 assert_eq!(
14729 f.run(&[b"TDIGEST.INFO", b"e"]),
14730 f.run(&[b"TDIGEST.INFO", b"e2"])
14731 );
14732 }
14733
14734 #[test]
14737 fn a_sample_is_read_the_way_redis_reads_a_double() {
14738 let mut f = Fixture::new();
14739 f.run(&[b"TDIGEST.CREATE", b"a"]);
14740 for bad in [
14743 &b"nan"[..],
14744 b"1e400",
14745 b"-1e400",
14746 b"1e309",
14747 b"1e-400",
14748 b"",
14749 b" 1",
14750 b"1 ",
14751 b"1e",
14752 b"--1",
14753 ] {
14754 assert_eq!(
14755 f.run(&[b"TDIGEST.ADD", b"a", bad]),
14756 "-ERR T-Digest: error parsing val parameter\r\n",
14757 "{}",
14758 String::from_utf8_lossy(bad)
14759 );
14760 }
14761 for word in [&b"inf"[..], b"-inf", b"+INF", b"Infinity"] {
14764 assert_eq!(
14765 f.run(&[b"TDIGEST.ADD", b"a", word]),
14766 "-ERR T-Digest: val parameter needs to be a finite number\r\n",
14767 "{}",
14768 String::from_utf8_lossy(word)
14769 );
14770 }
14771 for good in [&b"0x10"[..], b".5", b"1.", b"1e-320", b"-0", b"0"] {
14774 assert_eq!(
14775 f.run(&[b"TDIGEST.ADD", b"a", good]),
14776 "+OK\r\n",
14777 "{}",
14778 String::from_utf8_lossy(good)
14779 );
14780 }
14781 assert!(
14783 f.run(&[b"TDIGEST.INFO", b"a"])
14784 .contains("Observations\r\n:6\r\n")
14785 );
14786 assert_eq!(
14789 f.run(&[b"TDIGEST.ADD", b"a", b"1", b"zzz"]),
14790 "-ERR T-Digest: error parsing val parameter\r\n"
14791 );
14792 assert!(
14793 f.run(&[b"TDIGEST.INFO", b"a"])
14794 .contains("Observations\r\n:6\r\n")
14795 );
14796 }
14797
14798 #[test]
14801 fn a_merge_sweeps_the_destination_between_its_inputs() {
14802 let mut f = Fixture::new();
14803 f.run(&[b"TDIGEST.CREATE", b"m1", b"COMPRESSION", b"100"]);
14804 f.run(&[b"TDIGEST.ADD", b"m1", b"1", b"2", b"3"]);
14805 f.run(&[b"TDIGEST.CREATE", b"m2", b"COMPRESSION", b"200"]);
14806 f.run(&[b"TDIGEST.ADD", b"m2", b"4", b"5", b"6"]);
14807 assert_eq!(
14808 f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"m2"]),
14809 "+OK\r\n"
14810 );
14811 let info = f.run(&[b"TDIGEST.INFO", b"d"]);
14816 assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
14817 assert!(info.contains("Merged nodes\r\n:3\r\n"), "{info}");
14818 assert!(info.contains("Unmerged nodes\r\n:3\r\n"), "{info}");
14819 assert!(info.contains("Total compressions\r\n:1\r\n"), "{info}");
14820 assert_eq!(f.run(&[b"TDIGEST.MIN", b"d"]), "$1\r\n1\r\n");
14821 assert_eq!(f.run(&[b"TDIGEST.MAX", b"d"]), "$1\r\n6\r\n");
14822 assert!(
14825 f.run(&[b"TDIGEST.INFO", b"m1"])
14826 .contains("Merged nodes\r\n:3\r\n")
14827 );
14828 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1"]);
14831 let info = f.run(&[b"TDIGEST.INFO", b"d"]);
14832 assert!(info.contains("Observations\r\n:9\r\n"), "{info}");
14833 assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
14834 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"OVERRIDE"]);
14837 let info = f.run(&[b"TDIGEST.INFO", b"d"]);
14838 assert!(info.contains("Observations\r\n:3\r\n"), "{info}");
14839 assert!(info.contains("Compression\r\n:100\r\n"), "{info}");
14840 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION", b"500"]);
14842 assert!(
14843 f.run(&[b"TDIGEST.INFO", b"d"])
14844 .contains("Compression\r\n:500\r\n")
14845 );
14846 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"d"]);
14848 assert!(
14849 f.run(&[b"TDIGEST.INFO", b"d"])
14850 .contains("Observations\r\n:12\r\n")
14851 );
14852 assert_eq!(
14854 f.run(&[b"TDIGEST.MERGE", b"d", b"zzz", b"m1"]),
14855 "-ERR T-Digest: error parsing numkeys\r\n"
14856 );
14857 assert_eq!(
14858 f.run(&[b"TDIGEST.MERGE", b"d", b"0", b"m1"]),
14859 "-ERR T-Digest: numkeys needs to be a positive integer\r\n"
14860 );
14861 assert!(
14862 f.run(&[b"TDIGEST.MERGE", b"d", b"3", b"m1", b"m2"])
14863 .contains("wrong number of arguments")
14864 );
14865 assert!(
14866 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION"])
14867 .contains("wrong number of arguments")
14868 );
14869 assert_eq!(
14870 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"NOPE"]),
14871 "-ERR T-Digest: wrong keyword\r\n"
14872 );
14873 assert_eq!(
14876 f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"gone"]),
14877 "-ERR T-Digest: key does not exist\r\n"
14878 );
14879 assert!(
14880 f.run(&[b"TDIGEST.INFO", b"d"])
14881 .contains("Observations\r\n:12\r\n")
14882 );
14883 assert_eq!(
14886 f.run(&[b"TDIGEST.MERGE", b"gone", b"1", b"gone"]),
14887 "-ERR T-Digest: key does not exist\r\n"
14888 );
14889 }
14890
14891 #[test]
14893 fn a_digest_answers_doubles_and_a_map_on_resp3() {
14894 let mut f = Fixture::new();
14895 f.run(&[b"HELLO", b"3"]);
14896 f.run(&[b"TDIGEST.CREATE", b"s"]);
14897 f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]);
14898 assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), ",1\r\n");
14899 assert_eq!(
14900 f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"1"]),
14901 "*2\r\n,1\r\n,4\r\n"
14902 );
14903 assert_eq!(f.run(&[b"TDIGEST.CDF", b"s", b"1"]), "*1\r\n,0.125\r\n");
14904 assert_eq!(f.run(&[b"TDIGEST.BYRANK", b"s", b"4"]), "*1\r\n,inf\r\n");
14906 assert_eq!(
14907 f.run(&[b"TDIGEST.BYREVRANK", b"s", b"4"]),
14908 "*1\r\n,-inf\r\n"
14909 );
14910 f.run(&[b"TDIGEST.CREATE", b"e"]);
14911 assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), ",nan\r\n");
14912 assert_eq!(f.run(&[b"TDIGEST.RANK", b"s", b"1"]), "*1\r\n:0\r\n");
14914 assert_eq!(
14917 f.run(&[b"TDIGEST.INFO", b"s"]),
14918 "%9\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:4\r\n\
14919 +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:4\r\n+Unmerged weight\r\n:0\r\n\
14920 +Observations\r\n:4\r\n+Total compressions\r\n:1\r\n+Memory usage\r\n:9840\r\n"
14921 );
14922 }
14923
14924 #[test]
14927 fn a_t_digest_is_a_module_key_to_the_rest_of_the_keyspace() {
14928 let mut f = Fixture::new();
14929 f.run(&[b"SET", b"s", b"text"]);
14930 for cmd in [
14931 vec![&b"TDIGEST.CREATE"[..], b"s"],
14932 vec![&b"TDIGEST.RESET"[..], b"s"],
14933 vec![&b"TDIGEST.ADD"[..], b"s", b"1"],
14934 vec![&b"TDIGEST.MIN"[..], b"s"],
14935 vec![&b"TDIGEST.MAX"[..], b"s"],
14936 vec![&b"TDIGEST.QUANTILE"[..], b"s", b"0.5"],
14937 vec![&b"TDIGEST.CDF"[..], b"s", b"1"],
14938 vec![&b"TDIGEST.TRIMMED_MEAN"[..], b"s", b"0.1", b"0.9"],
14939 vec![&b"TDIGEST.RANK"[..], b"s", b"1"],
14940 vec![&b"TDIGEST.REVRANK"[..], b"s", b"1"],
14941 vec![&b"TDIGEST.BYRANK"[..], b"s", b"0"],
14942 vec![&b"TDIGEST.BYREVRANK"[..], b"s", b"0"],
14943 vec![&b"TDIGEST.INFO"[..], b"s"],
14944 ] {
14945 let name = String::from_utf8_lossy(cmd[0]).into_owned();
14946 let reply = f.run(&cmd);
14947 assert!(reply.starts_with("-WRONGTYPE"), "{name}: {reply}");
14948 }
14949 f.run(&[b"TDIGEST.CREATE", b"t"]);
14951 assert!(
14952 f.run(&[b"TDIGEST.MERGE", b"s", b"1", b"t"])
14953 .starts_with("-WRONGTYPE")
14954 );
14955 assert!(
14956 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"s"])
14957 .starts_with("-WRONGTYPE")
14958 );
14959 assert_eq!(
14960 f.run(&[b"COPY", b"t", b"t2"]),
14961 "-ERR not supported for this module key\r\n"
14962 );
14963 assert_eq!(
14964 f.run(&[b"DUMP", b"t"]),
14965 "-ERR DUMP is not supported for this module key\r\n"
14966 );
14967 assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
14968 assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
14969 assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
14970 assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TDIS-TYPE\r\n");
14971 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
14972 assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
14973 assert_eq!(
14976 f.run(&[b"TDIGEST.INFO", b"t3"]),
14977 "-ERR T-Digest: key does not exist\r\n"
14978 );
14979 assert_eq!(
14982 f.run(&[b"TDIGEST.QUANTILE", b"t3", b"zzz"]),
14983 "-ERR T-Digest: key does not exist\r\n"
14984 );
14985 }
14986
14987 fn without_memory(reply: &str) -> String {
14997 let head = "+memoryUsage\r\n:";
14998 let at = reply.find(head).expect("every TS.INFO reports memory");
14999 let rest = &reply[at + head.len()..];
15000 let end = rest.find("\r\n").expect("and it is a whole number");
15001 format!("{}{}", &reply[..at + head.len()], &rest[end..])
15002 }
15003
15004 #[test]
15007 fn a_series_is_made_empty_and_reports_on_itself() {
15008 let mut f = Fixture::new();
15009 assert_eq!(f.run(&[b"TS.CREATE", b"t"]), "+OK\r\n");
15010 assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
15011 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t"]), "$3\r\nraw\r\n");
15012 assert_eq!(
15016 without_memory(&f.run(&[b"TS.INFO", b"t"])),
15017 "*28\r\n\
15018 +totalSamples\r\n:0\r\n\
15019 +memoryUsage\r\n:\r\n\
15020 +firstTimestamp\r\n:0\r\n\
15021 +lastTimestamp\r\n:0\r\n\
15022 +retentionTime\r\n:0\r\n\
15023 +chunkCount\r\n:1\r\n\
15024 +chunkSize\r\n:4096\r\n\
15025 +chunkType\r\n+compressed\r\n\
15026 +duplicatePolicy\r\n+block\r\n\
15027 +labels\r\n*0\r\n\
15028 +sourceKey\r\n$-1\r\n\
15029 +rules\r\n*0\r\n\
15030 +ignoreMaxTimeDiff\r\n:0\r\n\
15031 +ignoreMaxValDiff\r\n$1\r\n0\r\n"
15032 );
15033 assert_eq!(
15036 f.run(&[b"TS.CREATE", b"t"]),
15037 "-ERR TSDB: key already exists\r\n"
15038 );
15039 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
15040 assert_eq!(
15041 f.run(&[b"TS.CREATE", b"str"]),
15042 "-ERR TSDB: key already exists\r\n"
15043 );
15044 assert_eq!(
15047 f.run(&[b"TS.CREATE", b"t", b"RETENTION", b"abc"]),
15048 "-ERR TSDB: Couldn't parse RETENTION\r\n"
15049 );
15050 let wrong = "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
15056 assert_eq!(f.run(&[b"TS.INFO", b"str"]), wrong);
15057 assert_eq!(f.run(&[b"TS.GET", b"str"]), wrong);
15058 assert_eq!(f.run(&[b"TS.ALTER", b"str"]), wrong);
15059 assert_eq!(f.run(&[b"TS.DEL", b"str", b"0", b"1"]), wrong);
15060 assert_eq!(f.run(&[b"TS.INCRBY", b"str", b"1"]), wrong);
15061 assert_eq!(
15062 f.run(&[b"TS.ADD", b"str", b"1", b"1"]),
15063 "-ERR TSDB: the key is not a TSDB key\r\n"
15064 );
15065 assert_eq!(
15067 f.run(&[b"TS.INFO", b"nope"]),
15068 "-ERR TSDB: the key does not exist\r\n"
15069 );
15070 assert_eq!(
15071 f.run(&[b"TS.GET", b"nope"]),
15072 "-ERR TSDB: the key does not exist\r\n"
15073 );
15074 assert_eq!(
15075 f.run(&[b"TS.ALTER", b"nope"]),
15076 "-ERR TSDB: the key does not exist\r\n"
15077 );
15078 assert_eq!(
15079 f.run(&[b"TS.DEL", b"nope", b"1", b"2"]),
15080 "-ERR TSDB: the key does not exist\r\n"
15081 );
15082 }
15083
15084 #[test]
15087 fn the_options_are_a_keyword_scan_and_not_a_grammar() {
15088 let mut f = Fixture::new();
15089 assert_eq!(
15090 f.run(&[
15091 b"TS.CREATE",
15092 b"t",
15093 b"RETENTION",
15094 b"5000",
15095 b"ENCODING",
15096 b"UNCOMPRESSED",
15097 b"CHUNK_SIZE",
15098 b"128",
15099 b"DUPLICATE_POLICY",
15100 b"LAST",
15101 b"IGNORE",
15102 b"10",
15103 b"0.5",
15104 b"LABELS",
15105 b"room",
15106 b"kitchen"
15107 ]),
15108 "+OK\r\n"
15109 );
15110 let info = f.run(&[b"TS.INFO", b"t"]);
15111 assert!(info.contains("+retentionTime\r\n:5000\r\n"), "{info}");
15112 assert!(info.contains("+chunkSize\r\n:128\r\n"), "{info}");
15113 assert!(info.contains("+chunkType\r\n+uncompressed\r\n"), "{info}");
15114 assert!(info.contains("+duplicatePolicy\r\n+last\r\n"), "{info}");
15115 assert!(info.contains("+ignoreMaxTimeDiff\r\n:10\r\n"), "{info}");
15116 assert!(
15119 info.contains("+ignoreMaxValDiff\r\n$3\r\n0.5\r\n"),
15120 "{info}"
15121 );
15122 assert!(
15123 info.contains("+labels\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n"),
15124 "{info}"
15125 );
15126
15127 assert_eq!(f.run(&[b"TS.CREATE", b"junk", b"FOO"]), "+OK\r\n");
15129 assert_eq!(
15133 f.run(&[
15134 b"TS.CREATE",
15135 b"g",
15136 b"LABELS",
15137 b"a",
15138 b"b",
15139 b"RETENTION",
15140 b"5"
15141 ]),
15142 "+OK\r\n"
15143 );
15144 let greedy = f.run(&[b"TS.INFO", b"g"]);
15145 assert!(greedy.contains("+retentionTime\r\n:5\r\n"), "{greedy}");
15146 assert!(
15147 greedy.contains("*2\r\n$1\r\na\r\n$1\r\nb\r\n*2\r\n$9\r\nRETENTION\r\n$1\r\n5\r\n"),
15148 "{greedy}"
15149 );
15150
15151 assert_eq!(
15153 f.run(&[b"TS.CREATE", b"e", b"LABELS", b"a", b"b(c"]),
15154 "-ERR TSDB: Couldn't parse LABELS\r\n"
15155 );
15156 assert_eq!(
15157 f.run(&[b"TS.CREATE", b"e", b"LABELS", b"", b"b"]),
15158 "-ERR TSDB: Couldn't parse LABELS\r\n"
15159 );
15160 assert_eq!(
15161 f.run(&[b"TS.CREATE", b"e", b"RETENTION"]),
15162 "-ERR TSDB: Couldn't parse RETENTION\r\n"
15163 );
15164 assert_eq!(
15167 f.run(&[b"TS.CREATE", b"e", b"RETENTION", b"-1"]),
15168 "-TSDB: Couldn't parse RETENTION\r\n"
15169 );
15170 assert_eq!(
15171 f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"abc"]),
15172 "-ERR TSDB: Couldn't parse CHUNK_SIZE\r\n"
15173 );
15174 assert_eq!(
15175 f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"100"]),
15176 "-ERR TSDB: CHUNK_SIZE value must be a multiple of 8 in the range [48 .. 1048576]\r\n"
15177 );
15178 assert_eq!(
15179 f.run(&[b"TS.CREATE", b"e", b"ENCODING", b"nope"]),
15180 "-ERR TSDB: unknown ENCODING parameter\r\n"
15181 );
15182 assert!(
15185 f.run(&[b"TS.CREATE", b"e", b"ENCODING"])
15186 .contains("wrong number of arguments for 'ts.create' command")
15187 );
15188 assert_eq!(
15189 f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY"]),
15190 "-ERR TSDB: Couldn't parse DUPLICATE_POLICY\r\n"
15191 );
15192 assert_eq!(
15193 f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY", b"nope"]),
15194 "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
15195 );
15196 assert_eq!(
15197 f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"10"]),
15198 "-ERR TSDB: Couldn't parse IGNORE\r\n"
15199 );
15200 assert_eq!(
15201 f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"-1", b"1"]),
15202 "-ERR TSDB: IGNORE arguments cannot be negative\r\n"
15203 );
15204 assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
15205
15206 assert_eq!(f.run(&[b"TS.ALTER", b"t", b"RETENTION", b"9"]), "+OK\r\n");
15209 let after = f.run(&[b"TS.INFO", b"t"]);
15210 assert!(after.contains("+retentionTime\r\n:9\r\n"), "{after}");
15211 assert!(after.contains("+chunkSize\r\n:128\r\n"), "{after}");
15212 assert!(after.contains("+duplicatePolicy\r\n+last\r\n"), "{after}");
15213 assert_eq!(
15214 f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"nope"]),
15215 "-ERR TSDB: unknown ENCODING parameter\r\n"
15216 );
15217 assert_eq!(
15219 f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"COMPRESSED"]),
15220 "+OK\r\n"
15221 );
15222 assert!(
15223 f.run(&[b"TS.INFO", b"t"])
15224 .contains("+chunkType\r\n+uncompressed\r\n")
15225 );
15226 }
15227
15228 #[test]
15231 fn samples_land_where_they_are_put_and_the_newest_comes_back() {
15232 let mut f = Fixture::new();
15233 assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1.5"]), ":100\r\n");
15234 assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
15236 assert_eq!(f.run(&[b"TS.ADD", b"t", b"200", b"2"]), ":200\r\n");
15237 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+2\r\n");
15240 assert_eq!(f.run(&[b"TS.ADD", b"t", b"300", b"1e300"]), ":300\r\n");
15241 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+1E300\r\n");
15242 assert_eq!(f.run(&[b"TS.CREATE", b"empty"]), "+OK\r\n");
15245 assert_eq!(f.run(&[b"TS.GET", b"empty"]), "*0\r\n");
15246
15247 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
15250 assert_eq!(
15251 f.run(&[b"TS.ADD", b"str", b"1", b".5"]),
15252 "-ERR TSDB: invalid value\r\n"
15253 );
15254 for bad in [
15258 &b".5"[..],
15259 b"1.",
15260 b"+1",
15261 b" 1",
15262 b"0x10",
15263 b"inf",
15264 b"1e400",
15265 b"--1",
15266 b"1e",
15267 ] {
15268 assert_eq!(
15269 f.run(&[b"TS.ADD", b"v", b"1", bad]),
15270 "-ERR TSDB: invalid value\r\n",
15271 "{}",
15272 String::from_utf8_lossy(bad)
15273 );
15274 }
15275 assert_eq!(f.run(&[b"TS.ADD", b"v", b"1", b"NaN"]), ":1\r\n");
15277
15278 assert_eq!(
15281 f.run(&[b"TS.ADD", b"t", b"abc", b"1"]),
15282 "-ERR TSDB: invalid timestamp\r\n"
15283 );
15284 assert_eq!(
15285 f.run(&[b"TS.ADD", b"t", b"-1", b"1"]),
15286 "-ERR TSDB: invalid timestamp, must be a nonnegative integer\r\n"
15287 );
15288
15289 assert_eq!(
15292 f.run(&[b"TS.ADD", b"t", b"300", b"7"]),
15293 "-ERR TSDB: Error at upsert, update is not supported when DUPLICATE_POLICY is set to BLOCK mode, or either current or new value is NaN and DUPLICATE_POLICY is MAX/MIN/SUM\r\n"
15294 );
15295 assert_eq!(
15296 f.run(&[b"TS.ADD", b"t", b"300", b"7", b"ON_DUPLICATE", b"LAST"]),
15297 ":300\r\n"
15298 );
15299 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+7\r\n");
15300 assert_eq!(
15303 f.run(&[b"TS.ADD", b"fresh", b"1", b"1", b"ON_DUPLICATE", b"nope"]),
15304 ":1\r\n"
15305 );
15306 assert_eq!(
15307 f.run(&[b"TS.ADD", b"fresh", b"2", b"1", b"ON_DUPLICATE", b"nope"]),
15308 "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
15309 );
15310
15311 assert_eq!(f.run(&[b"TS.CREATE", b"r", b"RETENTION", b"50"]), "+OK\r\n");
15314 assert_eq!(f.run(&[b"TS.ADD", b"r", b"1000", b"1"]), ":1000\r\n");
15315 assert_eq!(f.run(&[b"TS.ADD", b"r", b"960", b"1"]), ":960\r\n");
15316 assert_eq!(
15317 f.run(&[b"TS.ADD", b"r", b"940", b"1"]),
15318 "-ERR TSDB: Timestamp is older than retention\r\n"
15319 );
15320 assert_eq!(f.run(&[b"TS.ADD", b"r", b"1100", b"1"]), ":1100\r\n");
15322 assert!(
15323 f.run(&[b"TS.INFO", b"r"])
15324 .contains("+totalSamples\r\n:1\r\n")
15325 );
15326
15327 assert_eq!(
15330 f.run(&[
15331 b"TS.CREATE",
15332 b"i",
15333 b"DUPLICATE_POLICY",
15334 b"LAST",
15335 b"IGNORE",
15336 b"10",
15337 b"0.5"
15338 ]),
15339 "+OK\r\n"
15340 );
15341 assert_eq!(f.run(&[b"TS.ADD", b"i", b"1000", b"1"]), ":1000\r\n");
15342 assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"1.2"]), ":1000\r\n");
15343 assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"9"]), ":1005\r\n");
15344 }
15345
15346 #[test]
15349 fn a_madd_answers_each_triple_and_creates_nothing() {
15350 let mut f = Fixture::new();
15351 assert_eq!(f.run(&[b"TS.CREATE", b"a"]), "+OK\r\n");
15352 assert_eq!(f.run(&[b"TS.CREATE", b"b"]), "+OK\r\n");
15353 assert_eq!(
15354 f.run(&[
15355 b"TS.MADD", b"a", b"100", b"1", b"b", b"100", b"2", b"a", b"200", b"3"
15356 ]),
15357 "*3\r\n:100\r\n:100\r\n:200\r\n"
15358 );
15359 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
15362 assert_eq!(
15363 f.run(&[
15364 b"TS.MADD", b"gone", b"1", b"1", b"str", b"1", b"1", b"a", b"300", b"4"
15365 ]),
15366 "*3\r\n\
15367 -ERR TSDB: the key is not a TSDB key\r\n\
15368 -ERR TSDB: the key is not a TSDB key\r\n\
15369 :300\r\n"
15370 );
15371 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
15372 assert_eq!(
15374 f.run(&[b"TS.MADD", b"a", b"400", b"zzz", b"a", b"abc", b"1"]),
15375 "*2\r\n-ERR TSDB: invalid value\r\n-ERR TSDB: invalid timestamp\r\n"
15376 );
15377 assert!(
15379 f.run(&[b"TS.MADD", b"a", b"1", b"1", b"a"])
15380 .contains("wrong number of arguments for 'ts.madd' command")
15381 );
15382 }
15383
15384 #[test]
15386 fn an_increment_walks_the_newest_value_up_and_down() {
15387 let mut f = Fixture::new();
15388 assert_eq!(
15389 f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
15390 ":100\r\n"
15391 );
15392 assert_eq!(
15393 f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
15394 ":100\r\n"
15395 );
15396 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n+10\r\n");
15399 assert_eq!(
15400 f.run(&[b"TS.DECRBY", b"t", b"3", b"TIMESTAMP", b"200"]),
15401 ":200\r\n"
15402 );
15403 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+7\r\n");
15404 assert_eq!(
15407 f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP", b"150"]),
15408 "-TSDB: timestamp must be equal to or higher than the maximum existing timestamp\r\n"
15409 );
15410 assert_eq!(
15414 f.run(&[b"TS.INCRBY", b"p", b"+5", b"TIMESTAMP", b"1"]),
15415 ":1\r\n"
15416 );
15417 assert_eq!(
15418 f.run(&[b"TS.INCRBY", b"q", b".5", b"TIMESTAMP", b"1"]),
15419 ":1\r\n"
15420 );
15421 assert_eq!(
15422 f.run(&[b"TS.INCRBY", b"t", b"nan"]),
15423 "-ERR TSDB: invalid increase/decrease value\r\n"
15424 );
15425 assert_eq!(
15426 f.run(&[b"TS.INCRBY", b"t", b"zzz"]),
15427 "-ERR TSDB: invalid increase/decrease value\r\n"
15428 );
15429 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
15432 assert_eq!(
15433 f.run(&[b"TS.INCRBY", b"str", b"zzz"]),
15434 "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15435 );
15436 assert_eq!(
15441 f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP"]),
15442 "-ERR TSDB: invalid timestamp\r\n"
15443 );
15444 assert_eq!(
15447 f.run(&[b"TS.INCRBY", b"lab", b"1", b"LABELS", b"TIMESTAMP", b"5"]),
15448 format!(":{}\r\n", f.server.now_ms())
15449 );
15450 assert_eq!(f.run(&[b"TS.ADD", b"n", b"1", b"nan"]), ":1\r\n");
15452 assert_eq!(
15453 f.run(&[b"TS.INCRBY", b"n", b"1", b"TIMESTAMP", b"2"]),
15454 "-ERR TSDB: cannot increment/decrement NaN value\r\n"
15455 );
15456 }
15457
15458 #[test]
15460 fn deleting_takes_out_a_span_and_answers_how_many_went() {
15461 let mut f = Fixture::new();
15462 for at in [b"100".as_slice(), b"200", b"300", b"400"] {
15463 f.run(&[b"TS.ADD", b"t", at, b"1"]);
15464 }
15465 assert_eq!(f.run(&[b"TS.DEL", b"t", b"200", b"300"]), ":2\r\n");
15466 assert!(
15467 f.run(&[b"TS.INFO", b"t"])
15468 .contains("+totalSamples\r\n:2\r\n")
15469 );
15470 assert_eq!(f.run(&[b"TS.DEL", b"t", b"400", b"100"]), ":0\r\n");
15472 assert_eq!(f.run(&[b"TS.DEL", b"t", b"-", b"+"]), ":2\r\n");
15474 let empty = f.run(&[b"TS.INFO", b"t"]);
15477 assert!(empty.contains("+totalSamples\r\n:0\r\n"), "{empty}");
15478 assert!(empty.contains("+chunkCount\r\n:1\r\n"), "{empty}");
15479 assert!(empty.contains("+firstTimestamp\r\n:0\r\n"), "{empty}");
15480 assert!(empty.contains("+lastTimestamp\r\n:0\r\n"), "{empty}");
15481 assert_eq!(f.run(&[b"TS.DEL", b"t", b"0", b"1000"]), ":0\r\n");
15482 assert_eq!(
15484 f.run(&[b"TS.DEL", b"t", b"abc", b"5"]),
15485 "-ERR TSDB: wrong fromTimestamp\r\n"
15486 );
15487 assert_eq!(
15488 f.run(&[b"TS.DEL", b"t", b"5", b"abc"]),
15489 "-ERR TSDB: wrong toTimestamp\r\n"
15490 );
15491 assert_eq!(
15492 f.run(&[b"TS.DEL", b"t", b"-5", b"5"]),
15493 "-ERR TSDB: wrong fromTimestamp\r\n"
15494 );
15495 }
15496
15497 #[test]
15500 fn resp3_writes_a_sample_as_a_double_and_the_info_as_a_map() {
15501 let mut f = Fixture::new();
15502 f.out = Out::new(Proto::Resp3);
15503 assert_eq!(
15504 f.run(&[b"TS.CREATE", b"t", b"LABELS", b"room", b"kitchen"]),
15505 "+OK\r\n"
15506 );
15507 assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1e300"]), ":100\r\n");
15508 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n,1e+300\r\n");
15510 assert_eq!(
15511 without_memory(&f.run(&[b"TS.INFO", b"t"])),
15512 "%14\r\n\
15513 +totalSamples\r\n:1\r\n\
15514 +memoryUsage\r\n:\r\n\
15515 +firstTimestamp\r\n:100\r\n\
15516 +lastTimestamp\r\n:100\r\n\
15517 +retentionTime\r\n:0\r\n\
15518 +chunkCount\r\n:1\r\n\
15519 +chunkSize\r\n:4096\r\n\
15520 +chunkType\r\n+compressed\r\n\
15521 +duplicatePolicy\r\n+block\r\n\
15522 +labels\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
15523 +sourceKey\r\n_\r\n\
15524 +rules\r\n%0\r\n\
15525 +ignoreMaxTimeDiff\r\n:0\r\n\
15526 +ignoreMaxValDiff\r\n,0\r\n"
15527 );
15528 }
15529
15530 #[test]
15533 fn a_range_walks_a_span_and_a_revrange_walks_it_backwards() {
15534 let mut f = Fixture::new();
15535 for (at, v) in [
15536 (b"100".as_slice(), b"1".as_slice()),
15537 (b"200", b"2"),
15538 (b"300", b"3"),
15539 (b"400", b"4"),
15540 ] {
15541 f.run(&[b"TS.ADD", b"t", at, v]);
15542 }
15543 assert_eq!(
15544 f.run(&[b"TS.RANGE", b"t", b"-", b"+"]),
15545 "*4\r\n*2\r\n:100\r\n+1\r\n*2\r\n:200\r\n+2\r\n\
15546 *2\r\n:300\r\n+3\r\n*2\r\n:400\r\n+4\r\n"
15547 );
15548 assert_eq!(
15550 f.run(&[b"TS.RANGE", b"t", b"150", b"350"]),
15551 "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
15552 );
15553 assert_eq!(
15556 f.run(&[b"TS.REVRANGE", b"t", b"-", b"+", b"COUNT", b"2"]),
15557 "*2\r\n*2\r\n:400\r\n+4\r\n*2\r\n:300\r\n+3\r\n"
15558 );
15559 assert_eq!(f.run(&[b"TS.RANGE", b"t", b"400", b"100"]), "*0\r\n");
15561 assert_eq!(
15563 f.run(&[
15564 b"TS.RANGE",
15565 b"t",
15566 b"-",
15567 b"+",
15568 b"FILTER_BY_VALUE",
15569 b"2",
15570 b"3"
15571 ]),
15572 "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
15573 );
15574 assert_eq!(
15575 f.run(&[
15576 b"TS.RANGE",
15577 b"t",
15578 b"-",
15579 b"+",
15580 b"FILTER_BY_TS",
15581 b"100",
15582 b"400"
15583 ]),
15584 "*2\r\n*2\r\n:100\r\n+1\r\n*2\r\n:400\r\n+4\r\n"
15585 );
15586 assert_eq!(
15588 f.run(&[
15589 b"TS.RANGE",
15590 b"t",
15591 b"-",
15592 b"+",
15593 b"ZZZ",
15594 b"FILTER_BY_TS",
15595 b"400"
15596 ]),
15597 "*1\r\n*2\r\n:400\r\n+4\r\n"
15598 );
15599 assert_eq!(
15601 f.run(&[b"TS.RANGE", b"t", b"-", b"+", b"LATEST", b"COUNT", b"1"]),
15602 "*1\r\n*2\r\n:100\r\n+1\r\n"
15603 );
15604 }
15605
15606 #[test]
15608 fn aggregation_puts_one_column_a_reduction_in_a_flat_row() {
15609 let mut f = Fixture::new();
15610 for (at, v) in [
15611 (b"100".as_slice(), b"1".as_slice()),
15612 (b"200", b"2"),
15613 (b"300", b"3"),
15614 (b"400", b"4"),
15615 ] {
15616 f.run(&[b"TS.ADD", b"t", at, v]);
15617 }
15618 assert_eq!(
15619 f.run(&[
15620 b"TS.RANGE",
15621 b"t",
15622 b"-",
15623 b"+",
15624 b"AGGREGATION",
15625 b"avg",
15626 b"200"
15627 ]),
15628 "*3\r\n*2\r\n:0\r\n+1\r\n*2\r\n:200\r\n+2.5\r\n*2\r\n:400\r\n+4\r\n"
15629 );
15630 assert_eq!(
15633 f.run(&[
15634 b"TS.RANGE",
15635 b"t",
15636 b"-",
15637 b"+",
15638 b"AGGREGATION",
15639 b"min,max,count",
15640 b"200"
15641 ]),
15642 "*3\r\n\
15643 *4\r\n:0\r\n+1\r\n+1\r\n+1\r\n\
15644 *4\r\n:200\r\n+2\r\n+3\r\n+2\r\n\
15645 *4\r\n:400\r\n+4\r\n+4\r\n+1\r\n"
15646 );
15647 assert_eq!(
15649 f.run(&[
15650 b"TS.RANGE",
15651 b"t",
15652 b"-",
15653 b"+",
15654 b"AGGREGATION",
15655 b"avg",
15656 b"200",
15657 b"BUCKETTIMESTAMP",
15658 b"+"
15659 ]),
15660 "*3\r\n*2\r\n:200\r\n+1\r\n*2\r\n:400\r\n+2.5\r\n*2\r\n:600\r\n+4\r\n"
15661 );
15662 assert_eq!(
15664 f.run(&[
15665 b"TS.RANGE",
15666 b"t",
15667 b"100",
15668 b"400",
15669 b"ALIGN",
15670 b"100",
15671 b"AGGREGATION",
15672 b"sum",
15673 b"200"
15674 ]),
15675 "*2\r\n*2\r\n:100\r\n+3\r\n*2\r\n:300\r\n+7\r\n"
15676 );
15677 assert_eq!(
15680 f.run(&[
15681 b"TS.RANGE",
15682 b"t",
15683 b"-",
15684 b"+",
15685 b"AGGREGATION",
15686 b"count",
15687 b"200"
15688 ]),
15689 "*3\r\n*2\r\n:0\r\n+1\r\n*2\r\n:200\r\n+2\r\n*2\r\n:400\r\n+1\r\n"
15690 );
15691 assert_eq!(
15692 f.run(&[
15693 b"TS.RANGE",
15694 b"t",
15695 b"-",
15696 b"+",
15697 b"AGGREGATION",
15698 b"count",
15699 b"200",
15700 b"COUNT",
15701 b"1"
15702 ]),
15703 "*1\r\n*2\r\n:0\r\n+1\r\n"
15704 );
15705 }
15706
15707 #[test]
15710 fn empty_fills_a_gap_and_last_carries_the_reading_before_it() {
15711 let mut f = Fixture::new();
15712 for (at, v) in [
15713 (b"0".as_slice(), b"1".as_slice()),
15714 (b"100", b"2"),
15715 (b"500", b"nan"),
15716 (b"600", b"3"),
15717 ] {
15718 f.run(&[b"TS.ADD", b"g", at, v]);
15719 }
15720 assert_eq!(
15723 f.run(&[
15724 b"TS.RANGE",
15725 b"g",
15726 b"-",
15727 b"+",
15728 b"AGGREGATION",
15729 b"avg",
15730 b"100"
15731 ]),
15732 "*3\r\n*2\r\n:0\r\n+1\r\n*2\r\n:100\r\n+2\r\n*2\r\n:600\r\n+3\r\n"
15733 );
15734 assert_eq!(
15736 f.run(&[
15737 b"TS.RANGE",
15738 b"g",
15739 b"-",
15740 b"+",
15741 b"AGGREGATION",
15742 b"sum",
15743 b"100",
15744 b"EMPTY"
15745 ]),
15746 "*7\r\n*2\r\n:0\r\n+1\r\n*2\r\n:100\r\n+2\r\n*2\r\n:200\r\n+0\r\n\
15747 *2\r\n:300\r\n+0\r\n*2\r\n:400\r\n+0\r\n*2\r\n:500\r\n+0\r\n\
15748 *2\r\n:600\r\n+3\r\n"
15749 );
15750 assert_eq!(
15755 f.run(&[
15756 b"TS.RANGE",
15757 b"g",
15758 b"-",
15759 b"+",
15760 b"AGGREGATION",
15761 b"last",
15762 b"100",
15763 b"EMPTY"
15764 ]),
15765 "*7\r\n*2\r\n:0\r\n+1\r\n*2\r\n:100\r\n+2\r\n*2\r\n:200\r\n+2\r\n\
15766 *2\r\n:300\r\n+2\r\n*2\r\n:400\r\n+2\r\n*2\r\n:500\r\n+2\r\n\
15767 *2\r\n:600\r\n+3\r\n"
15768 );
15769 assert_eq!(
15770 f.run(&[
15771 b"TS.REVRANGE",
15772 b"g",
15773 b"-",
15774 b"+",
15775 b"AGGREGATION",
15776 b"last",
15777 b"100",
15778 b"EMPTY"
15779 ]),
15780 "*7\r\n*2\r\n:600\r\n+3\r\n*2\r\n:500\r\n+3\r\n*2\r\n:400\r\n+2\r\n\
15781 *2\r\n:300\r\n+2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:100\r\n+2\r\n\
15782 *2\r\n:0\r\n+1\r\n"
15783 );
15784 assert_eq!(
15787 f.run(&[
15788 b"TS.RANGE",
15789 b"g",
15790 b"500",
15791 b"600",
15792 b"AGGREGATION",
15793 b"last",
15794 b"100",
15795 b"EMPTY"
15796 ]),
15797 "*2\r\n*2\r\n:500\r\n+NaN\r\n*2\r\n:600\r\n+3\r\n"
15798 );
15799 }
15800
15801 #[test]
15804 fn a_range_says_what_the_module_says_when_the_options_do_not_add_up() {
15805 let mut f = Fixture::new();
15806 f.run(&[b"TS.ADD", b"t", b"100", b"1"]);
15807 f.run(&[b"SET", b"str", b"x"]);
15808 let cases: &[(&[&[u8]], &str)] = &[
15809 (
15810 &[b"TS.RANGE", b"t"],
15811 "-ERR wrong number of arguments for 'ts.range' command\r\n",
15812 ),
15813 (
15815 &[b"TS.RANGE", b"gone", b"-", b"+", b"COUNT", b"x"],
15816 "-ERR TSDB: the key does not exist\r\n",
15817 ),
15818 (
15819 &[b"TS.RANGE", b"str", b"-", b"+"],
15820 "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n",
15821 ),
15822 (
15823 &[b"TS.RANGE", b"t", b"abc", b"+"],
15824 "-ERR TSDB: wrong fromTimestamp\r\n",
15825 ),
15826 (
15827 &[b"TS.RANGE", b"t", b"-", b"abc"],
15828 "-ERR TSDB: wrong toTimestamp\r\n",
15829 ),
15830 (
15831 &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT"],
15832 "-ERR TSDB: COUNT argument is missing\r\n",
15833 ),
15834 (
15835 &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"x"],
15836 "-ERR TSDB: Couldn't parse COUNT\r\n",
15837 ),
15838 (
15839 &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"0"],
15840 "-ERR TSDB: Invalid COUNT value\r\n",
15841 ),
15842 (
15843 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg"],
15844 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
15845 ),
15846 (
15847 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"x"],
15848 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
15849 ),
15850 (
15851 &[
15852 b"TS.RANGE",
15853 b"t",
15854 b"-",
15855 b"+",
15856 b"AGGREGATION",
15857 b"nope",
15858 b"100",
15859 ],
15860 "-ERR TSDB: Unknown aggregation type\r\n",
15861 ),
15862 (
15863 &[
15864 b"TS.RANGE",
15865 b"t",
15866 b"-",
15867 b"+",
15868 b"AGGREGATION",
15869 b"avg,,min",
15870 b"100",
15871 ],
15872 "-ERR TSDB: Empty aggregation type in list\r\n",
15873 ),
15874 (
15876 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"nope", b"0"],
15877 "-ERR TSDB: Unknown aggregation type\r\n",
15878 ),
15879 (
15880 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"0"],
15881 "-ERR TSDB: bucketDuration must be greater than zero\r\n",
15882 ),
15883 (
15884 &[
15885 b"TS.RANGE",
15886 b"t",
15887 b"-",
15888 b"+",
15889 b"AGGREGATION",
15890 b"avg",
15891 b"100",
15892 b"X",
15893 b"EMPTY",
15894 ],
15895 "-ERR TSDB: EMPTY flag should be the 3rd or 5th flag after AGGREGATION flag\r\n",
15896 ),
15897 (
15898 &[
15899 b"TS.RANGE",
15900 b"t",
15901 b"-",
15902 b"+",
15903 b"AGGREGATION",
15904 b"avg",
15905 b"100",
15906 b"BUCKETTIMESTAMP",
15907 b"z",
15908 ],
15909 "-ERR TSDB: unknown BUCKETTIMESTAMP parameter\r\n",
15910 ),
15911 (
15912 &[
15913 b"TS.RANGE",
15914 b"t",
15915 b"-",
15916 b"+",
15917 b"AGGREGATION",
15918 b"avg",
15919 b"100",
15920 b"X",
15921 b"Y",
15922 b"BUCKETTIMESTAMP",
15923 b"-",
15924 ],
15925 "-ERR TSDB: BUCKETTIMESTAMP flag should be the 3rd or 4th flag after \
15926 AGGREGATION flag\r\n",
15927 ),
15928 (
15929 &[
15930 b"TS.RANGE",
15931 b"t",
15932 b"-",
15933 b"+",
15934 b"ALIGN",
15935 b"z",
15936 b"AGGREGATION",
15937 b"avg",
15938 b"100",
15939 ],
15940 "-ERR TSDB: unknown ALIGN parameter\r\n",
15941 ),
15942 (
15943 &[b"TS.RANGE", b"t", b"-", b"+", b"ALIGN", b"5"],
15944 "-ERR TSDB: ALIGN parameter can only be used with AGGREGATION\r\n",
15945 ),
15946 (
15947 &[
15948 b"TS.RANGE",
15949 b"t",
15950 b"-",
15951 b"+",
15952 b"ALIGN",
15953 b"-",
15954 b"AGGREGATION",
15955 b"avg",
15956 b"100",
15957 ],
15958 "-ERR TSDB: start alignment can only be used with explicit start timestamp\r\n",
15959 ),
15960 (
15961 &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_VALUE", b"1"],
15962 "-ERR TSDB: FILTER_BY_VALUE one or more arguments are missing\r\n",
15963 ),
15964 (
15965 &[
15966 b"TS.RANGE",
15967 b"t",
15968 b"-",
15969 b"+",
15970 b"FILTER_BY_VALUE",
15971 b"x",
15972 b"2",
15973 ],
15974 "-ERR TSDB: Couldn't parse MIN\r\n",
15975 ),
15976 (
15977 &[
15978 b"TS.RANGE",
15979 b"t",
15980 b"-",
15981 b"+",
15982 b"FILTER_BY_VALUE",
15983 b"1",
15984 b"y",
15985 ],
15986 "-ERR TSDB: Couldn't parse MAX\r\n",
15987 ),
15988 (
15989 &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_TS"],
15990 "-ERR TSDB: FILTER_BY_TS one or more arguments are missing\r\n",
15991 ),
15992 ];
15993 for (argv, want) in cases {
15994 let got = f.run(argv);
15995 assert_eq!(&got, want, "{:?}", argv.last());
15996 }
15997 f.run(&[b"TS.ADD", b"wide", b"0", b"1"]);
16001 f.run(&[b"TS.ADD", b"wide", b"1000000000000", b"2"]);
16002 assert_eq!(
16003 f.run(&[
16004 b"TS.RANGE",
16005 b"wide",
16006 b"-",
16007 b"+",
16008 b"AGGREGATION",
16009 b"avg",
16010 b"1",
16011 b"EMPTY"
16012 ]),
16013 "-ERR TSDB: the requested range holds too many empty buckets\r\n"
16014 );
16015 }
16016
16017 #[test]
16019 fn resp3_writes_a_read_value_as_a_double() {
16020 let mut f = Fixture::new();
16021 f.out = Out::new(Proto::Resp3);
16022 for (at, v) in [
16023 (b"0".as_slice(), b"1".as_slice()),
16024 (b"100", b"2"),
16025 (b"500", b"nan"),
16026 (b"600", b"3"),
16027 ] {
16028 f.run(&[b"TS.ADD", b"g", at, v]);
16029 }
16030 assert_eq!(
16031 f.run(&[
16032 b"TS.RANGE",
16033 b"g",
16034 b"0",
16035 b"100",
16036 b"AGGREGATION",
16037 b"avg,min",
16038 b"200"
16039 ]),
16040 "*1\r\n*3\r\n:0\r\n,1.5\r\n,1\r\n"
16041 );
16042 assert_eq!(
16043 f.run(&[
16044 b"TS.RANGE",
16045 b"g",
16046 b"500",
16047 b"600",
16048 b"AGGREGATION",
16049 b"last",
16050 b"100",
16051 b"EMPTY"
16052 ]),
16053 "*2\r\n*2\r\n:500\r\n,nan\r\n*2\r\n:600\r\n,3\r\n"
16054 );
16055 }
16056
16057 fn joined() -> Fixture {
16060 let mut f = Fixture::new();
16061 f.run(&[b"TS.CREATE", b"z"]);
16062 for (at, v) in [
16063 (b"10".as_slice(), b"1".as_slice()),
16064 (b"20", b"2"),
16065 (b"40", b"4"),
16066 (b"50", b"5"),
16067 ] {
16068 f.run(&[b"TS.ADD", b"x", at, v]);
16069 }
16070 for (at, v) in [
16071 (b"20".as_slice(), b"20".as_slice()),
16072 (b"30", b"30"),
16073 (b"50", b"50"),
16074 (b"60", b"60"),
16075 ] {
16076 f.run(&[b"TS.ADD", b"y", at, v]);
16077 }
16078 f
16079 }
16080
16081 #[test]
16085 fn an_nrange_joins_its_keys_on_the_timestamp() {
16086 let mut f = joined();
16087 assert_eq!(
16089 f.run(&[b"TS.NRANGE", b"1", b"x", b"-", b"+"]),
16090 "*4\r\n*2\r\n:10\r\n*1\r\n+1\r\n*2\r\n:20\r\n*1\r\n+2\r\n\
16091 *2\r\n:40\r\n*1\r\n+4\r\n*2\r\n:50\r\n*1\r\n+5\r\n"
16092 );
16093 assert_eq!(
16095 f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+"]),
16096 "*6\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n\
16097 *2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
16098 *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
16099 *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
16100 *2\r\n:50\r\n*2\r\n+5\r\n+50\r\n\
16101 *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
16102 );
16103 assert_eq!(
16106 f.run(&[b"TS.NRANGE", b"2", b"x", b"z", b"20", b"40"]),
16107 "*2\r\n*2\r\n:20\r\n*2\r\n+2\r\n+NaN\r\n*2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n"
16108 );
16109 assert_eq!(
16110 f.run(&[b"TS.NRANGE", b"2", b"x", b"x", b"40", b"50"]),
16111 "*2\r\n*2\r\n:40\r\n*2\r\n+4\r\n+4\r\n*2\r\n:50\r\n*2\r\n+5\r\n+5\r\n"
16112 );
16113 assert_eq!(
16116 f.run(&[
16117 b"TS.NREVRANGE",
16118 b"2",
16119 b"x",
16120 b"y",
16121 b"-",
16122 b"+",
16123 b"COUNT",
16124 b"1"
16125 ]),
16126 "*1\r\n*2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
16127 );
16128 assert_eq!(
16129 f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+", b"COUNT", b"1"]),
16130 "*1\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n"
16131 );
16132 assert_eq!(
16134 f.run(&[
16135 b"TS.NRANGE",
16136 b"2",
16137 b"x",
16138 b"y",
16139 b"-",
16140 b"+",
16141 b"FILTER_BY_VALUE",
16142 b"2",
16143 b"30"
16144 ]),
16145 "*4\r\n*2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
16146 *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
16147 *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
16148 *2\r\n:50\r\n*2\r\n+5\r\n+NaN\r\n"
16149 );
16150 }
16151
16152 #[test]
16156 fn an_nrange_aggregation_names_one_reduction_a_key() {
16157 let mut f = joined();
16158 assert_eq!(
16159 f.run(&[
16160 b"TS.NRANGE",
16161 b"2",
16162 b"x",
16163 b"y",
16164 b"-",
16165 b"+",
16166 b"AGGREGATION",
16167 b"sum",
16168 b"sum",
16169 b"20"
16170 ]),
16171 "*4\r\n*2\r\n:0\r\n*2\r\n+1\r\n+NaN\r\n\
16172 *2\r\n:20\r\n*2\r\n+2\r\n+50\r\n\
16173 *2\r\n:40\r\n*2\r\n+9\r\n+50\r\n\
16174 *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
16175 );
16176 assert_eq!(
16178 f.run(&[
16179 b"TS.NRANGE",
16180 b"2",
16181 b"x",
16182 b"y",
16183 b"-",
16184 b"+",
16185 b"AGGREGATION",
16186 b"sum,count",
16187 b"avg",
16188 b"20"
16189 ]),
16190 "*4\r\n*2\r\n:0\r\n*3\r\n+1\r\n+1\r\n+NaN\r\n\
16191 *2\r\n:20\r\n*3\r\n+2\r\n+1\r\n+25\r\n\
16192 *2\r\n:40\r\n*3\r\n+9\r\n+2\r\n+50\r\n\
16193 *2\r\n:60\r\n*3\r\n+NaN\r\n+NaN\r\n+60\r\n"
16194 );
16195 assert_eq!(
16198 f.run(&[
16199 b"TS.NRANGE",
16200 b"2",
16201 b"x",
16202 b"y",
16203 b"-",
16204 b"+",
16205 b"AGGREGATION",
16206 b"avg",
16207 b"sum",
16208 b"100",
16209 b"EMPTY",
16210 b"BUCKETTIMESTAMP",
16211 b"end"
16212 ]),
16213 "*1\r\n*2\r\n:100\r\n*2\r\n+3\r\n+160\r\n"
16214 );
16215 assert_eq!(
16218 f.run(&[
16219 b"TS.NRANGE",
16220 b"2",
16221 b"x",
16222 b"y",
16223 b"-",
16224 b"+",
16225 b"AGGREGATION",
16226 b"avg",
16227 b"COUNT",
16228 b"100"
16229 ]),
16230 "*1\r\n*2\r\n:0\r\n*2\r\n+3\r\n+4\r\n"
16231 );
16232 }
16233
16234 #[test]
16237 fn an_nrange_says_what_the_module_says_when_it_does_not_add_up() {
16238 let mut f = joined();
16239 f.run(&[b"SET", b"str", b"hi"]);
16240 let bad_keys = "-ERR TSDB: numkeys must be a positive integer\r\n";
16241 let numkeys = "-ERR TSDB: the number of AGGREGATION arguments \
16242 must be equal to numkeys\r\n";
16243 let cases: &[(&[&[u8]], &str)] = &[
16244 (&[b"TS.NRANGE", b"0", b"x", b"-", b"+"], bad_keys),
16245 (&[b"TS.NRANGE", b"-1", b"x", b"-", b"+"], bad_keys),
16246 (&[b"TS.NRANGE", b"abc", b"x", b"-", b"+"], bad_keys),
16247 (
16250 &[b"TS.NRANGE", b"2", b"x", b"-", b"+"],
16251 "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
16252 ),
16253 (
16254 &[b"TS.NRANGE", b"99", b"x", b"-", b"+"],
16255 "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
16256 ),
16257 (
16260 &[
16261 b"TS.NRANGE",
16262 b"2",
16263 b"x",
16264 b"y",
16265 b"abc",
16266 b"+",
16267 b"AGGREGATION",
16268 b"nope",
16269 b"sum",
16270 b"100",
16271 ],
16272 "-ERR TSDB: Unknown aggregation type\r\n",
16273 ),
16274 (
16275 &[b"TS.NRANGE", b"2", b"x", b"y", b"abc", b"+"],
16276 "-ERR TSDB: wrong fromTimestamp\r\n",
16277 ),
16278 (
16279 &[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"abc"],
16280 "-ERR TSDB: wrong toTimestamp\r\n",
16281 ),
16282 (
16286 &[
16287 b"TS.NRANGE",
16288 b"2",
16289 b"x",
16290 b"y",
16291 b"-",
16292 b"+",
16293 b"AGGREGATION",
16294 b"avg",
16295 ],
16296 numkeys,
16297 ),
16298 (
16299 &[
16300 b"TS.NRANGE",
16301 b"2",
16302 b"x",
16303 b"y",
16304 b"-",
16305 b"+",
16306 b"AGGREGATION",
16307 b"100",
16308 b"sum",
16309 b"100",
16310 ],
16311 numkeys,
16312 ),
16313 (
16314 &[
16315 b"TS.NRANGE",
16316 b"2",
16317 b"x",
16318 b"y",
16319 b"-",
16320 b"+",
16321 b"AGGREGATION",
16322 b"avg",
16323 b"sum",
16324 b"sum",
16325 b"100",
16326 ],
16327 numkeys,
16328 ),
16329 (
16330 &[
16331 b"TS.NRANGE",
16332 b"2",
16333 b"x",
16334 b"y",
16335 b"-",
16336 b"+",
16337 b"AGGREGATION",
16338 b"avg",
16339 b"sum",
16340 b"abc",
16341 ],
16342 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
16343 ),
16344 (
16345 &[
16346 b"TS.NRANGE",
16347 b"2",
16348 b"x",
16349 b"y",
16350 b"-",
16351 b"+",
16352 b"AGGREGATION",
16353 b"avg",
16354 b"sum",
16355 b"0",
16356 ],
16357 "-ERR TSDB: bucketDuration must be greater than zero\r\n",
16358 ),
16359 (
16362 &[b"TS.NRANGE", b"1", b"x", b"-", b"+", b"AGGREGATION", b"100"],
16363 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
16364 ),
16365 (
16366 &[
16367 b"TS.NRANGE",
16368 b"1",
16369 b"x",
16370 b"-",
16371 b"+",
16372 b"AGGREGATION",
16373 b"100",
16374 b"200",
16375 ],
16376 "-ERR TSDB: Unknown aggregation type\r\n",
16377 ),
16378 (
16380 &[b"TS.NRANGE", b"2", b"x", b"nope", b"-", b"+"],
16381 "-ERR TSDB: the key does not exist\r\n",
16382 ),
16383 (
16384 &[b"TS.NRANGE", b"2", b"str", b"nope", b"-", b"+"],
16385 "-ERR WRONGTYPE Operation against a key \
16386 holding the wrong kind of value\r\n",
16387 ),
16388 ];
16389 for (argv, want) in cases {
16390 let got = f.run(argv);
16391 assert_eq!(&got, want, "{argv:?}");
16392 }
16393 }
16394
16395 #[test]
16397 fn a_read_walks_from_a_timestamp_to_the_end_of_the_series() {
16398 let mut f = joined();
16399 assert_eq!(
16400 f.run(&[b"TS.READ", b"x", b"-"]),
16401 "*4\r\n*2\r\n:10\r\n+1\r\n*2\r\n:20\r\n+2\r\n\
16402 *2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
16403 );
16404 assert_eq!(
16407 f.run(&[b"TS.READ", b"x", b"+"]),
16408 "*1\r\n*2\r\n:50\r\n+5\r\n"
16409 );
16410 assert_eq!(
16411 f.run(&[b"TS.READ", b"x", b"25"]),
16412 "*2\r\n*2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
16413 );
16414 assert_eq!(f.run(&[b"TS.READ", b"x", b"99"]), "*0\r\n");
16417 assert_eq!(f.run(&[b"TS.READ", b"z", b"-"]), "*0\r\n");
16418 assert_eq!(f.run(&[b"TS.READ", b"z", b"+"]), "*0\r\n");
16419 assert_eq!(f.run(&[b"TS.READ", b"nope", b"-"]), "*0\r\n");
16420 assert_eq!(
16424 f.run(&[b"TS.READ", b"x", b"abc"]),
16425 "-TSDB: invalid timestamp\r\n"
16426 );
16427 assert_eq!(
16428 f.run(&[b"TS.READ", b"x", b"-1"]),
16429 "-TSDB: invalid timestamp\r\n"
16430 );
16431 f.run(&[b"SET", b"str", b"hi"]);
16432 assert_eq!(
16433 f.run(&[b"TS.READ", b"str", b"-"]),
16434 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
16435 );
16436 assert_eq!(
16439 f.run(&[b"TS.READ", b"x"]),
16440 "-ERR wrong number of arguments for 'ts.read' command\r\n"
16441 );
16442 assert_eq!(
16443 f.run(&[b"TS.READ", b"x", b"-", b"COUNT", b"1"]),
16444 "-ERR wrong number of arguments for 'ts.read' command\r\n"
16445 );
16446 }
16447
16448 #[test]
16451 fn getkeys_reads_the_count_of_a_joined_read() {
16452 let mut f = Fixture::new();
16453 assert_eq!(
16454 f.run(&[
16455 b"COMMAND",
16456 b"GETKEYS",
16457 b"TS.NRANGE",
16458 b"2",
16459 b"a",
16460 b"b",
16461 b"-",
16462 b"+"
16463 ]),
16464 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
16465 );
16466 assert_eq!(
16467 f.run(&[
16468 b"COMMAND",
16469 b"GETKEYS",
16470 b"TS.NREVRANGE",
16471 b"1",
16472 b"a",
16473 b"-",
16474 b"+"
16475 ]),
16476 "*1\r\n$1\r\na\r\n"
16477 );
16478 for n in [b"0".as_slice(), b"9", b"abc"] {
16481 assert_eq!(
16482 f.run(&[b"COMMAND", b"GETKEYS", b"TS.NRANGE", n, b"a", b"-", b"+"]),
16483 "-ERR Invalid arguments specified for command\r\n"
16484 );
16485 }
16486 }
16487
16488 fn labelled() -> Fixture {
16490 let mut f = Fixture::new();
16491 f.run(&[
16492 b"TS.CREATE",
16493 b"a",
16494 b"LABELS",
16495 b"room",
16496 b"kitchen",
16497 b"x",
16498 b"1",
16499 ]);
16500 f.run(&[
16501 b"TS.CREATE",
16502 b"b",
16503 b"LABELS",
16504 b"room",
16505 b"bedroom",
16506 b"x",
16507 b"2",
16508 ]);
16509 f.run(&[b"TS.CREATE", b"c", b"LABELS", b"room", b"kitchen"]);
16510 f.run(&[b"TS.CREATE", b"d"]);
16511 f.run(&[b"TS.CREATE", b"e", b"LABELS", b"r", b"bb", b"r", b"b"]);
16512 f.run(&[b"TS.ADD", b"a", b"100", b"1.5"]);
16513 f.run(&[b"TS.ADD", b"b", b"200", b"2"]);
16514 f
16515 }
16516
16517 #[test]
16520 fn a_filter_is_taken_apart_the_way_the_module_takes_one_apart() {
16521 let mut f = labelled();
16522 let cases: &[(&[&[u8]], &str)] = &[
16523 (
16526 &[b"TS.QUERYINDEX", b"room=kitchen"],
16527 "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
16528 ),
16529 (
16530 &[b"TS.QUERYINDEX", b"room=kitchen", b"x=1"],
16531 "*1\r\n$1\r\na\r\n",
16532 ),
16533 (
16534 &[b"TS.QUERYINDEX", b"room=(kitchen,bedroom)"],
16535 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n",
16536 ),
16537 (&[b"TS.QUERYINDEX", b"room=()"], "*0\r\n"),
16540 (
16542 &[b"TS.QUERYINDEX", b"x=", b"room=kitchen"],
16543 "*1\r\n$1\r\nc\r\n",
16544 ),
16545 (
16546 &[b"TS.QUERYINDEX", b"room=kitchen", b"x!="],
16547 "*1\r\n$1\r\na\r\n",
16548 ),
16549 (
16550 &[b"TS.QUERYINDEX", b"room!=kitchen", b"x!="],
16551 "-ERR TSDB: please provide at least one matcher\r\n",
16552 ),
16553 (
16556 &[b"TS.QUERYINDEX", b"room==kitchen"],
16557 "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
16558 ),
16559 (
16560 &[b"TS.QUERYINDEX", b"room=kitchen=zz"],
16561 "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
16562 ),
16563 (&[b"TS.QUERYINDEX", b"room!!=kitchen", b"x=1"], "*0\r\n"),
16564 (&[b"TS.QUERYINDEX", b"()=1"], "*0\r\n"),
16567 (
16568 &[b"TS.QUERYINDEX", b"=(1)"],
16569 "-ERR TSDB: failed parsing labels\r\n",
16570 ),
16571 (
16572 &[b"TS.QUERYINDEX", b"room=(kitchen,)"],
16573 "-ERR TSDB: failed parsing labels\r\n",
16574 ),
16575 (
16576 &[b"TS.QUERYINDEX", b"room=(kitchen"],
16577 "-ERR TSDB: failed parsing labels\r\n",
16578 ),
16579 (&[b"TS.QUERYINDEX", b"room=x()"], "*0\r\n"),
16580 (
16581 &[b"TS.QUERYINDEX", b"nonsense"],
16582 "-ERR TSDB: failed parsing labels\r\n",
16583 ),
16584 (
16586 &[b"TS.QUERYINDEX", b"room!=kitchen"],
16587 "-ERR TSDB: please provide at least one matcher\r\n",
16588 ),
16589 (&[b"TS.QUERYINDEX", b"ROOM=kitchen"], "*0\r\n"),
16591 (&[b"TS.QUERYINDEX", b"room=KITCHEN"], "*0\r\n"),
16592 (
16593 &[b"TS.QUERYINDEX"],
16594 "-ERR wrong number of arguments for 'ts.queryindex' command\r\n",
16595 ),
16596 ];
16597 for (argv, want) in cases {
16598 let got = f.run(argv);
16599 assert_eq!(&got, want, "{:?}", argv.last());
16600 }
16601 }
16602
16603 #[test]
16605 fn querylabels_says_which_names_are_worn_and_what_they_are_set_to() {
16606 let mut f = labelled();
16607 let cases: &[(&[&[u8]], &str)] = &[
16608 (
16609 &[b"TS.QUERYLABELS", b"LABELS"],
16610 "*3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
16611 ),
16612 (
16613 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=kitchen"],
16614 "*2\r\n$4\r\nroom\r\n$1\r\nx\r\n",
16615 ),
16616 (
16617 &[b"TS.QUERYLABELS", b"VALUES", b"room"],
16618 "*2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
16619 ),
16620 (&[b"TS.QUERYLABELS", b"VALUES", b"r"], "*1\r\n$1\r\nb\r\n"),
16623 (&[b"TS.QUERYLABELS", b"VALUES", b"nolabel"], "*0\r\n"),
16624 (
16625 &[b"TS.QUERYLABELS", b"VALUES"],
16626 "-ERR wrong number of arguments for 'ts.querylabels' command\r\n",
16627 ),
16628 (
16629 &[b"TS.QUERYLABELS", b"ZZZ"],
16630 "-ERR TSDB: unknown subtype, must be one of LABELS|VALUES\r\n",
16631 ),
16632 (
16633 &[b"TS.QUERYLABELS", b"LABELS", b"ZZZ"],
16634 "-ERR TSDB: unknown argument, expected FILTER\r\n",
16635 ),
16636 (
16637 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER"],
16638 "-ERR TSDB: FILTER given with no filter expressions\r\n",
16639 ),
16640 (
16644 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room!=kitchen"],
16645 "-ERR TSDB: please provide at least one matcher\r\n",
16646 ),
16647 (
16648 &[
16649 b"TS.QUERYLABELS",
16650 b"LABELS",
16651 b"FILTER",
16652 b"room=kitchen",
16653 b"x=",
16654 ],
16655 "*1\r\n$4\r\nroom\r\n",
16656 ),
16657 ];
16658 for (argv, want) in cases {
16659 let got = f.run(argv);
16660 assert_eq!(&got, want, "{:?}", argv.last());
16661 }
16662 }
16663
16664 #[test]
16667 fn mget_writes_the_newest_sample_and_the_labels_that_were_asked_for() {
16668 let mut f = labelled();
16669 let cases: &[(&[&[u8]], &str)] = &[
16670 (
16673 &[b"TS.MGET", b"FILTER", b"room=kitchen"],
16674 "*2\r\n*3\r\n$1\r\na\r\n*0\r\n*2\r\n:100\r\n+1.5\r\n\
16675 *3\r\n$1\r\nc\r\n*0\r\n*0\r\n",
16676 ),
16677 (
16678 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
16679 "*2\r\n*3\r\n$1\r\na\r\n*2\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
16680 *2\r\n$1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n+1.5\r\n\
16681 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n*0\r\n",
16682 ),
16683 (
16685 &[
16686 b"TS.MGET",
16687 b"SELECTED_LABELS",
16688 b"x",
16689 b"FILTER",
16690 b"room=kitchen",
16691 ],
16692 "*2\r\n*3\r\n$1\r\na\r\n*1\r\n*2\r\n$1\r\nx\r\n$1\r\n1\r\n\
16693 *2\r\n:100\r\n+1.5\r\n\
16694 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$1\r\nx\r\n$-1\r\n*0\r\n",
16695 ),
16696 (
16699 &[b"TS.MGET", b"SELECTED_LABELS", b"r", b"FILTER", b"r=b"],
16700 "*1\r\n*3\r\n$1\r\ne\r\n*1\r\n*2\r\n$1\r\nr\r\n$2\r\nbb\r\n*0\r\n",
16701 ),
16702 (
16703 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
16704 "*1\r\n*3\r\n$1\r\ne\r\n*2\r\n*2\r\n$1\r\nr\r\n$2\r\nbb\r\n\
16705 *2\r\n$1\r\nr\r\n$1\r\nb\r\n*0\r\n",
16706 ),
16707 (
16710 &[b"TS.MGET", b"ZZZ", b"FILTER", b"room=bedroom"],
16711 "*1\r\n*3\r\n$1\r\nb\r\n*0\r\n*2\r\n:200\r\n+2\r\n",
16712 ),
16713 (
16714 &[b"TS.MGET", b"a", b"b", b"c"],
16715 "-ERR wrong number of arguments for 'ts.mget' command\r\n",
16716 ),
16717 (
16718 &[b"TS.MGET", b"FILTER"],
16719 "-ERR wrong number of arguments for 'ts.mget' command\r\n",
16720 ),
16721 (
16724 &[
16725 b"TS.MGET",
16726 b"WITHLABELS",
16727 b"SELECTED_LABELS",
16728 b"x",
16729 b"FILTER",
16730 b"bad",
16731 ],
16732 "-ERR TSDB: cannot accept WITHLABELS and SELECT_LABELS together\r\n",
16733 ),
16734 (
16735 &[b"TS.MGET", b"SELECTED_LABELS", b"FILTER", b"bad"],
16736 "-ERR TSDB: SELECT_LABELS should have at least 1 parameter\r\n",
16737 ),
16738 ];
16739 for (argv, want) in cases {
16740 let got = f.run(argv);
16741 assert_eq!(&got, want, "{:?}", argv.last());
16742 }
16743 }
16744
16745 #[test]
16748 fn resp3_writes_the_label_surface_as_sets_and_maps() {
16749 let mut f = labelled();
16750 f.out = Out::new(Proto::Resp3);
16751 let cases: &[(&[&[u8]], &str)] = &[
16752 (
16753 &[b"TS.QUERYINDEX", b"room=kitchen"],
16754 "~2\r\n$1\r\na\r\n$1\r\nc\r\n",
16755 ),
16756 (
16757 &[b"TS.QUERYLABELS", b"LABELS"],
16758 "~3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
16759 ),
16760 (
16761 &[b"TS.QUERYLABELS", b"VALUES", b"room"],
16762 "~2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
16763 ),
16764 (
16767 &[b"TS.MGET", b"FILTER", b"room=kitchen"],
16768 "%2\r\n$1\r\na\r\n*2\r\n%0\r\n*2\r\n:100\r\n,1.5\r\n\
16769 $1\r\nc\r\n*2\r\n%0\r\n*0\r\n",
16770 ),
16771 (
16772 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
16773 "%2\r\n$1\r\na\r\n*2\r\n%2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
16774 $1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n,1.5\r\n\
16775 $1\r\nc\r\n*2\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n*0\r\n",
16776 ),
16777 (
16778 &[
16779 b"TS.MGET",
16780 b"SELECTED_LABELS",
16781 b"x",
16782 b"FILTER",
16783 b"room=kitchen",
16784 ],
16785 "%2\r\n$1\r\na\r\n*2\r\n%1\r\n$1\r\nx\r\n$1\r\n1\r\n\
16786 *2\r\n:100\r\n,1.5\r\n\
16787 $1\r\nc\r\n*2\r\n%1\r\n$1\r\nx\r\n_\r\n*0\r\n",
16788 ),
16789 (
16792 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
16793 "%1\r\n$1\r\ne\r\n*2\r\n%2\r\n$1\r\nr\r\n$2\r\nbb\r\n\
16794 $1\r\nr\r\n$1\r\nb\r\n*0\r\n",
16795 ),
16796 ];
16797 for (argv, want) in cases {
16798 let got = f.run(argv);
16799 assert_eq!(&got, want, "{:?}", argv.last());
16800 }
16801 }
16802
16803 fn spanned() -> Fixture {
16806 let mut f = labelled();
16807 f.run(&[b"TS.ADD", b"a", b"200", b"2.5"]);
16808 f.run(&[b"TS.ADD", b"c", b"100", b"10"]);
16809 f.run(&[b"TS.ADD", b"c", b"300", b"30"]);
16810 f
16811 }
16812
16813 #[test]
16816 fn mrange_reads_every_series_and_folds_the_groups_it_is_asked_for() {
16817 let mut f = spanned();
16818 let cases: &[(&[&[u8]], &str)] = &[
16819 (
16820 &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=kitchen"],
16821 "*2\r\n*3\r\n$1\r\na\r\n*0\r\n*2\r\n*2\r\n:100\r\n+1.5\r\n*2\r\n:200\r\n+2.5\r\n\
16822 *3\r\n$1\r\nc\r\n*0\r\n*2\r\n*2\r\n:100\r\n+10\r\n*2\r\n:300\r\n+30\r\n",
16823 ),
16824 (
16827 &[
16828 b"TS.MREVRANGE",
16829 b"-",
16830 b"+",
16831 b"WITHLABELS",
16832 b"FILTER",
16833 b"room=kitchen",
16834 ],
16835 "*2\r\n*3\r\n$1\r\na\r\n*2\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
16836 *2\r\n$1\r\nx\r\n$1\r\n1\r\n*2\r\n*2\r\n:200\r\n+2.5\r\n*2\r\n:100\r\n+1.5\r\n\
16837 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
16838 *2\r\n*2\r\n:300\r\n+30\r\n*2\r\n:100\r\n+10\r\n",
16839 ),
16840 (
16843 &[
16844 b"TS.MRANGE",
16845 b"-",
16846 b"+",
16847 b"SELECTED_LABELS",
16848 b"x",
16849 b"FILTER",
16850 b"room=kitchen",
16851 ],
16852 "*2\r\n*3\r\n$1\r\na\r\n*1\r\n*2\r\n$1\r\nx\r\n$1\r\n1\r\n\
16853 *2\r\n*2\r\n:100\r\n+1.5\r\n*2\r\n:200\r\n+2.5\r\n\
16854 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$1\r\nx\r\n$-1\r\n\
16855 *2\r\n*2\r\n:100\r\n+10\r\n*2\r\n:300\r\n+30\r\n",
16856 ),
16857 (
16860 &[
16861 b"TS.MRANGE",
16862 b"-",
16863 b"+",
16864 b"FILTER",
16865 b"room=kitchen",
16866 b"GROUPBY",
16867 b"room",
16868 b"REDUCE",
16869 b"sum",
16870 ],
16871 "*1\r\n*3\r\n$12\r\nroom=kitchen\r\n*0\r\n*3\r\n*2\r\n:100\r\n+11.5\r\n\
16872 *2\r\n:200\r\n+2.5\r\n*2\r\n:300\r\n+30\r\n",
16873 ),
16874 (
16877 &[
16878 b"TS.MRANGE",
16879 b"-",
16880 b"+",
16881 b"WITHLABELS",
16882 b"FILTER",
16883 b"room=kitchen",
16884 b"GROUPBY",
16885 b"room",
16886 b"REDUCE",
16887 b"max",
16888 ],
16889 "*1\r\n*3\r\n$12\r\nroom=kitchen\r\n*3\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
16890 *2\r\n$11\r\n__reducer__\r\n$3\r\nmax\r\n\
16891 *2\r\n$10\r\n__source__\r\n$3\r\na,c\r\n\
16892 *3\r\n*2\r\n:100\r\n+10\r\n*2\r\n:200\r\n+2.5\r\n*2\r\n:300\r\n+30\r\n",
16893 ),
16894 (
16896 &[
16897 b"TS.MREVRANGE",
16898 b"-",
16899 b"+",
16900 b"COUNT",
16901 b"1",
16902 b"FILTER",
16903 b"room=kitchen",
16904 b"GROUPBY",
16905 b"room",
16906 b"REDUCE",
16907 b"count",
16908 ],
16909 "*1\r\n*3\r\n$12\r\nroom=kitchen\r\n*0\r\n*1\r\n*2\r\n:300\r\n+1\r\n",
16910 ),
16911 (
16913 &[
16914 b"TS.MRANGE",
16915 b"-",
16916 b"+",
16917 b"FILTER",
16918 b"room=kitchen",
16919 b"GROUPBY",
16920 b"nope",
16921 b"REDUCE",
16922 b"sum",
16923 ],
16924 "*0\r\n",
16925 ),
16926 (
16927 &[
16928 b"TS.MRANGE",
16929 b"-",
16930 b"+",
16931 b"AGGREGATION",
16932 b"sum,avg",
16933 b"100",
16934 b"FILTER",
16935 b"room=bedroom",
16936 ],
16937 "*1\r\n*3\r\n$1\r\nb\r\n*0\r\n*1\r\n*3\r\n:200\r\n+2\r\n+2\r\n",
16938 ),
16939 (
16941 &[b"TS.MRANGE", b"-", b"+", b"room=kitchen"],
16942 "-ERR TSDB: missing FILTER argument\r\n",
16943 ),
16944 (
16945 &[b"TS.MRANGE", b"-", b"+", b"FILTER"],
16946 "-ERR TSDB: missing labels for filter argument\r\n",
16947 ),
16948 (
16949 &[
16950 b"TS.MRANGE",
16951 b"-",
16952 b"+",
16953 b"GROUPBY",
16954 b"room",
16955 b"REDUCE",
16956 b"sum",
16957 b"FILTER",
16958 b"room=kitchen",
16959 ],
16960 "-ERR TSDB: GROUPBY should always come after filter\r\n",
16961 ),
16962 (
16965 &[
16966 b"TS.MRANGE",
16967 b"-",
16968 b"+",
16969 b"FILTER",
16970 b"room=kitchen",
16971 b"GROUPBY",
16972 b"room",
16973 b"REDUCE",
16974 b"sum",
16975 b"x",
16976 ],
16977 "-ERR wrong number of arguments for 'ts.mrange' command\r\n",
16978 ),
16979 (
16981 &[
16982 b"TS.MRANGE",
16983 b"-",
16984 b"+",
16985 b"FILTER",
16986 b"nope",
16987 b"GROUPBY",
16988 b"room",
16989 b"REDUCE",
16990 b"sum",
16991 b"x",
16992 ],
16993 "-ERR TSDB: failed parsing labels\r\n",
16994 ),
16995 (
16996 &[
16997 b"TS.MRANGE",
16998 b"-",
16999 b"+",
17000 b"FILTER",
17001 b"room=kitchen",
17002 b"GROUPBY",
17003 b"room",
17004 b"REDUCE",
17005 b"twa",
17006 ],
17007 "-ERR TSDB: Invalid reducer type\r\n",
17008 ),
17009 (
17010 &[
17011 b"TS.MRANGE",
17012 b"-",
17013 b"+",
17014 b"AGGREGATION",
17015 b"sum,avg",
17016 b"100",
17017 b"FILTER",
17018 b"room=kitchen",
17019 b"GROUPBY",
17020 b"room",
17021 b"REDUCE",
17022 b"sum",
17023 ],
17024 "-ERR TSDB: GROUPBY is not allowed when multiple aggregators are specified\r\n",
17025 ),
17026 (
17029 &[
17030 b"TS.MRANGE",
17031 b"-",
17032 b"+",
17033 b"SELECTED_LABELS",
17034 b"COUNT",
17035 b"FILTER",
17036 b"room=kitchen",
17037 ],
17038 "-ERR TSDB: Couldn't parse COUNT\r\n",
17039 ),
17040 ];
17041 for (argv, want) in cases {
17042 let got = f.run(argv);
17043 assert_eq!(&got, want, "{argv:?}");
17044 }
17045 }
17046
17047 #[test]
17050 fn resp3_writes_a_multi_key_read_as_a_map_of_four() {
17051 let mut f = spanned();
17052 f.out = Out::new(Proto::Resp3);
17053 let cases: &[(&[&[u8]], &str)] = &[
17054 (
17055 &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=bedroom"],
17056 "%1\r\n$1\r\nb\r\n*3\r\n%0\r\n%1\r\n$11\r\naggregators\r\n*0\r\n\
17057 *1\r\n*2\r\n:200\r\n,2\r\n",
17058 ),
17059 (
17062 &[
17063 b"TS.MRANGE",
17064 b"-",
17065 b"+",
17066 b"AGGREGATION",
17067 b"sum,avg",
17068 b"100",
17069 b"FILTER",
17070 b"room=bedroom",
17071 ],
17072 "%1\r\n$1\r\nb\r\n*3\r\n%0\r\n%1\r\n$11\r\naggregators\r\n*2\r\n$3\r\nsum\r\n\
17073 $3\r\navg\r\n*1\r\n*3\r\n:200\r\n,2\r\n,2\r\n",
17074 ),
17075 (
17076 &[
17077 b"TS.MRANGE",
17078 b"-",
17079 b"+",
17080 b"FILTER",
17081 b"room=kitchen",
17082 b"GROUPBY",
17083 b"room",
17084 b"REDUCE",
17085 b"sum",
17086 ],
17087 "%1\r\n$12\r\nroom=kitchen\r\n*4\r\n%0\r\n%1\r\n$8\r\nreducers\r\n*1\r\n\
17088 $3\r\nsum\r\n%1\r\n$7\r\nsources\r\n*2\r\n$1\r\na\r\n$1\r\nc\r\n\
17089 *3\r\n*2\r\n:100\r\n,11.5\r\n*2\r\n:200\r\n,2.5\r\n*2\r\n:300\r\n,30\r\n",
17090 ),
17091 (
17094 &[
17095 b"TS.MRANGE",
17096 b"-",
17097 b"+",
17098 b"WITHLABELS",
17099 b"FILTER",
17100 b"room=kitchen",
17101 b"GROUPBY",
17102 b"room",
17103 b"REDUCE",
17104 b"max",
17105 ],
17106 "%1\r\n$12\r\nroom=kitchen\r\n*4\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
17107 %1\r\n$8\r\nreducers\r\n*1\r\n$3\r\nmax\r\n\
17108 %1\r\n$7\r\nsources\r\n*2\r\n$1\r\na\r\n$1\r\nc\r\n\
17109 *3\r\n*2\r\n:100\r\n,10\r\n*2\r\n:200\r\n,2.5\r\n*2\r\n:300\r\n,30\r\n",
17110 ),
17111 (
17112 &[
17113 b"TS.MRANGE",
17114 b"-",
17115 b"+",
17116 b"FILTER",
17117 b"room=kitchen",
17118 b"GROUPBY",
17119 b"nope",
17120 b"REDUCE",
17121 b"sum",
17122 ],
17123 "%0\r\n",
17124 ),
17125 ];
17126 for (argv, want) in cases {
17127 let got = f.run(argv);
17128 assert_eq!(&got, want, "{argv:?}");
17129 }
17130 }
17131
17132 #[test]
17134 fn createrule_checks_the_two_keys_last_and_the_two_links_after_that() {
17135 let mut f = Fixture::new();
17136 f.run(&[b"TS.CREATE", b"src"]);
17137 f.run(&[b"TS.CREATE", b"dst"]);
17138 f.run(&[b"SET", b"plain", b"v"]);
17139 let cases: &[(&[&[u8]], &str)] = &[
17140 (
17144 &[
17145 b"TS.CREATERULE",
17146 b"src",
17147 b"dst",
17148 b"AGGREGATION",
17149 b"nope",
17150 b"x",
17151 ],
17152 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
17153 ),
17154 (
17155 &[
17156 b"TS.CREATERULE",
17157 b"src",
17158 b"dst",
17159 b"AGGREGATION",
17160 b"nope",
17161 b"10",
17162 ],
17163 "-ERR TSDB: Unknown aggregation type\r\n",
17164 ),
17165 (
17166 &[
17167 b"TS.CREATERULE",
17168 b"src",
17169 b"dst",
17170 b"AGGREGATION",
17171 b"avg",
17172 b"0",
17173 ],
17174 "-ERR TSDB: bucketDuration must be greater than zero\r\n",
17175 ),
17176 (
17177 &[
17178 b"TS.CREATERULE",
17179 b"src",
17180 b"dst",
17181 b"AGGREGATION",
17182 b"avg",
17183 b"10",
17184 b"x",
17185 ],
17186 "-ERR TSDB: Couldn't parse alignTimestamp\r\n",
17187 ),
17188 (
17189 &[
17190 b"TS.CREATERULE",
17191 b"src",
17192 b"src",
17193 b"AGGREGATION",
17194 b"avg",
17195 b"10",
17196 ],
17197 "-ERR TSDB: the source key and destination key should be different\r\n",
17198 ),
17199 (
17203 &[
17204 b"TS.CREATERULE",
17205 b"nope",
17206 b"plain",
17207 b"AGGREGATION",
17208 b"avg",
17209 b"10",
17210 ],
17211 "-ERR TSDB: the key does not exist\r\n",
17212 ),
17213 (
17214 &[
17215 b"TS.CREATERULE",
17216 b"src",
17217 b"nope",
17218 b"AGGREGATION",
17219 b"avg",
17220 b"10",
17221 ],
17222 "-ERR TSDB: the key does not exist\r\n",
17223 ),
17224 (
17227 &[b"TS.CREATERULE", b"src", b"dst", b"NOPE", b"avg", b"10"],
17228 "-ERR wrong number of arguments for 'ts.createrule' command\r\n",
17229 ),
17230 (
17231 &[
17232 b"TS.CREATERULE",
17233 b"src",
17234 b"dst",
17235 b"AGGREGATION",
17236 b"avg",
17237 b"10",
17238 ],
17239 "+OK\r\n",
17240 ),
17241 (
17244 &[
17245 b"TS.CREATERULE",
17246 b"src",
17247 b"dst",
17248 b"AGGREGATION",
17249 b"avg",
17250 b"10",
17251 ],
17252 "-ERR TSDB: the destination key already has a src rule\r\n",
17253 ),
17254 (
17257 &[
17258 b"TS.CREATERULE",
17259 b"dst",
17260 b"src",
17261 b"AGGREGATION",
17262 b"avg",
17263 b"10",
17264 ],
17265 "-ERR TSDB: the source key already has a source rule\r\n",
17266 ),
17267 (&[b"TS.DELETERULE", b"src", b"dst"], "+OK\r\n"),
17268 (
17269 &[b"TS.DELETERULE", b"src", b"dst"],
17270 "-ERR TSDB: compaction rule does not exist\r\n",
17271 ),
17272 (
17276 &[b"TS.DELETERULE", b"src", b"nope"],
17277 "-ERR TSDB: compaction rule does not exist\r\n",
17278 ),
17279 (
17280 &[b"TS.DELETERULE", b"nope", b"dst"],
17281 "-ERR TSDB: the key does not exist\r\n",
17282 ),
17283 ];
17284 for (argv, want) in cases {
17285 let got = f.run(argv);
17286 assert_eq!(&got, want, "{argv:?}");
17287 }
17288 }
17289
17290 #[test]
17292 fn a_rule_writes_a_bucket_when_a_later_reading_closes_it() {
17293 let mut f = Fixture::new();
17294 f.run(&[b"TS.CREATE", b"src"]);
17295 f.run(&[b"TS.CREATE", b"dst"]);
17296 f.run(&[b"TS.ADD", b"src", b"10", b"1"]);
17299 f.run(&[
17300 b"TS.CREATERULE",
17301 b"src",
17302 b"dst",
17303 b"AGGREGATION",
17304 b"sum",
17305 b"100",
17306 ]);
17307 f.run(&[b"TS.ADD", b"src", b"20", b"2"]);
17308 assert_eq!(f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]), "*0\r\n");
17309 assert_eq!(f.run(&[b"TS.GET", b"dst", b"LATEST"]), "*2\r\n:0\r\n+2\r\n");
17312 f.run(&[b"TS.ADD", b"src", b"110", b"4"]);
17313 assert_eq!(
17314 f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
17315 "*1\r\n*2\r\n:0\r\n+2\r\n"
17316 );
17317 f.run(&[b"TS.ADD", b"src", b"30", b"8"]);
17320 assert_eq!(
17321 f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
17322 "*1\r\n*2\r\n:0\r\n+11\r\n"
17323 );
17324 assert_eq!(f.run(&[b"TS.DEL", b"src", b"0", b"25"]), ":2\r\n");
17327 assert_eq!(
17328 f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
17329 "*1\r\n*2\r\n:0\r\n+8\r\n"
17330 );
17331 assert_eq!(
17332 f.run(&[b"TS.GET", b"dst", b"LATEST"]),
17333 "*2\r\n:100\r\n+4\r\n"
17334 );
17335 assert!(f.run(&[b"TS.INFO", b"dst"]).contains("sourceKey"));
17337 f.run(&[b"DEL", b"dst"]);
17338 assert_eq!(
17339 f.run(&[b"TS.DELETERULE", b"src", b"dst"]),
17340 "-ERR TSDB: compaction rule does not exist\r\n"
17341 );
17342 }
17343
17344 #[test]
17347 fn xadd_ids_only_ever_go_up() {
17348 let mut f = Fixture::new();
17349 assert_eq!(f.run(&[b"XADD", b"s", b"5", b"a", b"1"]), "$3\r\n5-0\r\n");
17351 assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"2"]), "$3\r\n5-1\r\n");
17353 assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"3"]), "$3\r\n5-2\r\n");
17354 assert_eq!(f.run(&[b"XADD", b"s", b"6-9", b"a", b"4"]), "$3\r\n6-9\r\n");
17355 assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
17356
17357 assert!(
17358 f.run(&[b"XADD", b"s", b"6-9", b"a", b"5"])
17359 .contains("equal or smaller")
17360 );
17361 assert!(
17362 f.run(&[b"XADD", b"s", b"0-0", b"a", b"5"])
17363 .contains("must be greater than 0-0")
17364 );
17365 assert!(
17366 f.run(&[b"XADD", b"s", b"nonsense", b"a", b"5"])
17367 .contains("Invalid stream ID")
17368 );
17369 assert!(
17372 f.run(&[b"XADD", b"s", b"*", b"a"])
17373 .contains("wrong number of arguments")
17374 );
17375
17376 assert_eq!(
17379 f.run(&[b"XADD", b"gone", b"NOMKSTREAM", b"*", b"a", b"1"]),
17380 "$-1\r\n"
17381 );
17382 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
17383 assert_eq!(f.run(&[b"TYPE", b"s"]), "+stream\r\n");
17384 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$6\r\nstream\r\n");
17385 }
17386
17387 #[test]
17390 fn trimming_reads_its_options_the_way_redis_does() {
17391 let mut f = Fixture::new();
17392 for i in 1..=10u32 {
17393 f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
17394 }
17395 assert_eq!(f.run(&[b"XTRIM", b"s", b"MAXLEN", b"4"]), ":6\r\n");
17396 assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
17397 assert_eq!(f.run(&[b"XTRIM", b"s", b"MINID", b"9"]), ":2\r\n");
17398 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
17399
17400 assert!(
17404 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"~"])
17405 .contains("not an integer")
17406 );
17407 assert!(
17408 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"-1"])
17409 .contains("MAXLEN argument must be >= 0")
17410 );
17411 assert!(
17414 f.run(&[b"XTRIM", b"s", b"LIMIT", b"5"])
17415 .contains("without specifying a trimming strategy")
17416 );
17417 assert!(
17418 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"LIMIT", b"5"])
17419 .contains("without the special ~ option")
17420 );
17421 assert!(
17422 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"MINID", b"5"])
17423 .contains("at the same time are not compatible")
17424 );
17425 assert!(
17427 f.run(&[b"XTRIM", b"s", b"NOMKSTREAM", b"MAXLEN", b"5"])
17428 .contains("syntax error")
17429 );
17430 assert_eq!(f.run(&[b"XTRIM", b"missing", b"MAXLEN", b"5"]), ":0\r\n");
17431 }
17432
17433 #[test]
17435 fn xrange_looks_the_key_up_before_it_reads_the_count() {
17436 let mut f = Fixture::new();
17437 f.run(&[b"XADD", b"s", b"5-1", b"a", b"1"]);
17438 f.run(&[b"XADD", b"s", b"6-1", b"b", b"2"]);
17439
17440 assert_eq!(
17441 f.run(&[b"XRANGE", b"s", b"-", b"+"]),
17442 "*2\r\n*2\r\n$3\r\n5-1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n\
17443 *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
17444 );
17445 assert_eq!(
17446 f.run(&[b"XREVRANGE", b"s", b"+", b"-", b"COUNT", b"1"]),
17447 "*1\r\n*2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
17448 );
17449 assert_eq!(
17453 f.run(&[b"XRANGE", b"s", b"-", b"(6"]),
17454 "*2\r\n*2\r\n$3\r\n5-1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n\
17455 *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
17456 );
17457 assert_eq!(
17458 f.run(&[b"XRANGE", b"s", b"(5-1", b"+"]),
17459 "*1\r\n*2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
17460 );
17461 assert!(
17462 f.run(&[b"XRANGE", b"s", b"(-", b"+"])
17463 .contains("Invalid stream ID")
17464 );
17465
17466 assert_eq!(
17470 f.run(&[b"XRANGE", b"missing", b"-", b"+", b"COUNT", b"0"]),
17471 "*0\r\n"
17472 );
17473 assert_eq!(
17474 f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"]),
17475 "*-1\r\n"
17476 );
17477 f.run(&[b"SET", b"str", b"v"]);
17478 assert!(
17479 f.run(&[b"XRANGE", b"str", b"-", b"+", b"COUNT", b"0"])
17480 .starts_with("-WRONGTYPE")
17481 );
17482 assert_eq!(
17484 f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"2", b"COUNT", b"1"]),
17485 "*1\r\n*2\r\n$3\r\n5-1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
17486 );
17487 }
17488
17489 #[test]
17491 fn a_bad_id_late_in_the_list_stops_the_whole_command() {
17492 let mut f = Fixture::new();
17493 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
17494 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
17495 assert!(
17496 f.run(&[b"XDEL", b"s", b"1-1", b"nonsense"])
17497 .contains("Invalid stream ID")
17498 );
17499 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
17500 assert_eq!(f.run(&[b"XDEL", b"s", b"1-1", b"9-9"]), ":1\r\n");
17501 assert_eq!(f.run(&[b"XLEN", b"s"]), ":1\r\n");
17502 assert_eq!(f.run(&[b"XDEL", b"missing", b"1-1"]), ":0\r\n");
17503 assert_eq!(f.run(&[b"XACK", b"missing", b"g", b"1-1"]), ":0\r\n");
17504 }
17505
17506 #[test]
17508 fn xgroup_has_an_arity_per_subcommand() {
17509 let mut f = Fixture::new();
17510 assert!(
17511 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
17512 .contains("requires the key")
17513 );
17514 assert_eq!(
17515 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$", b"MKSTREAM"]),
17516 "+OK\r\n"
17517 );
17518 assert!(
17521 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
17522 .starts_with("-BUSYGROUP")
17523 );
17524 assert_eq!(
17525 f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
17526 ":1\r\n"
17527 );
17528 assert_eq!(
17529 f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
17530 ":0\r\n"
17531 );
17532 assert_eq!(
17533 f.run(&[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c"]),
17534 ":0\r\n"
17535 );
17536
17537 let short = f.run(&[b"XGROUP", b"DESTROY", b"s"]);
17539 assert!(
17540 short.contains("wrong number of arguments for 'xgroup|destroy' command"),
17541 "{short}"
17542 );
17543 let odd = f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0", b"ENTRIESREAD"]);
17545 assert!(
17546 odd.contains("unknown subcommand or wrong number of arguments for 'SETID'"),
17547 "{odd}"
17548 );
17549 assert!(
17550 f.run(&[b"XGROUP", b"NOSUCH", b"s"])
17551 .contains("Try XGROUP HELP")
17552 );
17553
17554 assert_eq!(f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0"]), "+OK\r\n");
17555 assert!(
17556 f.run(&[b"XGROUP", b"SETID", b"s", b"nogroup", b"0"])
17557 .starts_with("-NOGROUP")
17558 );
17559 assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":1\r\n");
17560 assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":0\r\n");
17561 assert!(
17562 f.run(&[b"XGROUP", b"DESTROY", b"missing", b"g"])
17563 .contains("requires the key")
17564 );
17565 }
17566
17567 #[test]
17569 fn xreadgroup_hands_out_and_xack_takes_back() {
17570 let mut f = Fixture::new();
17571 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
17572 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
17573 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
17574
17575 let first = f.run(&[
17576 b"XREADGROUP",
17577 b"GROUP",
17578 b"g",
17579 b"c1",
17580 b"COUNT",
17581 b"1",
17582 b"STREAMS",
17583 b"s",
17584 b">",
17585 ]);
17586 assert_eq!(
17587 first,
17588 "*1\r\n*2\r\n$1\r\ns\r\n*1\r\n*2\r\n$3\r\n1-1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
17589 );
17590 assert_eq!(
17593 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b"0"]),
17594 "*1\r\n*2\r\n$1\r\ns\r\n*0\r\n"
17595 );
17596 assert_eq!(
17597 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
17598 "*1\r\n*2\r\n$1\r\ns\r\n*1\r\n*2\r\n$3\r\n1-1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
17599 );
17600
17601 assert_eq!(
17602 f.run(&[b"XPENDING", b"s", b"g"]),
17603 "*4\r\n:1\r\n$3\r\n1-1\r\n$3\r\n1-1\r\n*1\r\n*2\r\n$2\r\nc1\r\n$1\r\n1\r\n"
17604 );
17605 assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":1\r\n");
17606 assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":0\r\n");
17607 assert_eq!(
17609 f.run(&[b"XPENDING", b"s", b"g"]),
17610 "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n"
17611 );
17612
17613 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
17616 f.run(&[b"XDEL", b"s", b"2-1"]);
17617 assert_eq!(
17618 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
17619 "*1\r\n*2\r\n$1\r\ns\r\n*1\r\n*2\r\n$3\r\n2-1\r\n$-1\r\n"
17620 );
17621
17622 assert!(
17625 f.run(&[
17626 b"XREADGROUP",
17627 b"GROUP",
17628 b"nope",
17629 b"c",
17630 b"STREAMS",
17631 b"s",
17632 b"+"
17633 ])
17634 .starts_with("-NOGROUP")
17635 );
17636 assert!(
17637 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"])
17638 .contains("meaningless in the context of XREADGROUP")
17639 );
17640 assert!(
17641 f.run(&[b"XREAD", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"0"])
17642 .contains("only supported by XREADGROUP")
17643 );
17644 assert!(
17645 f.run(&[
17646 b"XREADGROUP",
17647 b"GROUP",
17648 b"g",
17649 b"c",
17650 b"STREAMS",
17651 b"s",
17652 b"a",
17653 b"b"
17654 ])
17655 .contains("Unbalanced 'xreadgroup' list of streams")
17656 );
17657 }
17658
17659 #[test]
17662 fn xread_with_no_block_writes_the_null_itself() {
17663 let mut f = Fixture::new();
17664 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
17665 assert_eq!(
17666 f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
17667 "*1\r\n*2\r\n$1\r\ns\r\n*1\r\n*2\r\n$3\r\n1-1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
17668 );
17669 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "*-1\r\n");
17672 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"missing", b"0"]), "*-1\r\n");
17673 f.run(&[b"XADD", b"other", b"1-1", b"b", b"2"]);
17674 assert_eq!(
17675 f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"1-1", b"0"]),
17676 "*1\r\n*2\r\n$5\r\nother\r\n*1\r\n*2\r\n$3\r\n1-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
17677 );
17678 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"$"]), "*-1\r\n");
17680 assert_eq!(
17682 f.run(&[b"XREAD", b"COUNT", b"5", b"STREAMS", b"s", b"+"]),
17683 "*1\r\n*2\r\n$1\r\ns\r\n*1\r\n*2\r\n$3\r\n1-1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
17684 );
17685 assert_eq!(
17688 f.run(&[b"XREAD", b"COUNT", b"0", b"STREAMS", b"s", b"0"]),
17689 "*1\r\n*2\r\n$1\r\ns\r\n*1\r\n*2\r\n$3\r\n1-1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
17690 );
17691 assert!(
17693 f.run(&[b"XREAD", b"BLOCK", b"0.5", b"STREAMS", b"s", b"$"])
17694 .contains("not an integer")
17695 );
17696 assert!(
17697 f.run(&[b"XREAD", b"BLOCK", b"-1", b"STREAMS", b"s", b"$"])
17698 .contains("timeout is negative")
17699 );
17700 assert!(
17701 f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"0"])
17702 .contains("Unbalanced 'xread' list of streams")
17703 );
17704 }
17705
17706 #[test]
17708 fn a_blocked_xread_wakes_on_the_next_entry() {
17709 let mut f = Fixture::new();
17710 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
17711 let (flow, reply) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
17712 assert_eq!(flow, Flow::Block);
17713 assert!(reply.is_empty());
17714
17715 f.session = Session::new(8);
17720 let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
17721 assert_eq!(flow, Flow::Block);
17722 assert_eq!(f.server.parked(), 2);
17723
17724 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
17725 let want = "*1\r\n*2\r\n$1\r\ns\r\n*1\r\n*2\r\n$3\r\n2-1\r\n*2\r\n$1\r\na\r\n$1\r\n2\r\n";
17726 for client in [7, 8] {
17727 let mut out = Out::new(Proto::Resp2);
17728 assert!(f.server.serve_waiter(client, 0, &mut out));
17729 assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
17730 }
17731
17732 f.server.forget_waiters(7);
17735 f.server.forget_waiters(8);
17736 let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"50", b"STREAMS", b"s", b"$"]);
17737 assert_eq!(flow, Flow::Block);
17738 let mut out = Out::new(Proto::Resp2);
17739 assert!(!f.server.serve_waiter(8, 0, &mut out));
17740 assert!(out.as_slice().is_empty());
17741 assert!(f.server.serve_waiter(8, u64::MAX, &mut out));
17742 assert_eq!(
17743 core::str::from_utf8(out.as_slice()).expect("ascii"),
17744 "*-1\r\n"
17745 );
17746 }
17747
17748 #[test]
17750 fn losing_a_group_while_blocked_is_the_ordinary_sentence() {
17751 let mut f = Fixture::new();
17752 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
17753 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"]);
17754 let (flow, _) = f.flow(&[
17755 b"XREADGROUP",
17756 b"GROUP",
17757 b"g",
17758 b"c",
17759 b"BLOCK",
17760 b"0",
17761 b"STREAMS",
17762 b"s",
17763 b">",
17764 ]);
17765 assert_eq!(flow, Flow::Block);
17766
17767 f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]);
17768 let mut out = Out::new(Proto::Resp2);
17769 assert!(f.server.serve_waiter(7, 0, &mut out));
17770 assert_eq!(
17773 core::str::from_utf8(out.as_slice()).expect("ascii"),
17774 "-NOGROUP No such key 's' or consumer group 'g' in XREADGROUP with GROUP option\r\n"
17775 );
17776 }
17777
17778 #[test]
17780 fn xclaim_reads_ids_until_one_will_not_parse() {
17781 let mut f = Fixture::new();
17782 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
17783 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
17784 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
17785 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
17786
17787 assert!(
17790 f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"-"])
17791 .contains("Unrecognized XCLAIM option '-'")
17792 );
17793 assert_eq!(
17794 f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1", b"JUSTID"]),
17795 "*1\r\n$3\r\n1-1\r\n"
17796 );
17797 f.run(&[b"XDEL", b"s", b"2-1"]);
17800 assert_eq!(
17801 f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1"]),
17802 "*0\r\n"
17803 );
17804 assert!(
17805 f.run(&[b"XPENDING", b"s", b"g"])
17806 .starts_with("*4\r\n:1\r\n")
17807 );
17808 assert!(
17809 f.run(&[b"XCLAIM", b"s", b"nope", b"c", b"0", b"1-1"])
17810 .starts_with("-NOGROUP")
17811 );
17812 assert!(
17813 f.run(&[b"XCLAIM", b"s", b"g", b"c", b"nan", b"1-1"])
17814 .contains("Invalid min-idle-time argument for XCLAIM")
17815 );
17816 }
17817
17818 #[test]
17820 fn xautoclaim_reports_what_it_dropped() {
17821 let mut f = Fixture::new();
17822 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
17823 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
17824 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
17825 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
17826 f.run(&[b"XDEL", b"s", b"1-1"]);
17827
17828 assert_eq!(
17831 f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"JUSTID"]),
17832 "*3\r\n$3\r\n0-0\r\n*1\r\n$3\r\n2-1\r\n*1\r\n$3\r\n1-1\r\n"
17833 );
17834 assert!(
17835 f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"COUNT", b"0"])
17836 .contains("COUNT must be > 0")
17837 );
17838 assert!(
17839 f.run(&[b"XAUTOCLAIM", b"s", b"nope", b"c", b"0", b"-"])
17840 .starts_with("-NOGROUP")
17841 );
17842 }
17843
17844 #[test]
17846 fn xdelex_answers_one_integer_an_id() {
17847 let mut f = Fixture::new();
17848 for i in 1..=4 {
17849 f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
17850 }
17851 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
17852 f.run(&[
17853 b"XREADGROUP",
17854 b"GROUP",
17855 b"g",
17856 b"c",
17857 b"COUNT",
17858 b"2",
17859 b"STREAMS",
17860 b"s",
17861 b">",
17862 ]);
17863
17864 assert_eq!(
17866 f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1", b"9-9"]),
17867 "*2\r\n:1\r\n:-1\r\n"
17868 );
17869 assert!(
17872 f.run(&[b"XPENDING", b"s", b"g"])
17873 .starts_with("*4\r\n:2\r\n")
17874 );
17875 assert_eq!(
17877 f.run(&[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"]),
17878 "*1\r\n:1\r\n"
17879 );
17880 assert_eq!(
17882 f.run(&[b"XPENDING", b"s", b"g"]),
17883 "*4\r\n:1\r\n$3\r\n1-1\r\n$3\r\n1-1\r\n*1\r\n*2\r\n$1\r\nc\r\n$1\r\n1\r\n"
17884 );
17885
17886 assert_eq!(
17890 f.run(&[b"XDELEX", b"s", b"ACKED", b"IDS", b"2", b"3-1", b"4-1"]),
17891 "*2\r\n:2\r\n:2\r\n"
17892 );
17893
17894 assert_eq!(
17896 f.run(&[b"XDELEX", b"nope", b"IDS", b"2", b"bad", b"worse"]),
17897 "*2\r\n:-1\r\n:-1\r\n"
17898 );
17899 assert!(
17901 f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"3-1", b"bad"])
17902 .starts_with("-ERR Invalid stream ID")
17903 );
17904 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
17905
17906 assert!(
17907 f.run(&[b"XDELEX", b"s", b"IDS", b"0", b"1-1"])
17908 .contains("Number of IDs must be a positive integer")
17909 );
17910 assert!(
17911 f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1"])
17912 .contains("The `numids` parameter must match the number of arguments")
17913 );
17914 assert!(
17917 f.run(&[b"XDELEX", b"s", b"KEEPREF", b"DELREF", b"IDS", b"1", b"1-1"])
17918 .starts_with("-ERR syntax error")
17919 );
17920 assert!(
17921 f.run(&[b"XDELEX", b"s", b"IDS", b"1", b"1-1", b"2-1"])
17922 .starts_with("-ERR syntax error")
17923 );
17924 f.run(&[b"SET", b"str", b"v"]);
17926 assert!(
17927 f.run(&[b"XDELEX", b"str", b"BOGUS", b"IDS", b"0", b"1-1"])
17928 .starts_with("-WRONGTYPE")
17929 );
17930 }
17931
17932 #[test]
17934 fn xackdel_reports_what_the_group_was_holding() {
17935 let mut f = Fixture::new();
17936 for i in 1..=3 {
17937 f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
17938 }
17939 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
17940 f.run(&[
17941 b"XREADGROUP",
17942 b"GROUP",
17943 b"g",
17944 b"c",
17945 b"COUNT",
17946 b"1",
17947 b"STREAMS",
17948 b"s",
17949 b">",
17950 ]);
17951
17952 assert_eq!(
17956 f.run(&[b"XACKDEL", b"s", b"g", b"IDS", b"2", b"1-1", b"2-1"]),
17957 "*2\r\n:1\r\n:-1\r\n"
17958 );
17959 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
17960
17961 assert_eq!(
17963 f.run(&[b"XACKDEL", b"s", b"nope", b"IDS", b"1", b"2-1"]),
17964 "*1\r\n:-1\r\n"
17965 );
17966 assert_eq!(
17967 f.run(&[b"XACKDEL", b"nope", b"g", b"IDS", b"1", b"2-1"]),
17968 "*1\r\n:-1\r\n"
17969 );
17970
17971 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b">"]);
17974 f.run(&[b"XGROUP", b"CREATE", b"s", b"g2", b"0"]);
17975 assert_eq!(
17976 f.run(&[b"XACKDEL", b"s", b"g", b"ACKED", b"IDS", b"1", b"2-1"]),
17977 "*1\r\n:2\r\n"
17978 );
17979 assert_eq!(
17980 f.run(&[b"XPENDING", b"s", b"g"]),
17981 "*4\r\n:1\r\n$3\r\n3-1\r\n$3\r\n3-1\r\n*1\r\n*2\r\n$1\r\nc\r\n$1\r\n1\r\n"
17982 );
17983 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
17984 }
17985
17986 #[test]
17988 fn xnack_releases_an_entry_for_the_next_claim() {
17989 let mut f = Fixture::new();
17990 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
17991 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
17992 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
17993 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
17994 f.run(&[b"XCLAIM", b"s", b"g", b"c1", b"0", b"1-1", b"2-1"]);
17997
17998 assert_eq!(
17999 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1"]),
18000 ":1\r\n"
18001 );
18002 assert_eq!(
18006 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]),
18007 "*2\r\n*4\r\n$3\r\n1-1\r\n$0\r\n\r\n:-1\r\n:2\r\n*4\r\n$3\r\n2-1\r\n$2\r\nc1\r\n:0\r\n:2\r\n"
18008 );
18009 assert_eq!(
18011 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
18012 "*1\r\n*4\r\n$3\r\n2-1\r\n$2\r\nc1\r\n:0\r\n:2\r\n"
18013 );
18014 assert_eq!(
18016 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b">"]),
18017 "*-1\r\n"
18018 );
18019 assert_eq!(
18021 f.run(&[
18022 b"XAUTOCLAIM",
18023 b"s",
18024 b"g",
18025 b"c2",
18026 b"99999999",
18027 b"-",
18028 b"JUSTID"
18029 ]),
18030 "*3\r\n$3\r\n0-0\r\n*1\r\n$3\r\n1-1\r\n*0\r\n"
18031 );
18032
18033 f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
18037 assert!(
18038 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
18039 .contains(":-1\r\n:1\r\n")
18040 );
18041 f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
18043 f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
18044 assert!(
18045 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
18046 .contains(":-1\r\n:0\r\n")
18047 );
18048 f.run(&[b"XNACK", b"s", b"g", b"FATAL", b"IDS", b"1", b"1-1"]);
18050 assert!(
18051 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
18052 .contains(":9223372036854775807\r\n")
18053 );
18054 f.run(&[
18055 b"XNACK",
18056 b"s",
18057 b"g",
18058 b"FATAL",
18059 b"IDS",
18060 b"1",
18061 b"1-1",
18062 b"RETRYCOUNT",
18063 b"3",
18064 ]);
18065 assert!(
18066 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
18067 .contains(":-1\r\n:3\r\n")
18068 );
18069
18070 f.run(&[b"XACK", b"s", b"g", b"2-1"]);
18074 assert_eq!(
18075 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"]),
18076 ":0\r\n"
18077 );
18078 assert_eq!(
18079 f.run(&[
18080 b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1", b"FORCE"
18081 ]),
18082 ":1\r\n"
18083 );
18084 assert!(
18085 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
18086 .contains(":-1\r\n:0\r\n")
18087 );
18088 assert_eq!(
18090 f.run(&[
18091 b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"9-9", b"FORCE"
18092 ]),
18093 ":0\r\n"
18094 );
18095
18096 assert_eq!(
18099 f.run(&[b"XNACK", b"s", b"nope", b"BOGUS", b"IDS", b"1", b"1-1"]),
18100 "-NOGROUP No such key 's' or consumer group 'nope'\r\n"
18101 );
18102 assert!(
18103 f.run(&[b"XNACK", b"s", b"g", b"BOGUS", b"IDS", b"1", b"1-1"])
18104 .starts_with("-ERR")
18105 );
18106 assert!(
18108 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"0", b"1-1"])
18109 .contains("numids must be a positive integer")
18110 );
18111 assert!(
18112 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"2", b"1-1"])
18113 .contains("number of IDs doesn't match numids")
18114 );
18115 assert!(
18118 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1", b"2-1"])
18119 .contains("Unrecognized XNACK option '2-1'")
18120 );
18121 }
18122
18123 #[test]
18125 fn xinfo_reports_the_stream_the_groups_and_the_consumers() {
18126 let mut f = Fixture::new();
18127 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
18128 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
18129 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
18130 f.run(&[
18131 b"XREADGROUP",
18132 b"GROUP",
18133 b"g",
18134 b"c1",
18135 b"COUNT",
18136 b"1",
18137 b"STREAMS",
18138 b"s",
18139 b">",
18140 ]);
18141
18142 let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
18143 assert!(info.starts_with("*20\r\n"), "{info}");
18146 assert!(info.contains("$6\r\nlength\r\n:2\r\n"), "{info}");
18147 assert!(
18148 info.contains("$17\r\nlast-generated-id\r\n$3\r\n2-1\r\n"),
18149 "{info}"
18150 );
18151 assert!(info.contains("$13\r\nentries-added\r\n:2\r\n"), "{info}");
18152 assert!(info.contains("$6\r\ngroups\r\n:1\r\n"), "{info}");
18153
18154 let groups = f.run(&[b"XINFO", b"GROUPS", b"s"]);
18155 assert!(groups.starts_with("*1\r\n*12\r\n"), "{groups}");
18156 assert!(groups.contains("$9\r\nconsumers\r\n:1\r\n"), "{groups}");
18157 assert!(groups.contains("$7\r\npending\r\n:1\r\n"), "{groups}");
18158 assert!(groups.contains("$3\r\nlag\r\n:1\r\n"), "{groups}");
18159
18160 f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c2"]);
18164 let consumers = f.run(&[b"XINFO", b"CONSUMERS", b"s", b"g"]);
18165 assert!(consumers.starts_with("*2\r\n"), "{consumers}");
18166 assert!(
18167 consumers.contains("$8\r\ninactive\r\n:-1\r\n"),
18168 "{consumers}"
18169 );
18170 let c1 = consumers.find("c1").unwrap();
18172 let c2 = consumers.find("c2").unwrap();
18173 assert!(c1 < c2, "{consumers}");
18174
18175 let full = f.run(&[b"XINFO", b"STREAM", b"s", b"FULL"]);
18176 assert!(full.starts_with("*18\r\n"), "{full}");
18177 assert!(full.contains("$12\r\nnacked-count\r\n:0\r\n"), "{full}");
18178 assert!(full.contains("$11\r\nactive-time\r\n"), "{full}");
18179
18180 assert!(
18181 f.run(&[b"XINFO", b"STREAM", b"missing"])
18182 .contains("no such key")
18183 );
18184 assert!(
18185 f.run(&[b"XINFO", b"GROUPS", b"missing"])
18186 .contains("no such key")
18187 );
18188 assert!(
18189 f.run(&[b"XINFO", b"CONSUMERS", b"s", b"nope"])
18190 .starts_with("-NOGROUP")
18191 );
18192 assert!(
18193 f.run(&[b"XINFO", b"NOSUCH", b"s"])
18194 .contains("Try XINFO HELP")
18195 );
18196 assert!(f.run(&[b"XINFO", b"HELP"]).contains("XINFO <subcommand>"));
18197 assert!(f.run(&[b"XGROUP", b"HELP"]).contains("XGROUP <subcommand>"));
18198 }
18199
18200 #[test]
18202 fn xpending_takes_the_consumer_only_when_the_count_comes_out_right() {
18203 let mut f = Fixture::new();
18204 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
18205 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
18206 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
18207
18208 let list = f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]);
18209 assert_eq!(list, "*1\r\n*4\r\n$3\r\n1-1\r\n$2\r\nc1\r\n:0\r\n:1\r\n");
18210 assert_eq!(
18211 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
18212 "*1\r\n*4\r\n$3\r\n1-1\r\n$2\r\nc1\r\n:0\r\n:1\r\n"
18213 );
18214 assert_eq!(
18216 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"nope"]),
18217 "*0\r\n"
18218 );
18219 assert_eq!(
18220 f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0", b"-", b"+", b"10"]),
18221 list
18222 );
18223 assert!(
18225 f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0"])
18226 .contains("syntax error")
18227 );
18228 assert!(
18229 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+"])
18230 .contains("syntax error")
18231 );
18232 assert_eq!(
18233 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"-1"]),
18234 "*0\r\n"
18235 );
18236 assert!(
18237 f.run(&[b"XPENDING", b"missing", b"g"])
18238 .starts_with("-NOGROUP")
18239 );
18240 }
18241
18242 #[test]
18244 fn xsetid_will_not_go_below_what_is_there() {
18245 let mut f = Fixture::new();
18246 f.run(&[b"XADD", b"s", b"5-5", b"a", b"1"]);
18247 assert_eq!(f.run(&[b"XSETID", b"s", b"9-9"]), "+OK\r\n");
18248 assert_eq!(
18249 f.run(&[
18250 b"XSETID",
18251 b"s",
18252 b"10-1",
18253 b"ENTRIESADDED",
18254 b"7",
18255 b"MAXDELETEDID",
18256 b"9-1"
18257 ]),
18258 "+OK\r\n"
18259 );
18260 let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
18261 assert!(info.contains("$13\r\nentries-added\r\n:7\r\n"), "{info}");
18262 assert!(
18263 info.contains("$20\r\nmax-deleted-entry-id\r\n$3\r\n9-1\r\n"),
18264 "{info}"
18265 );
18266
18267 assert!(
18268 f.run(&[b"XSETID", b"s", b"1-1"])
18269 .contains("smaller than the target stream top item")
18270 );
18271 assert!(
18272 f.run(&[b"XSETID", b"s", b"10-1", b"ENTRIESADDED", b"-1"])
18273 .contains("entries_added must be positive")
18274 );
18275 assert!(
18276 f.run(&[b"XSETID", b"missing", b"1-1"])
18277 .contains("no such key")
18278 );
18279 }
18280
18281 #[test]
18283 fn xread_answers_a_map_on_resp3_and_the_fields_stay_flat() {
18284 let mut f = Fixture::new();
18285 f.run(&[b"HELLO", b"3"]);
18286 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
18287 assert_eq!(
18290 f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
18291 "%1\r\n$1\r\ns\r\n*1\r\n*2\r\n$3\r\n1-1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
18292 );
18293 assert_eq!(
18296 f.run(&[b"XRANGE", b"s", b"-", b"+"]),
18297 "*1\r\n*2\r\n$3\r\n1-1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
18298 );
18299 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "_\r\n");
18300 }
18301
18302 struct Mem {
18308 blobs: Vec<Vec<u8>>,
18309 }
18310
18311 impl yo_kv::cold::Blocks for Mem {
18312 fn put(&mut self, bytes: &[u8]) -> yo_common::Result<yo_common::Addr> {
18313 self.blobs.push(bytes.to_vec());
18314 Ok(yo_common::Addr::new(
18315 yo_common::Space::Log,
18316 (self.blobs.len() - 1) as u64,
18317 ))
18318 }
18319
18320 fn get(&self, at: yo_common::Addr) -> yo_common::Result<&[u8]> {
18321 self.blobs
18322 .get(at.offset() as usize)
18323 .map(Vec::as_slice)
18324 .ok_or_else(|| {
18325 yo_common::Error::new(yo_common::Code::Corrupt, "no chunk at that address")
18326 })
18327 }
18328
18329 fn bytes(&self) -> u64 {
18330 self.blobs.iter().map(|b| b.len() as u64).sum()
18331 }
18332 }
18333
18334 fn filled(attach: bool) -> (Fixture, usize) {
18338 let mut f = Fixture::new();
18339 if attach {
18340 f.server
18341 .striped(0)
18342 .hold_stripe(0)
18343 .attach(Box::new(Mem { blobs: Vec::new() }));
18344 }
18345 let val = vec![b'v'; 256];
18346 for i in 0..24000u32 {
18347 let k = format!("key:{i:08}");
18348 f.run(&[b"SET", k.as_bytes(), &val]);
18349 }
18350 let full = f.server.memory_bytes();
18351 assert!(full > 3 * 1024 * 1024, "the arena is several segments");
18352 (f, full)
18353 }
18354
18355 fn press(f: &mut Fixture, limit: usize) {
18361 let val = vec![b'v'; 256];
18362 for i in 0..3000u32 {
18363 let k = format!("new:{i:08}");
18364 assert_eq!(
18365 f.run(&[b"SET", k.as_bytes(), &val]),
18366 "+OK\r\n",
18367 "write {i} was refused"
18368 );
18369 f.server.refresh_memory();
18370 if f.server.memory_bytes() <= limit {
18371 return;
18372 }
18373 }
18374 panic!(
18375 "it never got under: {} against {limit}",
18376 f.server.memory_bytes()
18377 );
18378 }
18379
18380 #[test]
18381 fn the_storage_limit_reads_back_and_minus_one_is_no_limit() {
18382 let mut f = Fixture::new();
18383 assert_eq!(
18384 f.run(&[b"CONFIG", b"GET", b"maxstore"]),
18385 "*2\r\n$8\r\nmaxstore\r\n$2\r\n-1\r\n",
18386 "no limit is the default"
18387 );
18388 for (typed, bytes) in [
18391 (&b"0"[..], "0"),
18392 (b"1024", "1024"),
18393 (b"1k", "1000"),
18394 (b"1gb", "1073741824"),
18395 (b"-1", "-1"),
18396 ] {
18397 assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxstore", typed]), "+OK\r\n");
18398 assert_eq!(
18399 f.run(&[b"CONFIG", b"GET", b"maxstore"]),
18400 format!("*2\r\n$8\r\nmaxstore\r\n${}\r\n{bytes}\r\n", bytes.len()),
18401 "set {}",
18402 String::from_utf8_lossy(typed)
18403 );
18404 }
18405 for bad in [&b"1tb"[..], b"-2", b"", b"lots"] {
18406 assert_eq!(
18407 f.run(&[b"CONFIG", b"SET", b"maxstore", bad]),
18408 "-ERR CONFIG SET failed (possibly related to argument 'maxstore') - argument must be a memory value or -1\r\n",
18409 "refused {}",
18410 String::from_utf8_lossy(bad)
18411 );
18412 }
18413 let info = f.run(&[b"INFO", b"memory"]);
18415 assert!(info.contains("maxstore:-1"), "{info}");
18416 assert!(info.contains("yo_memory_regime:evict"), "{info}");
18417 assert!(info.contains("yo_store_bytes:0"), "{info}");
18418 }
18419
18420 #[test]
18421 fn a_memory_limit_moves_values_to_the_file_instead_of_dropping_keys() {
18422 let (mut f, full) = filled(true);
18426 let keys = f.run(&[b"DBSIZE"]);
18427 assert!(
18428 f.run(&[b"INFO", b"memory"])
18429 .contains("yo_memory_regime:migrate"),
18430 "a database with somewhere to put values migrates"
18431 );
18432
18433 let limit = full - 2 * 1024 * 1024;
18434 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
18435 f.run(&[
18436 b"CONFIG",
18437 b"SET",
18438 b"maxmemory",
18439 limit.to_string().as_bytes(),
18440 ]);
18441 press(&mut f, limit);
18442
18443 assert!(
18444 f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
18445 "nothing was thrown away"
18446 );
18447 let after: usize = f.run(&[b"DBSIZE"])[1..]
18448 .trim_end()
18449 .parse()
18450 .expect("a count");
18451 let before: usize = keys[1..].trim_end().parse().expect("a count");
18452 assert!(after > before, "the keys that came in are all still here");
18453 assert!(
18454 f.server.store_bytes() > 0,
18455 "and what came out of memory went to the file"
18456 );
18457 let val = format!("$256\r\n{}\r\n", "v".repeat(256));
18460 assert_eq!(f.run(&[b"GET", b"key:00000000"]), val);
18461 assert_eq!(f.run(&[b"GET", b"key:00023999"]), val);
18462 }
18463
18464 #[test]
18465 fn a_storage_limit_of_zero_restores_redis_behaviour_exactly() {
18466 let (mut f, full) = filled(true);
18470 f.run(&[b"CONFIG", b"SET", b"maxstore", b"0"]);
18471 assert!(
18472 f.run(&[b"INFO", b"memory"])
18473 .contains("yo_memory_regime:evict"),
18474 "nothing may go to the file"
18475 );
18476
18477 let limit = full - 2 * 1024 * 1024;
18478 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
18479 f.run(&[
18480 b"CONFIG",
18481 b"SET",
18482 b"maxmemory",
18483 limit.to_string().as_bytes(),
18484 ]);
18485 press(&mut f, limit);
18486
18487 assert!(
18488 !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
18489 "keys were thrown away, which is what was asked for"
18490 );
18491 assert_eq!(f.server.store_bytes(), 0, "and the file was never written");
18492 }
18493
18494 #[test]
18495 fn a_full_file_goes_back_to_evicting() {
18496 let (mut f, full) = filled(true);
18500 f.run(&[b"CONFIG", b"SET", b"maxstore", b"64kb"]);
18501 let limit = full - 2 * 1024 * 1024;
18502 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
18503 f.run(&[
18504 b"CONFIG",
18505 b"SET",
18506 b"maxmemory",
18507 limit.to_string().as_bytes(),
18508 ]);
18509 press(&mut f, limit);
18510
18511 assert!(f.server.store_bytes() >= 64 * 1024, "the file filled up");
18512 assert!(
18513 !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
18514 "and then it started evicting"
18515 );
18516 assert!(
18517 f.run(&[b"INFO", b"memory"])
18518 .contains("yo_memory_regime:evict"),
18519 "and it says so"
18520 );
18521 }
18522 #[test]
18538 fn the_string_group_answers_the_same_however_many_stripes_there_are() {
18539 let script: &[&[&[u8]]] = &[
18540 &[b"SET", b"k1", b"v1"],
18543 &[b"SET", b"k2", b"v2"],
18544 &[b"GET", b"k1"],
18545 &[b"GET", b"nothing"],
18546 &[b"GETSET", b"k1", b"v1b"],
18547 &[b"SETNX", b"k1", b"no"],
18548 &[b"SETNX", b"k3", b"yes"],
18549 &[b"APPEND", b"k3", b"!"],
18550 &[b"STRLEN", b"k3"],
18551 &[b"SETRANGE", b"k3", b"1", b"XY"],
18552 &[b"GETRANGE", b"k3", b"0", b"-1"],
18553 &[b"INCR", b"n1"],
18554 &[b"INCRBY", b"n1", b"41"],
18555 &[b"DECRBY", b"n1", b"2"],
18556 &[b"INCRBYFLOAT", b"f1", b"1.5"],
18557 &[b"SETEX", b"e1", b"100", b"v"],
18558 &[b"PSETEX", b"e2", b"100000", b"v"],
18559 &[b"GETEX", b"e1", b"PERSIST"],
18560 &[b"GETDEL", b"k2"],
18561 &[b"GET", b"k2"],
18562 &[b"DIGEST", b"k1"],
18563 &[b"DELEX", b"k3"],
18564 &[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"],
18567 &[b"MGET", b"a", b"b", b"c", b"missing"],
18568 &[b"MSETNX", b"d", b"4", b"e", b"5"],
18569 &[b"MSETNX", b"e", b"6", b"f", b"7"],
18570 &[b"MGET", b"d", b"e", b"f"],
18571 &[b"MSETEX", b"2", b"g", b"7", b"h", b"8", b"NX"],
18572 &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"NX"],
18573 &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"XX"],
18574 &[b"MGET", b"g", b"h"],
18575 &[b"SET", b"s1", b"ohmytext"],
18576 &[b"SET", b"s2", b"mynewtext"],
18577 &[b"LCS", b"s1", b"s2"],
18578 &[b"LCS", b"s1", b"s2", b"LEN"],
18579 &[b"LCS", b"s1", b"s2", b"IDX", b"MINMATCHLEN", b"4"],
18580 &[b"LCS", b"s1", b"s2", b"IDX", b"WITHMATCHLEN"],
18581 &[b"LCS", b"s1", b"gone"],
18582 &[b"MSET", b"odd"],
18584 &[b"LCS", b"s1", b"s2", b"LEN", b"IDX"],
18585 &[b"MGET"],
18586 ];
18587
18588 let mut one = Fixture::new();
18589 let mut many = Fixture::striped(8);
18590 for parts in script {
18591 let a = one.run(parts);
18592 let b = many.run(parts);
18593 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
18594 }
18595 }
18596
18597 #[test]
18602 fn a_striped_database_spreads_the_keys_it_is_given() {
18603 let mut f = Fixture::striped(8);
18604 for i in 0..256 {
18605 let key = format!("key:{i}");
18606 f.run(&[b"SET", key.as_bytes(), b"v"]);
18607 }
18608 assert_eq!(f.run(&[b"DBSIZE"]), ":256\r\n");
18609 }
18610
18611 #[test]
18614 fn a_wrong_type_in_the_middle_of_an_mget_is_still_one_nil() {
18615 let mut one = Fixture::new();
18616 let mut many = Fixture::striped(8);
18617 for f in [&mut one, &mut many] {
18618 f.run(&[b"SET", b"str", b"v"]);
18619 f.server
18625 .striped(0)
18626 .hold(b"list")
18627 .push(b"list", yo_kv::End::Right, core::iter::once(&b"v"[..]))
18628 .expect("a new list");
18629 }
18630 assert_eq!(
18631 one.run(&[b"MGET", b"str", b"list", b"gone"]),
18632 many.run(&[b"MGET", b"str", b"list", b"gone"])
18633 );
18634 }
18635
18636 #[test]
18644 fn the_keyspace_group_answers_the_same_however_many_stripes_there_are() {
18645 let script: &[&[&[u8]]] = &[
18646 &[b"SET", b"k1", b"v1"],
18647 &[b"SET", b"k2", b"v2"],
18648 &[b"EXISTS", b"k1", b"k2", b"k1", b"gone"],
18649 &[b"TYPE", b"k1"],
18650 &[b"TYPE", b"gone"],
18651 &[b"TOUCH", b"k1", b"k2", b"k1", b"gone"],
18652 &[b"EXPIRE", b"k1", b"100"],
18653 &[b"TTL", b"k1"],
18654 &[b"EXPIRE", b"k1", b"200", b"NX"],
18655 &[b"PERSIST", b"k1"],
18656 &[b"TTL", b"k1"],
18657 &[b"PEXPIREAT", b"k2", b"1900000000000"],
18658 &[b"EXPIRETIME", b"k2"],
18659 &[b"PEXPIRETIME", b"k2"],
18660 &[b"PERSIST", b"k2"],
18661 &[b"OBJECT", b"ENCODING", b"k1"],
18662 &[b"OBJECT", b"REFCOUNT", b"k1"],
18663 &[b"OBJECT", b"IDLETIME", b"k1"],
18664 &[b"OBJECT", b"FREQ", b"k1"],
18665 &[b"OBJECT", b"ENCODING", b"gone"],
18666 &[b"OBJECT", b"HELP"],
18667 &[b"RENAME", b"k1", b"k9"],
18668 &[b"GET", b"k9"],
18669 &[b"RENAME", b"gone", b"x"],
18670 &[b"RENAMENX", b"k9", b"k2"],
18671 &[b"RENAMENX", b"k9", b"k8"],
18672 &[b"GET", b"k8"],
18673 &[b"COPY", b"k8", b"c1"],
18674 &[b"COPY", b"k8", b"c1"],
18675 &[b"COPY", b"k8", b"c1", b"REPLACE"],
18676 &[b"COPY", b"k8", b"k8"],
18677 &[b"COPY", b"gone", b"c2"],
18678 &[b"COPY", b"k8", b"k8", b"DB", b"1"],
18679 &[b"COPY", b"k8", b"c9", b"DB", b"9"],
18680 &[b"MOVE", b"c1", b"1"],
18681 &[b"MOVE", b"c1", b"1"],
18682 &[b"MOVE", b"k8", b"0"],
18683 &[b"DEL", b"k2", b"gone"],
18684 &[b"UNLINK", b"k8", b"k8"],
18685 &[b"DBSIZE"],
18686 ];
18687
18688 let mut one = Fixture::new();
18689 let mut many = Fixture::striped(8);
18690 for parts in script {
18691 let a = one.run(parts);
18692 let b = many.run(parts);
18693 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
18694 }
18695
18696 for f in [&mut one, &mut many] {
18702 f.run(&[b"SET", b"d1", b"payload"]);
18703 let payload = f
18704 .server
18705 .striped(0)
18706 .hold(b"d1")
18707 .dump(b"d1")
18708 .expect("a key that is there");
18709 assert!(
18710 f.run(&[b"DUMP", b"d1"])
18711 .starts_with(&format!("${}", payload.len())),
18712 "a payload of the length the store gave"
18713 );
18714 assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
18715 assert_eq!(f.run(&[b"RESTORE", b"d2", b"0", &payload]), "+OK\r\n");
18716 assert_eq!(f.run(&[b"GET", b"d2"]), "$7\r\npayload\r\n");
18717 assert_eq!(
18718 f.run(&[b"RESTORE", b"d2", b"0", &payload]),
18719 "-BUSYKEY Target key name already exists.\r\n"
18720 );
18721 assert_eq!(
18722 f.run(&[b"RESTORE", b"d3", b"0", b"rubbish"]),
18723 "-ERR DUMP payload version or checksum are wrong\r\n"
18724 );
18725 }
18726 }
18727
18728 #[test]
18735 fn a_scan_of_a_striped_database_walks_all_of_it() {
18736 let mut f = Fixture::striped(8);
18737 for i in 0..500 {
18738 let key = format!("key:{i}");
18739 f.run(&[b"SET", key.as_bytes(), b"v"]);
18740 }
18741
18742 let mut seen = Vec::new();
18743 let mut cursor = "0".to_owned();
18744 let mut calls = 0;
18745 loop {
18746 let reply = f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"10"]);
18747 let (next, keys) = scan_reply(&reply);
18748 seen.extend(keys);
18749 cursor = next;
18750 calls += 1;
18751 assert!(calls < 5_000, "a scan that will not finish");
18752 if cursor == "0" {
18753 break;
18754 }
18755 }
18756 seen.sort();
18757 assert_eq!(seen.len(), 500, "a quiet scan answered a key twice");
18758 assert_eq!(seen, sorted(&f.run(&[b"KEYS", b"*"])));
18759
18760 let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"MATCH", b"key:4?"]);
18764 let (_, keys) = scan_reply(&reply);
18765 assert_eq!(keys.len(), 10, "key:40 through key:49");
18766 let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"TYPE", b"list"]);
18767 let (_, keys) = scan_reply(&reply);
18768 assert!(keys.is_empty(), "nothing here is a list");
18769 }
18770
18771 #[test]
18777 fn a_random_key_can_come_from_any_stripe() {
18778 let mut f = Fixture::striped(8);
18779 assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
18780 for i in 0..200 {
18781 let key = format!("key:{i}");
18782 f.run(&[b"SET", key.as_bytes(), b"v"]);
18783 }
18784 let mut homes = std::collections::HashSet::new();
18785 for _ in 0..200 {
18786 let got = f.run(&[b"RANDOMKEY"]);
18787 let key = got.split("\r\n").nth(1).expect("a key").to_owned();
18788 assert_eq!(f.run(&[b"EXISTS", key.as_bytes()]), ":1\r\n");
18789 homes.insert(f.server.striped(0).stripe_of(key.as_bytes()));
18790 }
18791 assert_eq!(homes.len(), 8, "some stripe was never drawn from");
18792 }
18793
18794 fn apart(f: &mut Fixture, src: &str) -> String {
18798 let home = f.server.striped(0).stripe_of(src.as_bytes());
18799 for i in 0..1_000 {
18800 let dst = format!("dst:{i}");
18801 if f.server.striped(0).stripe_of(dst.as_bytes()) != home {
18802 return dst;
18803 }
18804 }
18805 panic!("eight stripes and a thousand keys all landed in one place");
18806 }
18807
18808 #[test]
18811 fn a_rename_across_stripes_takes_everything_with_it() {
18812 let mut f = Fixture::striped(8);
18813 let dst = apart(&mut f, "src");
18814 let (src, dst) = (b"src".as_slice(), dst.as_bytes());
18815
18816 f.run(&[b"SET", src, b"v"]);
18817 f.run(&[b"EXPIRE", src, b"100"]);
18818 assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
18819 assert_eq!(f.run(&[b"EXISTS", src, dst]), ":1\r\n");
18820 assert_eq!(f.run(&[b"GET", dst]), "$1\r\nv\r\n");
18821 assert_eq!(f.run(&[b"TTL", dst]), ":100\r\n", "the deadline came too");
18822
18823 f.server
18828 .striped(0)
18829 .hold(src)
18830 .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
18831 .expect("a new list");
18832 assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
18833 assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n");
18834 assert_eq!(
18835 f.server.striped(0).hold(dst).llen(dst).expect("a list"),
18836 2,
18837 "the members are on the stripe the key moved to"
18838 );
18839
18840 f.run(&[b"SET", src, b"v"]);
18843 assert_eq!(f.run(&[b"RENAMENX", src, dst]), ":0\r\n");
18844 assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n", "and left it alone");
18845 assert_eq!(f.run(&[b"GET", src]), "$1\r\nv\r\n", "and left the source");
18846 }
18847
18848 #[test]
18850 fn a_copy_across_stripes_leaves_the_source_where_it_was() {
18851 let mut f = Fixture::striped(8);
18852 let dst = apart(&mut f, "src");
18853 let (src, dst) = (b"src".as_slice(), dst.as_bytes());
18854
18855 f.run(&[b"SET", src, b"v"]);
18856 assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
18857 assert_eq!(f.run(&[b"EXISTS", src, dst]), ":2\r\n");
18858 assert_eq!(
18859 f.run(&[b"COPY", src, dst]),
18860 ":0\r\n",
18861 "the destination is taken"
18862 );
18863 f.run(&[b"SET", src, b"w"]);
18864 assert_eq!(f.run(&[b"COPY", src, dst, b"REPLACE"]), ":1\r\n");
18865 assert_eq!(f.run(&[b"GET", dst]), "$1\r\nw\r\n");
18866
18867 f.run(&[b"DEL", src, dst]);
18871 f.server
18872 .striped(0)
18873 .hold(src)
18874 .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
18875 .expect("a new list");
18876 assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
18877 f.server
18878 .striped(0)
18879 .hold(src)
18880 .push(src, yo_kv::End::Right, core::iter::once(&b"c"[..]))
18881 .expect("a list that is there");
18882 assert_eq!(f.server.striped(0).hold(src).llen(src).expect("a list"), 3);
18883 assert_eq!(f.server.striped(0).hold(dst).llen(dst).expect("a list"), 2);
18884 }
18885
18886 #[test]
18894 fn the_bitmap_group_answers_the_same_however_many_stripes_there_are() {
18895 let script: &[&[&[u8]]] = &[
18896 &[b"SET", b"k1", b"foobar"],
18897 &[b"SETBIT", b"b1", b"7", b"1"],
18898 &[b"SETBIT", b"b1", b"7", b"0"],
18899 &[b"GETBIT", b"k1", b"6"],
18900 &[b"GETBIT", b"k1", b"100"],
18901 &[b"BITCOUNT", b"k1"],
18902 &[b"BITCOUNT", b"k1", b"0", b"0"],
18903 &[b"BITCOUNT", b"k1", b"5", b"30", b"BIT"],
18904 &[b"BITPOS", b"k1", b"1"],
18905 &[b"BITPOS", b"k1", b"0", b"2"],
18906 &[b"BITPOS", b"k1", b"1", b"2", b"-1", b"BIT"],
18907 &[
18908 b"BITFIELD",
18909 b"bf",
18910 b"SET",
18911 b"u8",
18912 b"0",
18913 b"255",
18914 b"GET",
18915 b"u8",
18916 b"0",
18917 ],
18918 &[
18919 b"BITFIELD",
18920 b"bf",
18921 b"OVERFLOW",
18922 b"SAT",
18923 b"INCRBY",
18924 b"u8",
18925 b"0",
18926 b"10",
18927 ],
18928 &[b"BITFIELD_RO", b"bf", b"GET", b"u8", b"0"],
18929 &[b"SET", b"s1", b"abc"],
18932 &[b"SET", b"s2", b"abd"],
18933 &[b"SET", b"s3", b"a"],
18934 &[b"BITOP", b"AND", b"d1", b"s1", b"s2"],
18935 &[b"GET", b"d1"],
18936 &[b"BITOP", b"OR", b"d2", b"s1", b"s2", b"s3"],
18937 &[b"GET", b"d2"],
18938 &[b"BITOP", b"XOR", b"d3", b"s1", b"s2"],
18939 &[b"STRLEN", b"d3"],
18940 &[b"BITOP", b"NOT", b"d4", b"s1"],
18941 &[b"STRLEN", b"d4"],
18942 &[b"BITOP", b"DIFF", b"d5", b"s1", b"s2"],
18943 &[b"BITOP", b"DIFF1", b"d6", b"s1", b"s2"],
18944 &[b"BITOP", b"ANDOR", b"d7", b"s1", b"s2"],
18945 &[b"BITOP", b"ONE", b"d8", b"s1", b"s2"],
18946 &[b"BITOP", b"AND", b"d1", b"gone", b"also-gone"],
18949 &[b"EXISTS", b"d1"],
18950 &[b"BITOP", b"OR", b"d9", b"s1", b"gone"],
18951 &[b"GET", b"d9"],
18952 &[b"BITOP", b"AND", b"d1", b"s1", b"list"],
18956 &[b"BITOP", b"AND", b"list", b"s1", b"s2"],
18957 &[b"BITOP", b"NOT", b"d1", b"s1", b"s2"],
18958 &[b"BITOP", b"DIFF", b"d1", b"s1"],
18959 &[b"BITOP", b"NOPE", b"d1", b"s1"],
18960 &[b"BITCOUNT", b"list"],
18961 &[b"BITFIELD_RO", b"bf", b"SET", b"u8", b"0", b"1"],
18962 ];
18963
18964 let mut one = Fixture::new();
18965 let mut many = Fixture::striped(8);
18966 for f in [&mut one, &mut many] {
18967 plant_list(f, b"list");
18968 }
18969 for parts in script {
18970 let a = one.run(parts);
18971 let b = many.run(parts);
18972 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
18973 }
18974 }
18975
18976 fn plant_list(f: &mut Fixture, key: &[u8]) {
18982 f.server
18983 .striped(0)
18984 .hold(key)
18985 .push(key, yo_kv::End::Right, core::iter::once(&b"x"[..]))
18986 .expect("a new list");
18987 }
18988
18989 #[test]
18996 fn a_bitop_across_stripes_reads_every_source() {
18997 let mut f = Fixture::striped(8);
18998 let other = apart(&mut f, "src");
18999 let (src, far) = (b"src".as_slice(), other.as_bytes());
19000 assert_ne!(
19001 f.server.striped(0).stripe_of(src),
19002 f.server.striped(0).stripe_of(far),
19003 "the two keys are the point of the test"
19004 );
19005
19006 f.run(&[b"SET", src, b"abc"]);
19007 f.run(&[b"SET", far, b"abd"]);
19008 assert_eq!(f.run(&[b"BITOP", b"AND", far, src, far]), ":3\r\n");
19009 assert_eq!(
19010 f.run(&[b"GET", far]),
19011 "$3\r\nab`\r\n",
19012 "a destination that is also a source"
19013 );
19014 f.run(&[b"SET", far, b"abd"]);
19015 assert_eq!(f.run(&[b"BITOP", b"XOR", src, src, far]), ":3\r\n");
19016 assert_eq!(
19017 f.run(&[b"GET", src]),
19018 "$3\r\n\0\0\x07\r\n",
19019 "and the other way round"
19020 );
19021
19022 f.run(&[b"SET", src, b"abc"]);
19026 f.run(&[b"DEL", far]);
19027 assert_eq!(f.run(&[b"BITOP", b"AND", src, far, b"gone"]), ":0\r\n");
19028 assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n");
19029 f.run(&[b"SET", src, b"abc"]);
19030 f.run(&[b"DEL", far]);
19031 plant_list(&mut f, far);
19032 assert_eq!(
19033 f.run(&[b"BITOP", b"OR", b"out", src, far]),
19034 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19035 );
19036 assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
19037 }
19038
19039 #[test]
19041 fn the_hyperloglog_group_answers_the_same_however_many_stripes_there_are() {
19042 let script: &[&[&[u8]]] = &[
19043 &[b"PFADD", b"h1", b"a", b"b", b"c"],
19044 &[b"PFADD", b"h1", b"a"],
19045 &[b"PFADD", b"h2"],
19046 &[b"PFADD", b"h2", b"c", b"d", b"e"],
19047 &[b"PFCOUNT", b"h1"],
19048 &[b"PFCOUNT", b"h2"],
19049 &[b"PFCOUNT", b"missing"],
19050 &[b"PFCOUNT", b"h1", b"h2"],
19052 &[b"PFCOUNT", b"h1", b"missing"],
19053 &[b"PFMERGE", b"m", b"h1", b"h2"],
19054 &[b"PFCOUNT", b"m"],
19055 &[b"STRLEN", b"m"],
19056 &[b"PFMERGE", b"m"],
19057 &[b"PFCOUNT", b"m"],
19058 &[b"PFMERGE", b"m2", b"missing"],
19059 &[b"PFCOUNT", b"m2"],
19060 &[b"PFDEBUG", b"ENCODING", b"h1"],
19063 &[b"PFDEBUG", b"DECODE", b"h1"],
19064 &[b"PFDEBUG", b"TODENSE", b"h1"],
19065 &[b"PFDEBUG", b"ENCODING", b"h1"],
19066 &[b"PFDEBUG", b"TODENSE", b"h1"],
19067 &[b"PFCOUNT", b"h1", b"h2"],
19068 &[b"PFSELFTEST"],
19069 &[b"SET", b"plain", b"not a sketch at all"],
19071 &[b"PFADD", b"plain", b"a"],
19072 &[b"PFCOUNT", b"plain"],
19073 &[b"PFCOUNT", b"h1", b"plain"],
19074 &[b"PFMERGE", b"plain", b"h1"],
19075 &[b"PFMERGE", b"m", b"plain"],
19076 &[b"PFDEBUG", b"ENCODING", b"gone"],
19077 &[b"PFDEBUG", b"NOPE", b"h1"],
19078 ];
19079
19080 let mut one = Fixture::new();
19081 let mut many = Fixture::striped(8);
19082 for parts in script {
19083 let a = one.run(parts);
19084 let b = many.run(parts);
19085 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
19086 }
19087 }
19088
19089 #[test]
19098 fn the_set_group_answers_the_same_however_many_stripes_there_are() {
19099 const UNORDERED: [&str; 4] = ["SMEMBERS", "SINTER", "SUNION", "SDIFF"];
19100 let script: &[&[&[u8]]] = &[
19101 &[b"SADD", b"s1", b"a", b"b", b"c"],
19102 &[b"SADD", b"s1", b"a"],
19103 &[b"SADD", b"s2", b"b", b"c", b"d"],
19104 &[b"SADD", b"ints", b"1", b"2", b"3"],
19105 &[b"SCARD", b"s1"],
19106 &[b"SISMEMBER", b"s1", b"a"],
19107 &[b"SISMEMBER", b"s1", b"z"],
19108 &[b"SMISMEMBER", b"s1", b"a", b"z", b"c"],
19109 &[b"SMEMBERS", b"s1"],
19110 &[b"SREM", b"s1", b"c"],
19111 &[b"SADD", b"s1", b"c"],
19112 &[b"SSCAN", b"s1", b"0"],
19113 &[b"SSCAN", b"s1", b"0", b"COUNT", b"100", b"MATCH", b"a*"],
19114 &[b"SADD", b"one", b"m"],
19117 &[b"SRANDMEMBER", b"one"],
19118 &[b"SRANDMEMBER", b"one", b"-3"],
19119 &[b"SRANDMEMBER", b"gone"],
19120 &[b"SPOP", b"one"],
19121 &[b"SPOP", b"one"],
19122 &[b"SPOP", b"gone", b"2"],
19123 &[b"SMOVE", b"s1", b"s2", b"a"],
19125 &[b"SMOVE", b"s1", b"s2", b"zzz"],
19126 &[b"SMOVE", b"gone", b"s2", b"a"],
19127 &[b"SMEMBERS", b"s1"],
19128 &[b"SMEMBERS", b"s2"],
19129 &[b"SINTER", b"s1", b"s2"],
19131 &[b"SUNION", b"s1", b"s2"],
19132 &[b"SDIFF", b"s2", b"s1"],
19133 &[b"SINTER", b"s1", b"gone"],
19134 &[b"SUNION", b"s1", b"gone"],
19135 &[b"SDIFF", b"gone", b"s1"],
19136 &[b"SINTER", b"ints", b"s1"],
19137 &[b"SINTERCARD", b"2", b"s1", b"s2"],
19138 &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"1"],
19139 &[b"SUNIONCARD", b"2", b"s1", b"s2"],
19140 &[b"SDIFFCARD", b"2", b"s2", b"s1"],
19141 &[b"SINTERSTORE", b"d1", b"s1", b"s2"],
19142 &[b"SMEMBERS", b"d1"],
19143 &[b"SUNIONSTORE", b"d2", b"s1", b"s2"],
19144 &[b"SCARD", b"d2"],
19145 &[b"SDIFFSTORE", b"d3", b"s2", b"s1"],
19146 &[b"SCARD", b"d3"],
19147 &[b"SINTERSTORE", b"d4", b"s1", b"gone"],
19150 &[b"EXISTS", b"d4"],
19151 &[b"SUNIONSTORE", b"s2", b"s1", b"s2"],
19153 &[b"SCARD", b"s2"],
19154 &[b"SET", b"str", b"v"],
19156 &[b"SADD", b"str", b"a"],
19157 &[b"SINTER", b"s1", b"str"],
19158 &[b"SINTERSTORE", b"d5", b"s1", b"str"],
19159 &[b"EXISTS", b"d5"],
19160 &[b"SMOVE", b"str", b"s2", b"a"],
19161 &[b"SMOVE", b"s1", b"str", b"b"],
19162 &[b"SMOVE", b"gone", b"str", b"b"],
19163 &[b"SINTERCARD", b"0", b"s1"],
19164 &[b"SINTERCARD", b"3", b"s1", b"s2"],
19165 &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"-1"],
19166 &[b"SPOP", b"s1", b"-1"],
19167 ];
19168
19169 let mut one = Fixture::new();
19170 let mut many = Fixture::striped(8);
19171 for parts in script {
19172 let a = one.run(parts);
19173 let b = many.run(parts);
19174 let name = String::from_utf8_lossy(parts[0]).to_uppercase();
19175 if UNORDERED.contains(&name.as_str()) && a.starts_with(['*', '~']) {
19176 assert_eq!(sorted(&a), sorted(&b), "{name}");
19177 } else {
19178 assert_eq!(a, b, "{name}");
19179 }
19180 }
19181 }
19182
19183 #[test]
19185 fn a_set_operation_across_stripes_reads_every_set() {
19186 let mut f = Fixture::striped(8);
19187 let second = apart(&mut f, "s1");
19188 let third = apart(&mut f, &second);
19189 let (s1, s2, s3) = (b"s1".as_slice(), second.as_bytes(), third.as_bytes());
19190
19191 f.run(&[b"SADD", s1, b"a", b"b", b"c"]);
19192 f.run(&[b"SADD", s2, b"b", b"c", b"d"]);
19193 assert_eq!(sorted(&f.run(&[b"SINTER", s1, s2])), ["b", "c"]);
19194 assert_eq!(
19195 sorted(&f.run(&[b"SUNION", s1, s2])),
19196 ["a", "b", "c", "d"],
19197 "a union of two stripes is both of them"
19198 );
19199 assert_eq!(sorted(&f.run(&[b"SDIFF", s1, s2])), ["a"]);
19200 assert_eq!(f.run(&[b"SINTERCARD", b"2", s1, s2]), ":2\r\n");
19201 assert_eq!(f.run(&[b"SUNIONCARD", b"2", s1, s2]), ":4\r\n");
19202 assert_eq!(f.run(&[b"SDIFFCARD", b"2", s1, s2]), ":1\r\n");
19203
19204 assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, s2]), ":2\r\n");
19206 assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["b", "c"]);
19207 assert_eq!(f.run(&[b"SUNIONSTORE", s2, s1, s2]), ":4\r\n");
19208 assert_eq!(sorted(&f.run(&[b"SMEMBERS", s2])), ["a", "b", "c", "d"]);
19209 assert_eq!(f.run(&[b"SDIFFSTORE", s3, s2, s1]), ":1\r\n");
19210 assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["d"]);
19211
19212 assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, b"gone"]), ":0\r\n");
19215 assert_eq!(f.run(&[b"EXISTS", s3]), ":0\r\n");
19216 f.run(&[b"SET", s3, b"v"]);
19217 assert_eq!(
19218 f.run(&[b"SINTER", s1, s3]),
19219 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19220 );
19221 assert_eq!(f.run(&[b"GET", s3]), "$1\r\nv\r\n", "and left it alone");
19222 }
19223
19224 #[test]
19226 fn a_move_across_stripes_takes_the_member_with_it() {
19227 let mut f = Fixture::striped(8);
19228 let other = apart(&mut f, "src");
19229 let (src, dst) = (b"src".as_slice(), other.as_bytes());
19230
19231 f.run(&[b"SADD", src, b"a", b"b"]);
19232 f.run(&[b"SADD", dst, b"c"]);
19233 assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":1\r\n");
19234 assert_eq!(sorted(&f.run(&[b"SMEMBERS", src])), ["b"]);
19235 assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["a", "c"]);
19236 assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":0\r\n", "it has gone");
19237
19238 f.run(&[b"DEL", dst]);
19241 assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":1\r\n");
19242 assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n", "the source is empty");
19243 assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["b"]);
19244
19245 f.run(&[b"SET", dst, b"v"]);
19249 assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":0\r\n");
19250 f.run(&[b"SADD", src, b"b"]);
19251 assert_eq!(
19252 f.run(&[b"SMOVE", src, dst, b"b"]),
19253 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19254 );
19255 }
19256
19257 #[test]
19259 fn a_pfcount_and_a_pfmerge_reach_across_stripes() {
19260 let mut f = Fixture::striped(8);
19261 let other = apart(&mut f, "src");
19262 let (src, far) = (b"src".as_slice(), other.as_bytes());
19263
19264 for i in 0..150 {
19265 let ele = format!("e:{i}");
19266 f.run(&[b"PFADD", src, ele.as_bytes()]);
19267 }
19268 for i in 150..200 {
19269 let ele = format!("e:{i}");
19270 f.run(&[b"PFADD", far, ele.as_bytes()]);
19271 }
19272 assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n");
19275 assert_eq!(f.run(&[b"PFCOUNT", far]), ":49\r\n");
19276 assert_eq!(f.run(&[b"PFCOUNT", src, far]), ":199\r\n");
19277
19278 let dest = apart(&mut f, &other);
19281 assert_eq!(f.run(&[b"PFMERGE", dest.as_bytes(), src, far]), "+OK\r\n");
19282 assert_eq!(f.run(&[b"PFCOUNT", dest.as_bytes()]), ":199\r\n");
19283 assert_eq!(f.run(&[b"PFMERGE", far, src]), "+OK\r\n");
19284 assert_eq!(f.run(&[b"PFCOUNT", far]), ":199\r\n", "and kept its own");
19285 assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n", "and left the source");
19286 }
19287
19288 #[test]
19295 fn the_sorted_set_group_answers_the_same_however_many_stripes_there_are() {
19296 let script: &[&[&[u8]]] = &[
19297 &[b"ZADD", b"z1", b"1", b"a", b"2", b"b", b"3", b"c"],
19298 &[b"ZADD", b"z1", b"NX", b"9", b"a"],
19299 &[b"ZADD", b"z1", b"XX", b"CH", b"5", b"a"],
19300 &[b"ZADD", b"z1", b"GT", b"CH", b"1", b"a"],
19301 &[b"ZADD", b"z1", b"INCR", b"2", b"a"],
19302 &[b"ZINCRBY", b"z1", b"1.5", b"b"],
19303 &[b"ZADD", b"z2", b"1", b"b", b"2", b"c", b"3", b"d"],
19304 &[b"ZADD", b"lex", b"0", b"a", b"0", b"b", b"0", b"c"],
19305 &[b"ZADD", b"one", b"1", b"m"],
19306 &[b"ZCARD", b"z1"],
19307 &[b"ZCARD", b"gone"],
19308 &[b"ZSCORE", b"z1", b"a"],
19309 &[b"ZSCORE", b"z1", b"zz"],
19310 &[b"ZMSCORE", b"z1", b"a", b"zz", b"c"],
19311 &[b"ZRANK", b"z1", b"c"],
19312 &[b"ZRANK", b"z1", b"c", b"WITHSCORE"],
19313 &[b"ZREVRANK", b"z1", b"c"],
19314 &[b"ZRANK", b"z1", b"gone"],
19315 &[b"ZCOUNT", b"z1", b"-inf", b"+inf"],
19316 &[b"ZCOUNT", b"z1", b"(1", b"3"],
19317 &[b"ZLEXCOUNT", b"lex", b"-", b"+"],
19318 &[b"ZRANGE", b"z1", b"0", b"-1"],
19320 &[b"ZRANGE", b"z1", b"0", b"-1", b"WITHSCORES"],
19321 &[b"ZRANGE", b"z1", b"1", b"9", b"BYSCORE"],
19322 &[b"ZRANGE", b"z1", b"9", b"1", b"BYSCORE", b"REV"],
19323 &[b"ZRANGE", b"lex", b"[a", b"(c", b"BYLEX"],
19324 &[b"ZREVRANGE", b"z1", b"0", b"-1"],
19325 &[
19326 b"ZRANGEBYSCORE",
19327 b"z1",
19328 b"-inf",
19329 b"+inf",
19330 b"LIMIT",
19331 b"1",
19332 b"1",
19333 ],
19334 &[b"ZREVRANGEBYLEX", b"lex", b"+", b"-"],
19335 &[b"ZSCAN", b"z1", b"0"],
19336 &[b"ZSCAN", b"z1", b"0", b"MATCH", b"a*", b"COUNT", b"100"],
19337 &[b"ZRANDMEMBER", b"one"],
19340 &[b"ZRANDMEMBER", b"one", b"-3", b"WITHSCORES"],
19341 &[b"ZRANDMEMBER", b"gone"],
19342 &[b"ZRANGESTORE", b"d0", b"z1", b"0", b"1"],
19344 &[b"ZRANGE", b"d0", b"0", b"-1", b"WITHSCORES"],
19345 &[b"ZRANGESTORE", b"d0", b"z1", b"5", b"1"],
19346 &[b"EXISTS", b"d0"],
19347 &[b"ZUNION", b"2", b"z1", b"z2"],
19349 &[b"ZUNION", b"2", b"z1", b"z2", b"WITHSCORES"],
19350 &[
19351 b"ZUNION",
19352 b"2",
19353 b"z1",
19354 b"z2",
19355 b"WEIGHTS",
19356 b"2",
19357 b"3",
19358 b"AGGREGATE",
19359 b"MAX",
19360 b"WITHSCORES",
19361 ],
19362 &[b"ZINTER", b"2", b"z1", b"z2", b"WITHSCORES"],
19363 &[b"ZDIFF", b"2", b"z1", b"z2", b"WITHSCORES"],
19364 &[b"ZDIFF", b"2", b"gone", b"z1"],
19365 &[b"ZINTERCARD", b"2", b"z1", b"z2"],
19366 &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"1"],
19367 &[b"ZUNIONSTORE", b"d1", b"2", b"z1", b"z2"],
19368 &[b"ZRANGE", b"d1", b"0", b"-1", b"WITHSCORES"],
19369 &[
19370 b"ZINTERSTORE",
19371 b"d2",
19372 b"2",
19373 b"z1",
19374 b"z2",
19375 b"AGGREGATE",
19376 b"MIN",
19377 ],
19378 &[b"ZRANGE", b"d2", b"0", b"-1", b"WITHSCORES"],
19379 &[b"ZDIFFSTORE", b"d3", b"2", b"z1", b"z2"],
19380 &[b"ZCARD", b"d3"],
19381 &[b"ZINTERSTORE", b"d4", b"2", b"z1", b"gone"],
19384 &[b"EXISTS", b"d4"],
19385 &[b"SADD", b"plain", b"a", b"x"],
19388 &[b"ZUNIONSTORE", b"d5", b"2", b"z1", b"plain"],
19389 &[b"ZRANGE", b"d5", b"0", b"-1", b"WITHSCORES"],
19390 &[b"ZUNIONSTORE", b"z2", b"2", b"z1", b"z2"],
19392 &[b"ZRANGE", b"z2", b"0", b"-1", b"WITHSCORES"],
19393 &[b"ZREM", b"d5", b"x", b"nothere"],
19395 &[b"ZREMRANGEBYRANK", b"d5", b"0", b"0"],
19396 &[b"ZREMRANGEBYSCORE", b"d1", b"-inf", b"1"],
19397 &[b"ZREMRANGEBYLEX", b"lex", b"[a", b"[a"],
19398 &[b"ZPOPMIN", b"z1"],
19399 &[b"ZPOPMAX", b"z1", b"2"],
19400 &[b"ZPOPMIN", b"gone"],
19401 &[b"ZMPOP", b"2", b"gone", b"z2", b"MIN"],
19402 &[b"ZMPOP", b"2", b"gone", b"nothere", b"MAX", b"COUNT", b"2"],
19403 &[b"SET", b"str", b"v"],
19405 &[b"ZADD", b"str", b"1", b"a"],
19406 &[b"ZSCORE", b"str", b"a"],
19407 &[b"ZADD", b"z1", b"nan", b"a"],
19408 &[b"ZUNION", b"2", b"z1", b"str"],
19409 &[b"ZUNIONSTORE", b"d6", b"2", b"z1", b"str"],
19410 &[b"EXISTS", b"d6"],
19411 &[b"ZINTERCARD", b"0", b"z1"],
19412 &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"-1"],
19413 &[b"ZRANGESTORE", b"d7", b"str", b"0", b"-1"],
19414 &[b"ZMPOP", b"1", b"str", b"MIN"],
19415 &[b"ZPOPMIN", b"z1", b"-1"],
19416 ];
19417
19418 let mut one = Fixture::new();
19419 let mut many = Fixture::striped(8);
19420 for parts in script {
19421 let a = one.run(parts);
19422 let b = many.run(parts);
19423 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
19424 }
19425 }
19426
19427 #[test]
19429 fn a_sorted_set_operation_across_stripes_reads_every_input() {
19430 let mut f = Fixture::striped(8);
19431 let second = apart(&mut f, "z1");
19432 let third = apart(&mut f, &second);
19433 let (z1, z2, z3) = (b"z1".as_slice(), second.as_bytes(), third.as_bytes());
19434
19435 f.run(&[b"ZADD", z1, b"1", b"a", b"2", b"b"]);
19436 f.run(&[b"ZADD", z2, b"3", b"b", b"4", b"c"]);
19437 assert_eq!(
19440 f.run(&[b"ZUNION", b"2", z1, z2]),
19441 "*3\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n"
19442 );
19443 assert_eq!(f.run(&[b"ZINTER", b"2", z1, z2]), "*1\r\n$1\r\nb\r\n");
19444 assert_eq!(f.run(&[b"ZDIFF", b"2", z1, z2]), "*1\r\n$1\r\na\r\n");
19445 assert_eq!(f.run(&[b"ZINTERCARD", b"2", z1, z2]), ":1\r\n");
19446 assert_eq!(
19447 f.run(&[b"ZINTERCARD", b"2", z1, z2, b"LIMIT", b"1"]),
19448 ":1\r\n"
19449 );
19450
19451 assert_eq!(f.run(&[b"ZUNIONSTORE", z3, b"2", z1, z2]), ":3\r\n");
19454 assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n5\r\n");
19455 assert_eq!(
19456 f.run(&[
19457 b"ZUNIONSTORE",
19458 z3,
19459 b"2",
19460 z1,
19461 z2,
19462 b"WEIGHTS",
19463 b"2",
19464 b"3",
19465 b"AGGREGATE",
19466 b"MAX"
19467 ]),
19468 ":3\r\n"
19469 );
19470 assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n9\r\n");
19471 assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, z2]), ":1\r\n");
19472 assert_eq!(f.run(&[b"ZCARD", z3]), ":1\r\n");
19473 assert_eq!(f.run(&[b"ZDIFFSTORE", z3, b"2", z2, z1]), ":1\r\n");
19474 assert_eq!(f.run(&[b"ZSCORE", z3, b"c"]), "$1\r\n4\r\n");
19475
19476 let popped = format!(
19479 "*2\r\n${}\r\n{second}\r\n*1\r\n*2\r\n$1\r\nb\r\n$1\r\n3\r\n",
19480 second.len()
19481 );
19482 assert_eq!(f.run(&[b"ZMPOP", b"3", b"gone", z2, z1, b"MIN"]), popped);
19483 f.run(&[b"ZADD", z2, b"3", b"b"]);
19484
19485 assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, b"gone"]), ":0\r\n");
19488 assert_eq!(f.run(&[b"EXISTS", z3]), ":0\r\n");
19489 f.run(&[b"SET", z3, b"v"]);
19490 assert_eq!(
19491 f.run(&[b"ZUNION", b"2", z1, z3]),
19492 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19493 );
19494 assert_eq!(f.run(&[b"GET", z3]), "$1\r\nv\r\n", "and left it alone");
19495
19496 assert_eq!(f.run(&[b"ZUNIONSTORE", z2, b"2", z1, z2]), ":3\r\n");
19500 assert_eq!(f.run(&[b"ZSCORE", z2, b"b"]), "$1\r\n5\r\n");
19501 assert_eq!(f.run(&[b"ZCARD", z2]), ":3\r\n");
19502 }
19503
19504 #[test]
19506 fn a_range_store_across_stripes_copies_the_window() {
19507 let mut f = Fixture::striped(8);
19508 let other = apart(&mut f, "src");
19509 let third = apart(&mut f, &other);
19510 let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
19511
19512 f.run(&[b"ZADD", src, b"1", b"a", b"2", b"b", b"3", b"c"]);
19513 assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"0", b"1"]), ":2\r\n");
19514 assert_eq!(
19515 f.run(&[b"ZRANGE", dst, b"0", b"-1", b"WITHSCORES"]),
19516 "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
19517 );
19518 assert_eq!(f.run(&[b"ZCARD", src]), ":3\r\n", "the source kept its own");
19519
19520 assert_eq!(
19523 f.run(&[
19524 b"ZRANGESTORE",
19525 dst,
19526 src,
19527 b"+inf",
19528 b"-inf",
19529 b"BYSCORE",
19530 b"REV",
19531 b"LIMIT",
19532 b"0",
19533 b"2"
19534 ]),
19535 ":2\r\n"
19536 );
19537 assert_eq!(
19538 f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
19539 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
19540 );
19541
19542 assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"5", b"1"]), ":0\r\n");
19545 assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
19546 f.run(&[b"ZRANGESTORE", dst, src, b"0", b"-1"]);
19547 f.run(&[b"SET", plain, b"v"]);
19548 assert_eq!(
19549 f.run(&[b"ZRANGESTORE", dst, plain, b"0", b"-1"]),
19550 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19551 );
19552 assert_eq!(
19553 f.run(&[b"ZCARD", dst]),
19554 ":3\r\n",
19555 "and left the destination"
19556 );
19557 }
19558
19559 #[test]
19566 fn the_list_group_answers_the_same_however_many_stripes_there_are() {
19567 let script: &[&[&[u8]]] = &[
19568 &[b"RPUSH", b"l1", b"a", b"b", b"c"],
19569 &[b"LPUSH", b"l1", b"z"],
19570 &[b"RPUSHX", b"l1", b"d"],
19571 &[b"LPUSHX", b"gone", b"x"],
19572 &[b"RPUSHX", b"gone", b"x"],
19573 &[b"LLEN", b"l1"],
19574 &[b"LLEN", b"gone"],
19575 &[b"LRANGE", b"l1", b"0", b"-1"],
19576 &[b"LRANGE", b"l1", b"1", b"2"],
19577 &[b"LRANGE", b"l1", b"5", b"9"],
19578 &[b"LINDEX", b"l1", b"0"],
19579 &[b"LINDEX", b"l1", b"-1"],
19580 &[b"LINDEX", b"l1", b"99"],
19581 &[b"LSET", b"l1", b"0", b"y"],
19582 &[b"LINSERT", b"l1", b"BEFORE", b"b", b"aa"],
19583 &[b"LINSERT", b"l1", b"AFTER", b"nothere", b"x"],
19584 &[b"LPOS", b"l1", b"b"],
19585 &[b"LPOS", b"l1", b"b", b"COUNT", b"0"],
19586 &[b"LPOS", b"l1", b"nothere"],
19587 &[b"LPOS", b"l1", b"b", b"RANK", b"-1", b"MAXLEN", b"2"],
19588 &[b"LREM", b"l1", b"1", b"aa"],
19589 &[b"LTRIM", b"l1", b"0", b"3"],
19590 &[b"LRANGE", b"l1", b"0", b"-1"],
19591 &[b"LPOP", b"l1"],
19592 &[b"RPOP", b"l1"],
19593 &[b"LPOP", b"l1", b"2"],
19594 &[b"LPOP", b"gone"],
19595 &[b"LPOP", b"gone", b"2"],
19596 &[b"EXISTS", b"l1"],
19597 &[b"RPUSH", b"src", b"a", b"b", b"c", b"d"],
19600 &[b"LMOVE", b"src", b"dst", b"LEFT", b"RIGHT"],
19601 &[b"RPOPLPUSH", b"src", b"dst"],
19602 &[b"LRANGE", b"dst", b"0", b"-1"],
19603 &[b"LMOVE", b"gone", b"dst", b"LEFT", b"RIGHT"],
19604 &[b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT"],
19605 &[
19606 b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK",
19607 ],
19608 &[
19609 b"LMOVEM", b"dst", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"OBO",
19610 ],
19611 &[b"LRANGE", b"dst", b"0", b"-1"],
19612 &[
19613 b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"EXACTLY", b"9", b"BULK",
19614 ],
19615 &[b"LMPOP", b"2", b"gone", b"dst", b"LEFT"],
19616 &[b"LMPOP", b"2", b"gone", b"dst", b"RIGHT", b"COUNT", b"2"],
19617 &[b"LMPOP", b"1", b"gone", b"LEFT"],
19618 &[b"RPUSH", b"q", b"a", b"b", b"c"],
19621 &[b"BLPOP", b"gone", b"q", b"0"],
19622 &[b"BRPOP", b"q", b"0"],
19623 &[b"BLMPOP", b"0", b"2", b"gone", b"q", b"LEFT"],
19624 &[b"RPUSH", b"q", b"x", b"y", b"z"],
19625 &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
19626 &[b"BRPOPLPUSH", b"q", b"dst", b"0"],
19627 &[b"BLMOVEM", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
19628 &[b"BLPOP", b"q", b"0"],
19629 &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
19630 &[b"SET", b"plain", b"v"],
19632 &[b"LPUSH", b"plain", b"a"],
19633 &[b"LLEN", b"plain"],
19634 &[b"LMOVE", b"dst", b"plain", b"LEFT", b"RIGHT"],
19635 &[b"LRANGE", b"dst", b"0", b"-1"],
19636 &[b"LMOVEM", b"dst", b"plain", b"LEFT", b"RIGHT"],
19637 &[b"LSET", b"gone", b"0", b"v"],
19638 &[b"LSET", b"dst", b"99", b"v"],
19639 &[b"LPOP", b"dst", b"-1"],
19640 &[b"LMPOP", b"0", b"dst", b"LEFT"],
19641 &[b"LPOS", b"dst", b"a", b"RANK", b"0"],
19642 ];
19643
19644 let mut one = Fixture::new();
19645 let mut many = Fixture::striped(8);
19646 for parts in script {
19647 let a = one.run(parts);
19648 let b = many.run(parts);
19649 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
19650 }
19651 }
19652
19653 #[test]
19655 fn a_list_move_across_stripes_takes_the_elements_with_it() {
19656 let mut f = Fixture::striped(8);
19657 let other = apart(&mut f, "src");
19658 let third = apart(&mut f, &other);
19659 let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
19660
19661 f.run(&[b"RPUSH", src, b"a", b"b", b"c", b"d"]);
19662 assert_eq!(
19663 f.run(&[b"LMOVE", src, dst, b"LEFT", b"RIGHT"]),
19664 "$1\r\na\r\n"
19665 );
19666 assert_eq!(f.run(&[b"RPOPLPUSH", src, dst]), "$1\r\nd\r\n");
19667 assert_eq!(
19668 f.run(&[b"LRANGE", dst, b"0", b"-1"]),
19669 "*2\r\n$1\r\nd\r\n$1\r\na\r\n",
19670 "one went on each end of the destination"
19671 );
19672 assert_eq!(
19673 f.run(&[b"LRANGE", src, b"0", b"-1"]),
19674 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
19675 );
19676
19677 assert_eq!(
19679 f.run(&[
19680 b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
19681 ]),
19682 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
19683 );
19684 assert_eq!(
19685 f.run(&[b"LRANGE", dst, b"0", b"-1"]),
19686 "*4\r\n$1\r\nd\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
19687 );
19688 assert_eq!(
19689 f.run(&[b"EXISTS", src]),
19690 ":0\r\n",
19691 "and the source is gone with its last element"
19692 );
19693
19694 f.run(&[b"RPUSH", src, b"e", b"f"]);
19697 assert_eq!(
19698 f.run(&[
19699 b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"EXACTLY", b"3", b"BULK"
19700 ]),
19701 "*-1\r\n"
19702 );
19703 assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n", "and took none of them");
19704 assert_eq!(
19705 f.run(&[b"LMOVE", b"gone", dst, b"LEFT", b"RIGHT"]),
19706 "$-1\r\n"
19707 );
19708 assert_eq!(
19709 f.run(&[b"LMOVEM", b"gone", dst, b"LEFT", b"RIGHT"]),
19710 "*-1\r\n"
19711 );
19712
19713 f.run(&[b"SET", plain, b"v"]);
19717 assert_eq!(
19718 f.run(&[b"LMOVE", src, plain, b"LEFT", b"RIGHT"]),
19719 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19720 );
19721 assert_eq!(
19722 f.run(&[b"LLEN", src]),
19723 ":2\r\n",
19724 "and left the source alone"
19725 );
19726 assert_eq!(
19727 f.run(&[b"LMOVEM", src, plain, b"LEFT", b"RIGHT"]),
19728 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19729 );
19730 assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n");
19731 }
19732
19733 #[test]
19739 fn a_parked_client_is_served_from_the_stripe_its_key_is_on() {
19740 let mut f = Fixture::striped(8);
19741 let other = apart(&mut f, "q");
19742 let (q, far) = (b"q".as_slice(), other.as_bytes());
19743
19744 assert_eq!(f.flow(&[b"BLPOP", q, far, b"0"]).0, Flow::Block);
19745 assert_eq!(f.server.parked(), 1);
19746 f.run(&[b"RPUSH", far, b"v"]);
19747 let mut out = Out::new(Proto::Resp2);
19748 assert!(f.server.serve_waiter(7, 0, &mut out));
19749 let want = format!("*2\r\n${}\r\n{other}\r\n$1\r\nv\r\n", other.len());
19750 assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
19751 assert_eq!(
19752 f.run(&[b"EXISTS", far]),
19753 ":0\r\n",
19754 "and it took the element with it"
19755 );
19756
19757 f.server.forget_waiters(7);
19760 assert_eq!(
19761 f.flow(&[b"BLMOVE", q, far, b"LEFT", b"RIGHT", b"0"]).0,
19762 Flow::Block
19763 );
19764 f.run(&[b"RPUSH", q, b"w"]);
19765 let mut out = Out::new(Proto::Resp2);
19766 assert!(f.server.serve_waiter(7, 0, &mut out));
19767 assert_eq!(
19768 core::str::from_utf8(out.as_slice()).expect("ascii"),
19769 "$1\r\nw\r\n"
19770 );
19771 assert_eq!(f.run(&[b"LRANGE", far, b"0", b"-1"]), "*1\r\n$1\r\nw\r\n");
19772 }
19773
19774 #[test]
19780 fn the_stream_group_answers_the_same_however_many_stripes_there_are() {
19781 let script: &[&[&[u8]]] = &[
19782 &[b"XADD", b"s", b"1-1", b"a", b"1"],
19783 &[b"XADD", b"s", b"2-1", b"b", b"2", b"c", b"3"],
19784 &[b"XADD", b"s", b"3-1", b"d", b"4"],
19785 &[b"XADD", b"s", b"1-1", b"e", b"5"],
19786 &[b"XADD", b"nomk", b"NOMKSTREAM", b"1-1", b"a", b"1"],
19787 &[b"XLEN", b"s"],
19788 &[b"XLEN", b"gone"],
19789 &[b"XRANGE", b"s", b"-", b"+"],
19790 &[b"XRANGE", b"s", b"2", b"+", b"COUNT", b"1"],
19791 &[b"XRANGE", b"gone", b"-", b"+", b"COUNT", b"0"],
19792 &[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"],
19793 &[b"XREVRANGE", b"s", b"+", b"-"],
19794 &[b"XREAD", b"COUNT", b"2", b"STREAMS", b"s", b"0"],
19795 &[b"XREAD", b"STREAMS", b"s", b"gone", b"0", b"0"],
19796 &[b"XREAD", b"STREAMS", b"s", b"$"],
19797 &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
19799 &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
19800 &[b"XGROUP", b"CREATE", b"gone", b"g", b"0"],
19801 &[b"XGROUP", b"CREATE", b"made", b"g", b"$", b"MKSTREAM"],
19802 &[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"idle"],
19803 &[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"],
19804 &[
19805 b"XREADGROUP",
19806 b"GROUP",
19807 b"g",
19808 b"c1",
19809 b"COUNT",
19810 b"1",
19811 b"STREAMS",
19812 b"s",
19813 b"0",
19814 ],
19815 &[
19816 b"XREADGROUP",
19817 b"GROUP",
19818 b"nope",
19819 b"c1",
19820 b"STREAMS",
19821 b"s",
19822 b">",
19823 ],
19824 &[b"XPENDING", b"s", b"g"],
19825 &[b"XPENDING", b"s", b"g", b"-", b"+", b"10"],
19826 &[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"],
19827 &[b"XPENDING", b"s", b"nope"],
19828 &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1"],
19829 &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1", b"JUSTID"],
19830 &[b"XAUTOCLAIM", b"s", b"g", b"c3", b"0", b"0"],
19831 &[b"XACK", b"s", b"g", b"1-1"],
19832 &[b"XACK", b"s", b"g", b"1-1"],
19833 &[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"],
19834 &[b"XPENDING", b"s", b"g"],
19835 &[b"XINFO", b"STREAM", b"s"],
19836 &[b"XINFO", b"GROUPS", b"s"],
19837 &[b"XINFO", b"CONSUMERS", b"s", b"g"],
19838 &[b"XINFO", b"STREAM", b"gone"],
19839 &[b"XDEL", b"s", b"3-1"],
19841 &[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"],
19842 &[b"XACKDEL", b"s", b"g", b"KEEPREF", b"IDS", b"1", b"1-1"],
19843 &[b"XADD", b"s", b"9-1", b"z", b"9"],
19844 &[b"XTRIM", b"s", b"MAXLEN", b"1"],
19845 &[b"XTRIM", b"s", b"MINID", b"9"],
19846 &[b"XSETID", b"s", b"99-1"],
19847 &[b"XSETID", b"s", b"1-1"],
19848 &[b"XLEN", b"s"],
19849 &[b"XGROUP", b"SETID", b"s", b"g", b"0"],
19850 &[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c1"],
19851 &[b"XGROUP", b"DESTROY", b"s", b"g"],
19852 &[b"XGROUP", b"DESTROY", b"s", b"g"],
19853 &[b"SET", b"plain", b"v"],
19855 &[b"XADD", b"plain", b"1-1", b"a", b"1"],
19856 &[b"XLEN", b"plain"],
19857 &[b"XREAD", b"STREAMS", b"plain", b"0"],
19858 &[b"XRANGE", b"s", b"bogus", b"+"],
19859 &[b"XADD", b"s", b"1-1", b"a"],
19860 &[b"XREAD", b"STREAMS", b"s", b"gone", b"0"],
19861 &[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"],
19862 ];
19863
19864 let mut one = Fixture::new();
19865 let mut many = Fixture::striped(8);
19866 for parts in script {
19867 let a = one.run(parts);
19868 let b = many.run(parts);
19869 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
19870 }
19871 }
19872
19873 #[test]
19879 fn a_stream_read_across_stripes_reads_every_key() {
19880 let mut f = Fixture::striped(8);
19881 let other = apart(&mut f, "s1");
19882 let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
19883
19884 f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
19885 f.run(&[b"XADD", s2, b"2-1", b"b", b"2"]);
19886 let got = f.run(&[b"XREAD", b"STREAMS", s1, s2, b"0", b"0"]);
19887 assert!(got.starts_with("*2\r\n"), "both streams answered: {got}");
19888 assert!(got.contains("1-1"), "the first one is in there: {got}");
19889 assert!(got.contains("2-1"), "and so is the second: {got}");
19890
19891 f.run(&[b"XGROUP", b"CREATE", s1, b"g", b"0"]);
19894 let got = f.run(&[
19895 b"XREADGROUP",
19896 b"GROUP",
19897 b"g",
19898 b"c",
19899 b"STREAMS",
19900 s1,
19901 s2,
19902 b">",
19903 b">",
19904 ]);
19905 assert!(got.starts_with("-NOGROUP"), "{got}");
19906 assert_eq!(
19907 f.run(&[b"XPENDING", s1, b"g"]),
19908 "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n",
19909 "and read nothing from the key that did have the group"
19910 );
19911
19912 f.run(&[b"XGROUP", b"CREATE", s2, b"g", b"0"]);
19913 let got = f.run(&[
19914 b"XREADGROUP",
19915 b"GROUP",
19916 b"g",
19917 b"c",
19918 b"STREAMS",
19919 s1,
19920 s2,
19921 b">",
19922 b">",
19923 ]);
19924 assert!(got.starts_with("*2\r\n"), "now both are read: {got}");
19925 }
19926
19927 #[test]
19929 fn a_parked_stream_reader_is_served_from_the_stripe_its_key_is_on() {
19930 let mut f = Fixture::striped(8);
19931 let other = apart(&mut f, "s1");
19932 let (s1, far) = (b"s1".as_slice(), other.as_bytes());
19933 f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
19934 f.run(&[b"XADD", far, b"1-1", b"a", b"1"]);
19935
19936 assert_eq!(
19937 f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", s1, far, b"$", b"$"])
19938 .0,
19939 Flow::Block
19940 );
19941 f.run(&[b"XADD", far, b"2-1", b"b", b"2"]);
19942 let mut out = Out::new(Proto::Resp2);
19943 assert!(f.server.serve_waiter(7, 0, &mut out));
19944 let want = format!(
19945 "*1\r\n*2\r\n${}\r\n{other}\r\n*1\r\n*2\r\n$3\r\n2-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n",
19946 other.len()
19947 );
19948 assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
19949 }
19950
19951 #[test]
19953 fn the_json_group_answers_the_same_however_many_stripes_there_are() {
19954 let script: &[&[&[u8]]] = &[
19955 &[
19956 b"JSON.SET",
19957 b"d",
19958 b"$",
19959 br#"{"a":1,"b":[1,2,3],"s":"hi","t":true}"#,
19960 ],
19961 &[b"JSON.SET", b"d", b"$.a", b"2"],
19962 &[b"JSON.SET", b"d", b"$.new", b"9", b"NX"],
19963 &[b"JSON.SET", b"d", b"$.new", b"8", b"NX"],
19964 &[b"JSON.SET", b"d", b"$.nope", b"7", b"XX"],
19965 &[b"JSON.GET", b"d"],
19966 &[b"JSON.GET", b"d", b"$.b"],
19967 &[b"JSON.GET", b"gone", b"$"],
19968 &[b"JSON.TYPE", b"d", b"$.b"],
19969 &[b"JSON.TYPE", b"d", b"$.s"],
19970 &[b"JSON.TOGGLE", b"d", b"$.t"],
19971 &[b"JSON.ARRLEN", b"d", b"$.b"],
19972 &[b"JSON.OBJLEN", b"d", b"$"],
19973 &[b"JSON.OBJKEYS", b"d", b"$"],
19974 &[b"JSON.STRLEN", b"d", b"$.s"],
19975 &[b"JSON.STRAPPEND", b"d", b"$.s", br#""there""#],
19976 &[b"JSON.ARRAPPEND", b"d", b"$.b", b"4"],
19977 &[b"JSON.ARRINSERT", b"d", b"$.b", b"0", b"0"],
19978 &[b"JSON.ARRINDEX", b"d", b"$.b", b"3"],
19979 &[b"JSON.ARRTRIM", b"d", b"$.b", b"1", b"3"],
19980 &[b"JSON.ARRPOP", b"d", b"$.b"],
19981 &[b"JSON.NUMINCRBY", b"d", b"$.a", b"5"],
19982 &[b"JSON.NUMMULTBY", b"d", b"$.a", b"2"],
19983 &[b"JSON.NUMPOWBY", b"d", b"$.a", b"2"],
19984 &[b"JSON.MERGE", b"d", b"$", br#"{"a":null,"m":1}"#],
19985 &[b"JSON.RESP", b"d", b"$.b"],
19986 &[b"JSON.DEBUG", b"MEMORY", b"d"],
19987 &[b"JSON.CLEAR", b"d", b"$.b"],
19988 &[b"JSON.DEL", b"d", b"$.m"],
19989 &[b"JSON.FORGET", b"d", b"$.nothere"],
19990 &[
19992 b"JSON.MSET",
19993 b"m1",
19994 b"$",
19995 b"1",
19996 b"m2",
19997 b"$",
19998 b"2",
19999 b"m3",
20000 b"$",
20001 b"3",
20002 ],
20003 &[b"JSON.MGET", b"m1", b"m2", b"m3", b"gone", b"$"],
20004 &[b"JSON.MSET", b"m1", b"$", b"9", b"m2", b"$.deep", b"9"],
20005 &[b"JSON.GET", b"m1", b"$"],
20006 &[b"JSON.MSET", b"m1", b"$", b"nonsense", b"m2", b"$", b"5"],
20007 &[b"JSON.GET", b"m2", b"$"],
20008 &[b"SET", b"plain", b"v"],
20010 &[b"JSON.GET", b"plain", b"$"],
20011 &[b"JSON.SET", b"plain", b"$", b"1"],
20012 &[b"JSON.MGET", b"m1", b"plain", b"$"],
20013 &[b"JSON.SET", b"d", b"$.b", b"["],
20014 &[b"JSON.DEL", b"plain"],
20015 ];
20016
20017 let mut one = Fixture::new();
20018 let mut many = Fixture::striped(8);
20019 for parts in script {
20020 let a = one.run(parts);
20021 let b = many.run(parts);
20022 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20023 }
20024 }
20025
20026 #[test]
20033 fn a_json_multi_write_across_stripes_reaches_every_key() {
20034 let mut f = Fixture::striped(8);
20035 let second = apart(&mut f, "m1");
20036 let third = apart(&mut f, &second);
20037 let (m1, m2, m3) = (b"m1".as_slice(), second.as_bytes(), third.as_bytes());
20038
20039 assert_eq!(
20040 f.run(&[b"JSON.MSET", m1, b"$", b"1", m2, b"$", b"2", m3, b"$", b"3"]),
20041 "+OK\r\n"
20042 );
20043 assert_eq!(
20044 f.run(&[b"JSON.MGET", m1, m2, m3, b"gone", b"$"]),
20045 "*4\r\n$3\r\n[1]\r\n$3\r\n[2]\r\n$3\r\n[3]\r\n$-1\r\n"
20046 );
20047
20048 assert_eq!(
20051 f.run(&[b"JSON.MSET", m1, b"$", b"9", m2, b"$", b"nonsense"]),
20052 "-this is not the start of a value, at byte 0 of the JSON text\r\n"
20053 );
20054 assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[1]\r\n");
20055
20056 assert_eq!(
20060 f.run(&[
20061 b"JSON.MSET",
20062 m1,
20063 b"$",
20064 b"9",
20065 m2,
20066 b"$.deep",
20067 b"9",
20068 m3,
20069 b"$",
20070 b"7"
20071 ]),
20072 "$-1\r\n"
20073 );
20074 assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[9]\r\n");
20075 assert_eq!(f.run(&[b"JSON.GET", m2, b"$"]), "$3\r\n[2]\r\n");
20076 assert_eq!(f.run(&[b"JSON.GET", m3, b"$"]), "$3\r\n[7]\r\n");
20077 }
20078
20079 #[test]
20081 fn the_geo_group_answers_the_same_however_many_stripes_there_are() {
20082 let script: &[&[&[u8]]] = &[
20083 &[
20084 b"GEOADD",
20085 b"g",
20086 b"13.361389",
20087 b"38.115556",
20088 b"palermo",
20089 b"15.087269",
20090 b"37.502669",
20091 b"catania",
20092 ],
20093 &[
20094 b"GEOADD",
20095 b"g",
20096 b"NX",
20097 b"13.361389",
20098 b"38.115556",
20099 b"palermo",
20100 ],
20101 &[b"GEOADD", b"g", b"XX", b"CH", b"13.4", b"38.1", b"palermo"],
20102 &[b"GEOPOS", b"g", b"palermo", b"nothere"],
20103 &[b"GEOHASH", b"g", b"palermo", b"catania"],
20104 &[b"GEODIST", b"g", b"palermo", b"catania"],
20105 &[b"GEODIST", b"g", b"palermo", b"catania", b"KM"],
20106 &[b"GEODIST", b"g", b"palermo", b"nothere"],
20107 &[
20108 b"GEOSEARCH",
20109 b"g",
20110 b"FROMLONLAT",
20111 b"15",
20112 b"37",
20113 b"BYRADIUS",
20114 b"200",
20115 b"KM",
20116 b"ASC",
20117 b"WITHCOORD",
20118 b"WITHDIST",
20119 b"WITHHASH",
20120 ],
20121 &[
20122 b"GEOSEARCH",
20123 b"g",
20124 b"FROMMEMBER",
20125 b"palermo",
20126 b"BYBOX",
20127 b"400",
20128 b"400",
20129 b"KM",
20130 b"DESC",
20131 ],
20132 &[
20133 b"GEORADIUS",
20134 b"g",
20135 b"15",
20136 b"37",
20137 b"200",
20138 b"KM",
20139 b"COUNT",
20140 b"1",
20141 ],
20142 &[b"GEORADIUSBYMEMBER", b"g", b"palermo", b"200", b"KM"],
20143 &[b"GEORADIUSBYMEMBER_RO", b"g", b"nothere", b"200", b"KM"],
20144 &[
20145 b"GEOSEARCHSTORE",
20146 b"dst",
20147 b"g",
20148 b"FROMLONLAT",
20149 b"15",
20150 b"37",
20151 b"BYRADIUS",
20152 b"200",
20153 b"KM",
20154 ],
20155 &[b"ZRANGE", b"dst", b"0", b"-1"],
20156 &[
20157 b"GEOSEARCHSTORE",
20158 b"dst",
20159 b"g",
20160 b"FROMLONLAT",
20161 b"15",
20162 b"37",
20163 b"BYRADIUS",
20164 b"1",
20165 b"M",
20166 b"STOREDIST",
20167 ],
20168 &[b"EXISTS", b"dst"],
20169 &[
20170 b"GEORADIUS",
20171 b"g",
20172 b"15",
20173 b"37",
20174 b"200",
20175 b"KM",
20176 b"STORE",
20177 b"dst",
20178 ],
20179 &[b"ZCARD", b"dst"],
20180 &[b"GEOADD", b"g", b"181", b"38", b"nowhere"],
20182 &[b"SET", b"plain", b"v"],
20183 &[b"GEOPOS", b"plain", b"a"],
20184 &[b"GEOSEARCH", b"g", b"FROMLONLAT", b"15", b"37"],
20185 &[
20186 b"GEOSEARCHSTORE",
20187 b"dst",
20188 b"g",
20189 b"FROMLONLAT",
20190 b"15",
20191 b"37",
20192 b"BYRADIUS",
20193 b"200",
20194 b"KM",
20195 b"WITHCOORD",
20196 ],
20197 ];
20198
20199 let mut one = Fixture::new();
20200 let mut many = Fixture::striped(8);
20201 for parts in script {
20202 let a = one.run(parts);
20203 let b = many.run(parts);
20204 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20205 }
20206 }
20207
20208 #[test]
20210 fn a_geo_search_store_across_stripes_writes_what_it_found() {
20211 let mut f = Fixture::striped(8);
20212 let other = apart(&mut f, "g");
20213 let third = apart(&mut f, &other);
20214 let (g, dst, plain) = (b"g".as_slice(), other.as_bytes(), third.as_bytes());
20215
20216 f.run(&[
20217 b"GEOADD",
20218 g,
20219 b"13.361389",
20220 b"38.115556",
20221 b"palermo",
20222 b"15.087269",
20223 b"37.502669",
20224 b"catania",
20225 ]);
20226 assert_eq!(
20227 f.run(&[
20228 b"GEOSEARCHSTORE",
20229 dst,
20230 g,
20231 b"FROMLONLAT",
20232 b"15",
20233 b"37",
20234 b"BYRADIUS",
20235 b"200",
20236 b"KM",
20237 b"ASC",
20238 ]),
20239 ":2\r\n"
20240 );
20241 assert_eq!(
20242 f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
20243 "*2\r\n$7\r\npalermo\r\n$7\r\ncatania\r\n",
20244 "the geohash is the score, so the order is not the search order"
20245 );
20246 assert_eq!(f.run(&[b"ZCARD", g]), ":2\r\n", "the source is untouched");
20247
20248 assert_eq!(
20251 f.run(&[
20252 b"GEOSEARCHSTORE",
20253 dst,
20254 g,
20255 b"FROMMEMBER",
20256 b"palermo",
20257 b"BYRADIUS",
20258 b"200",
20259 b"KM",
20260 b"STOREDIST",
20261 ]),
20262 ":2\r\n"
20263 );
20264 assert_eq!(
20265 f.run(&[b"ZSCORE", dst, b"palermo"]),
20266 "$1\r\n0\r\n",
20267 "the centre is nought away from itself"
20268 );
20269
20270 assert_eq!(
20274 f.run(&[
20275 b"GEOSEARCHSTORE",
20276 dst,
20277 g,
20278 b"FROMLONLAT",
20279 b"0",
20280 b"0",
20281 b"BYRADIUS",
20282 b"1",
20283 b"M",
20284 ]),
20285 ":0\r\n"
20286 );
20287 assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
20288 f.run(&[
20289 b"GEOSEARCHSTORE",
20290 dst,
20291 g,
20292 b"FROMLONLAT",
20293 b"15",
20294 b"37",
20295 b"BYRADIUS",
20296 b"200",
20297 b"KM",
20298 ]);
20299 f.run(&[b"SET", plain, b"v"]);
20300 assert_eq!(
20301 f.run(&[
20302 b"GEOSEARCHSTORE",
20303 dst,
20304 plain,
20305 b"FROMLONLAT",
20306 b"15",
20307 b"37",
20308 b"BYRADIUS",
20309 b"200",
20310 b"KM",
20311 ]),
20312 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
20313 );
20314 assert_eq!(
20315 f.run(&[b"ZCARD", dst]),
20316 ":2\r\n",
20317 "and left the destination"
20318 );
20319 }
20320
20321 #[test]
20327 fn the_time_series_group_answers_the_same_however_many_stripes_there_are() {
20328 let script: &[&[&[u8]]] = &[
20329 &[
20330 b"TS.CREATE",
20331 b"ts:a",
20332 b"LABELS",
20333 b"sensor",
20334 b"a",
20335 b"room",
20336 b"1",
20337 ],
20338 &[b"TS.CREATE", b"ts:a"],
20339 &[b"TS.ALTER", b"ts:a", b"RETENTION", b"0"],
20340 &[b"TS.ADD", b"ts:a", b"1000", b"1.5"],
20341 &[
20342 b"TS.ADD", b"ts:b", b"1000", b"2", b"LABELS", b"sensor", b"b", b"room", b"1",
20343 ],
20344 &[
20345 b"TS.MADD", b"ts:a", b"2000", b"2.5", b"ts:b", b"2000", b"3", b"gone", b"1", b"1",
20346 ],
20347 &[b"TS.INCRBY", b"ts:a", b"1", b"TIMESTAMP", b"3000"],
20348 &[b"TS.DECRBY", b"ts:a", b"0.5", b"TIMESTAMP", b"4000"],
20349 &[b"TS.GET", b"ts:a"],
20350 &[b"TS.GET", b"gone"],
20351 &[b"TS.RANGE", b"ts:a", b"-", b"+"],
20352 &[b"TS.RANGE", b"ts:a", b"1000", b"3000", b"COUNT", b"2"],
20353 &[
20354 b"TS.RANGE",
20355 b"ts:a",
20356 b"-",
20357 b"+",
20358 b"AGGREGATION",
20359 b"avg",
20360 b"2000",
20361 ],
20362 &[b"TS.REVRANGE", b"ts:a", b"-", b"+"],
20363 &[b"TS.NRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
20364 &[b"TS.NREVRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
20365 &[b"TS.NRANGE", b"2", b"ts:a", b"gone", b"-", b"+"],
20366 &[b"TS.READ", b"ts:a", b"0"],
20367 &[b"TS.READ", b"ts:a", b"+"],
20368 &[b"TS.QUERYINDEX", b"sensor=a"],
20370 &[b"TS.QUERYINDEX", b"room=1"],
20371 &[b"TS.QUERYINDEX", b"room=9"],
20372 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"],
20373 &[
20374 b"TS.QUERYLABELS",
20375 b"VALUES",
20376 b"sensor",
20377 b"FILTER",
20378 b"room=1",
20379 ],
20380 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=1"],
20381 &[
20382 b"TS.MGET",
20383 b"SELECTED_LABELS",
20384 b"sensor",
20385 b"FILTER",
20386 b"sensor=a",
20387 ],
20388 &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"],
20389 &[
20390 b"TS.MREVRANGE",
20391 b"-",
20392 b"+",
20393 b"WITHLABELS",
20394 b"FILTER",
20395 b"sensor=a",
20396 ],
20397 &[
20398 b"TS.MRANGE",
20399 b"-",
20400 b"+",
20401 b"FILTER",
20402 b"room=1",
20403 b"GROUPBY",
20404 b"room",
20405 b"REDUCE",
20406 b"max",
20407 ],
20408 &[b"TS.INFO", b"ts:a"],
20409 &[
20411 b"TS.CREATERULE",
20412 b"ts:a",
20413 b"ts:down",
20414 b"AGGREGATION",
20415 b"avg",
20416 b"1000",
20417 ],
20418 &[b"TS.CREATE", b"ts:down"],
20419 &[
20420 b"TS.CREATERULE",
20421 b"ts:a",
20422 b"ts:down",
20423 b"AGGREGATION",
20424 b"avg",
20425 b"1000",
20426 ],
20427 &[b"TS.ADD", b"ts:a", b"5000", b"4"],
20428 &[b"TS.ADD", b"ts:a", b"6000", b"5"],
20429 &[b"TS.RANGE", b"ts:down", b"-", b"+"],
20430 &[b"TS.GET", b"ts:down", b"LATEST"],
20431 &[b"TS.INFO", b"ts:down"],
20432 &[b"TS.DEL", b"ts:a", b"5000", b"6000"],
20433 &[b"TS.RANGE", b"ts:down", b"-", b"+"],
20434 &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
20435 &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
20436 &[b"TS.DEL", b"ts:a", b"0", b"1000"],
20437 &[b"SET", b"plain", b"v"],
20439 &[b"TS.ADD", b"plain", b"1", b"1"],
20440 &[b"TS.GET", b"plain"],
20441 &[b"TS.READ", b"plain", b"0"],
20442 &[b"TS.ALTER", b"gone", b"RETENTION", b"0"],
20443 &[b"TS.RANGE", b"gone", b"-", b"+"],
20444 &[b"TS.INFO", b"gone"],
20445 ];
20446
20447 let mut one = Fixture::new();
20448 let mut many = Fixture::striped(8);
20449 for parts in script {
20450 let a = one.run(parts);
20451 let b = many.run(parts);
20452 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20453 }
20454 }
20455
20456 #[test]
20466 fn a_compaction_rule_across_stripes_reaches_both_ends() {
20467 let mut many = Fixture::striped(8);
20468 let other = apart(&mut many, "src");
20469 let (src, dst) = (b"src".as_slice(), other.as_bytes());
20470 let mut one = Fixture::new();
20471 let mut both = |parts: &[&[u8]]| {
20472 let a = one.run(parts);
20473 let b = many.run(parts);
20474 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20475 a
20476 };
20477
20478 both(&[b"TS.CREATE", src]);
20479 both(&[b"TS.CREATE", dst]);
20480 assert_eq!(
20481 both(&[b"TS.CREATERULE", src, dst, b"AGGREGATION", b"avg", b"1000"]),
20482 "+OK\r\n"
20483 );
20484 both(&[b"TS.ADD", src, b"1000", b"1"]);
20485 both(&[b"TS.ADD", src, b"1500", b"3"]);
20486 assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
20489 let open = both(&[b"TS.GET", dst, b"LATEST"]);
20490 assert!(open.contains(":1000"), "the open bucket is folded: {open}");
20491
20492 both(&[b"TS.ADD", src, b"2000", b"5"]);
20495 let got = both(&[b"TS.RANGE", dst, b"-", b"+"]);
20496 assert!(got.starts_with("*1\r\n"), "the bucket was written: {got}");
20497 assert!(got.contains(":1000"), "{got}");
20498
20499 both(&[b"TS.DEL", src, b"1000", b"1999"]);
20501 assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
20502
20503 assert!(
20506 both(&[b"TS.INFO", dst]).contains("src"),
20507 "the source is named"
20508 );
20509 assert_eq!(both(&[b"TS.DELETERULE", src, dst]), "+OK\r\n");
20510 assert_eq!(
20511 both(&[b"TS.DELETERULE", src, dst]),
20512 "-ERR TSDB: compaction rule does not exist\r\n"
20513 );
20514 }
20515
20516 #[test]
20518 fn a_label_query_across_stripes_finds_every_series() {
20519 let names: [&[u8]; 6] = [b"q:1", b"q:2", b"q:3", b"q:4", b"q:5", b"q:6"];
20520 let mut many = Fixture::striped(8);
20521 let mut homes: Vec<usize> = names
20522 .iter()
20523 .map(|name| many.server.striped(0).stripe_of(name))
20524 .collect();
20525 homes.sort_unstable();
20526 homes.dedup();
20527 assert!(homes.len() > 1, "the six keys are not all on one stripe");
20528
20529 let mut one = Fixture::new();
20530 let mut both = |parts: &[&[u8]]| {
20531 let a = one.run(parts);
20532 let b = many.run(parts);
20533 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20534 a
20535 };
20536 for name in &names {
20537 both(&[b"TS.CREATE", name, b"LABELS", b"room", b"1"]);
20538 both(&[b"TS.ADD", name, b"1000", b"1"]);
20539 }
20540
20541 let got = both(&[b"TS.QUERYINDEX", b"room=1"]);
20542 assert!(got.starts_with("*6\r\n"), "every series answered: {got}");
20543 assert!(both(&[b"TS.MGET", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
20544 assert!(both(&[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
20545 assert_eq!(
20546 both(&[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"]),
20547 "*1\r\n$4\r\nroom\r\n"
20548 );
20549 }
20550
20551 #[test]
20558 fn the_hash_group_answers_the_same_however_many_stripes_there_are() {
20559 let script: &[&[&[u8]]] = &[
20560 &[b"HSET", b"h", b"a", b"1", b"b", b"2"],
20561 &[b"HMSET", b"h", b"c", b"3"],
20562 &[b"HSETNX", b"h", b"a", b"9"],
20563 &[b"HSETNX", b"h", b"d", b"4"],
20564 &[b"HGET", b"h", b"a"],
20565 &[b"HGET", b"h", b"nope"],
20566 &[b"HMGET", b"h", b"a", b"nope"],
20567 &[b"HLEN", b"h"],
20568 &[b"HEXISTS", b"h", b"a"],
20569 &[b"HSTRLEN", b"h", b"a"],
20570 &[b"HGETALL", b"h"],
20571 &[b"HKEYS", b"h"],
20572 &[b"HVALS", b"h"],
20573 &[b"HINCRBY", b"h", b"a", b"5"],
20574 &[b"HINCRBYFLOAT", b"h", b"a", b"1.5"],
20575 &[b"HSCAN", b"h", b"0"],
20576 &[b"HSCAN", b"h", b"0", b"MATCH", b"a", b"COUNT", b"10"],
20577 &[b"HSCAN", b"h", b"0", b"NOVALUES"],
20578 &[b"HDEL", b"h", b"d"],
20579 &[b"HSET", b"one", b"f", b"v"],
20580 &[b"HRANDFIELD", b"one"],
20581 &[b"HRANDFIELD", b"one", b"1", b"WITHVALUES"],
20582 &[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"],
20584 &[b"HTTL", b"h", b"FIELDS", b"1", b"a"],
20585 &[b"HPTTL", b"h", b"FIELDS", b"1", b"a"],
20586 &[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
20587 &[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
20588 &[b"HPERSIST", b"h", b"FIELDS", b"1", b"a"],
20589 &[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"],
20590 &[b"HGET", b"h", b"b"],
20591 &[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"e", b"5"],
20593 &[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"e"],
20594 &[b"HGETDEL", b"h", b"FIELDS", b"1", b"e"],
20595 &[b"HGET", b"h", b"e"],
20596 &[b"HIMPORT", b"PREPARE", b"fs", b"x", b"y"],
20598 &[b"HIMPORT", b"SET", b"imp", b"fs", b"1", b"2"],
20599 &[b"HGETALL", b"imp"],
20600 &[b"HIMPORT", b"SET", b"imp", b"nofs", b"1", b"2"],
20601 &[b"HIMPORT", b"DISCARD", b"fs"],
20602 &[b"SET", b"plain", b"v"],
20604 &[b"HSET", b"plain", b"a", b"1"],
20605 &[b"HGETALL", b"plain"],
20606 &[b"HGET", b"gone", b"a"],
20607 &[b"HINCRBY", b"h", b"a", b"nan"],
20608 ];
20609
20610 let mut one = Fixture::new();
20611 let mut many = Fixture::striped(8);
20612 one.server.set_clock_ms(1_700_000_000_000);
20616 many.server.set_clock_ms(1_700_000_000_000);
20617 for parts in script {
20618 let a = one.run(parts);
20619 let b = many.run(parts);
20620 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20621 }
20622 }
20623
20624 #[test]
20626 fn the_array_group_answers_the_same_however_many_stripes_there_are() {
20627 let script: &[&[&[u8]]] = &[
20628 &[b"ARSET", b"a", b"0", b"x", b"y", b"z"],
20629 &[b"ARMSET", b"a", b"5", b"p", b"7", b"q"],
20630 &[b"ARGET", b"a", b"1"],
20631 &[b"ARGET", b"a", b"99"],
20632 &[b"ARMGET", b"a", b"0", b"5", b"99"],
20633 &[b"ARGETRANGE", b"a", b"0", b"7"],
20634 &[b"ARLEN", b"a"],
20635 &[b"ARCOUNT", b"a"],
20636 &[b"ARINSERT", b"a", b"m", b"n"],
20637 &[b"ARSCAN", b"a", b"0", b"20"],
20638 &[b"ARSCAN", b"a", b"0", b"20", b"LIMIT", b"2"],
20639 &[b"ARGREP", b"a", b"0", b"20", b"EXACT", b"x"],
20640 &[b"ARGREP", b"a", b"0", b"20", b"GLOB", b"*", b"WITHVALUES"],
20641 &[b"ARLASTITEMS", b"a", b"2"],
20642 &[b"ARLASTITEMS", b"a", b"2", b"REV"],
20643 &[b"ARNEXT", b"a"],
20644 &[b"ARSEEK", b"a", b"3"],
20645 &[b"AROP", b"a", b"0", b"20", b"USED"],
20646 &[b"AROP", b"a", b"0", b"20", b"MATCH", b"x"],
20647 &[b"ARINFO", b"a"],
20648 &[b"ARINFO", b"a", b"FULL"],
20649 &[b"ARDEL", b"a", b"0"],
20650 &[b"ARDELRANGE", b"a", b"1", b"2"],
20651 &[b"ARCOUNT", b"a"],
20652 &[b"ARRING", b"r", b"3", b"1", b"2", b"3", b"4"],
20653 &[b"ARGETRANGE", b"r", b"0", b"9"],
20654 &[b"SET", b"plain", b"v"],
20656 &[b"ARGET", b"plain", b"0"],
20657 &[b"ARSET", b"plain", b"0", b"v"],
20658 &[b"ARGET", b"gone", b"0"],
20659 &[b"ARSET", b"a", b"bad", b"v"],
20660 ];
20661
20662 let mut one = Fixture::new();
20663 let mut many = Fixture::striped(8);
20664 for parts in script {
20665 let a = one.run(parts);
20666 let b = many.run(parts);
20667 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20668 }
20669 }
20670
20671 #[test]
20677 fn the_graph_and_vector_groups_answer_the_same_however_many_stripes_there_are() {
20678 let script: &[&[&[u8]]] = &[
20679 &[b"G.NADD", b"g", b"n1", b"name", b"one"],
20680 &[b"G.NADD", b"g", b"n2", b"name", b"two"],
20681 &[b"G.NADD", b"g", b"n3"],
20682 &[b"G.NGET", b"g", b"n1"],
20683 &[b"G.NGET", b"g", b"gone"],
20684 &[b"G.EADD", b"g", b"n1", b"n2", b"knows"],
20685 &[b"G.EADD", b"g", b"n2", b"n3", b"knows"],
20686 &[b"G.OUT", b"g", b"n1", b"knows"],
20687 &[b"G.IN", b"g", b"n2", b"knows"],
20688 &[b"G.DEG", b"g", b"n1", b"knows"],
20689 &[b"G.DEG", b"g", b"n2", b"knows", b"BOTH"],
20690 &[b"G.NEIGH", b"g", b"n1", b"knows", b"DEPTH", b"2"],
20691 &[b"G.PATH", b"g", b"n1", b"n3"],
20692 &[b"G.EDEL", b"g", b"n1", b"n2", b"knows"],
20693 &[b"G.NDEL", b"g", b"n3"],
20694 &[b"G.NGET", b"g", b"n3"],
20695 &[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"e1"],
20697 &[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"e2"],
20698 &[b"VCARD", b"v"],
20699 &[b"VDIM", b"v"],
20700 &[b"VEMB", b"v", b"e1"],
20701 &[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0"],
20702 &[b"VSIM", b"v", b"ELE", b"e1"],
20703 &[b"VISMEMBER", b"v", b"e1"],
20704 &[b"VISMEMBER", b"v", b"gone"],
20705 &[b"VSETATTR", b"v", b"e1", b"{\"k\":1}"],
20706 &[b"VGETATTR", b"v", b"e1"],
20707 &[b"VRANGE", b"v", b"-", b"+"],
20708 &[b"VLINKS", b"v", b"e1"],
20709 &[b"VINFO", b"v"],
20710 &[b"VREM", b"v", b"e2"],
20711 &[b"VCARD", b"v"],
20712 &[b"SET", b"plain", b"v"],
20714 &[b"G.NGET", b"plain", b"n1"],
20715 &[b"VCARD", b"plain"],
20716 &[b"G.NADD", b"gone2", b"n"],
20717 &[b"VEMB", b"gone3", b"e"],
20718 ];
20719
20720 let mut one = Fixture::new();
20721 let mut many = Fixture::striped(8);
20722 for parts in script {
20723 let a = one.run(parts);
20724 let b = many.run(parts);
20725 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20726 }
20727 }
20728
20729 #[test]
20732 fn the_probabilistic_groups_answer_the_same_however_many_stripes_there_are() {
20733 let script: &[&[&[u8]]] = &[
20734 &[b"BF.RESERVE", b"bf", b"0.01", b"100"],
20736 &[b"BF.ADD", b"bf", b"a"],
20737 &[b"BF.ADD", b"bf", b"a"],
20738 &[b"BF.MADD", b"bf", b"b", b"c"],
20739 &[b"BF.EXISTS", b"bf", b"a"],
20740 &[b"BF.MEXISTS", b"bf", b"a", b"zz"],
20741 &[b"BF.CARD", b"bf"],
20742 &[b"BF.INFO", b"bf"],
20743 &[b"BF.INFO", b"bf", b"CAPACITY"],
20744 &[b"BF.DEBUG", b"bf"],
20745 &[b"BF.INSERT", b"made", b"CAPACITY", b"50", b"ITEMS", b"x"],
20746 &[b"BF.EXISTS", b"made", b"x"],
20747 &[b"BF.SCANDUMP", b"bf", b"0"],
20748 &[b"CF.RESERVE", b"cf", b"100"],
20750 &[b"CF.ADD", b"cf", b"a"],
20751 &[b"CF.ADDNX", b"cf", b"a"],
20752 &[b"CF.COUNT", b"cf", b"a"],
20753 &[b"CF.EXISTS", b"cf", b"a"],
20754 &[b"CF.MEXISTS", b"cf", b"a", b"zz"],
20755 &[b"CF.INSERT", b"cf", b"ITEMS", b"b", b"c"],
20756 &[b"CF.DEL", b"cf", b"a"],
20757 &[b"CF.COMPACT", b"cf"],
20758 &[b"CF.INFO", b"cf"],
20759 &[b"CF.DEBUG", b"cf"],
20760 &[b"CF.SCANDUMP", b"cf", b"0"],
20761 &[b"CMS.INITBYDIM", b"cms", b"100", b"5"],
20763 &[b"CMS.INITBYPROB", b"cms2", b"0.01", b"0.01"],
20764 &[b"CMS.INCRBY", b"cms", b"a", b"5", b"b", b"3"],
20765 &[b"CMS.QUERY", b"cms", b"a", b"b", b"gone"],
20766 &[b"CMS.INFO", b"cms"],
20767 &[b"TOPK.RESERVE", b"tk", b"3"],
20769 &[b"TOPK.ADD", b"tk", b"a", b"b", b"a"],
20770 &[b"TOPK.INCRBY", b"tk", b"c", b"4"],
20771 &[b"TOPK.QUERY", b"tk", b"a", b"zz"],
20772 &[b"TOPK.COUNT", b"tk", b"a", b"c"],
20773 &[b"TOPK.LIST", b"tk"],
20774 &[b"TOPK.LIST", b"tk", b"WITHCOUNT"],
20775 &[b"TOPK.INFO", b"tk"],
20776 &[b"TDIGEST.CREATE", b"td"],
20778 &[b"TDIGEST.ADD", b"td", b"1", b"2", b"3", b"4", b"5"],
20779 &[b"TDIGEST.MIN", b"td"],
20780 &[b"TDIGEST.MAX", b"td"],
20781 &[b"TDIGEST.QUANTILE", b"td", b"0.5"],
20782 &[b"TDIGEST.CDF", b"td", b"3"],
20783 &[b"TDIGEST.RANK", b"td", b"3"],
20784 &[b"TDIGEST.REVRANK", b"td", b"3"],
20785 &[b"TDIGEST.BYRANK", b"td", b"0"],
20786 &[b"TDIGEST.BYREVRANK", b"td", b"0"],
20787 &[b"TDIGEST.TRIMMED_MEAN", b"td", b"0.1", b"0.9"],
20788 &[b"TDIGEST.INFO", b"td"],
20789 &[b"TDIGEST.RESET", b"td"],
20790 &[b"TDIGEST.MIN", b"td"],
20791 &[b"SET", b"plain", b"v"],
20793 &[b"BF.ADD", b"plain", b"a"],
20794 &[b"CF.ADD", b"plain", b"a"],
20795 &[b"CMS.QUERY", b"plain", b"a"],
20796 &[b"TOPK.ADD", b"plain", b"a"],
20797 &[b"TDIGEST.ADD", b"plain", b"1"],
20798 &[b"CMS.INFO", b"gone"],
20799 &[b"TOPK.INFO", b"gone"],
20800 &[b"TDIGEST.INFO", b"gone"],
20801 ];
20802
20803 let mut one = Fixture::new();
20804 let mut many = Fixture::striped(8);
20805 for parts in script {
20806 let a = one.run(parts);
20807 let b = many.run(parts);
20808 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20809 }
20810 }
20811
20812 #[test]
20819 fn a_sketch_merge_across_stripes_reads_every_source() {
20820 let mut many = Fixture::striped(8);
20821 let other = apart(&mut many, "s1");
20822 let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
20823 let mut one = Fixture::new();
20824 let mut both = |parts: &[&[u8]]| {
20825 let a = one.run(parts);
20826 let b = many.run(parts);
20827 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20828 a
20829 };
20830
20831 for key in [b"cd".as_slice(), s1, s2] {
20835 both(&[b"CMS.INITBYDIM", key, b"100", b"5"]);
20836 }
20837 both(&[b"CMS.INCRBY", s1, b"x", b"5"]);
20838 both(&[b"CMS.INCRBY", s2, b"x", b"3"]);
20839 assert_eq!(
20840 both(&[b"CMS.MERGE", b"cd", b"2", s1, s2]),
20841 "+OK\r\n",
20842 "the merge took both sources"
20843 );
20844 assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:8\r\n");
20845 both(&[b"CMS.MERGE", b"cd", b"2", s1, s2, b"WEIGHTS", b"2", b"1"]);
20847 assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
20848 both(&[b"SET", b"plain", b"v"]);
20850 assert!(both(&[b"CMS.MERGE", b"cd", b"2", s1, b"plain"]).starts_with('-'));
20851 assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
20852
20853 both(&[b"FLUSHALL"]);
20856 both(&[b"TDIGEST.CREATE", b"td"]);
20857 both(&[b"TDIGEST.CREATE", s1]);
20858 both(&[b"TDIGEST.CREATE", s2]);
20859 both(&[b"TDIGEST.ADD", s1, b"1", b"2"]);
20860 both(&[b"TDIGEST.ADD", s2, b"9", b"10"]);
20861 assert_eq!(both(&[b"TDIGEST.MERGE", b"td", b"2", s1, s2]), "+OK\r\n");
20862 assert_eq!(both(&[b"TDIGEST.MIN", b"td"]), "$1\r\n1\r\n");
20863 assert_eq!(both(&[b"TDIGEST.MAX", b"td"]), "$2\r\n10\r\n");
20864 }
20865
20866 #[test]
20873 fn the_sort_command_answers_the_same_however_many_stripes_there_are() {
20874 let script: &[&[&[u8]]] = &[
20875 &[b"RPUSH", b"l", b"3", b"1", b"2", b"10"],
20876 &[b"SORT", b"l"],
20877 &[b"SORT", b"l", b"DESC"],
20878 &[b"SORT", b"l", b"ALPHA"],
20879 &[b"SORT", b"l", b"LIMIT", b"1", b"2"],
20880 &[b"SORT_RO", b"l"],
20881 &[
20884 b"MSET", b"w_1", b"4", b"w_2", b"3", b"w_3", b"2", b"w_10", b"1",
20885 ],
20886 &[b"SORT", b"l", b"BY", b"w_*"],
20887 &[b"SORT", b"l", b"BY", b"w_*", b"DESC"],
20888 &[b"DEL", b"w_2"],
20889 &[b"SORT", b"l", b"BY", b"w_*"],
20890 &[b"MSET", b"d_1", b"one", b"d_3", b"three"],
20893 &[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"],
20894 &[b"HSET", b"h_1", b"f", b"9"],
20896 &[b"HSET", b"h_2", b"f", b"8"],
20897 &[b"HSET", b"h_3", b"f", b"7"],
20898 &[b"HSET", b"h_10", b"f", b"6"],
20899 &[b"SORT", b"l", b"BY", b"h_*->f"],
20900 &[b"SORT", b"l", b"BY", b"nosort", b"GET", b"h_*->f"],
20901 &[b"SORT", b"l", b"BY", b"w_*", b"STORE", b"out"],
20903 &[b"LRANGE", b"out", b"0", b"-1"],
20904 &[b"SORT", b"l", b"STORE", b"l"],
20905 &[b"LRANGE", b"l", b"0", b"-1"],
20906 &[b"SORT", b"missing", b"STORE", b"out"],
20909 &[b"EXISTS", b"out"],
20910 &[b"SADD", b"s", b"c", b"a", b"b"],
20913 &[b"SORT", b"s", b"ALPHA"],
20914 &[b"SORT", b"s", b"BY", b"nosort", b"STORE", b"out"],
20915 &[b"LRANGE", b"out", b"0", b"-1"],
20916 &[b"ZADD", b"z", b"3", b"c", b"1", b"a", b"2", b"b"],
20917 &[b"SORT", b"z", b"BY", b"nosort"],
20918 &[b"SORT", b"z", b"ALPHA", b"DESC"],
20919 &[b"SET", b"str", b"v"],
20922 &[b"SORT", b"str"],
20923 &[b"RPUSH", b"words", b"one", b"two"],
20924 &[b"SORT", b"words"],
20925 &[b"SORT_RO", b"l", b"STORE", b"out"],
20926 ];
20927
20928 let mut one = Fixture::new();
20929 let mut many = Fixture::striped(8);
20930 for parts in script {
20931 let a = one.run(parts);
20932 let b = many.run(parts);
20933 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20934 }
20935 }
20936
20937 #[test]
20944 fn a_sort_across_stripes_reads_every_pattern_key() {
20945 let mut f = Fixture::striped(8);
20946 let out = apart(&mut f, "l");
20947 let (list, dest) = (b"l".as_slice(), out.as_bytes());
20948
20949 f.run(&[b"RPUSH", list, b"a", b"b", b"c", b"d"]);
20950 f.run(&[
20951 b"MSET", b"w_a", b"4", b"w_b", b"3", b"w_c", b"2", b"w_d", b"1",
20952 ]);
20953 f.run(&[
20954 b"MSET", b"d_a", b"A", b"d_b", b"B", b"d_c", b"C", b"d_d", b"D",
20955 ]);
20956
20957 let db = f.server.striped(0);
20960 let weights: Vec<usize> = [b"w_a", b"w_b", b"w_c", b"w_d"]
20961 .iter()
20962 .map(|k| db.stripe_of(k.as_slice()))
20963 .collect();
20964 assert!(
20965 weights.iter().any(|s| *s != weights[0]),
20966 "the four weight keys all landed on one stripe, so this proves nothing"
20967 );
20968
20969 assert_eq!(
20970 f.run(&[b"SORT", list, b"BY", b"w_*", b"GET", b"d_*"]),
20971 "*4\r\n$1\r\nD\r\n$1\r\nC\r\n$1\r\nB\r\n$1\r\nA\r\n",
20972 "the order came off the weights and the answer off the data keys"
20973 );
20974 assert_eq!(
20975 f.run(&[b"SORT", list, b"BY", b"w_*", b"STORE", dest]),
20976 ":4\r\n"
20977 );
20978 assert_eq!(
20979 f.run(&[b"LRANGE", dest, b"0", b"-1"]),
20980 "*4\r\n$1\r\nd\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n",
20981 "the destination is on a stripe of its own and got the whole answer"
20982 );
20983 }
20984
20985 #[test]
20993 fn a_setting_reaches_every_stripe_and_reads_back_from_any_of_them() {
20994 let mut f = Fixture::striped(8);
20995 let other = apart(&mut f, "h");
20996 let (first, second) = (b"h".as_slice(), other.as_bytes());
20997
20998 assert_eq!(
20999 f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"2"]),
21000 "+OK\r\n"
21001 );
21002 assert_eq!(
21003 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
21004 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n",
21005 "the read comes off one stripe and has to answer for all of them"
21006 );
21007 for key in [first, second] {
21008 f.run(&[b"HSET", key, b"a", b"1", b"b", b"2"]);
21009 assert_eq!(
21010 f.run(&[b"OBJECT", b"ENCODING", key]),
21011 "$8\r\nlistpack\r\n",
21012 "two fields is still under the ladder"
21013 );
21014 f.run(&[b"HSET", key, b"c", b"3"]);
21015 assert_eq!(
21016 f.run(&[b"OBJECT", b"ENCODING", key]),
21017 "$9\r\nhashtable\r\n",
21018 "three fields is over it, on whichever stripe the key is on"
21019 );
21020 }
21021
21022 assert_eq!(
21025 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]),
21026 "+OK\r\n"
21027 );
21028 let db = f.server.striped(0);
21029 assert!(
21030 (0..db.width()).all(|i| db.hold_stripe(i).policy().name() == "allkeys-lru"),
21031 "a stripe kept the old policy"
21032 );
21033 }
21034
21035 fn held(f: &Fixture, name: &[u8]) -> (usize, u32) {
21041 let search = f.server.search.lock();
21042 let index = search.named(name).expect("the index is there");
21043 (index.held.docs.len(), index.held.docs.last())
21044 }
21045
21046 #[test]
21049 fn a_hash_that_is_written_reaches_the_index_that_follows_it() {
21050 let mut f = Fixture::new();
21051 f.run(&[
21052 b"FT.CREATE",
21053 b"ix",
21054 b"PREFIX",
21055 b"1",
21056 b"p:",
21057 b"SCHEMA",
21058 b"t",
21059 b"TEXT",
21060 ]);
21061 f.run(&[b"HSET", b"p:1", b"t", b"running dogs"]);
21062 assert_eq!(held(&f, b"ix"), (1, 1));
21063 f.run(&[b"HSET", b"other:1", b"t", b"running dogs"]);
21064 assert_eq!(held(&f, b"ix"), (1, 1));
21065
21066 f.run(&[b"HSET", b"p:1", b"u", b"beta"]);
21069 f.run(&[b"HDEL", b"p:1", b"u"]);
21070 assert_eq!(held(&f, b"ix"), (1, 3));
21071 let search = f.server.search.lock();
21072 let index = search.named(b"ix").expect("there");
21073 assert_eq!(index.held.docs.id(b"p:1"), Some(3));
21074 }
21075
21076 #[test]
21079 fn a_fresh_index_reads_the_keys_that_were_already_there() {
21080 let mut f = Fixture::new();
21081 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
21082 f.run(&[b"SET", b"p:str", b"not a hash"]);
21083 f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
21084 f.run(&[
21085 b"FT.CREATE",
21086 b"ix",
21087 b"PREFIX",
21088 b"1",
21089 b"p:",
21090 b"SCHEMA",
21091 b"t",
21092 b"TEXT",
21093 ]);
21094
21095 assert_eq!(held(&f, b"ix"), (1, 1));
21096 let search = f.server.search.lock();
21097 let index = search.named(b"ix").expect("there");
21098 assert_eq!(index.trouble.whole().failures(), 0);
21099 }
21100
21101 #[test]
21104 fn an_index_that_skipped_the_scan_fills_up_on_the_next_write() {
21105 let mut f = Fixture::new();
21106 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
21107 f.run(&[
21108 b"FT.CREATE",
21109 b"ix",
21110 b"PREFIX",
21111 b"1",
21112 b"p:",
21113 b"SKIPINITIALSCAN",
21114 b"SCHEMA",
21115 b"t",
21116 b"TEXT",
21117 ]);
21118 assert_eq!(held(&f, b"ix"), (0, 0));
21119 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
21120 assert_eq!(held(&f, b"ix"), (1, 1));
21121 }
21122
21123 #[test]
21131 fn only_a_real_change_gives_the_document_a_new_number() {
21132 let mut f = Fixture::new();
21133 f.run(&[
21134 b"FT.CREATE",
21135 b"ix",
21136 b"PREFIX",
21137 b"1",
21138 b"p:",
21139 b"SCHEMA",
21140 b"t",
21141 b"TEXT",
21142 ]);
21143 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
21144 assert_eq!(held(&f, b"ix"), (1, 1));
21145
21146 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
21147 assert_eq!(held(&f, b"ix"), (1, 2), "the same value still rewrites");
21148
21149 for quiet in [
21150 vec![b"HSETNX".as_slice(), b"p:1", b"t", b"other"],
21151 vec![b"HDEL".as_slice(), b"p:1", b"nosuch"],
21152 vec![b"HGET".as_slice(), b"p:1", b"t"],
21153 vec![b"HGETALL".as_slice(), b"p:1"],
21154 vec![b"HEXPIRE".as_slice(), b"p:1", b"100", b"FIELDS", b"1", b"t"],
21155 vec![b"HPERSIST".as_slice(), b"p:1", b"FIELDS", b"1", b"t"],
21156 vec![
21157 b"HGETEX".as_slice(),
21158 b"p:1",
21159 b"EX",
21160 b"100",
21161 b"FIELDS",
21162 b"1",
21163 b"t",
21164 ],
21165 vec![b"HGETDEL".as_slice(), b"p:1", b"FIELDS", b"1", b"nosuch"],
21166 ] {
21167 f.run(&quiet);
21168 assert_eq!(held(&f, b"ix"), (1, 2), "{:?} moved the document", quiet[0]);
21169 }
21170
21171 f.run(&[b"HSET", b"p:2", b"n", b"1"]);
21173 f.run(&[b"HINCRBY", b"p:2", b"n", b"1"]);
21174 assert_eq!(held(&f, b"ix"), (2, 4));
21175 f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"n"]);
21181 assert_eq!(held(&f, b"ix"), (1, 5));
21182 }
21183
21184 #[test]
21190 fn a_key_emptied_by_a_deadline_spends_a_number_and_one_emptied_by_hdel_does_not() {
21191 fn refused(f: &Fixture, name: &[u8]) -> u64 {
21193 let search = f.server.search.lock();
21194 let index = search.named(name).expect("the index is there");
21195 index.trouble.whole().failures()
21196 }
21197
21198 let mut f = Fixture::new();
21199 f.run(&[
21200 b"FT.CREATE",
21201 b"ix",
21202 b"PREFIX",
21203 b"1",
21204 b"p:",
21205 b"SCHEMA",
21206 b"t",
21207 b"TEXT",
21208 ]);
21209 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
21210 assert_eq!(held(&f, b"ix"), (1, 1));
21211 f.run(&[b"HDEL", b"p:1", b"t"]);
21212 assert_eq!(
21213 held(&f, b"ix"),
21214 (0, 1),
21215 "HDEL of the last field spends none"
21216 );
21217 assert_eq!(refused(&f, b"ix"), 1, "and is counted as a refusal");
21218
21219 f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
21220 assert_eq!(held(&f, b"ix"), (1, 2));
21221 f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"t"]);
21222 assert_eq!(held(&f, b"ix"), (0, 3), "a deadline spends one");
21223 assert_eq!(refused(&f, b"ix"), 1, "and is counted as nothing");
21224
21225 f.run(&[b"HSET", b"p:3", b"t", b"alpha"]);
21226 assert_eq!(held(&f, b"ix"), (1, 4));
21227 f.run(&[b"HGETDEL", b"p:3", b"FIELDS", b"1", b"t"]);
21228 assert_eq!(held(&f, b"ix"), (0, 5), "and so does HGETDEL");
21229
21230 f.run(&[b"HSET", b"p:4", b"t", b"alpha", b"u", b"beta"]);
21233 assert_eq!(held(&f, b"ix"), (1, 6));
21234 f.run(&[b"HEXPIRE", b"p:4", b"0", b"FIELDS", b"2", b"t", b"u"]);
21235 assert_eq!(held(&f, b"ix"), (0, 7));
21236 assert_eq!(refused(&f, b"ix"), 1);
21237 }
21238
21239 #[test]
21243 fn a_field_written_already_past_its_deadline_moves_the_number_twice() {
21244 let mut f = Fixture::new();
21245 f.run(&[
21246 b"FT.CREATE",
21247 b"ix",
21248 b"PREFIX",
21249 b"1",
21250 b"p:",
21251 b"SCHEMA",
21252 b"t",
21253 b"TEXT",
21254 b"u",
21255 b"TEXT",
21256 ]);
21257 f.run(&[b"HSET", b"p:1", b"u", b"keepme"]);
21258 assert_eq!(held(&f, b"ix"), (1, 1));
21259 f.run(&[
21260 b"HSETEX", b"p:1", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
21261 ]);
21262 assert_eq!(
21263 held(&f, b"ix"),
21264 (1, 3),
21265 "the key lived and the field did not"
21266 );
21267
21268 f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
21270 assert_eq!(held(&f, b"ix"), (2, 4));
21271 f.run(&[
21272 b"HSETEX", b"p:2", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
21273 ]);
21274 assert_eq!(held(&f, b"ix"), (1, 6));
21275 }
21276
21277 fn number(f: &Fixture, name: &[u8], key: &[u8]) -> Option<u32> {
21280 let search = f.server.search.lock();
21281 let index = search.named(name).expect("the index is there");
21282 index.held.docs.id(key)
21283 }
21284
21285 fn indexed() -> Fixture {
21288 let mut f = Fixture::new();
21289 f.run(&[
21290 b"FT.CREATE",
21291 b"ix",
21292 b"PREFIX",
21293 b"1",
21294 b"p:",
21295 b"SCHEMA",
21296 b"t",
21297 b"TEXT",
21298 ]);
21299 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
21300 f
21301 }
21302
21303 #[test]
21306 fn a_key_a_keyspace_command_takes_away_loses_its_document() {
21307 for take in [
21308 vec![b"DEL".as_slice(), b"p:1"],
21309 vec![b"UNLINK".as_slice(), b"p:1"],
21310 vec![b"PEXPIREAT".as_slice(), b"p:1", b"1"],
21311 vec![b"EXPIRE".as_slice(), b"p:1", b"-1"],
21312 ] {
21313 let mut f = indexed();
21314 assert_eq!(held(&f, b"ix"), (1, 1));
21315 f.run(&take);
21316 assert_eq!(held(&f, b"ix"), (0, 1), "{:?} left something", take[0]);
21317 let search = f.server.search.lock();
21318 let index = search.named(b"ix").expect("the index is there");
21319 assert_eq!(index.trouble.whole().failures(), 0, "{:?}", take[0]);
21320 }
21321
21322 let mut f = indexed();
21324 f.run(&[b"EXPIRE", b"p:1", b"1000"]);
21325 assert_eq!(held(&f, b"ix"), (1, 1));
21326 f.run(&[b"PERSIST", b"p:1"]);
21327 assert_eq!(held(&f, b"ix"), (1, 1));
21328 }
21329
21330 #[test]
21334 fn a_rename_inside_the_prefix_keeps_the_number_the_document_had() {
21335 let mut f = indexed();
21336 f.run(&[b"RENAME", b"p:1", b"p:2"]);
21337 assert_eq!(held(&f, b"ix"), (1, 1), "nothing was read again");
21338 assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
21339 assert_eq!(number(&f, b"ix", b"p:1"), None);
21340
21341 f.run(&[b"RENAME", b"p:2", b"q:1"]);
21342 assert_eq!(held(&f, b"ix"), (0, 1), "out of the prefix is an erase");
21343
21344 f.run(&[b"RENAME", b"q:1", b"p:3"]);
21345 assert_eq!(held(&f, b"ix"), (1, 2), "and into it is a reading");
21346 assert_eq!(number(&f, b"ix", b"p:3"), Some(2));
21347
21348 f.run(&[b"HSET", b"p:4", b"t", b"beta"]);
21351 assert_eq!(f.run(&[b"RENAMENX", b"p:3", b"p:4"]), ":0\r\n");
21352 assert_eq!(held(&f, b"ix"), (2, 3));
21353 f.run(&[b"RENAMENX", b"p:3", b"p:5"]);
21354 assert_eq!(number(&f, b"ix", b"p:5"), Some(2));
21355 }
21356
21357 #[test]
21360 fn a_rename_over_a_document_leaves_one_of_them() {
21361 let mut f = indexed();
21362 f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
21363 assert_eq!(held(&f, b"ix"), (2, 2));
21364 f.run(&[b"RENAME", b"p:1", b"p:2"]);
21365 assert_eq!(held(&f, b"ix"), (1, 2));
21366 assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
21367 }
21368
21369 #[test]
21373 fn a_key_that_arrives_under_the_prefix_is_read_and_one_overwritten_is_erased() {
21374 let mut f = indexed();
21375 f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
21376 f.run(&[b"COPY", b"q:1", b"p:2"]);
21377 assert_eq!(held(&f, b"ix"), (2, 2));
21378 assert_eq!(number(&f, b"ix", b"p:2"), Some(2));
21379
21380 f.run(&[b"COPY", b"p:1", b"q:2"]);
21382 assert_eq!(held(&f, b"ix"), (2, 2));
21383
21384 f.run(&[b"COPY", b"q:1", b"p:1", b"REPLACE"]);
21386 assert_eq!(held(&f, b"ix"), (2, 3));
21387 assert_eq!(number(&f, b"ix", b"p:1"), Some(3));
21388
21389 f.run(&[b"SET", b"s:1", b"plain"]);
21392 f.run(&[b"COPY", b"s:1", b"p:1", b"REPLACE"]);
21393 assert_eq!(held(&f, b"ix"), (1, 3));
21394 let dump = f.run(&[b"DUMP", b"q:1"]);
21395 assert!(dump.starts_with('$'), "{dump}");
21396 }
21397
21398 #[test]
21403 fn the_keyspace_group_reads_database_zero_whatever_database_it_ran_on() {
21404 let mut f = indexed();
21405 f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
21406 assert_eq!(held(&f, b"ix"), (2, 2));
21407 f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
21410 assert_eq!(held(&f, b"ix"), (2, 3));
21411 f.run(&[b"DEL", b"p:2"]);
21414 assert_eq!(held(&f, b"ix"), (1, 3));
21415 f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
21416 assert_eq!(held(&f, b"ix"), (1, 3), "the copy landed out of sight");
21417
21418 let dump = f.run(&[b"DUMP", b"p:1"]);
21420 assert!(dump.starts_with('$'), "{dump}");
21421 f.run(&[b"SELECT", b"1"]);
21422 f.run(&[b"HSET", b"q:1", b"t", b"gamma"]);
21423 f.run(&[b"RENAME", b"q:1", b"p:3"]);
21424 assert_eq!(held(&f, b"ix"), (1, 3), "and so is a rename");
21425 }
21426
21427 #[test]
21430 fn a_move_leaves_the_document_where_it_is() {
21431 let mut f = indexed();
21432 f.run(&[b"MOVE", b"p:1", b"1"]);
21433 assert_eq!(held(&f, b"ix"), (1, 1), "the key moved and nothing else");
21434 assert_eq!(number(&f, b"ix", b"p:1"), Some(1));
21435
21436 f.run(&[b"SELECT", b"1"]);
21437 f.run(&[b"HSET", b"p:1", b"t", b"beta"]);
21438 assert_eq!(held(&f, b"ix"), (1, 2), "and a write there still lands");
21439 f.run(&[b"DEL", b"p:1"]);
21440 assert_eq!(held(&f, b"ix"), (0, 2));
21441 }
21442
21443 #[test]
21445 fn a_flush_drops_the_indexes() {
21446 for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
21447 let mut f = indexed();
21448 f.run(&[flush]);
21449 assert!(f.server.search.lock().is_empty(), "{flush:?} kept an index");
21450 assert_eq!(f.run(&[b"FT._LIST"]), "*0\r\n");
21451 }
21452
21453 let mut f = indexed();
21456 f.run(&[b"SELECT", b"9"]);
21457 f.run(&[b"FLUSHDB"]);
21458 assert!(f.server.search.lock().is_empty());
21459 }
21460
21461 #[test]
21464 fn a_hash_that_will_not_read_is_counted_where_ft_info_reports_it() {
21465 let mut f = Fixture::new();
21466 f.run(&[
21467 b"FT.CREATE",
21468 b"ix",
21469 b"PREFIX",
21470 b"1",
21471 b"p:",
21472 b"SCHEMA",
21473 b"n",
21474 b"NUMERIC",
21475 ]);
21476 f.run(&[b"HSET", b"p:1", b"n", b"notanumber"]);
21477 assert_eq!(held(&f, b"ix"), (0, 0));
21478
21479 let reply = f.run(&[b"FT.INFO", b"ix"]);
21480 assert!(
21481 reply.contains("SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value: 'notanumber'"),
21482 "{reply}"
21483 );
21484 assert!(reply.contains("hash_indexing_failures"), "{reply}");
21485 }
21486
21487 #[test]
21490 fn an_index_can_only_be_made_on_database_zero() {
21491 let mut f = Fixture::new();
21492 f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]);
21493 f.run(&[b"SELECT", b"1"]);
21494 let refused = "-Cannot create index on db != 0\r\n";
21495 assert_eq!(
21496 f.run(&[b"FT.CREATE", b"jx", b"SCHEMA", b"t", b"TEXT"]),
21497 refused
21498 );
21499 assert_eq!(
21501 f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]),
21502 refused
21503 );
21504 assert_eq!(
21506 f.run(&[b"FT.CREATE", b"zz", b"BOGUS", b"SCHEMA", b"t", b"TEXT"]),
21507 refused
21508 );
21509 assert_eq!(
21511 f.run(&[b"FT._CREATEIFNX", b"ix", b"SCHEMA", b"t", b"TEXT"]),
21512 "+OK\r\n"
21513 );
21514 assert_eq!(f.server.search.lock().len(), 1);
21515 }
21516
21517 #[test]
21523 fn the_scan_is_one_database_and_the_following_is_all_of_them() {
21524 let mut f = Fixture::new();
21525 f.run(&[b"SELECT", b"1"]);
21526 f.run(&[b"HSET", b"p:9", b"t", b"on one"]);
21527 f.run(&[b"SELECT", b"0"]);
21528 f.run(&[b"HSET", b"p:0", b"t", b"on zero"]);
21529 f.run(&[
21530 b"FT.CREATE",
21531 b"ix",
21532 b"PREFIX",
21533 b"1",
21534 b"p:",
21535 b"SCHEMA",
21536 b"t",
21537 b"TEXT",
21538 ]);
21539 assert_eq!(held(&f, b"ix"), (1, 1), "the scan read database zero only");
21540
21541 f.run(&[b"SELECT", b"1"]);
21542 f.run(&[b"HSET", b"p:8", b"t", b"later"]);
21543 assert_eq!(
21544 held(&f, b"ix"),
21545 (2, 2),
21546 "and then it follows every database"
21547 );
21548 }
21549
21550 fn corpus(f: &mut Fixture) {
21553 f.run(&[
21554 b"FT.CREATE",
21555 b"sx",
21556 b"PREFIX",
21557 b"1",
21558 b"d:",
21559 b"SCHEMA",
21560 b"t",
21561 b"TEXT",
21562 b"g",
21563 b"TAG",
21564 b"n",
21565 b"NUMERIC",
21566 ]);
21567 for (key, text, tag, number) in [
21568 (b"d:1".as_slice(), "alpha beta", "aa,bb", "1"),
21569 (b"d:2", "alpha gamma", "bb", "2"),
21570 (b"d:3", "delta", "cc", "3"),
21571 (b"d:4", "alpha beta gamma", "aa,cc", "4"),
21572 ] {
21573 f.run(&[
21574 b"HSET",
21575 key,
21576 b"t",
21577 text.as_bytes(),
21578 b"g",
21579 tag.as_bytes(),
21580 b"n",
21581 number.as_bytes(),
21582 ]);
21583 }
21584 }
21585
21586 #[test]
21589 fn a_search_answers_a_total_and_then_the_rows() {
21590 let mut f = Fixture::new();
21591 corpus(&mut f);
21592 assert_eq!(
21593 f.run(&[b"FT.SEARCH", b"sx", b"delta"]),
21594 "*3\r\n:1\r\n$3\r\nd:3\r\n*6\r\n$1\r\nt\r\n$5\r\ndelta\r\n$1\r\ng\r\n$2\r\ncc\r\n$1\r\nn\r\n$1\r\n3\r\n"
21595 );
21596 f.run(&[b"HSET", b"d:3", b"extra", b"more"]);
21599 assert!(f.run(&[b"FT.SEARCH", b"sx", b"delta"]).contains("extra"));
21600 assert_eq!(
21603 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
21604 "*2\r\n:1\r\n$3\r\nd:3\r\n"
21605 );
21606 assert_eq!(
21607 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
21608 "*1\r\n:3\r\n"
21609 );
21610 }
21611
21612 #[test]
21615 fn the_window_is_ten_rows_and_a_million_wide_at_most() {
21616 let mut f = Fixture::new();
21617 corpus(&mut f);
21618 assert_eq!(
21619 f.run(&[
21620 b"FT.SEARCH",
21621 b"sx",
21622 b"alpha",
21623 b"NOCONTENT",
21624 b"LIMIT",
21625 b"1",
21626 b"1"
21627 ]),
21628 "*2\r\n:3\r\n$3\r\nd:2\r\n"
21629 );
21630 assert_eq!(
21631 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0"]),
21632 "-SEARCH_PARSE_ARGS LIMIT requires two arguments\r\n"
21633 );
21634 assert_eq!(
21635 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"-1"]),
21636 "-SEARCH_PARSE_ARGS LIMIT needs two numeric arguments\r\n"
21637 );
21638 assert_eq!(
21639 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"1000001"]),
21640 "-SEARCH_LIMIT_OVER LIMIT exceeds maximum of 1000000\r\n"
21641 );
21642 assert_eq!(
21643 f.run(&[
21644 b"FT.SEARCH",
21645 b"sx",
21646 b"alpha",
21647 b"NOCONTENT",
21648 b"LIMIT",
21649 b"999999",
21650 b"1000000"
21651 ]),
21652 "*1\r\n:3\r\n"
21653 );
21654 }
21655
21656 #[test]
21660 fn a_return_of_nothing_is_not_the_same_as_nocontent() {
21661 let mut f = Fixture::new();
21662 corpus(&mut f);
21663 let bare = "*2\r\n:1\r\n$3\r\nd:3\r\n";
21664 assert_eq!(
21665 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"0"]),
21666 bare
21667 );
21668 assert_eq!(
21669 f.run(&[
21670 b"FT.SEARCH",
21671 b"sx",
21672 b"delta",
21673 b"NOCONTENT",
21674 b"RETURN",
21675 b"1",
21676 b"t"
21677 ]),
21678 bare
21679 );
21680 assert_eq!(
21681 f.run(&[
21682 b"FT.SEARCH",
21683 b"sx",
21684 b"delta",
21685 b"RETURN",
21686 b"0",
21687 b"RETURN",
21688 b"1",
21689 b"t"
21690 ]),
21691 "*3\r\n:1\r\n$3\r\nd:3\r\n*2\r\n$1\r\nt\r\n$5\r\ndelta\r\n"
21692 );
21693 }
21694
21695 #[test]
21698 fn the_count_after_return_counts_words() {
21699 let mut f = Fixture::new();
21700 corpus(&mut f);
21701 assert_eq!(
21704 f.run(&[
21705 b"FT.SEARCH",
21706 b"sx",
21707 b"delta",
21708 b"RETURN",
21709 b"3",
21710 b"t",
21711 b"AS",
21712 b"x"
21713 ]),
21714 "*3\r\n:1\r\n$3\r\nd:3\r\n*2\r\n$1\r\nx\r\n$5\r\ndelta\r\n"
21715 );
21716 assert_eq!(
21719 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"2", b"t", b"AS"]),
21720 "-SEARCH_PARSE_ARGS RETURN path AS name - must be accompanied with NAME\r\n"
21721 );
21722 assert_eq!(
21723 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"3", b"t", b"AS"]),
21724 "-SEARCH_PARSE_ARGS Bad arguments for RETURN: Expected an argument, but none provided\r\n"
21725 );
21726 assert_eq!(
21730 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"AS"]),
21731 "*3\r\n:1\r\n$3\r\nd:3\r\n*0\r\n"
21732 );
21733 }
21734
21735 #[test]
21738 fn a_filter_is_a_range_written_outside_the_query() {
21739 let mut f = Fixture::new();
21740 corpus(&mut f);
21741 assert_eq!(
21742 f.run(&[
21743 b"FT.SEARCH",
21744 b"sx",
21745 b"alpha",
21746 b"NOCONTENT",
21747 b"FILTER",
21748 b"n",
21749 b"2",
21750 b"4"
21751 ]),
21752 "*3\r\n:2\r\n$3\r\nd:2\r\n$3\r\nd:4\r\n"
21753 );
21754 assert_eq!(
21755 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2"]),
21756 "-SEARCH_PARSE_ARGS FILTER requires 3 arguments\r\n"
21757 );
21758 assert_eq!(
21759 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"x", b"1"]),
21760 "-SEARCH_PARSE_ARGS Bad lower range: x\r\n"
21761 );
21762 assert_eq!(
21763 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2", b"1"]),
21764 "-SEARCH_SYNTAX Invalid numeric range (min > max): @n:[2.000000 1.000000]\r\n"
21765 );
21766 for field in [b"g".as_slice(), b"nope"] {
21769 assert_eq!(
21770 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", field, b"2", b"1"]),
21771 "*1\r\n:0\r\n"
21772 );
21773 }
21774 }
21775
21776 #[test]
21779 fn the_index_is_found_before_the_arguments_are_read() {
21780 let mut f = Fixture::new();
21781 corpus(&mut f);
21782 let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
21783 assert_eq!(f.run(&[b"FT.SEARCH", b"nope", b"alpha", b"BOGUS"]), missing);
21784 assert_eq!(
21785 f.run(&[b"FT.EXPLAIN", b"nope", b"alpha", b"BOGUS"]),
21786 missing
21787 );
21788 assert_eq!(
21791 f.run(&[b"FT.SEARCH", b"sx", b"@@@", b"BOGUS"]),
21792 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `BOGUS` at position 1 for <main>\r\n"
21793 );
21794 }
21795
21796 #[test]
21799 fn inkeys_comes_off_the_total() {
21800 let mut f = Fixture::new();
21801 corpus(&mut f);
21802 assert_eq!(
21803 f.run(&[
21804 b"FT.SEARCH",
21805 b"sx",
21806 b"alpha",
21807 b"NOCONTENT",
21808 b"INKEYS",
21809 b"1",
21810 b"d:1"
21811 ]),
21812 "*2\r\n:1\r\n$3\r\nd:1\r\n"
21813 );
21814 assert_eq!(
21815 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"NOCONTENT", b"INKEYS", b"0"]),
21816 "*1\r\n:0\r\n"
21817 );
21818 }
21819
21820 #[test]
21827 fn the_fields_are_read_from_the_session_database() {
21828 let mut f = Fixture::new();
21829 corpus(&mut f);
21830 f.run(&[b"SELECT", b"1"]);
21831 f.run(&[b"HSET", b"d:9", b"t", b"delta", b"n", b"9"]);
21832 assert_eq!(
21835 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
21836 "*3\r\n:2\r\n$3\r\nd:3\r\n$3\r\nd:9\r\n"
21837 );
21838 assert_eq!(
21839 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
21840 "*3\r\n:1\r\n$3\r\nd:9\r\n*2\r\n$1\r\nn\r\n$1\r\n9\r\n"
21841 );
21842 }
21843
21844 #[test]
21847 fn the_third_protocol_answers_a_map_of_five() {
21848 let mut f = Fixture::new();
21849 corpus(&mut f);
21850 f.out = Out::new(Proto::Resp3);
21851 assert_eq!(
21852 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
21853 concat!(
21854 "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
21855 "%3\r\n+id\r\n$3\r\nd:3\r\n+extra_attributes\r\n%1\r\n$1\r\nn\r\n$1\r\n3\r\n",
21856 "+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
21857 )
21858 );
21859 }
21860
21861 #[test]
21865 fn a_window_of_nothing_has_to_start_at_the_top() {
21866 let mut f = Fixture::new();
21867 corpus(&mut f);
21868 let refused = "-SEARCH_LIMIT_OVER The `offset` of the LIMIT must be 0 when `num` is 0\r\n";
21869 assert_eq!(
21870 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
21871 refused
21872 );
21873 assert_eq!(
21874 f.run(&[b"FT.EXPLAIN", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
21875 refused
21876 );
21877 assert_eq!(
21878 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
21879 refused
21880 );
21881 assert_eq!(
21882 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
21883 "*1\r\n:3\r\n"
21884 );
21885 }
21886
21887 #[test]
21890 fn an_aggregation_answers_a_count_and_then_the_properties() {
21891 let mut f = Fixture::new();
21892 corpus(&mut f);
21893 assert_eq!(
21894 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha"]),
21895 "*4\r\n:1\r\n*0\r\n*0\r\n*0\r\n"
21896 );
21897 assert_eq!(
21899 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"1", b"@t"]),
21900 concat!(
21901 "*4\r\n:3\r\n*2\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
21902 "*2\r\n$1\r\nt\r\n$11\r\nalpha gamma\r\n",
21903 "*2\r\n$1\r\nt\r\n$16\r\nalpha beta gamma\r\n"
21904 )
21905 );
21906 assert_eq!(
21910 f.run(&[
21911 b"FT.AGGREGATE",
21912 b"sx",
21913 b"alpha",
21914 b"LOAD",
21915 b"1",
21916 b"@n",
21917 b"LIMIT",
21918 b"1",
21919 b"2"
21920 ]),
21921 "*3\r\n:3\r\n*2\r\n$1\r\nn\r\n$1\r\n2\r\n*2\r\n$1\r\nn\r\n$1\r\n4\r\n"
21922 );
21923 assert_eq!(
21925 f.run(&[b"FT.AGGREGATE", b"sx", b"nope", b"LOAD", b"1", b"@t"]),
21926 "*1\r\n:0\r\n"
21927 );
21928 }
21929
21930 #[test]
21934 fn a_load_counts_words_and_can_rename_what_it_reads() {
21935 let mut f = Fixture::new();
21936 corpus(&mut f);
21937 assert_eq!(
21939 f.run(&[
21940 b"FT.AGGREGATE",
21941 b"sx",
21942 b"alpha",
21943 b"LOAD",
21944 b"3",
21945 b"@t",
21946 b"AS",
21947 b"text"
21948 ]),
21949 concat!(
21950 "*4\r\n:3\r\n*2\r\n$4\r\ntext\r\n$10\r\nalpha beta\r\n",
21951 "*2\r\n$4\r\ntext\r\n$11\r\nalpha gamma\r\n",
21952 "*2\r\n$4\r\ntext\r\n$16\r\nalpha beta gamma\r\n"
21953 )
21954 );
21955 assert_eq!(
21956 f.run(&[
21957 b"FT.AGGREGATE",
21958 b"sx",
21959 b"alpha",
21960 b"LOAD",
21961 b"*",
21962 b"LIMIT",
21963 b"0",
21964 b"1"
21965 ]),
21966 concat!(
21967 "*2\r\n:3\r\n*6\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
21968 "$1\r\ng\r\n$5\r\naa,bb\r\n$1\r\nn\r\n$1\r\n1\r\n"
21969 )
21970 );
21971 assert_eq!(
21973 f.run(&[
21974 b"FT.AGGREGATE",
21975 b"sx",
21976 b"alpha",
21977 b"LOAD",
21978 b"2",
21979 b"@n",
21980 b"@nope",
21981 b"LIMIT",
21982 b"0",
21983 b"2"
21984 ]),
21985 "*3\r\n:3\r\n*2\r\n$1\r\nn\r\n$1\r\n1\r\n*2\r\n$1\r\nn\r\n$1\r\n2\r\n"
21986 );
21987 }
21988
21989 #[test]
21992 fn a_load_refuses_a_count_it_cannot_use() {
21993 let mut f = Fixture::new();
21994 corpus(&mut f);
21995 let head = "-SEARCH_PARSE_ARGS Bad arguments for LOAD: ";
21996 assert_eq!(
21997 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"x"]),
21998 format!("{head}Expected number of fields or `*`\r\n")
21999 );
22000 assert_eq!(
22001 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"-1", b"@t"]),
22002 format!("{head}Value is outside acceptable bounds\r\n")
22003 );
22004 assert_eq!(
22005 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"5", b"@t"]),
22006 format!("{head}Expected an argument, but none provided\r\n")
22007 );
22008 assert_eq!(
22009 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD"]),
22010 format!("{head}Expected an argument, but none provided\r\n")
22011 );
22012 assert_eq!(
22016 f.run(&[
22017 b"FT.AGGREGATE",
22018 b"sx",
22019 b"alpha",
22020 b"LOAD",
22021 b"2",
22022 b"@t",
22023 b"AS"
22024 ]),
22025 "-SEARCH_PARSE_ARGS LOAD path AS name - must be accompanied with NAME\r\n"
22026 );
22027 assert_eq!(
22030 f.run(&[
22031 b"FT.AGGREGATE",
22032 b"sx",
22033 b"alpha",
22034 b"LOAD",
22035 b"2",
22036 b"@t",
22037 b"AS",
22038 b"VERBATIM"
22039 ]),
22040 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 5 for <main>\r\n"
22041 );
22042 assert_eq!(
22046 f.run(&[
22047 b"FT.AGGREGATE",
22048 b"sx",
22049 b"alpha",
22050 b"LOAD",
22051 b"0",
22052 b"LIMIT",
22053 b"0",
22054 b"1"
22055 ]),
22056 "*2\r\n:1\r\n*0\r\n"
22057 );
22058 }
22059
22060 #[test]
22063 fn a_pipeline_step_closes_the_door_on_the_search_words() {
22064 let mut f = Fixture::new();
22065 corpus(&mut f);
22066 assert_eq!(
22067 f.run(&[
22068 b"FT.AGGREGATE",
22069 b"sx",
22070 b"alpha",
22071 b"LOAD",
22072 b"1",
22073 b"@t",
22074 b"VERBATIM"
22075 ]),
22076 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 4 for <main>\r\n"
22077 );
22078 assert_eq!(
22079 f.run(&[
22080 b"FT.AGGREGATE",
22081 b"sx",
22082 b"alpha",
22083 b"LIMIT",
22084 b"0",
22085 b"1",
22086 b"VERBATIM"
22087 ]),
22088 "*2\r\n:1\r\n*0\r\n"
22089 );
22090 for word in [b"RETURN".as_slice(), b"SUMMARIZE", b"HIGHLIGHT"] {
22093 let name = core::str::from_utf8(word).expect("the three words are text");
22094 assert_eq!(
22095 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", word]),
22096 format!("-SEARCH_PARSE_ARGS {name} is not supported on FT.AGGREGATE\r\n")
22097 );
22098 }
22099 }
22100
22101 #[test]
22104 fn addscores_writes_a_shorter_score_than_withscores() {
22105 let mut f = Fixture::new();
22106 corpus(&mut f);
22107 assert_eq!(
22108 f.run(&[
22109 b"FT.AGGREGATE",
22110 b"sx",
22111 b"alpha",
22112 b"ADDSCORES",
22113 b"LOAD",
22114 b"1",
22115 b"@n",
22116 b"LIMIT",
22117 b"0",
22118 b"2"
22119 ]),
22120 concat!(
22121 "*3\r\n:3\r\n",
22122 "*4\r\n$7\r\n__score\r\n$14\r\n0.356674943939\r\n$1\r\nn\r\n$1\r\n1\r\n",
22123 "*4\r\n$7\r\n__score\r\n$14\r\n0.356674943939\r\n$1\r\nn\r\n$1\r\n2\r\n"
22124 )
22125 );
22126 assert_eq!(
22130 f.run(&[
22131 b"FT.AGGREGATE",
22132 b"sx",
22133 b"alpha",
22134 b"NOCONTENT",
22135 b"WITHSCORES",
22136 b"LIMIT",
22137 b"0",
22138 b"2"
22139 ]),
22140 "*3\r\n:1\r\n$18\r\n0.3566749439387324\r\n$18\r\n0.3566749439387324\r\n"
22141 );
22142 assert_eq!(
22143 f.run(&[
22144 b"FT.AGGREGATE",
22145 b"sx",
22146 b"alpha",
22147 b"WITHSORTKEYS",
22148 b"LOAD",
22149 b"1",
22150 b"@n",
22151 b"LIMIT",
22152 b"0",
22153 b"1"
22154 ]),
22155 "*3\r\n:3\r\n$-1\r\n*2\r\n$1\r\nn\r\n$1\r\n1\r\n"
22156 );
22157 }
22158
22159 #[test]
22162 fn a_normalising_scorer_answers_the_rows_backwards() {
22163 let mut f = Fixture::new();
22164 corpus(&mut f);
22165 assert_eq!(
22166 f.run(&[
22167 b"FT.AGGREGATE",
22168 b"sx",
22169 b"alpha",
22170 b"SCORER",
22171 b"BM25STD.NORM",
22172 b"ADDSCORES",
22173 b"LOAD",
22174 b"1",
22175 b"@n",
22176 b"LIMIT",
22177 b"1",
22178 b"2"
22179 ]),
22180 concat!(
22181 "*3\r\n:3\r\n",
22182 "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n2\r\n",
22183 "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n1\r\n"
22184 )
22185 );
22186 assert_eq!(
22189 f.run(&[
22190 b"FT.AGGREGATE",
22191 b"sx",
22192 b"alpha",
22193 b"SCORER",
22194 b"BM25STD.NORM",
22195 b"LOAD",
22196 b"1",
22197 b"@n",
22198 b"LIMIT",
22199 b"1",
22200 b"2"
22201 ]),
22202 "*3\r\n:3\r\n*2\r\n$1\r\nn\r\n$1\r\n2\r\n*2\r\n$1\r\nn\r\n$1\r\n4\r\n"
22203 );
22204 }
22205
22206 #[test]
22209 fn an_aggregation_answers_a_map_of_five_as_well() {
22210 let mut f = Fixture::new();
22211 corpus(&mut f);
22212 f.out = Out::new(Proto::Resp3);
22213 assert_eq!(
22214 f.run(&[
22215 b"FT.AGGREGATE",
22216 b"sx",
22217 b"alpha",
22218 b"ADDSCORES",
22219 b"WITHSCORES",
22220 b"WITHSORTKEYS",
22221 b"LOAD",
22222 b"1",
22223 b"@n",
22224 b"LIMIT",
22225 b"0",
22226 b"1"
22227 ]),
22228 concat!(
22229 "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
22230 "%4\r\n+score\r\n,0.3566749439387324\r\n+sortkey\r\n_\r\n",
22231 "+extra_attributes\r\n%2\r\n$7\r\n__score\r\n$14\r\n0.356674943939\r\n",
22232 "$1\r\nn\r\n$1\r\n1\r\n+values\r\n*0\r\n",
22233 "+total_results\r\n:3\r\n+warning\r\n*0\r\n"
22234 )
22235 );
22236 assert_eq!(
22239 f.run(&[
22240 b"FT.AGGREGATE",
22241 b"sx",
22242 b"alpha",
22243 b"NOCONTENT",
22244 b"LIMIT",
22245 b"0",
22246 b"1"
22247 ]),
22248 concat!(
22249 "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
22250 "%1\r\n+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
22251 )
22252 );
22253 }
22254}