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;
80mod suggest;
81pub mod table;
82mod tdigest;
83mod topk;
84mod ts;
85mod vectors;
86mod vfilter;
87mod zsets;
88
89pub use args::Args;
90pub use blocking::{Parked, Waiters};
91pub use server::parse_memory;
92pub use table::{COMMANDS, Spec, arity_ok, lookup};
93
94use crate::reply::Out;
95use std::cell::Cell;
96use std::path::{Path, PathBuf};
97use std::sync::atomic::Ordering::{Acquire, Relaxed, Release};
98use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize};
99use yo_common::lock::{Held, Lock};
100use yo_common::{Code, Error};
101use yo_kv::cold::Store;
102use yo_kv::{Clock, Db, Keyspace};
103use yo_search::Registry;
104
105use search::cursor::Cursors;
106
107pub const DATABASES: usize = 16;
114
115const ALL_DATABASES: u64 = if DATABASES == 64 {
122 u64::MAX
123} else {
124 (1u64 << DATABASES) - 1
125};
126const _: () = assert!(DATABASES <= 64);
127
128const EVICT_BUDGET: usize = 64;
140
141const NO_MAXSTORE: u64 = u64::MAX;
149
150const OOM: &[u8] = b"command not allowed when used memory > 'maxmemory'.";
155
156#[derive(Debug, Clone, Copy, PartialEq, Eq)]
158pub enum Flow {
159 Continue,
161 Close,
163 Block,
170}
171
172#[derive(Debug, Default)]
182pub struct Counter(AtomicU64);
183
184impl Counter {
185 fn bump(&self) {
187 self.0.store(self.get().wrapping_add(1), Relaxed);
188 }
189
190 fn drop_one(&self) {
197 self.0.store(self.get().saturating_sub(1), Relaxed);
198 }
199
200 fn get(&self) -> u64 {
202 self.0.load(Relaxed)
203 }
204
205 fn zero(&self) {
207 self.0.store(0, Relaxed);
208 }
209}
210
211#[derive(Debug, Default)]
217pub struct Stats {
218 clients: Counter,
220 connections: Counter,
222 commands: Counter,
224}
225
226impl Stats {
227 pub fn opened(&self) {
229 self.clients.bump();
230 self.connections.bump();
231 }
232
233 pub fn closed(&self) {
235 self.clients.drop_one();
236 }
237}
238
239#[derive(Debug, Clone, Copy, Default)]
241pub struct Totals {
242 pub clients: u64,
244 pub connections: u64,
246 pub commands: u64,
248}
249
250thread_local! {
251 static SLOT: Cell<usize> = const { Cell::new(usize::MAX) };
259}
260
261#[derive(Debug)]
272#[repr(align(64))]
273struct Local {
274 stats: Stats,
276 cmdstats: CommandStats,
278 dirty: AtomicU64,
287 turn: AtomicU64,
300 parked: AtomicUsize,
312}
313
314impl Default for Local {
315 fn default() -> Local {
316 Local {
317 stats: Stats::default(),
318 cmdstats: CommandStats::default(),
319 dirty: AtomicU64::new(0),
320 turn: AtomicU64::new(ALL_DATABASES),
321 parked: AtomicUsize::new(0),
322 }
323 }
324}
325
326impl Local {
327 fn mark(&self, dbs: u64) {
329 self.dirty.store(self.dirty.load(Relaxed) | dbs, Relaxed);
330 }
331
332 fn note(&self, dbs: u64) {
334 self.turn.store(self.turn.load(Relaxed) | dbs, Relaxed);
335 }
336
337 fn done(&self, at: usize) {
339 self.turn
340 .store(self.turn.load(Relaxed) & !(1u64 << at), Relaxed);
341 }
342
343 fn wanted(&self, at: usize) -> bool {
345 self.turn.load(Relaxed) & (1u64 << at) != 0
346 }
347
348 fn blocked(&self, n: usize) {
350 self.parked
351 .store(self.parked.load(Relaxed).saturating_add(n), Relaxed);
352 }
353
354 fn woke(&self, n: usize) {
356 self.parked
357 .store(self.parked.load(Relaxed).saturating_sub(n), Relaxed);
358 }
359}
360
361fn one_thread() -> Box<[Local]> {
363 slots(1)
364}
365
366fn slots(threads: usize) -> Box<[Local]> {
368 (0..threads.max(1)).map(|_| Local::default()).collect()
369}
370
371fn working_dir() -> PathBuf {
378 std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
379}
380
381#[derive(Debug, Clone, Copy, Default)]
390pub struct CommandStat {
391 pub calls: u64,
393 pub rejected: u64,
396 pub failed: u64,
398}
399
400impl CommandStat {
401 const fn seen(&self) -> bool {
407 self.calls != 0 || self.rejected != 0 || self.failed != 0
408 }
409}
410
411#[derive(Debug, Default)]
416struct Row {
417 calls: Counter,
419 rejected: Counter,
421 failed: Counter,
423}
424
425#[derive(Debug)]
432struct CommandStats(Box<[Row]>);
433
434impl Default for CommandStats {
435 fn default() -> CommandStats {
436 CommandStats((0..table::count()).map(|_| Row::default()).collect())
437 }
438}
439
440impl CommandStats {
441 fn at(&self, spec: &'static Spec) -> &Row {
443 &self.0[table::index_of(spec)]
444 }
445}
446
447pub type StoreSource = dyn FnMut(usize) -> Option<Store> + Send;
453
454const _: () = {
459 const fn shareable<T: Send + Sync>() {}
460 shareable::<Server>();
461};
462
463pub struct Server {
471 dbs: Vec<Db>,
472 width: usize,
478 clock: Clock,
479 started_ms: u64,
480 next_db: AtomicUsize,
489 conn_bytes: AtomicUsize,
506 maxmemory: AtomicU64,
514 store: Lock<Option<Box<StoreSource>>>,
531 maxstore: AtomicU64,
553 used: AtomicUsize,
570 evict_db: AtomicUsize,
580 expire_db: AtomicUsize,
587 expire_ms: AtomicU64,
597 waiters: Lock<Waiters>,
605 parked: AtomicUsize,
618 peers: Lock<migrate::Peers>,
630 locals: Box<[Local]>,
636 claimed: AtomicUsize,
638 next_client: AtomicU64,
646 dir: PathBuf,
654 backup: Lock<backup::State>,
664 sealed: AtomicBool,
674 search: Lock<Registry>,
689 cursors: Lock<Cursors>,
698 stopping: AtomicBool,
707}
708
709impl Server {
710 #[must_use]
712 pub fn new() -> Server {
713 let clock = Clock::system();
714 Server {
715 dbs: (0..DATABASES)
716 .map(|_| Db::with_clock(clock.clone(), 1))
717 .collect(),
718 width: 1,
719 started_ms: clock.now_ms(),
720 clock,
721 next_db: AtomicUsize::new(0),
722 conn_bytes: AtomicUsize::new(0),
723 maxmemory: AtomicU64::new(0),
724 store: Lock::new(None),
725 maxstore: AtomicU64::new(NO_MAXSTORE),
726 used: AtomicUsize::new(0),
727 evict_db: AtomicUsize::new(0),
728 expire_db: AtomicUsize::new(0),
729 expire_ms: AtomicU64::new(0),
730 waiters: Lock::default(),
731 parked: AtomicUsize::new(0),
732 peers: Lock::default(),
733 locals: one_thread(),
734 claimed: AtomicUsize::new(0),
735 next_client: AtomicU64::new(1),
736 dir: working_dir(),
737 backup: Lock::default(),
738 sealed: AtomicBool::new(false),
739 search: Lock::new(Registry::new()),
740 cursors: Lock::default(),
741 stopping: AtomicBool::new(false),
742 }
743 }
744
745 #[must_use]
756 pub fn with_width(width: usize) -> Server {
757 let mut server = Server::new();
758 let clock = server.clock.clone();
762 server.dbs = (0..DATABASES)
763 .map(|_| Db::with_clock(clock.clone(), width))
764 .collect();
765 server.width = server.dbs[0].width();
766 server
767 }
768
769 #[must_use]
771 pub fn with_clock(clock: Clock) -> Server {
772 Server {
773 dbs: (0..DATABASES)
774 .map(|_| Db::with_clock(clock.clone(), 1))
775 .collect(),
776 width: 1,
777 started_ms: clock.now_ms(),
778 clock,
779 next_db: AtomicUsize::new(0),
780 conn_bytes: AtomicUsize::new(0),
781 maxmemory: AtomicU64::new(0),
782 store: Lock::new(None),
783 maxstore: AtomicU64::new(NO_MAXSTORE),
784 used: AtomicUsize::new(0),
785 evict_db: AtomicUsize::new(0),
786 expire_db: AtomicUsize::new(0),
787 expire_ms: AtomicU64::new(0),
788 waiters: Lock::default(),
789 parked: AtomicUsize::new(0),
790 peers: Lock::default(),
791 locals: one_thread(),
792 claimed: AtomicUsize::new(0),
793 next_client: AtomicU64::new(1),
794 dir: working_dir(),
795 backup: Lock::default(),
796 sealed: AtomicBool::new(false),
797 search: Lock::new(Registry::new()),
798 cursors: Lock::default(),
799 stopping: AtomicBool::new(false),
800 }
801 }
802
803 pub fn striped(&self, i: usize) -> &Db {
827 self.mine().mark(1u64 << i);
828 &self.dbs[i]
829 }
830
831 fn keyspaces(&self) -> impl Iterator<Item = Held<'_, Keyspace>> {
838 self.dbs
839 .iter()
840 .flat_map(|db| (0..db.width()).map(|i| db.hold_stripe(i)))
841 }
842
843 const fn slots(&self) -> usize {
849 DATABASES * self.width
850 }
851
852 const fn slot_db(&self, i: usize) -> usize {
854 i / self.width
855 }
856
857 fn slot(&self, i: usize) -> Held<'_, Keyspace> {
859 let (db, stripe) = (i / self.width, i % self.width);
860 self.dbs[db].hold_stripe(stripe)
861 }
862
863 #[must_use]
865 pub fn dir(&self) -> &Path {
866 &self.dir
867 }
868
869 pub fn set_dir(&mut self, dir: PathBuf) {
876 self.dir = dir;
877 }
878
879 pub fn backup_expire(&self) {
885 backup::expire(self);
886 }
887
888 pub fn stop(&self) {
894 self.stopping.store(true, Release);
895 }
896
897 #[must_use]
903 pub fn stopping(&self) -> bool {
904 self.stopping.load(Acquire)
905 }
906
907 #[must_use]
914 pub fn striped_ref(&self, i: usize) -> &Db {
915 &self.dbs[i]
916 }
917
918 fn settings(&self) -> Held<'_, Keyspace> {
926 self.dbs[0].hold_stripe(0)
927 }
928
929 pub fn refresh_clock(&self) {
940 self.clock.refresh();
941 }
942
943 pub fn advance_clock_ms(&self, ms: u64) {
949 let now = self.clock.now_ms() + ms;
950 self.set_clock_ms(now);
951 }
952
953 pub fn set_clock_ms(&self, ms: u64) {
961 self.clock.set(ms);
962 }
963
964 #[must_use]
966 pub fn uptime_secs(&self) -> u64 {
967 self.clock.now_ms().saturating_sub(self.started_ms) / 1000
968 }
969
970 #[must_use]
978 pub fn memory_bytes(&self) -> usize {
979 self.keyspaces().map(|db| db.memory_bytes()).sum::<usize>() + self.conn_bytes()
980 }
981
982 #[must_use]
988 pub fn dataset_bytes(&self) -> usize {
989 self.keyspaces()
990 .map(|db| db.map().arena().live_bytes() as usize)
991 .sum()
992 }
993
994 #[must_use]
996 pub fn arena_bytes(&self) -> usize {
997 self.keyspaces()
998 .map(|db| db.map().arena().reserved_bytes() as usize)
999 .sum()
1000 }
1001
1002 #[must_use]
1004 pub fn index_bytes(&self) -> usize {
1005 self.keyspaces()
1006 .map(|db| db.map().index().memory_bytes())
1007 .sum()
1008 }
1009
1010 #[must_use]
1017 pub fn compaction(&self) -> yo_kv::Compaction {
1018 self.keyspaces().map(|db| db.map().compaction()).fold(
1019 yo_kv::Compaction::default(),
1020 |a, b| yo_kv::Compaction {
1021 walked: a.walked + b.walked,
1022 moved: a.moved + b.moved,
1023 bytes: a.bytes + b.bytes,
1024 },
1025 )
1026 }
1027
1028 #[must_use]
1030 pub fn segment_count(&self) -> usize {
1031 self.keyspaces()
1032 .map(|db| db.map().arena().resident_segments())
1033 .sum()
1034 }
1035
1036 #[must_use]
1038 pub fn conn_bytes(&self) -> usize {
1039 self.conn_bytes.load(Relaxed)
1040 }
1041
1042 pub fn note_conn_bytes(&self, delta: isize) {
1050 self.conn_bytes
1056 .store(self.conn_bytes().saturating_add_signed(delta), Relaxed);
1057 }
1058
1059 #[must_use]
1061 pub fn expired_keys(&self) -> u64 {
1062 self.keyspaces().map(|db| db.expired_keys()).sum()
1063 }
1064
1065 #[must_use]
1067 pub fn evicted_keys(&self) -> u64 {
1068 self.keyspaces().map(|db| db.evicted_keys()).sum()
1069 }
1070
1071 pub fn command_stats(&self) -> impl Iterator<Item = (&'static str, CommandStat)> {
1077 (0..table::count())
1078 .map(|at| (table::name_at(at), self.command_stat(at)))
1079 .filter(|(_, row)| row.seen())
1080 }
1081
1082 fn command_stat(&self, at: usize) -> CommandStat {
1084 let mut sum = CommandStat::default();
1085 for thread in &self.locals {
1086 let row = &thread.cmdstats.0[at];
1087 sum.calls += row.calls.get();
1088 sum.rejected += row.rejected.get();
1089 sum.failed += row.failed.get();
1090 }
1091 sum
1092 }
1093
1094 pub fn counted(&self) -> &Stats {
1103 &self.mine().stats
1104 }
1105
1106 pub fn next_client(&self) -> u64 {
1111 self.next_client.fetch_add(1, Relaxed)
1112 }
1113
1114 pub fn my_slot(&self) -> usize {
1121 self.mine_at()
1122 }
1123
1124 fn mine(&self) -> &Local {
1126 &self.locals[self.mine_at()]
1127 }
1128
1129 fn mine_at(&self) -> usize {
1136 let mut slot = SLOT.get();
1137 if slot == usize::MAX {
1138 slot = self.claimed.fetch_add(1, Relaxed);
1139 SLOT.set(slot);
1140 }
1141 slot % self.locals.len()
1142 }
1143
1144 #[must_use]
1146 pub fn totals(&self) -> Totals {
1147 let mut sum = Totals::default();
1148 for thread in &self.locals {
1149 sum.clients += thread.stats.clients.get();
1150 sum.connections += thread.stats.connections.get();
1151 sum.commands += thread.stats.commands.get();
1152 }
1153 sum
1154 }
1155
1156 pub fn reset_stats(&self) {
1163 for thread in &self.locals {
1164 thread.stats.connections.zero();
1165 thread.stats.commands.zero();
1166 }
1167 }
1168
1169 pub fn set_threads(&mut self, threads: usize) {
1176 self.locals = slots(threads);
1177 self.claimed = AtomicUsize::new(0);
1178 }
1179
1180 #[must_use]
1182 pub fn maxmemory(&self) -> u64 {
1183 self.maxmemory.load(Relaxed)
1184 }
1185
1186 pub fn set_maxmemory(&self, bytes: u64) {
1200 self.maxmemory.store(bytes, Relaxed);
1201 for db in &self.dbs {
1202 db.track_memory(bytes != 0);
1203 }
1204 self.used.store(self.settled_memory(), Relaxed);
1205 }
1206
1207 pub fn set_store_source(
1217 &mut self,
1218 source: impl FnMut(usize) -> Option<Store> + Send + 'static,
1219 ) {
1220 *self.store.lock() = Some(Box::new(source));
1221 }
1222
1223 #[must_use]
1225 pub fn has_store_source(&self) -> bool {
1226 self.store.lock().is_some()
1227 }
1228
1229 fn attach_store(&self, at: usize) {
1236 if self.slot(at).store_bytes().is_some() {
1237 return;
1238 }
1239 let mut source = self.store.lock();
1243 let Some(source) = source.as_mut() else {
1244 return;
1245 };
1246 if let Some(blocks) = source(at) {
1247 self.slot(at).attach(blocks);
1248 }
1249 }
1250
1251 #[must_use]
1253 pub fn maxstore(&self) -> Option<u64> {
1254 match self.maxstore.load(Relaxed) {
1255 NO_MAXSTORE => None,
1256 bytes => Some(bytes),
1257 }
1258 }
1259
1260 pub fn set_maxstore(&self, bytes: Option<u64>) {
1266 self.maxstore.store(bytes.unwrap_or(NO_MAXSTORE), Relaxed);
1267 }
1268
1269 #[must_use]
1275 pub fn store_bytes(&self) -> u64 {
1276 self.keyspaces().filter_map(|db| db.store_bytes()).sum()
1277 }
1278
1279 #[must_use]
1294 pub fn cold_stats(&self) -> yo_kv::tier::Stats {
1295 let mut total = yo_kv::tier::Stats::default();
1296 for db in self.keyspaces() {
1297 let Some(tier) = db.tier() else { continue };
1298 let s = tier.stats();
1299 total.demoted += s.demoted;
1300 total.promoted += s.promoted;
1301 total.faults += s.faults;
1302 total.served += s.served;
1303 total.bytes_out += s.bytes_out;
1304 total.bytes_in += s.bytes_in;
1305 }
1306 total
1307 }
1308
1309 #[must_use]
1316 pub fn regime(&self) -> &'static str {
1317 if (0..self.slots()).any(|at| self.migrates(at)) {
1318 "migrate"
1319 } else {
1320 "evict"
1321 }
1322 }
1323
1324 fn migrates(&self, at: usize) -> bool {
1336 let cap = self.maxstore();
1337 if cap == Some(0) {
1338 return false;
1339 }
1340 let bytes = self.slot(at).store_bytes();
1344 match bytes {
1345 Some(held) => cap.is_none_or(|cap| held < cap),
1346 None => self.store.lock().is_some(),
1350 }
1351 }
1352
1353 pub fn refresh_memory(&self) {
1358 if self.maxmemory() != 0 {
1359 self.used.store(self.settled_memory(), Relaxed);
1360 }
1361 }
1362
1363 fn settled_memory(&self) -> usize {
1370 self.keyspaces()
1371 .map(|mut db| db.settled_memory_bytes())
1372 .sum::<usize>()
1373 + self.conn_bytes()
1374 }
1375
1376 pub fn make_room(&self) -> bool {
1410 let limit = self.maxmemory();
1411 if limit == 0 || self.used.load(Relaxed) as u64 <= limit {
1412 return true;
1413 }
1414 let mut used = self.settled_memory();
1419 self.used.store(used, Relaxed);
1420 let mut budget = EVICT_BUDGET;
1421 while used as u64 > limit {
1422 let over = used - limit as usize;
1423 if !self.relieve_step(over) {
1424 return false;
1425 }
1426 self.compact_hard_step();
1427 used = self.settled_memory();
1428 self.used.store(used, Relaxed);
1429 budget -= 1;
1430 if budget == 0 {
1431 break;
1432 }
1433 }
1434 true
1435 }
1436
1437 fn relieve_step(&self, over: usize) -> bool {
1454 let from = self.evict_db.load(Relaxed);
1455 for turn in 0..self.slots() {
1456 let i = (from + turn) % self.slots();
1457 let used = !self.slot(i).is_empty();
1460 let gave = if used && self.migrates(i) {
1461 self.attach_store(i);
1462 self.slot(i)
1467 .relieve(over)
1468 .is_ok_and(yo_kv::tier::Relief::made_room)
1469 } else {
1470 self.slot(i).evict_one()
1471 };
1472 if gave {
1473 self.evict_db.store((i + 1) % self.slots(), Relaxed);
1474 self.mine().mark(1u64 << self.slot_db(i));
1475 return true;
1476 }
1477 }
1478 false
1479 }
1480
1481 pub fn expire_slice(&self, budget: usize) -> usize {
1496 let now = self.clock.now_ms();
1497 if now == self.expire_ms.load(Relaxed) {
1498 return 0;
1499 }
1500 self.expire_ms.store(now, Relaxed);
1501 self.expire_step(budget)
1502 }
1503
1504 pub fn expire_step(&self, budget: usize) -> usize {
1520 let mut spent = 0;
1521 let from = self.expire_db.load(Relaxed);
1522 for turn in 0..self.slots() {
1523 if spent >= budget {
1524 break;
1525 }
1526 let i = (from + turn) % self.slots();
1527 let c = self.slot(i).expire_cycle(budget - spent);
1528 spent += c.examined;
1529 if c.expired > 0 {
1530 self.expire_db.store((i + 1) % self.slots(), Relaxed);
1531 self.mine().note(1u64 << self.slot_db(i));
1532 }
1533 }
1534 spent
1535 }
1536
1537 fn compact_hard_step(&self) -> Option<usize> {
1543 let from = self.next_db.load(Relaxed);
1544 for turn in 0..self.slots() {
1545 let i = (from + turn) % self.slots();
1546 if let Some(moved) = self.slot(i).compact_hard() {
1547 self.next_db.store((i + 1) % self.slots(), Relaxed);
1548 return Some(moved);
1549 }
1550 }
1551 None
1552 }
1553
1554 fn collect_marks(&self) {
1563 let mut marked = 0;
1564 for thread in &self.locals {
1565 marked |= thread.dirty.swap(0, Relaxed);
1566 }
1567 self.mine().note(marked);
1568 }
1569
1570 pub fn compact_step(&self) -> Option<usize> {
1583 self.collect_marks();
1584 let mine = self.mine();
1585 let from = self.next_db.load(Relaxed);
1586 for turn in 0..self.slots() {
1587 let i = (from + turn) % self.slots();
1588 let at = self.slot_db(i);
1592 if !mine.wanted(at) {
1593 continue;
1594 }
1595 if let Some(moved) = self.slot(i).compact_step() {
1596 self.next_db.store((i + 1) % self.slots(), Relaxed);
1597 return Some(moved);
1598 }
1599 if i % self.width == self.width - 1 {
1603 mine.done(at);
1604 }
1605 }
1606 None
1607 }
1608}
1609
1610impl Default for Server {
1611 fn default() -> Server {
1612 Server::new()
1613 }
1614}
1615
1616pub struct Session {
1618 db: usize,
1619 id: u64,
1620 name: Vec<u8>,
1621 sets: himport::Fieldsets,
1627}
1628
1629impl Session {
1630 #[must_use]
1632 pub fn new(id: u64) -> Session {
1633 Session {
1634 db: 0,
1635 id,
1636 name: Vec::new(),
1637 sets: himport::Fieldsets::default(),
1638 }
1639 }
1640
1641 #[must_use]
1643 pub const fn id(&self) -> u64 {
1644 self.id
1645 }
1646
1647 #[must_use]
1649 pub const fn db(&self) -> usize {
1650 self.db
1651 }
1652
1653 #[must_use]
1655 pub fn name(&self) -> &[u8] {
1656 &self.name
1657 }
1658
1659 pub fn reset(&mut self) {
1664 self.db = 0;
1665 self.name.clear();
1666 self.sets.clear();
1670 }
1671
1672 fn set_name(&mut self, name: &[u8]) {
1674 yo_alloc::allow(|| {
1675 self.name.clear();
1676 self.name.extend_from_slice(name);
1677 });
1678 }
1679}
1680
1681pub fn execute(server: &Server, session: &mut Session, args: Args<'_>, out: &mut Out) -> Flow {
1686 if args.is_empty() {
1689 return Flow::Continue;
1690 }
1691 resolved(server, session, lookup(args.name()), args, out)
1692}
1693
1694pub fn resolved(
1706 server: &Server,
1707 session: &mut Session,
1708 spec: Option<&'static Spec>,
1709 args: Args<'_>,
1710 out: &mut Out,
1711) -> Flow {
1712 if args.is_empty() {
1713 return Flow::Continue;
1714 }
1715 server.mine().stats.commands.bump();
1716
1717 let Some(spec) = spec else {
1718 write_error(out, &args::unknown_command(args));
1719 return Flow::Continue;
1720 };
1721 if !arity_ok(spec, args.len()) {
1722 server.mine().cmdstats.at(spec).rejected.bump();
1723 write_error(out, &args::wrong_arity(spec.name));
1724 return Flow::Continue;
1725 }
1726
1727 if server.maxmemory() != 0 && !server.make_room() && spec.flags.contains(&"denyoom") {
1737 server.mine().cmdstats.at(spec).rejected.bump();
1738 out.error_line(b"OOM ", OOM);
1739 return Flow::Continue;
1740 }
1741
1742 server.mine().mark(match spec.group {
1749 "string" | "bitmap" | "hyperloglog" | "geo" | "set" | "hash" | "list" | "zset"
1750 | "array" | "stream" | "bloom" | "cuckoo" | "cms" | "topk" | "tdigest" | "ts" => {
1751 1u64 << session.db
1752 }
1753 _ => ALL_DATABASES,
1754 });
1755
1756 let mark = out.len();
1757 let done = if spec.flags.contains(&"blocking") {
1764 blocking::execute(server, session, spec, args, out)
1765 } else {
1766 match spec.group {
1767 "string" => {
1768 let db = session.db;
1769 strings::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1770 }
1771 "bitmap" => {
1775 let db = session.db;
1776 bits::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1777 }
1778 "hyperloglog" => {
1781 let db = session.db;
1782 hll::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1783 }
1784 "set" => {
1785 let db = session.db;
1786 sets::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1787 }
1788 "hash" if spec.name == "himport" => {
1793 let db = session.db;
1794 himport::execute(&server.dbs[db], &mut session.sets, args, out)
1795 .map(|()| Flow::Continue)
1796 }
1797 "hash" => {
1802 let db = session.db;
1803 let changed = hashes::execute(&server.dbs[db], spec, args, out);
1804 changed.map(|changed| {
1805 indexing::changed(server, db, args.get(1), changed);
1806 Flow::Continue
1807 })
1808 }
1809 "list" => {
1810 let db = session.db;
1811 lists::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1812 }
1813 "zset" => {
1814 let db = session.db;
1815 zsets::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1816 }
1817 "geo" => {
1821 let db = session.db;
1822 geo::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1823 }
1824 "array" => {
1825 let db = session.db;
1826 arrays::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1827 }
1828 "graph" => {
1829 let db = session.db;
1830 graph::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1831 }
1832 "json" => {
1836 let db = session.db;
1837 json::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1838 }
1839 "vector" => {
1840 let db = session.db;
1841 vectors::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1842 }
1843 "bloom" => {
1844 let db = session.db;
1845 bloom::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1846 }
1847 "cuckoo" => {
1848 let db = session.db;
1849 cuckoo::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1850 }
1851 "cms" => {
1852 let db = session.db;
1853 cms::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1854 }
1855 "topk" => {
1856 let db = session.db;
1857 topk::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1858 }
1859 "tdigest" => {
1860 let db = session.db;
1861 tdigest::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1862 }
1863 "ts" => {
1864 let db = session.db;
1865 ts::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1866 }
1867 "stream" => {
1872 let db = session.db;
1873 let now = server.now_ms();
1874 streams::execute(&server.dbs[db], spec, args, now, out).map(|()| Flow::Continue)
1875 }
1876 "keyspace" if spec.name == "migrate" => {
1880 migrate::execute(server, session.db, args, out).map(|()| Flow::Continue)
1881 }
1882 "keyspace" => {
1888 let mut touched = indexing::Touched::new(server);
1889 let done =
1890 keyspace::execute(&server.dbs, session.db, spec, args, out, &mut touched);
1891 done.map(|()| {
1892 indexing::touched(server, &touched);
1893 Flow::Continue
1894 })
1895 }
1896 "search" if spec.name == "FT.SEARCH" => {
1903 search::find(server, session.db, args, out).map(|()| Flow::Continue)
1908 }
1909 "search" if spec.name == "FT.AGGREGATE" => {
1910 search::roll(server, session.db, args, out).map(|()| Flow::Continue)
1911 }
1912 "search" if spec.name == "FT.PROFILE" => {
1913 search::profiled(server, session.db, args, out).map(|()| Flow::Continue)
1916 }
1917 "search" if spec.name.starts_with("FT.SUG") => {
1921 let db = session.db;
1922 suggest::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1923 }
1924 "search" if spec.name == "FT.CURSOR" => {
1925 search::cursor::execute(server, args, out).map(|()| Flow::Continue)
1929 }
1930 "search" => {
1931 let db = session.db;
1932 let made = search::execute(server, &mut server.search.lock(), db, spec, args, out);
1933 made.map(|made| {
1934 if let Some(fill) = made {
1935 indexing::scan(server, db, &fill);
1936 }
1937 Flow::Continue
1938 })
1939 }
1940 "scripting" => scripting::execute(spec, args, out).map(|()| Flow::Continue),
1941 _ => server::execute(server, session, spec, args, out),
1942 }
1943 };
1944 let flow = match done {
1945 Ok(flow) => flow,
1946 Err(e) => {
1947 out.truncate(mark);
1948 write_error(out, &e);
1949 Flow::Continue
1950 }
1951 };
1952
1953 let row = server.mine().cmdstats.at(spec);
1964 row.calls.bump();
1965 if matches!(out.as_slice().get(mark), Some(b'-' | b'!')) {
1966 row.failed.bump();
1967 }
1968 flow
1969}
1970
1971fn write_error(out: &mut Out, e: &Error) {
1981 let prefix: &[u8] = match e.code() {
1982 Code::WrongType => b"WRONGTYPE ",
1983 Code::Corrupt => b"INVALIDOBJ ",
1987 _ => b"ERR ",
1988 };
1989 out.error_line(prefix, e.message().as_bytes());
1990}
1991
1992#[cfg(test)]
1993mod tests {
1994 use super::*;
1995 use crate::proto::{Limits, Proto};
1996 use crate::request::Argv;
1997
1998 pub(crate) fn encode(parts: &[&[u8]]) -> Vec<u8> {
2003 let mut wire = format!("*{}\r\n", parts.len()).into_bytes();
2004 for p in parts {
2005 wire.extend_from_slice(format!("${}\r\n", p.len()).as_bytes());
2006 wire.extend_from_slice(p);
2007 wire.extend_from_slice(b"\r\n");
2008 }
2009 wire
2010 }
2011
2012 struct Fixture {
2014 server: Server,
2015 session: Session,
2016 argv: Argv,
2017 out: Out,
2018 }
2019
2020 impl Fixture {
2021 fn new() -> Fixture {
2022 Fixture::on(Server::new())
2023 }
2024
2025 fn striped(width: usize) -> Fixture {
2027 Fixture::on(Server::with_width(width))
2028 }
2029
2030 fn on(server: Server) -> Fixture {
2031 Fixture {
2032 server,
2033 session: Session::new(7),
2034 argv: Argv::new(),
2035 out: Out::new(Proto::Resp2),
2036 }
2037 }
2038
2039 fn run(&mut self, parts: &[&[u8]]) -> String {
2041 self.flow(parts).1
2042 }
2043
2044 fn raw(&mut self, parts: &[&[u8]]) -> Vec<u8> {
2050 let wire = encode(parts);
2051 self.argv.decode(&wire, &Limits::default()).unwrap();
2052 self.out.clear();
2053 execute(
2054 &self.server,
2055 &mut self.session,
2056 Args::new(&self.argv, &wire),
2057 &mut self.out,
2058 );
2059 self.out.as_slice().to_vec()
2060 }
2061
2062 fn advance(&mut self, ms: u64) {
2064 self.server.advance_clock_ms(ms);
2065 }
2066
2067 fn flow(&mut self, parts: &[&[u8]]) -> (Flow, String) {
2069 let wire = encode(parts);
2070 self.argv.decode(&wire, &Limits::default()).unwrap();
2071 self.out.clear();
2072 let flow = execute(
2073 &self.server,
2074 &mut self.session,
2075 Args::new(&self.argv, &wire),
2076 &mut self.out,
2077 );
2078 (
2079 flow,
2080 String::from_utf8_lossy(self.out.as_slice()).into_owned(),
2081 )
2082 }
2083 }
2084
2085 #[cfg_attr(miri, ignore = "megabytes through the arena")]
2098 #[test]
2099 fn rewriting_the_same_keys_does_not_grow_the_server() {
2100 let mut f = Fixture::new();
2101 let val = vec![b'v'; 1024];
2102 let keys: Vec<Vec<u8>> = (0..64).map(|i| format!("key:{i}").into_bytes()).collect();
2103
2104 for k in &keys {
2105 f.run(&[b"SET", k, &val]);
2106 }
2107 f.server.compact_step();
2108 let after_first = f.server.memory_bytes();
2109
2110 for _ in 0..500 {
2115 for k in &keys {
2116 f.run(&[b"SET", k, &val]);
2117 }
2118 f.server.compact_step();
2119 }
2120
2121 assert!(
2122 f.server.memory_bytes() <= after_first * 2,
2123 "held {} after five hundred passes against {after_first} after one",
2124 f.server.memory_bytes()
2125 );
2126 assert_eq!(f.run(&[b"DBSIZE"]), format!(":{}\r\n", keys.len()));
2127 assert_eq!(f.run(&[b"STRLEN", b"key:7"]), ":1024\r\n");
2128 }
2129
2130 #[cfg_attr(miri, ignore = "megabytes through the arena")]
2147 #[test]
2148 fn a_database_nobody_started_on_is_still_collected() {
2149 let mut f = Fixture::new();
2150 assert_eq!(f.run(&[b"SELECT", b"9"]), "+OK\r\n");
2151 let val = vec![b'v'; 1024];
2152 let keys: Vec<Vec<u8>> = (0..64).map(|i| format!("key:{i}").into_bytes()).collect();
2153
2154 for k in &keys {
2155 f.run(&[b"SET", k, &val]);
2156 }
2157 while f.server.compact_step().is_some() {}
2158 assert!(
2159 !f.server.mine().wanted(9),
2160 "database nine was drained and should not be asked again until it is written to"
2161 );
2162 let after_first = f.server.memory_bytes();
2163
2164 for _ in 0..500 {
2165 for k in &keys {
2166 f.run(&[b"SET", k, &val]);
2167 }
2168 f.server.compact_step();
2169 }
2170
2171 assert!(
2172 f.server.memory_bytes() <= after_first * 2,
2173 "held {} after five hundred passes against {after_first} after one",
2174 f.server.memory_bytes()
2175 );
2176 assert_eq!(f.run(&[b"DBSIZE"]), format!(":{}\r\n", keys.len()));
2177 assert_eq!(f.run(&[b"STRLEN", b"key:7"]), ":1024\r\n");
2178 f.run(&[b"SELECT", b"0"]);
2180 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
2181 }
2182
2183 #[test]
2184 fn a_command_goes_from_bytes_to_bytes() {
2185 let mut f = Fixture::new();
2186 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
2187 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
2188 assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
2189 assert_eq!(f.run(&[b"STRLEN", b"k"]), ":1\r\n");
2190 assert_eq!(f.run(&[b"set", b"k", b"v2", b"xx"]), "+OK\r\n");
2192 assert_eq!(f.run(&[b"GET", b"k"]), "$2\r\nv2\r\n");
2193 }
2194
2195 #[test]
2196 fn deleting_counts_keys_removed_and_existing_counts_arguments_matched() {
2197 let mut f = Fixture::new();
2198 f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
2199 assert_eq!(f.run(&[b"EXISTS", b"a", b"a", b"nosuch"]), ":2\r\n");
2202 assert_eq!(f.run(&[b"DEL", b"a", b"a", b"nosuch"]), ":1\r\n");
2203 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
2204 assert_eq!(f.run(&[b"UNLINK", b"b", b"c"]), ":2\r\n");
2206 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
2207 }
2208
2209 #[test]
2210 fn type_is_a_simple_string_and_says_none_for_a_key_that_is_not_there() {
2211 let mut f = Fixture::new();
2212 f.run(&[b"SET", b"k", b"v"]);
2213 assert_eq!(f.run(&[b"TYPE", b"k"]), "+string\r\n");
2216 assert_eq!(f.run(&[b"TYPE", b"nosuch"]), "+none\r\n");
2217 }
2218
2219 #[test]
2220 fn touch_counts_the_way_exists_counts() {
2221 let mut f = Fixture::new();
2222 f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
2223 assert_eq!(f.run(&[b"TOUCH", b"a", b"b"]), ":2\r\n");
2224 assert_eq!(
2225 f.run(&[b"TOUCH", b"a", b"a"]),
2226 ":2\r\n",
2227 "twice counts twice"
2228 );
2229 assert_eq!(f.run(&[b"TOUCH", b"a", b"nosuch"]), ":1\r\n");
2230 assert_eq!(f.run(&[b"TOUCH", b"nosuch"]), ":0\r\n");
2231 }
2232
2233 #[test]
2234 fn a_rename_moves_the_deadline_with_the_value_and_drops_the_one_it_lands_on() {
2235 let mut f = Fixture::new();
2236 f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
2237 f.run(&[b"SET", b"b", b"v2", b"EX", b"500"]);
2238
2239 assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "+OK\r\n");
2240 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
2241 assert_eq!(
2242 f.run(&[b"TTL", b"b"]),
2243 ":100\r\n",
2244 "the source's and not b's"
2245 );
2246 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
2247 }
2248
2249 #[test]
2250 fn a_rename_with_no_source_is_an_error_and_not_a_zero() {
2251 let mut f = Fixture::new();
2252 assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "-ERR no such key\r\n");
2253 assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), "-ERR no such key\r\n");
2256 }
2257
2258 #[test]
2259 fn renamenx_refuses_a_taken_name_including_the_one_it_already_has() {
2260 let mut f = Fixture::new();
2261 f.run(&[b"MSET", b"a", b"v1", b"b", b"v2"]);
2262
2263 assert_eq!(f.run(&[b"RENAMENX", b"a", b"b"]), ":0\r\n");
2264 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
2265 assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), ":0\r\n");
2268 assert_eq!(f.run(&[b"RENAME", b"a", b"a"]), "+OK\r\n");
2269 assert_eq!(f.run(&[b"RENAMENX", b"a", b"c"]), ":1\r\n");
2270 assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\nv1\r\n");
2271 }
2272
2273 #[test]
2274 fn renaming_a_set_does_not_touch_a_member() {
2275 let mut f = Fixture::new();
2276 for i in 0..300 {
2277 f.run(&[b"SADD", b"s", format!("m{i}").as_bytes()]);
2278 }
2279 let before = f.server.memory_bytes();
2280
2281 assert_eq!(f.run(&[b"RENAME", b"s", b"t"]), "+OK\r\n");
2282 assert_eq!(f.run(&[b"SCARD", b"t"]), ":300\r\n");
2283 assert_eq!(f.run(&[b"TYPE", b"t"]), "+set\r\n");
2284 assert!(
2285 f.server.memory_bytes().abs_diff(before) < 256,
2286 "the members were copied: {} against {before}",
2287 f.server.memory_bytes()
2288 );
2289 }
2290
2291 #[test]
2292 fn a_copy_is_a_second_value_and_not_a_second_name() {
2293 let mut f = Fixture::new();
2294 f.run(&[b"SADD", b"s", b"m1", b"m2"]);
2295
2296 assert_eq!(f.run(&[b"COPY", b"s", b"t"]), ":1\r\n");
2297 f.run(&[b"SADD", b"t", b"m3"]);
2298 assert_eq!(f.run(&[b"SCARD", b"s"]), ":2\r\n", "the original is intact");
2299 assert_eq!(f.run(&[b"SCARD", b"t"]), ":3\r\n");
2300 }
2301
2302 #[test]
2312 fn every_type_can_be_copied() {
2313 let mut f = Fixture::new();
2314 f.run(&[b"SET", b"str", b"v1"]);
2315 f.run(&[b"SADD", b"set", b"m1"]);
2316 f.run(&[b"HSET", b"hash", b"f", b"v"]);
2317 f.run(&[b"RPUSH", b"list", b"a", b"b"]);
2318 f.run(&[b"ZADD", b"zset", b"1", b"m1"]);
2319
2320 for name in [
2321 &b"str"[..],
2322 &b"set"[..],
2323 &b"hash"[..],
2324 &b"list"[..],
2325 &b"zset"[..],
2326 ] {
2327 let dst = [name, b":copy"].concat();
2328 assert_eq!(
2329 f.run(&[b"COPY", name, &dst]),
2330 ":1\r\n",
2331 "copying {}",
2332 String::from_utf8_lossy(name)
2333 );
2334 assert_eq!(f.run(&[b"TYPE", name]), f.run(&[b"TYPE", &dst]));
2335 }
2336
2337 assert_eq!(f.run(&[b"LRANGE", b"list:copy", b"0", b"-1"]), {
2338 let mut want = String::from("*2\r\n");
2339 want.push_str("$1\r\na\r\n$1\r\nb\r\n");
2340 want
2341 });
2342 assert_eq!(f.run(&[b"ZSCORE", b"zset:copy", b"m1"]), "$1\r\n1\r\n");
2343
2344 f.run(&[b"RPUSH", b"list:copy", b"c"]);
2346 assert_eq!(f.run(&[b"LLEN", b"list"]), ":2\r\n");
2347 assert_eq!(f.run(&[b"LLEN", b"list:copy"]), ":3\r\n");
2348 }
2349
2350 #[test]
2351 fn a_copy_refuses_a_taken_destination_until_it_is_told_it_can_have_it() {
2352 let mut f = Fixture::new();
2353 f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
2354 f.run(&[b"SET", b"b", b"v2"]);
2355
2356 assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":0\r\n");
2357 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
2358 assert_eq!(f.run(&[b"COPY", b"a", b"b", b"REPLACE"]), ":1\r\n");
2359 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
2360 assert_eq!(f.run(&[b"TTL", b"b"]), ":100\r\n", "the deadline came too");
2361 assert_eq!(f.run(&[b"COPY", b"nosuch", b"z"]), ":0\r\n");
2362 }
2363
2364 #[test]
2365 fn a_copy_into_another_database_is_a_copy_and_onto_itself_there_is_too() {
2366 let mut f = Fixture::new();
2367 f.run(&[b"SET", b"a", b"v1"]);
2368
2369 assert_eq!(f.run(&[b"COPY", b"a", b"a", b"DB", b"1"]), ":1\r\n");
2372 f.run(&[b"SELECT", b"1"]);
2373 assert_eq!(f.run(&[b"GET", b"a"]), "$2\r\nv1\r\n");
2374 assert_eq!(
2375 f.run(&[b"COPY", b"a", b"a", b"DB", b"0"]),
2376 ":0\r\n",
2377 "taken"
2378 );
2379 assert_eq!(
2380 f.run(&[b"COPY", b"a", b"a", b"DB", b"0", b"REPLACE"]),
2381 ":1\r\n"
2382 );
2383 }
2384
2385 #[test]
2386 fn sort_takes_its_options_in_any_order_and_the_last_one_wins() {
2387 let mut f = Fixture::new();
2388 f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
2389 assert_eq!(
2390 f.run(&[b"SORT", b"l"]),
2391 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
2392 );
2393 assert_eq!(
2395 f.run(&[b"SORT", b"l", b"DESC", b"asc"]),
2396 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
2397 );
2398 assert_eq!(
2399 f.run(&[b"sort", b"l", b"LIMIT", b"1", b"1", b"DESC"]),
2400 "*1\r\n$1\r\n2\r\n"
2401 );
2402 }
2403
2404 #[test]
2405 fn sort_reads_a_key_per_element_for_by_and_for_get() {
2406 let mut f = Fixture::new();
2407 f.run(&[b"RPUSH", b"l", b"a", b"b"]);
2408 f.run(&[b"MSET", b"w_a", b"2", b"w_b", b"1", b"d_b", b"bee"]);
2409 assert_eq!(
2412 f.run(&[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"]),
2413 "*4\r\n$1\r\nb\r\n$3\r\nbee\r\n$1\r\na\r\n$-1\r\n"
2414 );
2415 }
2416
2417 #[test]
2418 fn sort_store_writes_a_list_and_answers_its_length() {
2419 let mut f = Fixture::new();
2420 f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
2421 assert_eq!(f.run(&[b"SORT", b"l", b"STORE", b"out"]), ":3\r\n");
2422 assert_eq!(f.run(&[b"TYPE", b"out"]), "+list\r\n");
2423 assert_eq!(
2424 f.run(&[b"LRANGE", b"out", b"0", b"-1"]),
2425 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
2426 );
2427 assert_eq!(f.run(&[b"SORT", b"missing", b"STORE", b"out"]), ":0\r\n");
2430 assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
2431 }
2432
2433 #[test]
2434 fn sort_ro_does_not_know_the_word_store() {
2435 let mut f = Fixture::new();
2436 f.run(&[b"RPUSH", b"l", b"2", b"1"]);
2437 assert_eq!(f.run(&[b"SORT_RO", b"l"]), "*2\r\n$1\r\n1\r\n$1\r\n2\r\n");
2438 assert_eq!(
2439 f.run(&[b"SORT_RO", b"l", b"STORE", b"d"]),
2440 "-ERR syntax error\r\n"
2441 );
2442 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
2443 }
2444
2445 #[test]
2446 fn sort_refuses_what_it_cannot_sort() {
2447 let mut f = Fixture::new();
2448 assert_eq!(f.run(&[b"SORT", b"nosuchkey"]), "*0\r\n");
2449 f.run(&[b"SET", b"s", b"x"]);
2450 assert_eq!(
2451 f.run(&[b"SORT", b"s"]),
2452 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
2453 );
2454 f.run(&[b"RPUSH", b"words", b"one", b"two"]);
2455 assert_eq!(
2456 f.run(&[b"SORT", b"words"]),
2457 "-ERR One or more scores can't be converted into double\r\n"
2458 );
2459 assert_eq!(
2460 f.run(&[b"SORT", b"words", b"ALPHA"]),
2461 "*2\r\n$3\r\none\r\n$3\r\ntwo\r\n"
2462 );
2463 assert_eq!(f.run(&[b"SORT", b"words", b"BY"]), "-ERR syntax error\r\n");
2464 }
2465
2466 #[test]
2467 fn move_takes_the_key_out_of_one_database_and_puts_it_in_another() {
2468 let mut f = Fixture::new();
2469 assert_eq!(f.run(&[b"RPUSH", b"l", b"a", b"b"]), ":2\r\n");
2470 assert_eq!(f.run(&[b"MOVE", b"l", b"1"]), ":1\r\n");
2471 assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
2472 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2473 assert_eq!(
2474 f.run(&[b"LRANGE", b"l", b"0", b"-1"]),
2475 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
2476 );
2477 assert_eq!(f.run(&[b"MOVE", b"l", b"0"]), ":1\r\n");
2480 assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
2481 }
2482
2483 #[test]
2484 fn move_answers_zero_when_either_end_says_no() {
2485 let mut f = Fixture::new();
2486 assert_eq!(f.run(&[b"MOVE", b"nope", b"1"]), ":0\r\n");
2487 assert_eq!(f.run(&[b"SET", b"a", b"here"]), "+OK\r\n");
2488 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2489 assert_eq!(f.run(&[b"SET", b"a", b"there"]), "+OK\r\n");
2490 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
2491 assert_eq!(f.run(&[b"MOVE", b"a", b"1"]), ":0\r\n");
2494 assert_eq!(f.run(&[b"GET", b"a"]), "$4\r\nhere\r\n");
2495 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2496 assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nthere\r\n");
2497 }
2498
2499 #[test]
2500 fn move_refuses_a_database_that_is_not_one_and_the_one_it_is_on() {
2501 let mut f = Fixture::new();
2502 assert_eq!(
2503 f.run(&[b"MOVE", b"a", b"0"]),
2504 "-ERR source and destination objects are the same\r\n"
2505 );
2506 assert_eq!(
2507 f.run(&[b"MOVE", b"a", b"99"]),
2508 "-ERR DB index is out of range\r\n"
2509 );
2510 assert_eq!(
2511 f.run(&[b"MOVE", b"a", b"-1"]),
2512 "-ERR DB index is out of range\r\n"
2513 );
2514 assert_eq!(
2515 f.run(&[b"MOVE", b"a", b"x"]),
2516 "-ERR value is not an integer or out of range\r\n"
2517 );
2518 }
2519
2520 #[test]
2521 fn swapdb_swaps_what_two_connections_would_see() {
2522 let mut f = Fixture::new();
2523 assert_eq!(f.run(&[b"SET", b"k", b"zero"]), "+OK\r\n");
2524 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2525 assert_eq!(f.run(&[b"SET", b"k", b"one"]), "+OK\r\n");
2526 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
2527
2528 assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
2529 assert_eq!(f.run(&[b"GET", b"k"]), "$3\r\none\r\n");
2531 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2532 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
2533 assert_eq!(f.run(&[b"SWAPDB", b"1", b"1"]), "+OK\r\n");
2535 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
2536 }
2537
2538 #[test]
2543 fn a_wide_server_puts_its_databases_on_its_own_clock() {
2544 let mut f = Fixture::striped(8);
2545 f.server.set_clock_ms(1_700_000_000_000);
2546 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"100"]), "+OK\r\n");
2547 assert_eq!(f.run(&[b"EXPIRETIME", b"k"]), ":1700000100\r\n");
2548 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
2549 f.server.set_clock_ms(1_700_000_050_000);
2550 assert_eq!(f.run(&[b"TTL", b"k"]), ":50\r\n");
2551 }
2552
2553 #[test]
2558 fn swapdb_swaps_every_stripe_of_a_wide_database() {
2559 let mut f = Fixture::striped(4);
2560 for i in 0..16u32 {
2561 let key = format!("k{i}");
2562 assert_eq!(f.run(&[b"SET", key.as_bytes(), b"zero"]), "+OK\r\n");
2563 }
2564 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2565 assert_eq!(f.run(&[b"SET", b"only", b"one"]), "+OK\r\n");
2566 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
2567
2568 assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
2569 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
2570 assert_eq!(f.run(&[b"GET", b"only"]), "$3\r\none\r\n");
2571 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2572 assert_eq!(f.run(&[b"DBSIZE"]), ":16\r\n");
2573 for i in 0..16u32 {
2574 let key = format!("k{i}");
2575 assert_eq!(f.run(&[b"GET", key.as_bytes()]), "$4\r\nzero\r\n");
2576 }
2577 }
2578
2579 #[test]
2580 fn swapdb_says_which_index_it_could_not_read() {
2581 let mut f = Fixture::new();
2582 assert_eq!(
2583 f.run(&[b"SWAPDB", b"x", b"1"]),
2584 "-ERR invalid first DB index\r\n"
2585 );
2586 assert_eq!(
2587 f.run(&[b"SWAPDB", b"0", b"y"]),
2588 "-ERR invalid second DB index\r\n"
2589 );
2590 assert_eq!(
2594 f.run(&[b"SWAPDB", b"99999999999999", b"1"]),
2595 "-ERR invalid first DB index\r\n"
2596 );
2597 assert_eq!(
2598 f.run(&[b"SWAPDB", b"0", b"99"]),
2599 "-ERR DB index is out of range\r\n"
2600 );
2601 assert_eq!(
2602 f.run(&[b"SWAPDB", b"-1", b"0"]),
2603 "-ERR DB index is out of range\r\n"
2604 );
2605 }
2606
2607 #[test]
2608 fn wait_answers_zero_replicas_without_waiting() {
2609 let mut f = Fixture::new();
2610 assert_eq!(f.run(&[b"SET", b"a", b"v"]), "+OK\r\n");
2611 assert_eq!(f.run(&[b"WAIT", b"0", b"0"]), ":0\r\n");
2612 assert_eq!(f.run(&[b"WAIT", b"3", b"1000"]), ":0\r\n");
2615 assert_eq!(f.run(&[b"WAIT", b"-1", b"0"]), ":0\r\n");
2618 assert_eq!(
2619 f.run(&[b"WAIT", b"x", b"0"]),
2620 "-ERR value is not an integer or out of range\r\n"
2621 );
2622 assert_eq!(
2623 f.run(&[b"WAIT", b"0", b"-1"]),
2624 "-ERR timeout is negative\r\n"
2625 );
2626 assert_eq!(
2627 f.run(&[b"WAIT", b"0", b"1.5"]),
2628 "-ERR timeout is not an integer or out of range\r\n"
2629 );
2630 }
2631
2632 #[test]
2633 fn waitaof_answers_two_zeroes_and_refuses_a_local_wait() {
2634 let mut f = Fixture::new();
2635 assert_eq!(f.run(&[b"WAITAOF", b"0", b"0", b"0"]), "*2\r\n:0\r\n:0\r\n");
2636 assert_eq!(
2637 f.run(&[b"WAITAOF", b"1", b"0", b"0"]),
2638 "-ERR WAITAOF cannot be used when numlocal is set but appendonly is disabled.\r\n"
2639 );
2640 assert_eq!(
2641 f.run(&[b"WAITAOF", b"2", b"0", b"0"]),
2642 "-ERR value is out of range, value must between 0 and 1\r\n"
2643 );
2644 assert_eq!(
2645 f.run(&[b"WAITAOF", b"0", b"-1", b"0"]),
2646 "-ERR value is out of range, must be positive\r\n"
2647 );
2648 assert_eq!(
2651 f.run(&[b"WAITAOF", b"1", b"0", b"-5"]),
2652 "-ERR timeout is negative\r\n"
2653 );
2654 }
2655
2656 fn payload(reply: &[u8]) -> Vec<u8> {
2660 let head = reply.windows(2).position(|w| w == b"\r\n").unwrap();
2661 reply[head + 2..reply.len() - 2].to_vec()
2662 }
2663
2664 #[test]
2665 fn a_value_survives_a_dump_and_a_restore() {
2666 let mut f = Fixture::new();
2667 f.run(&[b"SET", b"s", b"hello"]);
2668 f.run(&[b"RPUSH", b"l", b"a", b"b", b"c"]);
2669 f.run(&[b"SADD", b"t", b"1", b"2", b"3"]);
2670 f.run(&[b"SADD", b"u", b"x", b"y"]);
2671 f.run(&[b"HSET", b"h", b"f", b"1", b"g", b"2"]);
2672 f.run(&[b"ZADD", b"z", b"1.5", b"a", b"2.5", b"b"]);
2673
2674 for key in [&b"s"[..], b"l", b"t", b"u", b"h", b"z"] {
2675 let mut copy = key.to_vec();
2676 copy.push(b'2');
2677 let bytes = payload(&f.raw(&[b"DUMP", key]));
2678 assert_eq!(f.run(&[b"RESTORE", ©, b"0", &bytes]), "+OK\r\n");
2679 assert_eq!(f.run(&[b"TYPE", ©]), f.run(&[b"TYPE", key]));
2680 }
2681
2682 assert_eq!(f.run(&[b"GET", b"s2"]), "$5\r\nhello\r\n");
2683 assert_eq!(
2684 f.run(&[b"LRANGE", b"l2", b"0", b"-1"]),
2685 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
2686 );
2687 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"t2"])), ["1", "2", "3"]);
2688 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"u2"])), ["x", "y"]);
2689 assert_eq!(f.run(&[b"HGET", b"h2", b"g"]), "$1\r\n2\r\n");
2690 assert_eq!(f.run(&[b"ZSCORE", b"z2", b"b"]), "$3\r\n2.5\r\n");
2691 assert_eq!(
2694 f.run(&[b"OBJECT", b"ENCODING", b"t2"]),
2695 f.run(&[b"OBJECT", b"ENCODING", b"t"])
2696 );
2697 }
2698
2699 #[test]
2700 fn a_dumped_hash_keeps_its_field_deadlines() {
2701 let mut f = Fixture::new();
2702 f.run(&[b"HSET", b"h", b"keep", b"1", b"go", b"2"]);
2703 assert_eq!(
2704 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"go"]),
2705 "*1\r\n:1\r\n"
2706 );
2707 let bytes = payload(&f.raw(&[b"DUMP", b"h"]));
2708 assert_eq!(f.run(&[b"RESTORE", b"h2", b"0", &bytes]), "+OK\r\n");
2709 assert_eq!(
2710 f.run(&[b"HTTL", b"h2", b"FIELDS", b"2", b"keep", b"go"]),
2711 "*2\r\n:-1\r\n:100\r\n"
2712 );
2713 }
2714
2715 #[test]
2716 fn dump_leaves_the_deadline_behind_and_restore_is_given_a_new_one() {
2717 let mut f = Fixture::new();
2718 f.run(&[b"SET", b"a", b"v", b"EX", b"100"]);
2719 let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
2720 assert_eq!(f.run(&[b"RESTORE", b"b", b"0", &bytes]), "+OK\r\n");
2721 assert_eq!(f.run(&[b"TTL", b"b"]), ":-1\r\n");
2722 assert_eq!(f.run(&[b"RESTORE", b"c", b"5000", &bytes]), "+OK\r\n");
2723 assert_eq!(f.run(&[b"TTL", b"c"]), ":5\r\n");
2724 assert_eq!(
2727 f.run(&[b"RESTORE", b"d", b"1", &bytes, b"ABSTTL"]),
2728 "+OK\r\n"
2729 );
2730 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
2731 }
2732
2733 #[test]
2734 fn dump_answers_nothing_for_a_key_that_is_not_there() {
2735 let mut f = Fixture::new();
2736 assert_eq!(f.run(&[b"DUMP", b"nope"]), "$-1\r\n");
2737 f.run(&[b"SET", b"gone", b"v", b"PX", b"10"]);
2738 f.advance(50);
2739 assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
2740 }
2741
2742 #[test]
2743 fn restore_refuses_a_key_that_is_there_unless_it_is_told_to_replace() {
2744 let mut f = Fixture::new();
2745 f.run(&[b"SET", b"a", b"first"]);
2746 f.run(&[b"SET", b"b", b"second"]);
2747 let bytes = payload(&f.raw(&[b"DUMP", b"b"]));
2748 assert_eq!(
2749 f.run(&[b"RESTORE", b"a", b"0", &bytes]),
2750 "-BUSYKEY Target key name already exists.\r\n"
2751 );
2752 assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nfirst\r\n");
2753 assert_eq!(
2754 f.run(&[b"RESTORE", b"a", b"0", &bytes, b"REPLACE"]),
2755 "+OK\r\n"
2756 );
2757 assert_eq!(f.run(&[b"GET", b"a"]), "$6\r\nsecond\r\n");
2758 }
2759
2760 #[test]
2764 fn restore_asks_about_the_key_before_it_looks_at_the_bytes() {
2765 let mut f = Fixture::new();
2766 f.run(&[b"SET", b"a", b"v"]);
2767 assert_eq!(
2768 f.run(&[b"RESTORE", b"a", b"0", b"rubbish"]),
2769 "-BUSYKEY Target key name already exists.\r\n"
2770 );
2771 assert_eq!(
2774 f.run(&[b"RESTORE", b"a", b"0", b"rubbish", b"FREQ", b"300"]),
2775 "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
2776 );
2777 }
2778
2779 #[test]
2780 fn restore_can_tell_a_bad_footer_from_bad_bytes() {
2781 let mut f = Fixture::new();
2782 f.run(&[b"SET", b"a", b"hello"]);
2783 let good = payload(&f.raw(&[b"DUMP", b"a"]));
2784
2785 let mut flipped = good.clone();
2786 flipped[2] ^= 0x40;
2787 assert_eq!(
2788 f.run(&[b"RESTORE", b"b", b"0", &flipped]),
2789 "-ERR DUMP payload version or checksum are wrong\r\n"
2790 );
2791 assert_eq!(
2792 f.run(&[b"RESTORE", b"b", b"0", b"short"]),
2793 "-ERR DUMP payload version or checksum are wrong\r\n"
2794 );
2795 let mut truncated = good[..1].to_vec();
2799 truncated.extend_from_slice(&good[good.len() - 10..good.len() - 8]);
2800 let crc = yo_common::crc::crc64(0, &truncated);
2801 truncated.extend_from_slice(&crc.to_le_bytes());
2802 assert_eq!(
2803 f.run(&[b"RESTORE", b"b", b"0", &truncated]),
2804 "-ERR Bad data format\r\n"
2805 );
2806 assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
2807 }
2808
2809 #[test]
2810 fn restore_checks_the_three_numbers_a_client_can_get_wrong() {
2811 let mut f = Fixture::new();
2812 f.run(&[b"SET", b"a", b"v"]);
2813 let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
2814 assert_eq!(
2815 f.run(&[b"RESTORE", b"b", b"-1", &bytes]),
2816 "-ERR Invalid TTL value, must be >= 0\r\n"
2817 );
2818 assert_eq!(
2819 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"-1"]),
2820 "-ERR Invalid IDLETIME value, must be >= 0\r\n"
2821 );
2822 assert_eq!(
2823 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ", b"256"]),
2824 "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
2825 );
2826 assert_eq!(
2828 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"90"]),
2829 "+OK\r\n"
2830 );
2831 assert_eq!(
2832 f.run(&[b"RESTORE", b"c", b"0", &bytes, b"FREQ", b"200", b"REPLACE"]),
2833 "+OK\r\n"
2834 );
2835 }
2836
2837 #[test]
2841 fn restore_takes_idletime_or_freq_and_not_both() {
2842 let mut f = Fixture::new();
2843 f.run(&[b"SET", b"a", b"v"]);
2844 let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
2845 assert_eq!(
2846 f.run(&[
2847 b"RESTORE",
2848 b"b",
2849 b"0",
2850 &bytes,
2851 b"IDLETIME",
2852 b"1",
2853 b"FREQ",
2854 b"2"
2855 ]),
2856 "-ERR syntax error\r\n"
2857 );
2858 assert_eq!(
2859 f.run(&[
2860 b"RESTORE",
2861 b"b",
2862 b"0",
2863 &bytes,
2864 b"FREQ",
2865 b"2",
2866 b"IDLETIME",
2867 b"1"
2868 ]),
2869 "-ERR syntax error\r\n"
2870 );
2871 assert_eq!(
2872 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ"]),
2873 "-ERR syntax error\r\n"
2874 );
2875 assert_eq!(
2876 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"NOSUCH"]),
2877 "-ERR syntax error\r\n"
2878 );
2879 }
2880
2881 #[test]
2882 fn copy_checks_its_options_before_it_looks_for_anything() {
2883 let mut f = Fixture::new();
2884 assert_eq!(
2887 f.run(&[b"COPY", b"a", b"b", b"DB", b"99"]),
2888 "-ERR DB index is out of range\r\n"
2889 );
2890 assert_eq!(
2891 f.run(&[b"COPY", b"a", b"b", b"DB", b"-1"]),
2892 "-ERR DB index is out of range\r\n"
2893 );
2894 assert_eq!(
2895 f.run(&[b"COPY", b"a", b"b", b"DB", b"x"]),
2896 "-ERR value is not an integer or out of range\r\n"
2897 );
2898 assert_eq!(
2899 f.run(&[b"COPY", b"a", b"b", b"nonsense"]),
2900 "-ERR syntax error\r\n"
2901 );
2902 assert_eq!(
2903 f.run(&[b"COPY", b"a", b"a"]),
2904 "-ERR source and destination objects are the same\r\n"
2905 );
2906 assert_eq!(
2908 f.run(&[b"COPY", b"a", b"b", b"dB", b"1", b"rEpLaCe", b"db", b"2"]),
2909 ":0\r\n"
2910 );
2911 }
2912
2913 #[test]
2914 fn time_is_two_bulk_strings_and_moves() {
2915 let mut f = Fixture::new();
2916 let first = f.run(&[b"TIME"]);
2917 assert!(first.starts_with("*2\r\n$"), "got {first}");
2918 let parts: Vec<&str> = first.split("\r\n").collect();
2919 let secs: i64 = parts[2].parse().expect("seconds as decimal text");
2920 let micros: i64 = parts[4].parse().expect("microseconds as decimal text");
2921 assert!(secs > 1_700_000_000, "a real wall clock, got {secs}");
2922 assert!((0..1_000_000).contains(µs), "got {micros}");
2923 assert_ne!(first, f.run(&[b"TIME"]));
2927 }
2928
2929 #[test]
2930 fn a_keyspace_scan_walks_every_key_once() {
2931 let n = if cfg!(miri) { 96 } else { 500 };
2935 let mut f = Fixture::new();
2936 for i in 0..n {
2937 f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
2938 }
2939
2940 let mut seen: Vec<String> = Vec::new();
2941 let mut cursor = "0".to_owned();
2942 let mut calls = 0;
2943 loop {
2944 let (next, keys) = scan_reply(&f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"32"]));
2945 seen.extend(keys);
2946 cursor = next;
2947 calls += 1;
2948 assert!(calls < 10_000, "the cursor is not advancing");
2949 if cursor == "0" {
2950 break;
2951 }
2952 }
2953
2954 seen.sort();
2955 seen.dedup();
2956 assert_eq!(seen.len(), n, "every key once and only once");
2957 assert!(calls > 1, "{n} keys came back in one batch");
2960 }
2961
2962 #[test]
2963 fn a_scan_narrows_by_pattern_and_by_type() {
2964 let mut f = Fixture::new();
2965 f.run(&[b"SET", b"str", b"v"]);
2966 f.run(&[b"SADD", b"members", b"a"]);
2967 f.run(&[b"HSET", b"fields", b"f", b"v"]);
2968
2969 let all = |f: &mut Fixture, args: &[&[u8]]| {
2970 let mut out: Vec<String> = Vec::new();
2971 let mut cursor = "0".to_owned();
2972 loop {
2973 let mut line: Vec<&[u8]> = vec![b"SCAN", cursor.as_bytes()];
2974 line.extend_from_slice(args);
2975 let (next, keys) = scan_reply(&f.run(&line));
2976 out.extend(keys);
2977 cursor = next;
2978 if cursor == "0" {
2979 break;
2980 }
2981 }
2982 out.sort();
2983 out
2984 };
2985
2986 assert_eq!(all(&mut f, &[]), ["fields", "members", "str"]);
2987 assert_eq!(all(&mut f, &[b"MATCH", b"*e*"]), ["fields", "members"]);
2988 assert_eq!(all(&mut f, &[b"TYPE", b"set"]), ["members"]);
2989 assert_eq!(all(&mut f, &[b"TYPE", b"HASH"]), ["fields"]);
2991 assert!(all(&mut f, &[b"TYPE", b"list"]).is_empty());
2993 assert!(all(&mut f, &[b"TYPE", b"banana"]).is_empty());
2994 assert!(all(&mut f, &[b"MATCH", b"str*", b"TYPE", b"set"]).is_empty());
2996 }
2997
2998 #[test]
2999 fn a_scan_says_what_is_wrong_with_it() {
3000 let mut f = Fixture::new();
3001 assert_eq!(f.run(&[b"SCAN", b"nope"]), "-ERR invalid cursor\r\n");
3002 assert_eq!(f.run(&[b"SCAN", b"-1"]), "-ERR invalid cursor\r\n");
3003 assert_eq!(f.run(&[b"SCAN", b"0", b"MATCH"]), "-ERR syntax error\r\n");
3004 assert_eq!(
3005 f.run(&[b"SCAN", b"0", b"COUNT", b"0"]),
3006 "-ERR syntax error\r\n"
3007 );
3008 assert_eq!(
3009 f.run(&[b"SCAN", b"0", b"COUNT", b"x"]),
3010 "-ERR value is not an integer or out of range\r\n"
3011 );
3012 assert_eq!(
3013 f.run(&[b"SCAN", b"0", b"WAT", b"1"]),
3014 "-ERR syntax error\r\n"
3015 );
3016 assert!(f.run(&[b"SCAN", b"18446744073709551615"]).starts_with("*2"));
3021 }
3022
3023 #[test]
3024 fn keys_and_randomkey_look_at_the_whole_database() {
3025 let mut f = Fixture::new();
3026 assert_eq!(f.run(&[b"KEYS", b"*"]), "*0\r\n");
3027 assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
3028
3029 for name in ["one", "two", "three"] {
3030 f.run(&[b"SET", name.as_bytes(), b"v"]);
3031 }
3032 assert_eq!(sorted(&f.run(&[b"KEYS", b"*"])), ["one", "three", "two"]);
3033 assert_eq!(sorted(&f.run(&[b"KEYS", b"t*"])), ["three", "two"]);
3034 assert_eq!(f.run(&[b"KEYS", b"nothing"]), "*0\r\n");
3035
3036 for _ in 0..50 {
3037 let got = f.run(&[b"RANDOMKEY"]);
3038 assert!(
3039 ["$3\r\none\r\n", "$3\r\ntwo\r\n", "$5\r\nthree\r\n"].contains(&got.as_str()),
3040 "got {got}"
3041 );
3042 }
3043 }
3044
3045 #[test]
3046 fn a_walk_does_not_answer_keys_that_have_expired() {
3047 let mut f = Fixture::new();
3048 f.run(&[b"SET", b"alive", b"v"]);
3049 f.run(&[b"SET", b"dead", b"v", b"PX", b"1"]);
3050 f.server.advance_clock_ms(2);
3051 assert_eq!(
3052 f.run(&[b"DBSIZE"]),
3053 ":2\r\n",
3054 "nothing has collected it yet"
3055 );
3056
3057 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$5\r\nalive\r\n");
3058 let (_, keys) = scan_reply(&f.run(&[b"SCAN", b"0", b"COUNT", b"1000"]));
3059 assert_eq!(keys, ["alive"]);
3060 for _ in 0..20 {
3061 assert_eq!(f.run(&[b"RANDOMKEY"]), "$5\r\nalive\r\n");
3062 }
3063 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
3066 }
3067
3068 #[test]
3069 fn a_key_deadline_goes_on_and_comes_back_in_all_four_units() {
3070 let mut f = Fixture::new();
3071 f.run(&[b"SET", b"k", b"v"]);
3072 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "there and no deadline");
3073 assert_eq!(f.run(&[b"TTL", b"nosuch"]), ":-2\r\n", "not there at all");
3074
3075 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100"]), ":1\r\n");
3076 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
3077 let ms = int(&f.run(&[b"PTTL", b"k"]));
3078 assert!((99_000..=100_000).contains(&ms), "got {ms}");
3079
3080 let at = int(&f.run(&[b"EXPIRETIME", b"k"]));
3082 let at_ms = int(&f.run(&[b"PEXPIRETIME", b"k"]));
3083 assert_eq!(at, (at_ms + 500) / 1000);
3084 assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
3085
3086 assert_eq!(f.run(&[b"PERSIST", b"k"]), ":1\r\n");
3087 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
3088 assert_eq!(
3089 f.run(&[b"PERSIST", b"k"]),
3090 ":0\r\n",
3091 "nothing to take off the second time"
3092 );
3093 assert_eq!(f.run(&[b"PERSIST", b"nosuch"]), ":0\r\n");
3094 assert_eq!(
3095 f.run(&[b"GET", b"k"]),
3096 "$1\r\nv\r\n",
3097 "and the value went through all of that untouched"
3098 );
3099 }
3100
3101 #[test]
3102 fn every_type_can_be_given_a_deadline_and_it_is_the_same_deadline() {
3103 let mut f = Fixture::new();
3104 f.run(&[b"SET", b"str", b"v"]);
3105 f.run(&[b"SADD", b"set", b"a", b"b"]);
3106 f.run(&[b"HSET", b"hash", b"f", b"v"]);
3107
3108 for key in [b"str".as_slice(), b"set", b"hash"] {
3109 assert_eq!(f.run(&[b"EXPIRE", key, b"100"]), ":1\r\n");
3110 assert_eq!(f.run(&[b"TTL", key]), ":100\r\n");
3111 }
3112 assert_eq!(f.run(&[b"SCARD", b"set"]), ":2\r\n");
3115 assert_eq!(f.run(&[b"HGET", b"hash", b"f"]), "$1\r\nv\r\n");
3116 assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
3117 }
3118
3119 #[test]
3120 fn a_deadline_that_has_already_gone_deletes_the_key_now() {
3121 let mut f = Fixture::new();
3122 for key in [b"a".as_slice(), b"b", b"c", b"d"] {
3123 f.run(&[b"SET", key, b"v"]);
3124 }
3125 assert_eq!(f.run(&[b"EXPIRE", b"a", b"0"]), ":1\r\n");
3129 assert_eq!(f.run(&[b"EXPIRE", b"b", b"-1"]), ":1\r\n");
3130 assert_eq!(f.run(&[b"EXPIREAT", b"c", b"1"]), ":1\r\n");
3131 assert_eq!(f.run(&[b"PEXPIREAT", b"d", b"1"]), ":1\r\n");
3132 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3133 assert_eq!(
3134 f.run(&[b"EXPIRE", b"a", b"100"]),
3135 ":0\r\n",
3136 "and the key really went, so there is nothing to put a deadline on"
3137 );
3138 }
3139
3140 #[test]
3141 fn the_four_conditions_decide_whether_the_deadline_moves() {
3142 let mut f = Fixture::new();
3143 f.run(&[b"SET", b"k", b"v"]);
3144
3145 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX"]), ":0\r\n");
3146 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "and XX left it alone");
3147 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"GT"]), ":0\r\n");
3148 assert_eq!(
3149 f.run(&[b"EXPIRE", b"k", b"100", b"LT"]),
3150 ":1\r\n",
3151 "no deadline reads as infinitely far away, so LT passes where GT fails"
3152 );
3153
3154 assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"NX"]), ":0\r\n");
3155 assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"GT"]), ":0\r\n");
3156 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
3157 assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"LT"]), ":1\r\n");
3158 assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"GT"]), ":1\r\n");
3159 assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
3160
3161 assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"NX"]), ":0\r\n");
3164 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":1\r\n");
3165 assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"XX"]), ":1\r\n");
3166 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n", "and XX let it through");
3167 }
3168
3169 #[test]
3170 fn the_conditions_are_a_set_and_not_a_keyword() {
3171 let mut f = Fixture::new();
3172 f.run(&[b"SET", b"k", b"v"]);
3173
3174 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"nx"]), ":1\r\n");
3175 assert_eq!(
3176 f.run(&[b"EXPIRE", b"k", b"100", b"nx", b"nx"]),
3177 ":0\r\n",
3178 "the same keyword twice means it once, and NX now has a deadline to fail on"
3179 );
3180
3181 assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"xx", b"gt"]), ":1\r\n");
3184 assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
3185 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"gt", b"xx"]), ":0\r\n");
3186 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]), ":1\r\n");
3187 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
3188 f.run(&[b"PERSIST", b"k"]);
3189 assert_eq!(
3190 f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]),
3191 ":0\r\n",
3192 "where LT on its own would have taken it"
3193 );
3194 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"LT"]), ":1\r\n");
3195 }
3196
3197 #[test]
3198 fn a_key_is_gone_once_its_moment_passes() {
3199 let mut f = Fixture::new();
3200 f.run(&[b"SET", b"k", b"v"]);
3201 f.run(&[b"EXPIRE", b"k", b"100"]);
3202
3203 let at = int(&f.run(&[b"PEXPIRETIME", b"k"]));
3204 f.server.set_clock_ms(at as u64 + 1);
3205 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
3206 assert_eq!(f.run(&[b"TTL", b"k"]), ":-2\r\n");
3207 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
3208 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3209 }
3210
3211 #[test]
3212 fn the_expiry_commands_refuse_what_a_real_server_refuses() {
3213 let mut f = Fixture::new();
3214 f.run(&[b"SET", b"k", b"v"]);
3215 for (bad, want) in [
3216 (
3217 &[b"EXPIRE".as_slice(), b"k", b"soon"][..],
3218 "-ERR value is not an integer or out of range\r\n",
3219 ),
3220 (
3221 &[b"EXPIRE", b"k", b"100", b"MAYBE"],
3222 "-ERR Unsupported option MAYBE\r\n",
3223 ),
3224 (
3225 &[b"EXPIRE", b"k", b"100", b"NX", b"XX"],
3226 "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
3227 ),
3228 (
3229 &[b"EXPIRE", b"k", b"100", b"NX", b"GT"],
3230 "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
3231 ),
3232 (
3233 &[b"EXPIRE", b"k", b"100", b"GT", b"LT", b"GT"],
3234 "-ERR GT and LT options at the same time are not compatible\r\n",
3235 ),
3236 (
3239 &[b"EXPIRE", b"k", b"9223372036854775807"],
3240 "-ERR invalid expire time in 'expire' command\r\n",
3241 ),
3242 (
3243 &[b"EXPIREAT", b"k", b"9223372036854775807"],
3244 "-ERR invalid expire time in 'expireat' command\r\n",
3245 ),
3246 (
3247 &[b"PEXPIRE", b"k", b"9223372036854775807"],
3248 "-ERR invalid expire time in 'pexpire' command\r\n",
3249 ),
3250 ] {
3251 assert_eq!(f.run(bad), want, "for {bad:?}");
3252 }
3253 assert_eq!(
3254 f.run(&[b"TTL", b"k"]),
3255 ":-1\r\n",
3256 "and none of those put a deadline on anything"
3257 );
3258
3259 assert_eq!(
3263 f.run(&[b"PEXPIREAT", b"k", b"9223372036854775807"]),
3264 ":1\r\n"
3265 );
3266 assert_eq!(f.run(&[b"PEXPIRETIME", b"k"]), ":70368744177663\r\n");
3267 }
3268
3269 #[test]
3270 fn flushing_empties_this_database_or_every_one_of_them() {
3271 let mut f = Fixture::new();
3272 f.run(&[b"SELECT", b"0"]);
3273 f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
3274 f.run(&[b"SELECT", b"1"]);
3275 f.run(&[b"SET", b"c", b"3"]);
3276 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
3277 assert_eq!(f.run(&[b"FLUSHDB", b"async"]), "+OK\r\n");
3280 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3281 f.run(&[b"SELECT", b"0"]);
3283 assert_eq!(f.run(&[b"DBSIZE"]), ":2\r\n");
3284 assert_eq!(f.run(&[b"FLUSHALL", b"SYNC"]), "+OK\r\n");
3285 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3286 f.run(&[b"SELECT", b"1"]);
3287 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3288 assert_eq!(f.run(&[b"FLUSHALL", b"nope"]), "-ERR syntax error\r\n");
3291 assert_eq!(
3292 f.run(&[b"FLUSHDB", b"sync", b"sync"]),
3293 "-ERR syntax error\r\n"
3294 );
3295 }
3296
3297 #[test]
3298 fn the_script_cache_and_the_library_set_answer_for_being_empty() {
3299 let mut f = Fixture::new();
3300 assert_eq!(f.run(&[b"SCRIPT", b"FLUSH"]), "+OK\r\n");
3301 assert_eq!(f.run(&[b"SCRIPT", b"FLUSH", b"async"]), "+OK\r\n");
3302 assert_eq!(f.run(&[b"FUNCTION", b"FLUSH", b"SYNC"]), "+OK\r\n");
3303 assert_eq!(
3306 f.run(&[b"SCRIPT", b"EXISTS", b"aaaa", b"bbbb"]),
3307 "*2\r\n:0\r\n:0\r\n"
3308 );
3309 assert_eq!(f.run(&[b"FUNCTION", b"LIST"]), "*0\r\n");
3310 assert_eq!(
3311 f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"x", b"WITHCODE"]),
3312 "*0\r\n"
3313 );
3314 assert_eq!(
3315 f.run(&[b"FUNCTION", b"DELETE", b"nosuch"]),
3316 "-ERR Library not found\r\n"
3317 );
3318
3319 assert_eq!(
3322 f.run(&[b"SCRIPT", b"FLUSH", b"nope"]),
3323 "-ERR SCRIPT FLUSH only support SYNC|ASYNC option\r\n"
3324 );
3325 assert_eq!(
3326 f.run(&[b"FUNCTION", b"FLUSH", b"nope"]),
3327 "-ERR FUNCTION FLUSH only supports SYNC|ASYNC option\r\n"
3328 );
3329 assert_eq!(
3332 f.run(&[b"FUNCTION", b"FLUSH", b"sync", b"sync"]),
3333 "-ERR unknown subcommand or wrong number of arguments for 'flush'. Try FUNCTION HELP.\r\n"
3334 );
3335 assert_eq!(
3336 f.run(&[b"FUNCTION", b"LIST", b"bogus"]),
3337 "-ERR Unknown argument bogus\r\n"
3338 );
3339 assert_eq!(
3340 f.run(&[b"SCRIPT", b"EXISTS"]),
3341 "-ERR wrong number of arguments for 'script|exists' command\r\n"
3342 );
3343
3344 assert_eq!(
3347 f.run(&[b"SCRIPT", b"LOAD", b"return 1"]),
3348 "-ERR unknown subcommand 'LOAD'. Try SCRIPT HELP.\r\n"
3349 );
3350 assert_eq!(
3351 f.run(&[b"FUNCTION", b"STATS"]),
3352 "-ERR unknown subcommand 'STATS'. Try FUNCTION HELP.\r\n"
3353 );
3354 }
3355
3356 #[test]
3357 fn a_counter_is_an_integer_and_not_a_string_of_digits() {
3358 let mut f = Fixture::new();
3359 assert_eq!(f.run(&[b"INCR", b"c"]), ":1\r\n");
3360 assert_eq!(f.run(&[b"INCRBY", b"c", b"41"]), ":42\r\n");
3361 assert_eq!(f.run(&[b"DECRBY", b"c", b"2"]), ":40\r\n");
3362 assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\n40\r\n");
3365 assert_eq!(f.run(&[b"INCRBYFLOAT", b"c", b"0.5"]), "$4\r\n40.5\r\n");
3366 f.run(&[b"SET", b"k", b"hello"]);
3369 assert_eq!(
3370 f.run(&[b"INCR", b"k"]),
3371 "-ERR value is not an integer or out of range\r\n"
3372 );
3373 assert_eq!(
3374 f.run(&[b"INCRBYFLOAT", b"c", b"inf"]),
3375 "-ERR increment would produce NaN or Infinity\r\n"
3376 );
3377 }
3378
3379 #[test]
3384 fn the_newer_commands_reply_in_the_shapes_a_real_server_sends() {
3385 let mut f = Fixture::new();
3386 assert_eq!(f.run(&[b"SET", b"k", b"hello"]), "+OK\r\n");
3387 assert_eq!(f.run(&[b"DIGEST", b"k"]), "$16\r\n9555e8555c62dcfd\r\n");
3390 assert_eq!(f.run(&[b"DIGEST", b"nosuch"]), "$-1\r\n");
3391 assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"1"]), ":1\r\n");
3392 assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"2", b"NX"]), ":0\r\n");
3393 assert_eq!(f.run(&[b"GET", b"a"]), "$1\r\n1\r\n");
3394 assert_eq!(f.run(&[b"INCREX", b"n"]), "*2\r\n:1\r\n:1\r\n");
3395 assert_eq!(
3396 f.run(&[b"INCREX", b"n", b"BYINT", b"5", b"UBOUND", b"3"]),
3397 "*2\r\n:1\r\n:0\r\n",
3398 "a refused increment reports the value it left alone and applied nothing"
3399 );
3400 assert_eq!(
3401 f.run(&[
3402 b"INCREX",
3403 b"n",
3404 b"BYINT",
3405 b"5",
3406 b"UBOUND",
3407 b"3",
3408 b"SATURATE"
3409 ]),
3410 "*2\r\n:3\r\n:2\r\n"
3411 );
3412 assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"2"]), ":0\r\n");
3413 assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"1"]), ":1\r\n");
3414 }
3415
3416 #[test]
3417 fn the_same_answers_come_out_in_resp3_spelling() {
3418 let mut f = Fixture::new();
3419 assert!(f.run(&[b"HELLO", b"3"]).starts_with("%7\r\n"));
3420 assert_eq!(f.run(&[b"GET", b"nosuch"]), "_\r\n");
3421 assert_eq!(
3424 f.run(&[b"INCREX", b"c", b"BYFLOAT", b"1.5"]),
3425 "*2\r\n,1.5\r\n,1.5\r\n"
3426 );
3427 assert_eq!(f.run(&[b"INCRBYFLOAT", b"f", b"2.5"]), "$3\r\n2.5\r\n");
3428 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
3431 assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
3432 }
3433
3434 #[test]
3435 fn a_command_nobody_has_heard_of_is_an_error_and_not_a_closed_socket() {
3436 let mut f = Fixture::new();
3437 let (flow, reply) = f.flow(&[b"NOPE", b"a", b"b"]);
3438 assert_eq!(flow, Flow::Continue);
3439 assert_eq!(
3440 reply,
3441 "-ERR unknown command 'NOPE', with args beginning with: 'a' 'b' \r\n"
3442 );
3443 let reply = f.run(&[b"NO\r\n+PONG\r\nPE"]);
3446 assert_eq!(reply.matches("\r\n").count(), 1);
3447 }
3448
3449 #[test]
3450 fn arity_is_checked_before_the_command_is() {
3451 let mut f = Fixture::new();
3452 assert_eq!(
3453 f.run(&[b"GET"]),
3454 "-ERR wrong number of arguments for 'get' command\r\n"
3455 );
3456 assert_eq!(
3457 f.run(&[b"MSET", b"k"]),
3458 "-ERR wrong number of arguments for 'mset' command\r\n"
3459 );
3460 assert_eq!(
3464 f.run(&[b"PING", b"a", b"b"]),
3465 "-ERR wrong number of arguments for 'ping' command\r\n"
3466 );
3467 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
3468 assert_eq!(f.run(&[b"PING", b"hi"]), "$2\r\nhi\r\n");
3469 assert_eq!(
3471 f.run(&[b"DELEX", b"k", b"IFEQ"]),
3472 "-ERR wrong number of arguments for 'delex' command\r\n"
3473 );
3474 }
3475
3476 #[test]
3480 fn the_option_combinations_are_the_ones_a_real_server_accepts() {
3481 let mut f = Fixture::new();
3482 let syntax = "-ERR syntax error\r\n";
3483 assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"XX"]), syntax);
3484 assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"IFEQ", b"a"]), syntax);
3485 assert_eq!(
3486 f.run(&[b"SET", b"k", b"v", b"KEEPTTL", b"EX", b"5"]),
3487 syntax
3488 );
3489 assert_eq!(
3490 f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"PX", b"5"]),
3491 syntax
3492 );
3493 assert_eq!(f.run(&[b"SET", b"k", b"v", b"PERSIST"]), syntax);
3494 assert_eq!(
3496 f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"EX", b"100"]),
3497 "+OK\r\n"
3498 );
3499 assert_eq!(f.run(&[b"SET", b"k", b"v", b"XX", b"XX"]), "+OK\r\n");
3500 assert_eq!(f.run(&[b"SET", b"k", b"v", b"GET", b"GET"]), "$1\r\nv\r\n");
3501 assert_eq!(
3503 f.run(&[b"INCREX", b"n", b"BYINT", b"1", b"BYINT", b"2"]),
3504 syntax
3505 );
3506 assert_eq!(
3507 f.run(&[b"INCREX", b"n", b"ENX"]),
3508 "-ERR ENX flag requires an expiration\r\n"
3509 );
3510 assert_eq!(
3511 f.run(&[b"INCREX", b"n", b"UBOUND", b"abc"]),
3512 "-ERR UBOUND is not an integer or out of range\r\n"
3513 );
3514 assert_eq!(
3515 f.run(&[b"INCREX", b"n", b"LBOUND", b"10", b"UBOUND", b"5"]),
3516 "-ERR LBOUND can't be greater than UBOUND\r\n"
3517 );
3518 assert_eq!(
3519 f.run(&[b"LCS", b"a", b"b", b"LEN", b"IDX"]),
3520 "-ERR If you want both the length and indexes, please just use IDX.\r\n"
3521 );
3522 }
3523
3524 #[test]
3528 fn the_expiry_rules_are_redis_own() {
3529 let mut f = Fixture::new();
3530 let bad = "-ERR invalid expire time in 'set' command\r\n";
3531 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"0"]), bad);
3532 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"-1"]), bad);
3533 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"0"]), bad);
3534 assert_eq!(
3535 f.run(&[b"SET", b"k", b"v", b"EX", b"9999999999999999"]),
3536 bad
3537 );
3538 assert_eq!(
3539 f.run(&[b"SET", b"k", b"v", b"PX", b"99999999999999999999"]),
3540 "-ERR value is not an integer or out of range\r\n"
3541 );
3542 assert_eq!(
3543 f.run(&[b"SETEX", b"k", b"0", b"v"]),
3544 "-ERR invalid expire time in 'setex' command\r\n"
3545 );
3546 assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"0"]), "$-1\r\n");
3547 assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"abc"]), "$-1\r\n");
3548 assert_eq!(
3549 f.run(&[b"GETEX", b"nosuch", b"KEEPTTL"]),
3550 "-ERR syntax error\r\n",
3551 "the option list is still checked before the key is looked up"
3552 );
3553 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
3555 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"1"]), "+OK\r\n");
3556 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
3557 }
3558
3559 #[test]
3560 fn mset_takes_its_pairs_from_the_read_buffer() {
3561 let mut f = Fixture::new();
3562 assert_eq!(f.run(&[b"MSET", b"a", b"1", b"b", b"2"]), "+OK\r\n");
3563 assert_eq!(
3564 f.run(&[b"MGET", b"a", b"b", b"nosuch"]),
3565 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$-1\r\n"
3566 );
3567 assert_eq!(f.run(&[b"MSETNX", b"b", b"9", b"c", b"3"]), ":0\r\n");
3568 assert_eq!(f.run(&[b"MSETNX", b"c", b"3", b"d", b"4"]), ":1\r\n");
3569 assert_eq!(
3570 f.run(&[b"MSETEX", b"2", b"e", b"5"]),
3571 "-ERR wrong number of key-value pairs\r\n"
3572 );
3573 assert_eq!(
3574 f.run(&[b"MSETEX", b"0", b"e", b"5"]),
3575 "-ERR invalid numkeys value\r\n"
3576 );
3577 assert_eq!(
3578 f.run(&[b"MSETEX", b"abc", b"e", b"5"]),
3579 "-ERR invalid numkeys value\r\n"
3580 );
3581 }
3582
3583 #[test]
3584 fn lcs_answers_the_length_the_string_and_the_runs() {
3585 let mut f = Fixture::new();
3586 f.run(&[b"MSET", b"a", b"ohmytext", b"b", b"mynewtext"]);
3587 assert_eq!(f.run(&[b"LCS", b"a", b"b"]), "$6\r\nmytext\r\n");
3588 assert_eq!(f.run(&[b"LCS", b"a", b"b", b"LEN"]), ":6\r\n");
3589 assert_eq!(
3590 f.run(&[b"LCS", b"a", b"b", b"IDX", b"MINMATCHLEN", b"4"]),
3591 "*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"
3592 );
3593 assert_eq!(
3596 f.run(&[b"LCS", b"a", b"b", b"MINMATCHLEN", b"4", b"WITHMATCHLEN"]),
3597 "$6\r\nmytext\r\n"
3598 );
3599 }
3600
3601 #[test]
3602 fn select_moves_the_connection_and_the_databases_stay_apart() {
3603 let mut f = Fixture::new();
3604 f.run(&[b"SET", b"k", b"zero"]);
3605 assert_eq!(f.run(&[b"SELECT", b"4"]), "+OK\r\n");
3606 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
3607 f.run(&[b"SET", b"k", b"four"]);
3608 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
3609 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
3610 assert_eq!(
3611 f.run(&[b"SELECT", b"99"]),
3612 "-ERR DB index is out of range\r\n"
3613 );
3614 assert_eq!(
3615 f.run(&[b"SELECT", b"-1"]),
3616 "-ERR DB index is out of range\r\n"
3617 );
3618 assert_eq!(
3619 f.run(&[b"SELECT", b"abc"]),
3620 "-ERR value is not an integer or out of range\r\n"
3621 );
3622 f.run(&[b"SELECT", b"4"]);
3624 f.run(&[b"RESET"]);
3625 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
3626 }
3627
3628 #[test]
3629 fn hello_agrees_on_a_protocol_and_refuses_the_ones_that_do_not_exist() {
3630 let mut f = Fixture::new();
3631 let reply = f.run(&[b"HELLO"]);
3632 assert!(reply.starts_with("*14\r\n"), "{reply}");
3633 assert!(reply.contains("$5\r\nredis\r\n"), "{reply}");
3634 assert!(reply.contains("$5\r\n8.8.0\r\n"), "{reply}");
3635 assert!(
3636 reply.contains(":7\r\n"),
3637 "the connection id is in there: {reply}"
3638 );
3639 assert_eq!(
3640 f.run(&[b"HELLO", b"4"]),
3641 "-NOPROTO unsupported protocol version\r\n"
3642 );
3643 assert_eq!(
3644 f.run(&[b"HELLO", b"abc"]),
3645 "-ERR Protocol version is not an integer or out of range\r\n"
3646 );
3647 assert_eq!(
3648 f.run(&[b"HELLO", b"3", b"SETNAME"]),
3649 "-ERR Syntax error in HELLO option 'SETNAME'\r\n"
3650 );
3651 assert!(
3652 f.run(&[b"HELLO", b"3", b"SETNAME", b"bob"])
3653 .starts_with("%7\r\n")
3654 );
3655 assert_eq!(f.session.name(), b"bob");
3656 f.run(&[b"RESET"]);
3657 assert_eq!(f.session.name(), b"");
3658 }
3659
3660 #[test]
3661 fn command_describes_this_server_in_the_shape_a_driver_reads() {
3662 let mut f = Fixture::new();
3663 let count = format!(":{}\r\n", COMMANDS.len());
3664 assert_eq!(f.run(&[b"COMMAND", b"COUNT"]), count);
3665 let info = f.run(&[b"COMMAND", b"INFO", b"get"]);
3666 assert_eq!(
3667 info,
3668 "*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\
3669 *3\r\n+@read\r\n+@string\r\n+@fast\r\n*0\r\n*0\r\n*0\r\n"
3670 );
3671 assert_eq!(f.run(&[b"COMMAND", b"INFO", b"nosuch"]), "*1\r\n$-1\r\n");
3673 assert_eq!(
3674 f.run(&[b"COMMAND", b"LIST", b"FILTERBY", b"PATTERN", b"getr*"]),
3675 "*1\r\n$8\r\ngetrange\r\n"
3676 );
3677 assert_eq!(
3678 f.run(&[b"COMMAND", b"NOPE"]),
3679 "-ERR unknown subcommand 'NOPE'. Try COMMAND HELP.\r\n"
3680 );
3681 }
3682
3683 #[test]
3687 fn command_getkeys_finds_the_keys_including_the_hidden_ones() {
3688 let mut f = Fixture::new();
3689 assert_eq!(
3690 f.run(&[b"COMMAND", b"GETKEYS", b"get", b"k"]),
3691 "*1\r\n$1\r\nk\r\n"
3692 );
3693 assert_eq!(
3694 f.run(&[b"COMMAND", b"GETKEYS", b"mset", b"a", b"1", b"b", b"2"]),
3695 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
3696 );
3697 assert_eq!(
3698 f.run(&[
3699 b"COMMAND", b"GETKEYS", b"msetex", b"2", b"a", b"1", b"b", b"2"
3700 ]),
3701 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
3702 );
3703 assert_eq!(
3704 f.run(&[b"COMMAND", b"GETKEYS", b"ping"]),
3705 "-ERR The command has no key arguments\r\n"
3706 );
3707 assert_eq!(
3708 f.run(&[b"COMMAND", b"GETKEYS", b"set"]),
3709 "-ERR Invalid number of arguments specified for command\r\n"
3710 );
3711 }
3712
3713 #[test]
3714 fn config_answers_what_it_can_and_refuses_what_it_cannot() {
3715 let mut f = Fixture::new();
3716 assert_eq!(
3717 f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
3718 "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n"
3719 );
3720 let both = f.run(&[b"CONFIG", b"GET", b"maxmemory*", b"maxmemory"]);
3723 assert!(both.starts_with("*6\r\n"), "{both}");
3724 assert_eq!(f.run(&[b"CONFIG", b"GET", b"nosuch"]), "*0\r\n");
3725 assert_eq!(f.run(&[b"CONFIG", b"SET", b"appendonly", b"no"]), "+OK\r\n");
3726 assert_eq!(
3727 f.run(&[b"CONFIG", b"SET", b"appendonly", b"yes"]),
3728 "-ERR CONFIG SET failed (possibly related to argument 'appendonly') - can't set immutable config\r\n"
3729 );
3730 assert_eq!(
3731 f.run(&[b"CONFIG", b"SET", b"nosuch", b"1"]),
3732 "-ERR Unknown option or number of arguments for CONFIG SET - 'nosuch'\r\n"
3733 );
3734 assert_eq!(
3735 f.run(&[b"CONFIG", b"GET"]),
3736 "-ERR wrong number of arguments for 'config|get' command\r\n"
3737 );
3738 assert_eq!(
3742 f.run(&[b"CONFIG", b"SET", b"appendonly"]),
3743 "-ERR wrong number of arguments for 'config|set' command\r\n"
3744 );
3745 assert_eq!(
3746 f.run(&[b"CONFIG", b"SET", b"appendonly", b"no", b"maxmemory"]),
3747 "-ERR syntax error\r\n"
3748 );
3749 assert_eq!(f.run(&[b"CONFIG", b"RESETSTAT"]), "+OK\r\n");
3750 assert_eq!(
3751 f.run(&[b"CONFIG", b"REWRITE"]),
3752 "-ERR The server is running without a config file\r\n"
3753 );
3754 }
3755
3756 #[test]
3757 fn the_eviction_policy_reads_back_what_was_written_to_it() {
3758 let mut f = Fixture::new();
3759 assert_eq!(
3760 f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
3761 "*2\r\n$16\r\nmaxmemory-policy\r\n$10\r\nnoeviction\r\n"
3762 );
3763 assert_eq!(
3764 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"AllKeys-LFU"]),
3765 "+OK\r\n",
3766 "the name is matched without regard to case, like every other one"
3767 );
3768 assert_eq!(
3769 f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
3770 "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
3771 );
3772 assert!(
3774 f.run(&[b"INFO", b"memory"])
3775 .contains("maxmemory_policy:allkeys-lfu"),
3776 "INFO and CONFIG disagree about the policy"
3777 );
3778 assert_eq!(
3782 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"garbage"]),
3783 "-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"
3784 );
3785 assert_eq!(
3788 f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
3789 "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
3790 );
3791 f.run(&[
3792 b"CONFIG",
3793 b"SET",
3794 b"hash-max-listpack-entries",
3795 b"7",
3796 b"maxmemory-policy",
3797 b"nonsense",
3798 ]);
3799 assert_eq!(
3800 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
3801 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n"
3802 );
3803 }
3804
3805 #[test]
3806 fn the_three_eviction_numbers_read_back_too() {
3807 let mut f = Fixture::new();
3808 for (name, default, set) in [
3809 ("maxmemory-samples", "5", "12"),
3810 ("lfu-log-factor", "10", "3"),
3811 ("lfu-decay-time", "1", "60"),
3812 ] {
3813 let get = || {
3814 format!(
3815 "*2\r\n${}\r\n{name}\r\n${}\r\n{default}\r\n",
3816 name.len(),
3817 default.len()
3818 )
3819 };
3820 assert_eq!(f.run(&[b"CONFIG", b"GET", name.as_bytes()]), get());
3821 assert_eq!(
3822 f.run(&[b"CONFIG", b"SET", name.as_bytes(), set.as_bytes()]),
3823 "+OK\r\n"
3824 );
3825 assert_eq!(
3826 f.run(&[b"CONFIG", b"GET", name.as_bytes()]),
3827 format!(
3828 "*2\r\n${}\r\n{name}\r\n${}\r\n{set}\r\n",
3829 name.len(),
3830 set.len()
3831 )
3832 );
3833 assert_eq!(
3836 f.run(&[b"CONFIG", b"SET", name.as_bytes(), b"soon"]),
3837 format!(
3838 "-ERR CONFIG SET failed (possibly related to argument '{name}') - argument couldn't be parsed into an integer\r\n"
3839 )
3840 );
3841 }
3842 }
3843
3844 #[test]
3845 fn the_memory_limit_reads_back_in_bytes_whatever_the_unit_was() {
3846 let mut f = Fixture::new();
3847 assert_eq!(
3848 f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
3849 "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n",
3850 "no limit is the default"
3851 );
3852 for (typed, bytes) in [
3855 (&b"1024"[..], "1024"),
3856 (b"1k", "1000"),
3857 (b"1kb", "1024"),
3858 (b"1M", "1000000"),
3859 (b"1Mb", "1048576"),
3860 (b"1gb", "1073741824"),
3861 (b"100mb", "104857600"),
3862 ] {
3863 assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxmemory", typed]), "+OK\r\n");
3864 assert_eq!(
3865 f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
3866 format!("*2\r\n$9\r\nmaxmemory\r\n${}\r\n{bytes}\r\n", bytes.len()),
3867 "set {}",
3868 String::from_utf8_lossy(typed)
3869 );
3870 }
3871 assert!(
3872 f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
3873 "the report agrees with the setting"
3874 );
3875
3876 for bad in [&b"1tb"[..], b"-1", b"", b"lots"] {
3879 assert_eq!(
3880 f.run(&[b"CONFIG", b"SET", b"maxmemory", bad]),
3881 "-ERR CONFIG SET failed (possibly related to argument 'maxmemory') - argument must be a memory value\r\n",
3882 "refused {}",
3883 String::from_utf8_lossy(bad)
3884 );
3885 }
3886 assert!(
3887 f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
3888 "and the refusal left the old one alone"
3889 );
3890 }
3891
3892 #[test]
3893 fn a_write_is_refused_when_there_is_no_room_and_nothing_to_evict() {
3894 let mut f = Fixture::new();
3895 f.run(&[b"SET", b"here", b"already"]);
3896 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1"]);
3900 assert_eq!(
3901 f.run(&[b"SET", b"k", b"v"]),
3902 "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
3903 );
3904 assert_eq!(
3905 f.run(&[b"LPUSH", b"l", b"v"]),
3906 "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
3907 );
3908 assert_eq!(f.run(&[b"GET", b"here"]), "$7\r\nalready\r\n");
3910 assert_eq!(f.run(&[b"DEL", b"here"]), ":1\r\n");
3911 assert!(f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"));
3912
3913 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
3915 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
3916 }
3917
3918 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
3922 #[test]
3923 fn an_allkeys_policy_makes_room_instead_of_refusing() {
3924 let mut f = Fixture::new();
3925 let val = vec![b'v'; 256];
3926 for i in 0..24000u32 {
3927 let k = format!("key:{i:08}");
3928 f.run(&[b"SET", k.as_bytes(), &val]);
3929 }
3930 let full = f.server.memory_bytes();
3931 assert!(
3932 full > 3 * 1024 * 1024,
3933 "the arena is several segments: {full}"
3934 );
3935
3936 let limit = full - 2 * 1024 * 1024;
3940 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
3941 f.run(&[
3942 b"CONFIG",
3943 b"SET",
3944 b"maxmemory",
3945 limit.to_string().as_bytes(),
3946 ]);
3947
3948 for i in 0..2000u32 {
3952 let k = format!("new:{i:08}");
3953 assert_eq!(
3954 f.run(&[b"SET", k.as_bytes(), &val]),
3955 "+OK\r\n",
3956 "write {i} was refused"
3957 );
3958 f.server.refresh_memory();
3959 if f.server.memory_bytes() <= limit {
3960 break;
3961 }
3962 }
3963 assert!(
3964 f.server.memory_bytes() <= limit,
3965 "it never got under: {} against {limit}",
3966 f.server.memory_bytes()
3967 );
3968 let info = f.run(&[b"INFO", b"stats"]);
3969 assert!(!info.contains("evicted_keys:0"), "{info}");
3970 assert!(
3971 f.run(&[b"DBSIZE"]) != ":0\r\n",
3972 "and it did not empty the database to get there"
3973 );
3974 }
3975
3976 #[cfg_attr(miri, ignore = "an hour of commands, and they cannot come down")]
3987 #[test]
3988 fn the_running_total_and_the_walk_agree_on_a_mixed_keyspace() {
3989 let mut f = Fixture::new();
3996 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
3997 let big = vec![b'v'; 200];
3998
3999 for i in 0..400u32 {
4000 let n = i.to_string();
4001 let n = n.as_bytes();
4002 f.run(&[b"SADD", b"s", n]);
4003 f.run(&[b"SADD", b"s2", &big]);
4004 f.run(&[b"HSET", b"h", n, &big]);
4005 f.run(&[b"RPUSH", b"l", &big]);
4006 f.run(&[b"ZADD", b"z", n, n]);
4007 f.run(&[b"ARSET", b"a", n, &big]);
4008 if i % 7 == 0 {
4009 f.run(&[b"SREM", b"s", n]);
4010 f.run(&[b"HDEL", b"h", n]);
4011 f.run(&[b"LPOP", b"l"]);
4012 f.run(&[b"ZREM", b"z", n]);
4013 f.run(&[b"ARDEL", b"a", n]);
4014 }
4015 if i % 53 == 0 {
4016 f.run(&[b"DEL", b"s2"]);
4019 }
4020 assert_eq!(
4021 f.server.settled_memory(),
4022 f.server.memory_bytes(),
4023 "after round {i}"
4024 );
4025 }
4026
4027 assert_eq!(f.run(&[b"DBSIZE"]), ":6\r\n");
4030 assert!(
4031 f.server.memory_bytes() > 512 * 1024,
4032 "{}",
4033 f.server.memory_bytes()
4034 );
4035
4036 f.run(&[b"FLUSHALL"]);
4038 assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
4039 }
4040
4041 #[test]
4042 fn taking_the_limit_away_stops_the_counting_and_putting_it_back_starts_again() {
4043 let mut f = Fixture::new();
4048 for i in 0..200u32 {
4049 let n = i.to_string();
4050 f.run(&[b"SADD", b"s", n.as_bytes()]);
4051 f.run(&[b"HSET", b"h", n.as_bytes(), b"value"]);
4052 }
4053 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
4054 assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
4055
4056 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
4057 for i in 200..400u32 {
4058 let n = i.to_string();
4059 f.run(&[b"SADD", b"s", n.as_bytes()]);
4060 }
4061 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
4062 assert_eq!(
4063 f.server.settled_memory(),
4064 f.server.memory_bytes(),
4065 "the writes it was not watching are in the number it started from"
4066 );
4067 }
4068
4069 #[test]
4070 fn evicted_keys_and_expired_keys_are_different_numbers() {
4071 let mut f = Fixture::new();
4072 f.run(&[b"SET", b"gone", b"v", b"PX", b"1"]);
4075 f.server.advance_clock_ms(20);
4076 f.run(&[b"GET", b"gone"]);
4077 let info = f.run(&[b"INFO", b"stats"]);
4078 assert!(info.contains("expired_keys:1"), "{info}");
4079 assert!(info.contains("evicted_keys:0"), "{info}");
4080 }
4081
4082 #[test]
4083 fn the_object_subcommands_follow_the_policy() {
4084 let mut f = Fixture::new();
4085 f.run(&[b"SET", b"s", b"v"]);
4086 assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
4090 assert!(
4091 f.run(&[b"OBJECT", b"FREQ", b"s"])
4092 .starts_with("-ERR An LFU maxmemory policy is not selected"),
4093 );
4094
4095 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lfu"]);
4096 assert!(
4097 f.run(&[b"OBJECT", b"IDLETIME", b"s"])
4098 .starts_with("-ERR An LFU maxmemory policy is selected"),
4099 );
4100 assert!(
4105 f.run(&[b"OBJECT", b"FREQ", b"s"]).starts_with(':'),
4106 "FREQ should answer under an LFU policy"
4107 );
4108 }
4109
4110 #[test]
4111 fn object_says_which_rung_of_the_ladder_a_key_is_on() {
4112 let mut f = Fixture::new();
4113 f.run(&[b"SET", b"s", b"hello"]);
4114 f.run(&[b"SET", b"n", b"123"]);
4115 f.run(&[b"SADD", b"si", b"1", b"2", b"3"]);
4116 f.run(&[b"SADD", b"ss", b"a", b"b"]);
4117 f.run(&[b"HSET", b"h", b"f", b"v"]);
4118 for (key, want) in [
4119 (b"s".as_slice(), "embstr"),
4120 (b"n", "int"),
4121 (b"si", "intset"),
4122 (b"ss", "listpack"),
4123 (b"h", "listpack"),
4124 ] {
4125 let reply = f.run(&[b"OBJECT", b"ENCODING", key]);
4126 assert_eq!(reply, format!("${}\r\n{want}\r\n", want.len()));
4127 }
4128
4129 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"f"]);
4132 assert_eq!(
4133 f.run(&[b"OBJECT", b"ENCODING", b"h"]),
4134 "$10\r\nlistpackex\r\n"
4135 );
4136
4137 assert_eq!(f.run(&[b"OBJECT", b"REFCOUNT", b"s"]), ":1\r\n");
4138 assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
4139 assert!(f.run(&[b"OBJECT", b"HELP"]).starts_with("*14\r\n+OBJECT "));
4140 }
4141
4142 #[test]
4143 fn object_answers_nil_for_a_key_that_is_not_there() {
4144 let mut f = Fixture::new();
4145 for sub in [b"ENCODING".as_slice(), b"REFCOUNT", b"IDLETIME", b"FREQ"] {
4146 assert_eq!(
4147 f.run(&[b"OBJECT", sub, b"nokey"]),
4148 "$-1\r\n",
4149 "a nil and not an error, which is what 8.10.1 does"
4150 );
4151 }
4152 f.run(&[b"SET", b"s", b"v"]);
4155 assert!(
4156 f.run(&[b"OBJECT", b"FREQ", b"s"])
4157 .starts_with("-ERR An LFU maxmemory policy is not"),
4158 );
4159 assert_eq!(
4160 f.run(&[b"OBJECT", b"NOPE", b"s"]),
4161 "-ERR unknown subcommand 'NOPE'. Try OBJECT HELP.\r\n"
4162 );
4163 assert_eq!(
4164 f.run(&[b"OBJECT", b"ENCODING"]),
4165 "-ERR wrong number of arguments for 'object|encoding' command\r\n"
4166 );
4167 assert_eq!(
4168 f.run(&[b"OBJECT", b"ENCODING", b"s", b"extra"]),
4169 "-ERR wrong number of arguments for 'object|encoding' command\r\n"
4170 );
4171 assert_eq!(
4172 f.run(&[b"OBJECT"]),
4173 "-ERR wrong number of arguments for 'object' command\r\n"
4174 );
4175 }
4176
4177 #[test]
4178 fn config_moves_the_ladder_and_object_encoding_agrees() {
4179 let mut f = Fixture::new();
4180 assert_eq!(
4181 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
4182 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
4183 "512 and not the 128 everyone remembers, which is what 8.10.1 says"
4184 );
4185 assert_eq!(
4188 f.run(&[b"CONFIG", b"GET", b"hash-max-ziplist-entries"]),
4189 "*2\r\n$24\r\nhash-max-ziplist-entries\r\n$3\r\n512\r\n"
4190 );
4191 assert!(
4192 f.run(&[b"CONFIG", b"GET", b"hash-max-*"])
4193 .starts_with("*8\r\n")
4194 );
4195 assert!(
4196 f.run(&[b"CONFIG", b"GET", b"set-max-*"])
4197 .starts_with("*6\r\n")
4198 );
4199
4200 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2", b"c", b"3"]);
4201 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$8\r\nlistpack\r\n");
4202
4203 assert_eq!(
4204 f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"2"]),
4205 "+OK\r\n",
4206 "written under the old name and read back under the new one"
4207 );
4208 assert_eq!(
4209 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
4210 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n"
4211 );
4212 assert_eq!(
4213 f.run(&[b"OBJECT", b"ENCODING", b"h"]),
4214 "$8\r\nlistpack\r\n",
4215 "the hash that already exists is left exactly where it was"
4216 );
4217 f.run(&[b"HSET", b"h2", b"a", b"1", b"b", b"2", b"c", b"3"]);
4218 assert_eq!(
4219 f.run(&[b"OBJECT", b"ENCODING", b"h2"]),
4220 "$9\r\nhashtable\r\n",
4221 "and the next one built goes straight to a table"
4222 );
4223
4224 f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"2"]);
4226 f.run(&[b"SADD", b"s", b"1", b"2", b"3"]);
4227 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$8\r\nlistpack\r\n");
4228 f.run(&[b"CONFIG", b"SET", b"set-max-listpack-value", b"2"]);
4229 f.run(&[b"SADD", b"s2", b"abcdefgh"]);
4230 assert_eq!(
4231 f.run(&[b"OBJECT", b"ENCODING", b"s2"]),
4232 "$9\r\nhashtable\r\n"
4233 );
4234 }
4235
4236 #[test]
4237 fn config_set_takes_all_of_the_ladder_or_none_of_it() {
4238 let mut f = Fixture::new();
4239 assert_eq!(
4240 f.run(&[
4241 b"CONFIG",
4242 b"SET",
4243 b"hash-max-listpack-entries",
4244 b"7",
4245 b"set-max-listpack-entries",
4246 b"abc"
4247 ]),
4248 "-ERR CONFIG SET failed (possibly related to argument 'set-max-listpack-entries') - argument couldn't be parsed into an integer\r\n"
4249 );
4250 assert_eq!(
4251 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
4252 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
4253 "the pair in front of the bad one did not go in"
4254 );
4255 assert_eq!(
4258 f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"abc"]),
4259 "-ERR CONFIG SET failed (possibly related to argument 'hash-max-ziplist-entries') - argument couldn't be parsed into an integer\r\n"
4260 );
4261 assert_eq!(
4262 f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"-1"]),
4263 "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument must be between 0 and 9223372036854775807 inclusive\r\n"
4264 );
4265 assert_eq!(
4268 f.run(&[
4269 b"CONFIG",
4270 b"SET",
4271 b"set-max-intset-entries",
4272 b"99999999999999999999"
4273 ]),
4274 "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument couldn't be parsed into an integer\r\n"
4275 );
4276 assert_eq!(
4277 f.run(&[
4278 b"CONFIG",
4279 b"SET",
4280 b"set-max-intset-entries",
4281 b"9223372036854775807"
4282 ]),
4283 "+OK\r\n"
4284 );
4285 }
4286
4287 #[test]
4288 fn a_setting_moved_on_one_database_moved_on_all_of_them() {
4289 let mut f = Fixture::new();
4290 f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"1"]);
4291 f.run(&[b"SELECT", b"3"]);
4292 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
4293 assert_eq!(
4294 f.run(&[b"OBJECT", b"ENCODING", b"h"]),
4295 "$9\r\nhashtable\r\n",
4296 "these are one server wide number in Redis, whatever a Keyspace carries"
4297 );
4298 }
4299
4300 #[test]
4301 fn info_reports_the_numbers_it_can_stand_behind() {
4302 let mut f = Fixture::new();
4303 f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
4304 let all = f.run(&[b"INFO"]);
4305 assert!(all.contains("redis_version:8.8.0"), "{all}");
4306 assert!(
4307 all.contains(concat!("yo_version:", env!("CARGO_PKG_VERSION"))),
4308 "{all}"
4309 );
4310 assert!(all.contains("db0:keys=2,expires=0,avg_ttl=0"), "{all}");
4311 assert!(all.contains("role:master"), "{all}");
4312 let clients = f.run(&[b"INFO", b"clients"]);
4314 assert!(clients.contains("connected_clients:0"), "{clients}");
4315 assert!(!clients.contains("redis_version"), "{clients}");
4316 assert_eq!(f.run(&[b"INFO", b"nosuch"]), "$0\r\n\r\n");
4317 }
4318
4319 #[test]
4326 fn commandstats_is_asked_for_and_replication_is_not() {
4327 let mut f = Fixture::new();
4328 for arg in ["", "all", "default", "everything"] {
4329 let info = if arg.is_empty() {
4330 f.run(&[b"INFO"])
4331 } else {
4332 f.run(&[b"INFO", arg.as_bytes()])
4333 };
4334 assert!(info.contains("redis_version"), "{arg}: {info}");
4335 assert!(info.contains("used_cpu_user"), "{arg}: {info}");
4336 assert!(info.contains("used_memory"), "{arg}: {info}");
4337 assert!(!info.contains("sentinel_tilt"), "{arg}: {info}");
4338 let asked = arg == "all" || arg == "everything";
4339 assert_eq!(
4340 info.contains("rejected_calls"),
4341 asked,
4342 "{arg} should{} carry the command counters: {info}",
4343 if asked { "" } else { " not" }
4344 );
4345 }
4346
4347 let cpu = f.run(&[b"INFO", b"cpu"]);
4348 assert!(cpu.contains("used_cpu_user"), "{cpu}");
4349 assert!(!cpu.contains("used_memory"), "{cpu}");
4350
4351 let stats = f.run(&[b"INFO", b"commandSTATS"]);
4354 assert!(!stats.contains("used_memory"), "{stats}");
4355 assert!(stats.contains("rejected_calls"), "{stats}");
4356
4357 let pair = f.run(&[b"INFO", b"cpu", b"sentinel"]);
4359 assert!(pair.contains("used_cpu_user"), "{pair}");
4360 assert!(!pair.contains("master_repl_offset"), "{pair}");
4361
4362 let with_all = f.run(&[b"INFO", b"cpu", b"all"]);
4363 assert!(with_all.contains("used_memory"), "{with_all}");
4364 assert!(with_all.contains("master_repl_offset"), "{with_all}");
4365 assert!(with_all.contains("rejected_calls"), "{with_all}");
4366 assert_eq!(
4368 with_all.matches("used_cpu_user_children").count(),
4369 1,
4370 "{with_all}"
4371 );
4372
4373 let with_default = f.run(&[b"INFO", b"cpu", b"default"]);
4374 assert!(with_default.contains("used_memory"), "{with_default}");
4375 assert!(
4376 with_default.contains("master_repl_offset"),
4377 "{with_default}"
4378 );
4379 assert!(!with_default.contains("rejected_calls"), "{with_default}");
4380 assert_eq!(
4381 with_default.matches("used_cpu_user_children").count(),
4382 1,
4383 "{with_default}"
4384 );
4385 }
4386
4387 #[test]
4396 fn info_memory_reports_the_cap_and_the_quarter_of_it_that_gets_used() {
4397 let mut f = Fixture::new();
4398 let info = f.run(&[b"INFO", b"memory"]);
4399 for field in [
4400 "total_system_memory:",
4401 "mem_cgroup_limit:",
4402 "mem_limit:",
4403 "mem_budget:",
4404 ] {
4405 assert!(info.contains(field), "no {field} in {info}");
4406 }
4407
4408 let field = |name: &str| -> u64 {
4409 info.lines()
4410 .find_map(|l| l.strip_prefix(name))
4411 .unwrap_or_else(|| panic!("no {name} in {info}"))
4412 .trim()
4413 .parse()
4414 .unwrap_or_else(|_| panic!("{name} is not a number in {info}"))
4415 };
4416 let limit = field("mem_limit:");
4417 assert_eq!(field("mem_budget:"), limit / 4, "{info}");
4418 if limit != 0 {
4421 let host = field("total_system_memory:");
4422 let cgroup = field("mem_cgroup_limit:");
4423 assert!(
4424 limit == host || limit == cgroup,
4425 "the limit came from neither number: {info}"
4426 );
4427 }
4428 }
4429
4430 #[test]
4439 fn a_command_counts_what_it_did_separately_from_what_it_refused() {
4440 let mut f = Fixture::new();
4441 f.run(&[b"SET", b"k", b"v"]);
4442 f.run(&[b"SET", b"k", b"w"]);
4443 f.run(&[b"LPUSH", b"k", b"x"]);
4445 f.run(&[b"LPUSH", b"k"]);
4447
4448 let stats = f.run(&[b"INFO", b"commandstats"]);
4449 assert!(
4450 stats.contains("cmdstat_set:calls=2,rejected_calls=0,failed_calls=0"),
4451 "{stats}"
4452 );
4453 assert!(
4454 stats.contains("cmdstat_lpush:calls=1,rejected_calls=1,failed_calls=1"),
4455 "{stats}"
4456 );
4457 assert!(
4458 !stats.contains("cmdstat_zadd"),
4459 "a command nobody has sent has no row: {stats}"
4460 );
4461 }
4462
4463 #[test]
4467 fn the_active_sweep_reclaims_keys_no_client_comes_back_for() {
4468 let (dead, live) = if cfg!(miri) {
4475 (375, 125)
4476 } else {
4477 (3_000, 1_000)
4478 };
4479 let mut f = Fixture::new();
4480 for i in 0..dead {
4481 f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
4482 }
4483 for i in 0..live {
4484 f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
4485 }
4486 let all = format!(":{}\r\n", dead + live);
4487 assert_eq!(f.run(&[b"DBSIZE"]), all);
4488 f.advance(100);
4489 assert_eq!(
4490 f.run(&[b"DBSIZE"]),
4491 all,
4492 "DBSIZE counts records and nothing has read past the dead ones yet"
4493 );
4494
4495 let rest = format!(":{live}\r\n");
4497 let mut spent = 0;
4498 for _ in 0..2_000 {
4499 spent += f.server.expire_step(4096);
4500 if f.run(&[b"DBSIZE"]) == rest {
4501 break;
4502 }
4503 }
4504 assert_eq!(f.run(&[b"DBSIZE"]), rest, "spent {spent} looks");
4505 assert!(
4506 f.run(&[b"INFO", b"stats"])
4507 .contains(&format!("expired_keys:{dead}"))
4508 );
4509 for i in 0..live {
4510 assert_eq!(
4511 f.run(&[b"GET", format!("k{i}").as_bytes()]),
4512 "$1\r\nv\r\n",
4513 "it took a key that had no deadline"
4514 );
4515 }
4516 }
4517
4518 #[test]
4519 fn a_sweep_of_a_server_with_no_deadlines_anywhere_costs_nothing() {
4520 let n = if cfg!(miri) { 200 } else { 2_000 };
4524 let mut f = Fixture::new();
4525 for i in 0..n {
4526 f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
4527 }
4528 assert_eq!(f.server.expire_step(4096), 0);
4529 f.run(&[b"SELECT", b"3"]);
4531 f.run(&[b"SET", b"x", b"v", b"PX", b"50"]);
4532 f.advance(100);
4533 for _ in 0..64 {
4534 f.server.expire_step(4096);
4535 }
4536 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4537 f.run(&[b"SELECT", b"0"]);
4538 assert_eq!(f.run(&[b"DBSIZE"]), format!(":{n}\r\n"));
4539 assert_eq!(f.server.expire_step(4096), 0, "and it is quiet again");
4540 }
4541
4542 #[test]
4545 fn the_sweep_the_loop_calls_runs_at_most_once_a_millisecond() {
4546 let mut f = Fixture::new();
4547 for i in 0..500u32 {
4548 f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
4549 }
4550 f.advance(100);
4551 let at = f.server.striped(0).now_ms();
4552 f.server.set_clock_ms(at);
4553 assert!(f.server.expire_slice(8) > 0, "the first one works");
4557 for _ in 0..1_000 {
4558 assert_eq!(
4559 f.server.expire_slice(8),
4560 0,
4561 "the millisecond has not moved and neither should this"
4562 );
4563 }
4564 assert!(
4565 f.server.striped(0).expires() > 400,
4566 "there is plenty left to take"
4567 );
4568 f.server.set_clock_ms(at + 1);
4569 assert!(f.server.expire_slice(8) > 0, "and then it goes again");
4570 }
4571
4572 #[test]
4575 fn info_keyspace_counts_the_keys_that_have_a_deadline() {
4576 let mut f = Fixture::new();
4577 f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
4578 assert!(
4579 f.run(&[b"INFO", b"keyspace"])
4580 .contains("db0:keys=3,expires=0"),
4581 "none of them has one yet"
4582 );
4583 f.run(&[b"EXPIRE", b"a", b"1000"]);
4584 f.run(&[b"EXPIRE", b"b", b"1000"]);
4585 let two = f.run(&[b"INFO", b"keyspace"]);
4586 assert!(two.contains("db0:keys=3,expires=2"), "{two}");
4587 f.run(&[b"PERSIST", b"a"]);
4588 f.run(&[b"DEL", b"b"]);
4589 let none = f.run(&[b"INFO", b"keyspace"]);
4590 assert!(none.contains("db0:keys=2,expires=0"), "{none}");
4591
4592 f.run(&[b"SELECT", b"1"]);
4594 f.run(&[b"SET", b"x", b"1", b"EX", b"1000"]);
4595 let both = f.run(&[b"INFO", b"keyspace"]);
4596 assert!(both.contains("db0:keys=2,expires=0"), "{both}");
4597 assert!(both.contains("db1:keys=1,expires=1"), "{both}");
4598 }
4599
4600 #[cfg(unix)]
4607 #[cfg_attr(miri, ignore = "no getrusage under Miri, so the number is fixed")]
4608 #[test]
4609 fn info_cpu_reports_processor_time_that_was_really_measured() {
4610 let mut f = Fixture::new();
4611 let cpu = f.run(&[b"INFO", b"cpu"]);
4612 assert!(cpu.contains("# CPU"), "{cpu}");
4613 assert!(cpu.contains("used_cpu_user:"), "{cpu}");
4615 assert!(cpu.contains("used_cpu_sys:"), "{cpu}");
4616 assert!(cpu.contains("used_cpu_user_children:0.000000"), "{cpu}");
4617 assert!(!cpu.contains("redis_version"), "{cpu}");
4618
4619 let before = used_cpu_user(&cpu);
4623 let mut n = 0u64;
4624 let mut rounds = 0;
4625 while used_cpu_user(&f.run(&[b"INFO", b"cpu"])) <= before {
4626 for i in 0..1_000_000u64 {
4627 n = n.wrapping_add(i.wrapping_mul(i));
4628 }
4629 rounds += 1;
4630 assert!(rounds < 1_000, "cpu time never moved, n is {n}");
4634 }
4635 }
4636
4637 #[cfg(unix)]
4639 fn used_cpu_user(info: &str) -> f64 {
4640 info.lines()
4641 .find_map(|l| l.strip_prefix("used_cpu_user:"))
4642 .expect("no used_cpu_user in the reply")
4643 .trim()
4644 .parse()
4645 .expect("used_cpu_user is not a number")
4646 }
4647
4648 #[test]
4654 fn a_command_that_fails_leaves_nothing_half_written() {
4655 let mut f = Fixture::new();
4656 let reply = f.run(&[b"SETRANGE", b"k", b"-1", b"x"]);
4657 assert_eq!(reply, "-ERR offset is out of range\r\n");
4658 assert!(!reply.contains(':'), "no integer went out in front of it");
4659 }
4660
4661 #[test]
4662 fn quit_answers_first_and_closes_after() {
4663 let mut f = Fixture::new();
4664 let (flow, reply) = f.flow(&[b"QUIT"]);
4665 assert_eq!(reply, "+OK\r\n");
4666 assert_eq!(flow, Flow::Close);
4667 }
4668
4669 #[test]
4676 fn shutdown_writes_nothing_and_sets_the_flag() {
4677 let mut f = Fixture::new();
4678 assert!(!f.server.stopping(), "nobody has asked yet");
4679
4680 let (flow, reply) = f.flow(&[b"SHUTDOWN"]);
4681 assert_eq!(reply, "");
4682 assert_eq!(flow, Flow::Close);
4683 assert!(f.server.stopping());
4684 }
4685
4686 #[test]
4694 fn shutdown_takes_the_flags_redis_takes() {
4695 for flags in [
4696 &[b"NOSAVE".as_slice()][..],
4697 &[b"SAVE"],
4698 &[b"NOW"],
4699 &[b"FORCE"],
4700 &[b"nosave"],
4701 &[b"NOW", b"NOW"],
4702 &[b"SAVE", b"SAVE"],
4703 &[b"NOSAVE", b"NOW", b"FORCE"],
4704 ] {
4705 let mut f = Fixture::new();
4706 let mut parts = vec![b"SHUTDOWN".as_slice()];
4707 parts.extend_from_slice(flags);
4708 let (flow, reply) = f.flow(&parts);
4709 assert_eq!(reply, "", "SHUTDOWN {flags:?} answered something");
4710 assert_eq!(flow, Flow::Close, "SHUTDOWN {flags:?} did not close");
4711 assert!(f.server.stopping(), "SHUTDOWN {flags:?} did not stop");
4712 }
4713
4714 for flags in [
4715 &[b"BOGUS".as_slice()][..],
4716 &[b"SAVE", b"NOSAVE"],
4717 &[b"NOSAVE", b"SAVE"],
4718 &[b"ABORT", b"NOW"],
4719 &[b"NOSAVE", b"ABORT"],
4720 &[b"NOW", b"FORCE", b"ABORT"],
4721 ] {
4722 let mut f = Fixture::new();
4723 let mut parts = vec![b"SHUTDOWN".as_slice()];
4724 parts.extend_from_slice(flags);
4725 assert_eq!(
4726 f.run(&parts),
4727 "-ERR syntax error\r\n",
4728 "SHUTDOWN {flags:?} was accepted"
4729 );
4730 assert!(!f.server.stopping(), "SHUTDOWN {flags:?} stopped anyway");
4731 }
4732 }
4733
4734 #[test]
4742 fn shutdown_abort_never_has_anything_to_abort() {
4743 let mut f = Fixture::new();
4744 for parts in [
4745 &[b"SHUTDOWN".as_slice(), b"ABORT"][..],
4746 &[b"SHUTDOWN", b"ABORT", b"ABORT"],
4747 ] {
4748 assert_eq!(f.run(parts), "-ERR No shutdown in progress.\r\n");
4749 assert!(!f.server.stopping(), "an abort stopped the server");
4750 }
4751 }
4752
4753 struct Backups {
4761 f: Fixture,
4762 dir: PathBuf,
4763 }
4764
4765 impl Backups {
4766 fn new(name: &str) -> Backups {
4767 let dir = std::env::temp_dir().join(format!("yo-backup-{name}-{}", std::process::id()));
4768 let _ = std::fs::remove_dir_all(&dir);
4769 std::fs::create_dir_all(&dir).expect("could not make a temporary directory");
4770 let mut f = Fixture::new();
4771 f.server.set_dir(dir.clone());
4772 Backups { f, dir }
4773 }
4774
4775 fn run(&mut self, parts: &[&[u8]]) -> String {
4776 self.f.run(parts)
4777 }
4778
4779 fn files(&self) -> Vec<String> {
4781 let mut names: Vec<String> = match std::fs::read_dir(self.dir.join("backupdir")) {
4782 Ok(entries) => entries
4783 .filter_map(|e| e.ok())
4784 .map(|e| e.file_name().to_string_lossy().into_owned())
4785 .collect(),
4786 Err(_) => Vec::new(),
4787 };
4788 names.sort();
4789 names
4790 }
4791
4792 fn read(&self, name: &str) -> Vec<u8> {
4793 std::fs::read(self.dir.join("backupdir").join(name)).expect("could not read")
4794 }
4795 }
4796
4797 impl Drop for Backups {
4798 fn drop(&mut self) {
4799 let _ = std::fs::remove_dir_all(&self.dir);
4800 }
4801 }
4802
4803 #[test]
4806 fn backup_walks_the_states_the_reference_walks() {
4807 let mut b = Backups::new("states");
4808 let status = |b: &mut Backups| b.run(&[b"BACKUP", b"STATUS"]);
4809
4810 assert!(status(&mut b).contains("idle"));
4811 assert!(b.files().is_empty(), "an idle server has written a backup");
4812
4813 assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
4814 assert!(status(&mut b).contains("incrementing"));
4815 assert_eq!(b.files(), ["appendonly.aof.1.base.rdb"]);
4816
4817 assert_eq!(b.run(&[b"BACKUP", b"SEAL"]), "+OK\r\n");
4818 assert!(status(&mut b).contains("sealed"));
4819 assert_eq!(
4820 b.files(),
4821 [
4822 "appendonly.aof.1.base.rdb",
4823 "appendonly.aof.1.incr.aof",
4824 "appendonly.aof.manifest",
4825 ]
4826 );
4827
4828 assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
4829 assert!(status(&mut b).contains("idle"));
4830 assert!(b.files().is_empty(), "cleanup left something behind");
4831 }
4832
4833 #[test]
4835 fn backup_refuses_the_moves_the_reference_refuses() {
4836 let mut b = Backups::new("refusals");
4837
4838 assert_eq!(
4839 b.run(&[b"BACKUP", b"SEAL"]),
4840 "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
4841 );
4842 assert_eq!(
4843 b.run(&[b"BACKUP", b"ABORT"]),
4844 "-ERR No backup in progress\r\n"
4845 );
4846 assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
4849
4850 b.run(&[b"BACKUP", b"START"]);
4851 assert_eq!(
4852 b.run(&[b"BACKUP", b"START"]),
4853 "-ERR A backup is already in progress, ABORT it first\r\n"
4854 );
4855 assert_eq!(
4856 b.run(&[b"BACKUP", b"CLEANUP"]),
4857 "-ERR Backup is in progress\r\n"
4858 );
4859
4860 b.run(&[b"BACKUP", b"SEAL"]);
4861 assert_eq!(
4862 b.run(&[b"BACKUP", b"START"]),
4863 "-ERR A sealed backup exists, CLEANUP it first\r\n"
4864 );
4865 assert_eq!(
4866 b.run(&[b"BACKUP", b"SEAL"]),
4867 "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
4868 );
4869 assert_eq!(
4870 b.run(&[b"BACKUP", b"ABORT"]),
4871 "-ERR No backup in progress\r\n"
4872 );
4873 }
4874
4875 #[test]
4882 fn backup_abort_removes_the_file_and_says_who_did_it() {
4883 let mut b = Backups::new("abort");
4884 b.run(&[b"BACKUP", b"START"]);
4885 assert_eq!(b.run(&[b"BACKUP", b"ABORT"]), "+OK\r\n");
4886
4887 let status = b.run(&[b"BACKUP", b"STATUS"]);
4888 assert!(status.contains("failed"), "{status}");
4889 assert!(status.contains("aborted by user"), "{status}");
4890 assert!(b.files().is_empty(), "abort left the base file behind");
4891 assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
4892
4893 assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
4895 assert_eq!(b.files(), ["appendonly.aof.2.base.rdb"]);
4896 let status = b.run(&[b"BACKUP", b"STATUS"]);
4897 assert!(status.contains("incrementing"), "{status}");
4898 assert!(!status.contains("aborted"), "the old error was kept");
4899 }
4900
4901 #[test]
4903 fn backup_list_names_the_files_that_are_pinned_so_far() {
4904 let mut b = Backups::new("list");
4905 assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
4906
4907 b.run(&[b"BACKUP", b"START"]);
4908 let base = b.dir.join("backupdir").join("appendonly.aof.1.base.rdb");
4909 let base = base.to_string_lossy().into_owned();
4910 assert_eq!(
4911 b.run(&[b"BACKUP", b"LIST"]),
4912 format!("*1\r\n${}\r\n{base}\r\n", base.len())
4913 );
4914
4915 b.run(&[b"BACKUP", b"SEAL"]);
4916 let listed = b.run(&[b"BACKUP", b"LIST"]);
4917 assert!(listed.starts_with("*3\r\n"), "{listed}");
4918 let names: Vec<&str> = listed
4921 .lines()
4922 .filter(|l| l.starts_with('/') || l.contains(":\\"))
4923 .collect();
4924 assert_eq!(names.len(), 3, "{listed}");
4925 assert!(names[0].ends_with("appendonly.aof.1.base.rdb"), "{listed}");
4926 assert!(names[1].ends_with("appendonly.aof.1.incr.aof"), "{listed}");
4927 assert!(names[2].ends_with("appendonly.aof.manifest"), "{listed}");
4928 }
4929
4930 #[test]
4938 fn a_backup_holds_the_dataset_as_it_was_at_start() {
4939 let mut b = Backups::new("contents");
4940 b.run(&[b"SET", b"bk", b"v1"]);
4941 b.run(&[b"BACKUP", b"START"]);
4942 b.run(&[b"SET", b"bk", b"v2"]);
4943 b.run(&[b"BACKUP", b"SEAL"]);
4944
4945 let base = b.read("appendonly.aof.1.base.rdb");
4946 assert!(base.starts_with(b"REDIS"), "not an RDB file");
4947 assert!(base.windows(2).any(|w| w == b"v1"), "the value is missing");
4948 assert!(
4949 !base.windows(2).any(|w| w == b"v2"),
4950 "the base file moved on after START"
4951 );
4952 let at = base
4957 .windows(8)
4958 .position(|w| w == b"aof-base")
4959 .expect("no aof-base aux field");
4960 assert_eq!(&base[at + 8..at + 10], b"\xc0\x01", "{:?}", &base[at..]);
4961
4962 assert!(b.read("appendonly.aof.1.incr.aof").is_empty());
4963 assert_eq!(
4964 String::from_utf8(b.read("appendonly.aof.manifest")).expect("the manifest is text"),
4965 "file appendonly.aof.1.base.rdb seq 1 type b\n\
4966 file appendonly.aof.1.incr.aof seq 1 type i startoffset 0 endoffset 0\n"
4967 );
4968 }
4969
4970 #[test]
4973 fn backup_status_is_a_map_on_resp3_and_a_flat_array_on_resp2() {
4974 let mut b = Backups::new("status");
4975 b.f.server.set_clock_ms(1_700_000_000_000);
4976
4977 assert_eq!(
4978 b.run(&[b"BACKUP", b"STATUS"]),
4979 "*8\r\n$5\r\nstate\r\n$4\r\nidle\r\n$5\r\nerror\r\n$0\r\n\r\n\
4980 $10\r\nstart_time\r\n:0\r\n$8\r\nend_time\r\n:0\r\n"
4981 );
4982
4983 b.f.out = Out::new(Proto::Resp3);
4984 b.run(&[b"BACKUP", b"START"]);
4985 assert_eq!(
4986 b.run(&[b"BACKUP", b"STATUS"]),
4987 "%4\r\n$5\r\nstate\r\n$12\r\nincrementing\r\n$5\r\nerror\r\n$0\r\n\r\n\
4988 $10\r\nstart_time\r\n:1700000000\r\n$8\r\nend_time\r\n:0\r\n"
4989 );
4990
4991 b.run(&[b"BACKUP", b"SEAL"]);
4992 let sealed = b.run(&[b"BACKUP", b"STATUS"]);
4993 assert!(sealed.contains("end_time\r\n:1700000000"), "{sealed}");
4994 }
4995
4996 #[test]
4999 fn a_sealed_backup_is_swept_away_after_the_timeout() {
5000 let mut b = Backups::new("ttl");
5001 b.f.server.set_clock_ms(1_000_000);
5002 assert_eq!(
5003 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"60"]),
5004 "+OK\r\n"
5005 );
5006 b.run(&[b"BACKUP", b"START"]);
5007 b.run(&[b"BACKUP", b"SEAL"]);
5008
5009 b.f.server.set_clock_ms(1_000_000 + 59_000);
5011 b.f.server.backup_expire();
5012 assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
5013 assert_eq!(b.files().len(), 3);
5014
5015 b.f.server.set_clock_ms(1_000_000 + 60_000);
5016 b.f.server.backup_expire();
5017 let status = b.run(&[b"BACKUP", b"STATUS"]);
5018 assert!(status.contains("idle"), "{status}");
5019 assert!(b.files().is_empty(), "the timeout left the files behind");
5020
5021 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"0"]);
5023 b.run(&[b"BACKUP", b"START"]);
5024 b.run(&[b"BACKUP", b"SEAL"]);
5025 b.f.server.set_clock_ms(9_000_000_000);
5026 b.f.server.backup_expire();
5027 assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
5028 }
5029
5030 #[test]
5033 fn the_backup_settings_behave_the_way_the_reference_does() {
5034 let mut b = Backups::new("config");
5035 let dir = b.dir.to_string_lossy().into_owned();
5036
5037 assert_eq!(
5038 b.run(&[b"CONFIG", b"GET", b"dir"]),
5039 format!("*2\r\n$3\r\ndir\r\n${}\r\n{dir}\r\n", dir.len())
5040 );
5041 assert_eq!(
5042 b.run(&[b"CONFIG", b"GET", b"backupdirname"]),
5043 "*2\r\n$13\r\nbackupdirname\r\n$9\r\nbackupdir\r\n"
5044 );
5045 assert_eq!(
5046 b.run(&[b"CONFIG", b"GET", b"backup-sealed-ttl"]),
5047 "*2\r\n$17\r\nbackup-sealed-ttl\r\n$1\r\n0\r\n"
5048 );
5049
5050 assert_eq!(
5053 b.run(&[b"CONFIG", b"SET", b"dir", dir.as_bytes()]),
5054 "-ERR CONFIG SET failed (possibly related to argument 'dir') - can't set protected config\r\n"
5055 );
5056 assert_eq!(
5057 b.run(&[b"CONFIG", b"SET", b"backupdirname", b"other"]),
5058 "-ERR CONFIG SET failed (possibly related to argument 'backupdirname') - can't set immutable config\r\n"
5059 );
5060 assert!(
5061 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"abc"])
5062 .contains("argument couldn't be parsed into an integer")
5063 );
5064 assert!(
5065 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"-1"])
5066 .contains("argument must be between 0 and 9223372036854775807 inclusive")
5067 );
5068 }
5069
5070 #[test]
5072 fn backup_help_is_the_text_the_reference_sends() {
5073 let mut f = Fixture::new();
5074 let help = f.run(&[b"BACKUP", b"HELP"]);
5075 assert!(help.starts_with("*17\r\n"), "{help}");
5076 assert!(
5077 help.contains("+BACKUP <subcommand> [<arg> [value] [opt] ...]. Subcommands are:\r\n")
5078 );
5079 assert!(help.contains("+ Start a new backup into the configured 'backupdirname'.\r\n"));
5080 assert!(help.contains("+ Freeze the current backup (BASE + INCR + manifest).\r\n"));
5081 assert!(help.contains("+ Return this help.\r\n+HELP\r\n+ Print this help.\r\n"));
5082 }
5083
5084 #[test]
5092 fn backup_refuses_what_it_cannot_read() {
5093 let mut f = Fixture::new();
5094 assert_eq!(
5095 f.run(&[b"BACKUP"]),
5096 "-ERR wrong number of arguments for 'backup' command\r\n"
5097 );
5098 assert_eq!(
5099 f.run(&[b"BACKUP", b"START", b"x"]),
5100 "-ERR wrong number of arguments for 'backup' command\r\n"
5101 );
5102 assert_eq!(
5103 f.run(&[b"BACKUP", b"NOPE"]),
5104 "-ERR unknown subcommand 'NOPE'. Try BACKUP HELP.\r\n"
5105 );
5106 }
5107
5108 #[test]
5109 fn the_command_counter_counts_every_command_including_the_bad_ones() {
5110 let mut f = Fixture::new();
5111 f.run(&[b"PING"]);
5112 f.run(&[b"NOPE"]);
5113 f.run(&[b"GET"]);
5114 assert_eq!(f.server.totals().commands, 3);
5115 }
5116
5117 #[test]
5118 fn what_a_thread_marked_is_taken_by_the_maintenance_turn() {
5119 let mut server = Server::new();
5120 server.set_threads(2);
5121 server.mine().turn.store(0, Relaxed);
5124 server.locals[1].mark(1 << 9);
5125 server.collect_marks();
5126 assert!(server.mine().wanted(9));
5127 assert_eq!(server.locals[1].dirty.load(Relaxed), 0);
5129 }
5130
5131 #[test]
5132 fn what_two_threads_counted_is_added_up_when_info_asks() {
5133 let mut server = Server::new();
5134 server.set_threads(2);
5135 let ping = lookup(b"PING").expect("PING is a command");
5139 for (at, calls) in [(0, 2), (1, 3)] {
5140 let counters = &server.locals[at];
5141 for _ in 0..calls {
5142 counters.stats.commands.bump();
5143 counters.cmdstats.at(ping).calls.bump();
5144 }
5145 counters.stats.opened();
5146 }
5147 assert_eq!(server.totals().commands, 5);
5148 assert_eq!(server.totals().clients, 2);
5149 assert_eq!(server.totals().connections, 2);
5150 let rows: Vec<_> = server.command_stats().collect();
5151 assert_eq!(rows.len(), 1);
5152 assert_eq!(rows[0].0, "ping");
5153 assert_eq!(rows[0].1.calls, 5);
5154 server.reset_stats();
5157 assert_eq!(server.totals().commands, 0);
5158 assert_eq!(server.totals().connections, 0);
5159 assert_eq!(server.totals().clients, 2);
5160 }
5161
5162 #[test]
5163 fn the_parked_count_says_what_the_waiter_list_says() {
5164 let mut f = Fixture::new();
5165 assert_eq!(f.server.parked(), 0);
5166 for client in 1..=3u64 {
5167 f.session = Session::new(client);
5168 assert_eq!(f.flow(&[b"BLPOP", b"q", b"0"]).0, Flow::Block);
5169 }
5170 assert_eq!(f.server.parked(), 3);
5171 assert_eq!(f.server.waiters().len(), 3);
5172
5173 f.server.forget_waiters(2);
5177 assert_eq!(f.server.parked(), f.server.waiters().len());
5178 f.server.forget_waiters(1);
5179 assert_eq!(f.server.parked(), f.server.waiters().len());
5180 f.run(&[b"RPUSH", b"q", b"v"]);
5181 let mut out = Out::new(Proto::Resp2);
5182 assert!(f.server.serve_waiter(3, 0, &mut out));
5183 f.server.forget_waiters(3);
5184 assert_eq!(f.server.parked(), 0);
5185 assert!(f.server.waiters().is_empty());
5186 }
5187
5188 #[test]
5189 fn a_set_goes_from_bytes_to_bytes() {
5190 let mut f = Fixture::new();
5191 assert_eq!(f.run(&[b"SADD", b"s", b"a", b"b", b"c"]), ":3\r\n");
5192 assert_eq!(f.run(&[b"SADD", b"s", b"b", b"d"]), ":1\r\n");
5193 assert_eq!(f.run(&[b"SCARD", b"s"]), ":4\r\n");
5194 assert_eq!(f.run(&[b"SISMEMBER", b"s", b"a"]), ":1\r\n");
5195 assert_eq!(f.run(&[b"SISMEMBER", b"s", b"z"]), ":0\r\n");
5196 assert_eq!(f.run(&[b"TYPE", b"s"]), "+set\r\n");
5197 assert_eq!(
5198 f.run(&[b"SMISMEMBER", b"s", b"a", b"z", b"d"]),
5199 "*3\r\n:1\r\n:0\r\n:1\r\n"
5200 );
5201 assert_eq!(f.run(&[b"SREM", b"s", b"a", b"z"]), ":1\r\n");
5202 assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
5203 }
5204
5205 #[test]
5206 fn a_set_command_at_a_key_that_is_not_there_answers_empty() {
5207 let mut f = Fixture::new();
5208 assert_eq!(f.run(&[b"SCARD", b"nope"]), ":0\r\n");
5209 assert_eq!(f.run(&[b"SISMEMBER", b"nope", b"a"]), ":0\r\n");
5210 assert_eq!(f.run(&[b"SREM", b"nope", b"a"]), ":0\r\n");
5211 assert_eq!(f.run(&[b"SMEMBERS", b"nope"]), "*0\r\n");
5212 assert_eq!(
5213 f.run(&[b"SMISMEMBER", b"nope", b"a", b"b"]),
5214 "*2\r\n:0\r\n:0\r\n"
5215 );
5216 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n", "and made nothing");
5217 }
5218
5219 #[test]
5220 fn smembers_answers_a_set_on_resp3_and_an_array_on_resp2() {
5221 let mut f = Fixture::new();
5225 f.run(&[b"SADD", b"s", b"one"]);
5226 assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$3\r\none\r\n");
5227
5228 f.run(&[b"HELLO", b"3"]);
5229 assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "~1\r\n$3\r\none\r\n");
5230 }
5231
5232 #[test]
5233 fn an_integer_member_comes_back_as_the_digits_it_never_stored() {
5234 let mut f = Fixture::new();
5237 f.run(&[b"SADD", b"s", b"42"]);
5238 assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$2\r\n42\r\n");
5239 assert_eq!(f.run(&[b"SISMEMBER", b"s", b"42"]), ":1\r\n");
5240 assert_eq!(
5241 f.run(&[b"SISMEMBER", b"s", b"042"]),
5242 ":0\r\n",
5243 "the member is the bytes and not the number they parse to"
5244 );
5245 }
5246
5247 #[test]
5248 fn the_wrong_command_at_the_wrong_type_says_so_both_ways() {
5249 let mut f = Fixture::new();
5250 f.run(&[b"SET", b"str", b"v"]);
5251 f.run(&[b"SADD", b"set", b"a"]);
5252
5253 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
5254 assert_eq!(f.run(&[b"SADD", b"str", b"a"]), wrong);
5255 assert_eq!(f.run(&[b"SCARD", b"str"]), wrong);
5256 assert_eq!(f.run(&[b"SMEMBERS", b"str"]), wrong);
5257 assert_eq!(f.run(&[b"SMISMEMBER", b"str", b"a"]), wrong);
5258 assert_eq!(f.run(&[b"GET", b"set"]), wrong);
5259 assert_eq!(f.run(&[b"APPEND", b"set", b"x"]), wrong);
5260 assert_eq!(f.run(&[b"INCR", b"set"]), wrong);
5261 assert_eq!(f.run(&[b"STRLEN", b"set"]), wrong);
5262
5263 assert_eq!(
5266 f.run(&[b"MGET", b"str", b"set", b"nope"]),
5267 "*3\r\n$1\r\nv\r\n$-1\r\n$-1\r\n"
5268 );
5269 assert_eq!(f.run(&[b"SET", b"set", b"now a string"]), "+OK\r\n");
5271 assert_eq!(f.run(&[b"TYPE", b"set"]), "+string\r\n");
5272 }
5273
5274 #[test]
5275 fn a_wrongtype_leaves_nothing_half_written() {
5276 let mut f = Fixture::new();
5280 f.run(&[b"SET", b"k", b"v"]);
5281 let reply = f.run(&[b"SMISMEMBER", b"k", b"a", b"b"]);
5282 assert!(reply.starts_with("-WRONGTYPE"), "got {reply}");
5283 assert!(!reply.contains('*'), "an array header went out in front");
5284 }
5285
5286 #[test]
5287 fn emptying_a_set_takes_the_key_with_it() {
5288 let mut f = Fixture::new();
5289 f.run(&[b"SADD", b"s", b"a", b"b"]);
5290 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
5291 assert_eq!(f.run(&[b"SREM", b"s", b"a", b"b"]), ":2\r\n");
5292 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
5293 assert_eq!(f.run(&[b"TYPE", b"s"]), "+none\r\n");
5294 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
5295 }
5296
5297 fn split_scan(reply: &str) -> (String, Vec<String>) {
5303 let mut lines = reply.split("\r\n");
5304 assert_eq!(lines.next(), Some("*2"), "got {reply}");
5305 lines.next().expect("the cursor header");
5306 let cursor = lines.next().expect("the cursor").to_owned();
5307 let header = lines.next().expect("the member header");
5308 let n: usize = header[1..].parse().expect("a member count");
5309 let mut members = Vec::with_capacity(n);
5310 for _ in 0..n {
5311 lines.next().expect("a member header");
5312 members.push(lines.next().expect("a member").to_owned());
5313 }
5314 (cursor, members)
5315 }
5316
5317 #[test]
5318 fn popping_takes_a_member_off_the_set_and_hands_it_back() {
5319 let mut f = Fixture::new();
5320 f.run(&[b"SADD", b"s", b"a", b"b", b"c", b"d"]);
5321
5322 let one = f.run(&[b"SPOP", b"s"]);
5323 assert!(
5324 ["$1\r\na\r\n", "$1\r\nb\r\n", "$1\r\nc\r\n", "$1\r\nd\r\n"].contains(&one.as_str()),
5325 "got {one}"
5326 );
5327 assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
5328
5329 let (_, rest) = ("", f.run(&[b"SPOP", b"s", b"3"]));
5331 assert!(rest.starts_with("*3\r\n"), "got {rest}");
5332 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
5333 assert_eq!(f.run(&[b"SPOP", b"s"]), "$-1\r\n");
5335 assert_eq!(f.run(&[b"SPOP", b"s", b"2"]), "*0\r\n");
5336 }
5337
5338 #[test]
5339 fn the_two_draws_disagree_about_the_reply_type_and_they_are_right_to() {
5340 let mut f = Fixture::new();
5345 f.run(&[b"HELLO", b"3"]);
5346 f.run(&[b"SADD", b"s", b"a", b"b", b"c"]);
5347
5348 assert!(f.run(&[b"SPOP", b"s", b"2"]).starts_with("~2\r\n"));
5349 assert!(f.run(&[b"SRANDMEMBER", b"s", b"1"]).starts_with("*1\r\n"));
5351
5352 f.run(&[b"SADD", b"one", b"z"]);
5356 assert_eq!(
5357 f.run(&[b"SRANDMEMBER", b"one", b"-3"]),
5358 "*3\r\n$1\r\nz\r\n$1\r\nz\r\n$1\r\nz\r\n"
5359 );
5360 }
5361
5362 #[test]
5363 fn drawing_a_member_removes_nothing_and_says_nil_at_a_missing_key() {
5364 let mut f = Fixture::new();
5365 f.run(&[b"SADD", b"s", b"only"]);
5366 assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
5367 assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
5368 assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n");
5369
5370 assert_eq!(f.run(&[b"SRANDMEMBER", b"nope"]), "$-1\r\n");
5371 assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"3"]), "*0\r\n");
5374 assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"-3"]), "*0\r\n");
5375 assert_eq!(f.run(&[b"SRANDMEMBER", b"s", b"9"]), "*1\r\n$4\r\nonly\r\n");
5377 }
5378
5379 #[test]
5380 fn a_pop_count_that_is_not_a_positive_number_says_so() {
5381 let mut f = Fixture::new();
5382 f.run(&[b"SADD", b"s", b"a"]);
5383 let bad = "-ERR value is out of range, must be positive\r\n";
5384 assert_eq!(f.run(&[b"SPOP", b"s", b"-1"]), bad);
5385 assert_eq!(f.run(&[b"SPOP", b"s", b"abc"]), bad);
5386 assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n", "and took nothing");
5387 assert_eq!(f.run(&[b"SPOP", b"s", b"0"]), "*0\r\n");
5389 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
5390 }
5391
5392 #[test]
5393 fn a_scan_walks_a_set_of_any_size_exactly_once() {
5394 let mut f = Fixture::new();
5395 let members: Vec<Vec<u8>> = (0..300).map(|i| format!("m{i}").into_bytes()).collect();
5396 let args: Vec<&[u8]> = [&b"SADD"[..], &b"s"[..]]
5397 .into_iter()
5398 .chain(members.iter().map(Vec::as_slice))
5399 .collect();
5400 f.run(&args);
5401
5402 let mut seen = Vec::new();
5403 let mut cursor = "0".to_owned();
5404 loop {
5405 let reply = f.run(&[b"SSCAN", b"s", cursor.as_bytes()]);
5406 let (next, got) = split_scan(&reply);
5407 seen.extend(got);
5408 cursor = next;
5409 if cursor == "0" {
5410 break;
5411 }
5412 }
5413 seen.sort();
5414 seen.dedup();
5415 assert_eq!(seen.len(), 300, "a walk saw a member twice or missed one");
5416
5417 f.run(&[b"SADD", b"small", b"a", b"b", b"c"]);
5420 let (cursor, got) = split_scan(&f.run(&[b"SSCAN", b"small", b"0", b"COUNT", b"1"]));
5421 assert_eq!(cursor, "0");
5422 assert_eq!(got.len(), 3);
5423 assert_eq!(f.run(&[b"SSCAN", b"nope", b"0"]), "*2\r\n$1\r\n0\r\n*0\r\n");
5425 }
5426
5427 #[test]
5428 fn a_scan_takes_match_and_count_and_refuses_anything_else() {
5429 let mut f = Fixture::new();
5430 f.run(&[b"SADD", b"s", b"aa", b"ab", b"ba", b"12", b"13"]);
5431
5432 let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"a*"]));
5433 let mut got = got;
5434 got.sort();
5435 assert_eq!(got, ["aa", "ab"]);
5436
5437 let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"1?"]));
5440 let mut got = got;
5441 got.sort();
5442 assert_eq!(got, ["12", "13"]);
5443
5444 assert_eq!(f.run(&[b"SSCAN", b"s", b"abc"]), "-ERR invalid cursor\r\n");
5445 assert_eq!(f.run(&[b"SSCAN", b"s", b"-1"]), "-ERR invalid cursor\r\n");
5446 assert_eq!(
5447 f.run(&[b"SSCAN", b"s", b"0", b"NOPE", b"1"]),
5448 "-ERR syntax error\r\n"
5449 );
5450 assert_eq!(
5453 f.run(&[b"SSCAN", b"s", b"0", b"COUNT", b"0"]),
5454 "-ERR syntax error\r\n"
5455 );
5456 }
5457
5458 #[test]
5459 fn moving_a_member_takes_it_off_one_set_and_puts_it_on_another() {
5460 let mut f = Fixture::new();
5461 f.run(&[b"SADD", b"src", b"a", b"b"]);
5462 f.run(&[b"SADD", b"dst", b"c"]);
5463
5464 assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"a"]), ":1\r\n");
5465 assert_eq!(f.run(&[b"SISMEMBER", b"src", b"a"]), ":0\r\n");
5466 assert_eq!(f.run(&[b"SISMEMBER", b"dst", b"a"]), ":1\r\n");
5467 assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"zz"]), ":0\r\n");
5469 assert_eq!(f.run(&[b"SCARD", b"dst"]), ":2\r\n");
5470
5471 assert_eq!(f.run(&[b"SMOVE", b"src", b"fresh", b"b"]), ":1\r\n");
5474 assert_eq!(f.run(&[b"EXISTS", b"src"]), ":0\r\n");
5475 assert_eq!(f.run(&[b"SMEMBERS", b"fresh"]), "*1\r\n$1\r\nb\r\n");
5476 }
5477
5478 #[test]
5479 fn moving_checks_the_types_in_the_order_redis_checks_them() {
5480 let mut f = Fixture::new();
5484 f.run(&[b"SET", b"str", b"v"]);
5485 f.run(&[b"SADD", b"set", b"a"]);
5486
5487 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
5488 assert_eq!(f.run(&[b"SMOVE", b"nope", b"str", b"a"]), ":0\r\n");
5489 assert_eq!(f.run(&[b"SMOVE", b"str", b"set", b"a"]), wrong);
5490 assert_eq!(f.run(&[b"SMOVE", b"set", b"str", b"a"]), wrong);
5491 assert_eq!(f.run(&[b"SPOP", b"str"]), wrong);
5492 assert_eq!(f.run(&[b"SRANDMEMBER", b"str"]), wrong);
5493 assert_eq!(f.run(&[b"SSCAN", b"str", b"0"]), wrong);
5494 assert_eq!(
5495 f.run(&[b"SISMEMBER", b"set", b"a"]),
5496 ":1\r\n",
5497 "and none of that moved anything"
5498 );
5499 }
5500
5501 #[test]
5502 fn a_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
5503 let mut f = Fixture::new();
5506 f.run(&[b"SADD", b"s", b"a"]);
5507 for bad in [
5508 &[b"SSCAN".as_slice(), b"s", b"abc"][..],
5509 &[b"SSCAN".as_slice(), b"s", b"0", b"COUNT", b"nope"][..],
5510 &[b"SSCAN".as_slice(), b"s", b"0", b"MATCH"][..],
5511 ] {
5512 let reply = f.run(bad);
5513 assert!(reply.starts_with("-ERR"), "got {reply}");
5514 assert!(!reply.contains('*'), "an array header went out in front");
5515 }
5516 }
5517
5518 #[test]
5519 fn a_hash_writes_reads_and_deletes_its_fields() {
5520 let mut f = Fixture::new();
5521 assert_eq!(f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]), ":2\r\n");
5522 assert_eq!(f.run(&[b"HSET", b"h", b"a", b"9"]), ":0\r\n", "a was there");
5523 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
5524 assert_eq!(f.run(&[b"HGET", b"h", b"nope"]), "$-1\r\n");
5525 assert_eq!(f.run(&[b"HGET", b"nokey", b"a"]), "$-1\r\n");
5526 assert_eq!(f.run(&[b"HLEN", b"h"]), ":2\r\n");
5527 assert_eq!(f.run(&[b"HEXISTS", b"h", b"a"]), ":1\r\n");
5528 assert_eq!(f.run(&[b"HEXISTS", b"h", b"nope"]), ":0\r\n");
5529 assert_eq!(f.run(&[b"HSTRLEN", b"h", b"a"]), ":1\r\n");
5530 assert_eq!(f.run(&[b"HSTRLEN", b"h", b"nope"]), ":0\r\n");
5531
5532 assert_eq!(f.run(&[b"HGET", b"h", b"2"]), "$-1\r\n");
5535
5536 assert_eq!(f.run(&[b"HDEL", b"h", b"a", b"nope"]), ":1\r\n");
5537 assert_eq!(f.run(&[b"HDEL", b"h", b"b"]), ":1\r\n");
5538 assert_eq!(
5539 f.run(&[b"EXISTS", b"h"]),
5540 ":0\r\n",
5541 "and losing the last field lost the key"
5542 );
5543 }
5544
5545 #[test]
5546 fn hgetall_answers_a_map_on_resp3_and_the_same_pairs_flat_on_resp2() {
5547 let mut f = Fixture::new();
5548 f.run(&[b"HSET", b"h", b"a", b"1"]);
5549 assert_eq!(f.run(&[b"HGETALL", b"h"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
5550 assert_eq!(f.run(&[b"HGETALL", b"nokey"]), "*0\r\n");
5551 assert_eq!(f.run(&[b"HKEYS", b"h"]), "*1\r\n$1\r\na\r\n");
5552 assert_eq!(f.run(&[b"HVALS", b"h"]), "*1\r\n$1\r\n1\r\n");
5553 assert_eq!(f.run(&[b"HKEYS", b"nokey"]), "*0\r\n");
5554
5555 f.run(&[b"HELLO", b"3"]);
5556 assert_eq!(f.run(&[b"HGETALL", b"h"]), "%1\r\n$1\r\na\r\n$1\r\n1\r\n");
5557 assert_eq!(
5558 f.run(&[b"HGETALL", b"nokey"]),
5559 "%0\r\n",
5560 "a missing key is the empty hash and never a nil"
5561 );
5562 assert_eq!(
5563 f.run(&[b"HKEYS", b"h"]),
5564 "*1\r\n$1\r\na\r\n",
5565 "and the two that answer one side stay arrays"
5566 );
5567 }
5568
5569 #[test]
5570 fn hmget_answers_once_per_field_and_hmset_answers_ok() {
5571 let mut f = Fixture::new();
5572 assert_eq!(f.run(&[b"HMSET", b"h", b"a", b"1", b"c", b"3"]), "+OK\r\n");
5573 assert_eq!(
5574 f.run(&[b"HMGET", b"h", b"a", b"b", b"c"]),
5575 "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n",
5576 "the reply is positional, so b is a nil and not a gap"
5577 );
5578 assert_eq!(
5579 f.run(&[b"HMGET", b"nokey", b"a", b"b"]),
5580 "*2\r\n$-1\r\n$-1\r\n",
5581 "and a missing key is all nils rather than an empty array"
5582 );
5583
5584 assert_eq!(f.run(&[b"HSETNX", b"h", b"a", b"9"]), ":0\r\n");
5585 assert_eq!(f.run(&[b"HSETNX", b"h", b"z", b"9"]), ":1\r\n");
5586 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
5587 }
5588
5589 #[test]
5590 fn a_hash_counts_up_and_says_so_when_it_cannot() {
5591 let mut f = Fixture::new();
5592 assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"5"]), ":5\r\n");
5593 assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"-7"]), ":-2\r\n");
5594 assert_eq!(f.run(&[b"HGET", b"h", b"n"]), "$2\r\n-2\r\n");
5595 assert_eq!(
5596 f.run(&[b"HINCRBYFLOAT", b"h", b"f", b"10.5"]),
5597 "$4\r\n10.5\r\n",
5598 "a bulk string and not a double, on both protocols"
5599 );
5600
5601 f.run(&[b"HSET", b"h", b"s", b"words"]);
5602 let bad = f.run(&[b"HINCRBY", b"h", b"s", b"1"]);
5603 assert!(
5604 bad.starts_with("-ERR hash value is not an integer"),
5605 "{bad}"
5606 );
5607 let bad = f.run(&[b"HINCRBY", b"h", b"n", b"nope"]);
5608 assert!(
5609 bad.starts_with("-ERR value is not an integer"),
5610 "a bad argument is not yet a hash value, {bad}"
5611 );
5612 assert_eq!(
5613 f.run(&[b"HGET", b"h", b"s"]),
5614 "$5\r\nwords\r\n",
5615 "and neither of them wrote anything"
5616 );
5617 }
5618
5619 #[test]
5620 fn a_hash_scan_walks_every_pair_once_and_novalues_drops_half_of_it() {
5621 let fields = if cfg!(miri) { 96 } else { 500 };
5626 let mut f = Fixture::new();
5627 for i in 0..fields {
5628 let field = format!("field-{i}");
5629 let value = format!("value-{i}");
5630 f.run(&[b"HSET", b"h", field.as_bytes(), value.as_bytes()]);
5631 }
5632
5633 let mut seen: Vec<String> = Vec::new();
5634 let mut cursor = "0".to_owned();
5635 loop {
5636 let reply = f.run(&[b"HSCAN", b"h", cursor.as_bytes(), b"COUNT", b"32"]);
5637 let (next, items) = scan_reply(&reply);
5638 assert_eq!(items.len() % 2, 0, "a pair went out half written");
5639 for pair in items.chunks(2) {
5640 assert_eq!(
5641 pair[0].strip_prefix("field-"),
5642 pair[1].strip_prefix("value-"),
5643 "a field came back with someone else's value"
5644 );
5645 seen.push(pair[0].clone());
5646 }
5647 cursor = next;
5648 if cursor == "0" {
5649 break;
5650 }
5651 }
5652 seen.sort();
5653 seen.dedup();
5654 assert_eq!(seen.len(), fields, "every field once and only once");
5655
5656 let (_, items) = scan_reply(&f.run(&[b"HSCAN", b"h", b"0", b"NOVALUES", b"COUNT", b"32"]));
5657 assert!(
5658 items.iter().all(|s| s.starts_with("field-")),
5659 "NOVALUES still sent the values"
5660 );
5661
5662 let last = fields - 1;
5663 let (_, one) = scan_reply(&f.run(&[
5664 b"HSCAN",
5665 b"h",
5666 b"0",
5667 b"MATCH",
5668 format!("field-{last}").as_bytes(),
5669 b"COUNT",
5670 b"1000",
5671 ]));
5672 assert_eq!(
5673 one,
5674 [format!("field-{last}"), format!("value-{last}")],
5675 "MATCH is on the field"
5676 );
5677 }
5678
5679 #[test]
5680 fn hrandfield_draws_what_it_was_asked_for_and_nests_values_on_resp3() {
5681 let mut f = Fixture::new();
5682 f.run(&[b"HSET", b"h", b"a", b"1"]);
5683 assert_eq!(f.run(&[b"HRANDFIELD", b"h"]), "$1\r\na\r\n");
5684 assert_eq!(f.run(&[b"HRANDFIELD", b"nokey"]), "$-1\r\n");
5685 assert_eq!(f.run(&[b"HRANDFIELD", b"nokey", b"3"]), "*0\r\n");
5686 assert_eq!(
5687 f.run(&[b"HRANDFIELD", b"h", b"3"]),
5688 "*1\r\n$1\r\na\r\n",
5689 "a positive count is capped at the size of the hash"
5690 );
5691 assert_eq!(
5692 f.run(&[b"HRANDFIELD", b"h", b"-3"]),
5693 "*3\r\n$1\r\na\r\n$1\r\na\r\n$1\r\na\r\n",
5694 "and a negative one repeats itself"
5695 );
5696 assert_eq!(
5697 f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
5698 "*2\r\n$1\r\na\r\n$1\r\n1\r\n",
5699 "flat on RESP2"
5700 );
5701
5702 f.run(&[b"HELLO", b"3"]);
5703 assert_eq!(
5704 f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
5705 "*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n",
5706 "and nested on RESP3, but still an array and never a map"
5707 );
5708 }
5709
5710 #[test]
5711 fn every_hash_command_says_wrongtype_and_writes_nothing() {
5712 let mut f = Fixture::new();
5713 f.run(&[b"SET", b"str", b"v"]);
5714 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
5715
5716 for cmd in [
5717 &[b"HSET".as_slice(), b"str", b"f", b"v"][..],
5718 &[b"HMSET".as_slice(), b"str", b"f", b"v"][..],
5719 &[b"HSETNX".as_slice(), b"str", b"f", b"v"][..],
5720 &[b"HGET".as_slice(), b"str", b"f"][..],
5721 &[b"HMGET".as_slice(), b"str", b"f"][..],
5722 &[b"HDEL".as_slice(), b"str", b"f"][..],
5723 &[b"HLEN".as_slice(), b"str"][..],
5724 &[b"HEXISTS".as_slice(), b"str", b"f"][..],
5725 &[b"HSTRLEN".as_slice(), b"str", b"f"][..],
5726 &[b"HGETALL".as_slice(), b"str"][..],
5727 &[b"HKEYS".as_slice(), b"str"][..],
5728 &[b"HVALS".as_slice(), b"str"][..],
5729 &[b"HINCRBY".as_slice(), b"str", b"f", b"1"][..],
5730 &[b"HINCRBYFLOAT".as_slice(), b"str", b"f", b"1"][..],
5731 &[b"HRANDFIELD".as_slice(), b"str"][..],
5732 &[b"HRANDFIELD".as_slice(), b"str", b"2"][..],
5733 &[b"HSCAN".as_slice(), b"str", b"0"][..],
5734 ] {
5735 let reply = f.run(cmd);
5736 assert_eq!(reply, wrong, "{:?}", cmd[0]);
5737 }
5738 assert_eq!(
5739 f.run(&[b"GET", b"str"]),
5740 "$1\r\nv\r\n",
5741 "and none of them touched the value"
5742 );
5743 }
5744
5745 #[test]
5746 fn a_hash_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
5747 let mut f = Fixture::new();
5748 f.run(&[b"HSET", b"h", b"f", b"v"]);
5749 for bad in [
5750 &[b"HSCAN".as_slice(), b"h", b"abc"][..],
5751 &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"nope"][..],
5752 &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"0"][..],
5753 &[b"HSCAN".as_slice(), b"h", b"0", b"MATCH"][..],
5754 ] {
5755 let reply = f.run(bad);
5756 assert!(reply.starts_with("-ERR"), "got {reply}");
5757 assert!(!reply.contains('*'), "an array header went out in front");
5758 }
5759 }
5760
5761 #[test]
5762 fn a_field_deadline_goes_on_and_comes_back_in_all_four_units() {
5763 let mut f = Fixture::new();
5764 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
5765 assert_eq!(
5766 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]),
5767 "*1\r\n:1\r\n"
5768 );
5769 assert_eq!(
5770 f.run(&[b"HTTL", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
5771 "*3\r\n:100\r\n:-1\r\n:-2\r\n",
5772 "one answer per field, and the two sentinels are TTL's own"
5773 );
5774
5775 let ms = int_reply(&f.run(&[b"HPTTL", b"h", b"FIELDS", b"1", b"a"]));
5778 assert!((99_000..=100_000).contains(&ms), "got {ms}");
5779 let at = int_reply(&f.run(&[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
5780 let at_ms = int_reply(&f.run(&[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
5781 assert_eq!(at, at_ms.div_euclid(1000) + i64::from(at_ms % 1000 != 0));
5782 assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
5783
5784 assert_eq!(
5785 f.run(&[b"HPERSIST", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
5786 "*3\r\n:1\r\n:-1\r\n:-2\r\n",
5787 "one for the deadline taken off, and it does not say what it was"
5788 );
5789 assert_eq!(
5790 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
5791 "*1\r\n:-1\r\n"
5792 );
5793 assert_eq!(
5794 f.run(&[b"HGET", b"h", b"a"]),
5795 "$1\r\n1\r\n",
5796 "and the field is still there with the value it had"
5797 );
5798 }
5799
5800 #[test]
5801 fn a_deadline_that_has_already_gone_deletes_the_field_now() {
5802 let mut f = Fixture::new();
5803 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
5804 assert_eq!(
5805 f.run(&[b"HEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"a"]),
5806 "*1\r\n:2\r\n",
5807 "two, and not one, because nothing was stored"
5808 );
5809 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
5810 assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
5811
5812 assert_eq!(
5813 f.run(&[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"]),
5814 "*1\r\n:2\r\n"
5815 );
5816 assert_eq!(
5817 f.run(&[b"EXISTS", b"h"]),
5818 ":0\r\n",
5819 "and the last field going took the key with it"
5820 );
5821
5822 f.run(&[b"HSET", b"h", b"a", b"1"]);
5825 assert_eq!(
5826 f.run(&[b"HEXPIRE", b"h", b"0", b"FIELDS", b"1", b"a"]),
5827 "*1\r\n:2\r\n"
5828 );
5829 }
5830
5831 #[test]
5832 fn a_field_is_gone_once_its_moment_passes() {
5833 let mut f = Fixture::new();
5834 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
5835 assert_eq!(
5836 f.run(&[b"HPEXPIRE", b"h", b"20", b"FIELDS", b"1", b"a"]),
5837 "*1\r\n:1\r\n"
5838 );
5839 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n", "not yet");
5840
5841 f.server.advance_clock_ms(60);
5845 assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
5846 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
5847 assert_eq!(
5848 f.run(&[b"HGETALL", b"h"]),
5849 "*2\r\n$1\r\nb\r\n$1\r\n2\r\n",
5850 "and the walks do not hand back a field that has expired"
5851 );
5852 }
5853
5854 #[test]
5855 fn a_missing_key_answers_the_no_field_sentinel_for_every_field() {
5856 let mut f = Fixture::new();
5857 for cmd in [
5858 &[
5859 b"HEXPIRE".as_slice(),
5860 b"nokey",
5861 b"100",
5862 b"FIELDS",
5863 b"2",
5864 b"a",
5865 b"b",
5866 ][..],
5867 &[b"HTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
5868 &[b"HPTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
5869 &[
5870 b"HEXPIRETIME".as_slice(),
5871 b"nokey",
5872 b"FIELDS",
5873 b"2",
5874 b"a",
5875 b"b",
5876 ][..],
5877 &[
5878 b"HPERSIST".as_slice(),
5879 b"nokey",
5880 b"FIELDS",
5881 b"2",
5882 b"a",
5883 b"b",
5884 ][..],
5885 ] {
5886 assert_eq!(f.run(cmd), "*2\r\n:-2\r\n:-2\r\n", "{:?}", cmd[0]);
5887 }
5888 }
5889
5890 #[test]
5891 fn writing_a_field_clears_the_deadline_that_was_on_it() {
5892 let mut f = Fixture::new();
5893 f.run(&[b"HSET", b"h", b"a", b"1"]);
5894 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]);
5895 f.run(&[b"HSET", b"h", b"a", b"2"]);
5896 assert_eq!(
5897 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
5898 "*1\r\n:-1\r\n",
5899 "Redis has done this since 7.4, and it is why HGETEX exists"
5900 );
5901 }
5902
5903 #[test]
5904 fn the_four_conditions_reach_the_store_the_way_they_were_written() {
5905 let mut f = Fixture::new();
5906 f.run(&[b"HSET", b"h", b"a", b"1"]);
5907 assert_eq!(
5908 f.run(&[b"HEXPIRE", b"h", b"100", b"XX", b"FIELDS", b"1", b"a"]),
5909 "*1\r\n:0\r\n",
5910 "XX on a field with no deadline changes nothing"
5911 );
5912 assert_eq!(
5913 f.run(&[b"HEXPIRE", b"h", b"100", b"NX", b"FIELDS", b"1", b"a"]),
5914 "*1\r\n:1\r\n"
5915 );
5916 assert_eq!(
5917 f.run(&[b"HEXPIRE", b"h", b"200", b"NX", b"FIELDS", b"1", b"a"]),
5918 "*1\r\n:0\r\n",
5919 "and NX will not move one that is already there"
5920 );
5921 assert_eq!(
5922 f.run(&[b"HEXPIRE", b"h", b"50", b"GT", b"FIELDS", b"1", b"a"]),
5923 "*1\r\n:0\r\n"
5924 );
5925 assert_eq!(
5926 f.run(&[b"HEXPIRE", b"h", b"500", b"GT", b"FIELDS", b"1", b"a"]),
5927 "*1\r\n:1\r\n"
5928 );
5929 assert_eq!(
5930 f.run(&[b"HEXPIRE", b"h", b"50", b"LT", b"FIELDS", b"1", b"a"]),
5931 "*1\r\n:1\r\n"
5932 );
5933 assert_eq!(
5934 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
5935 "*1\r\n:50\r\n"
5936 );
5937 }
5938
5939 #[test]
5940 fn the_field_ttl_family_leaves_nothing_half_written_on_a_bad_argument() {
5941 let mut f = Fixture::new();
5942 f.run(&[b"HSET", b"h", b"a", b"1"]);
5943 for (bad, want) in [
5944 (
5945 &[b"HEXPIRE".as_slice(), b"h", b"-1", b"FIELDS", b"1", b"a"][..],
5946 "-ERR invalid expire time, must be >= 0",
5947 ),
5948 (
5949 &[
5950 b"HEXPIRE".as_slice(),
5951 b"h",
5952 b"9999999999999999",
5953 b"FIELDS",
5954 b"1",
5955 b"a",
5956 ][..],
5957 "-ERR invalid expire time in 'hexpire' command",
5958 ),
5959 (
5960 &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELD", b"1", b"a"][..],
5961 "-ERR wrong number of arguments for 'hexpire' command",
5962 ),
5963 (
5964 &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"0", b"a"][..],
5965 "-ERR Parameter `numFields` should be greater than 0",
5966 ),
5967 (
5968 &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"2", b"a"][..],
5969 "-ERR wrong number of arguments",
5970 ),
5971 (
5972 &[b"HTTL".as_slice(), b"h", b"FIELDS", b"3", b"a", b"b"][..],
5973 "-ERR wrong number of arguments",
5974 ),
5975 ] {
5976 let reply = f.run(bad);
5977 assert!(reply.starts_with(want), "wanted {want}, got {reply}");
5978 assert!(!reply.contains('*'), "an array header went out in front");
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 "and not one of them put a deadline on anything"
5984 );
5985 }
5986
5987 #[test]
5988 fn every_field_ttl_command_says_wrongtype_and_writes_nothing() {
5989 let mut f = Fixture::new();
5990 f.run(&[b"SET", b"str", b"v"]);
5991 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
5992
5993 for cmd in [
5994 &[b"HEXPIRE".as_slice(), b"str", b"100", b"FIELDS", b"1", b"f"][..],
5995 &[
5996 b"HPEXPIRE".as_slice(),
5997 b"str",
5998 b"100",
5999 b"FIELDS",
6000 b"1",
6001 b"f",
6002 ][..],
6003 &[
6004 b"HEXPIREAT".as_slice(),
6005 b"str",
6006 b"9999999999",
6007 b"FIELDS",
6008 b"1",
6009 b"f",
6010 ][..],
6011 &[
6012 b"HPEXPIREAT".as_slice(),
6013 b"str",
6014 b"9999999999999",
6015 b"FIELDS",
6016 b"1",
6017 b"f",
6018 ][..],
6019 &[b"HTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
6020 &[b"HPTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
6021 &[b"HEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
6022 &[b"HPEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
6023 &[b"HPERSIST".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
6024 ] {
6025 assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
6026 }
6027 assert_eq!(
6028 f.run(&[b"GET", b"str"]),
6029 "$1\r\nv\r\n",
6030 "and none of them touched the value"
6031 );
6032 }
6033
6034 #[test]
6035 fn hgetdel_hands_the_value_out_and_then_takes_the_field() {
6036 let mut f = Fixture::new();
6037 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
6038 assert_eq!(
6039 f.run(&[b"HGETDEL", b"h", b"FIELDS", b"2", b"a", b"nope"]),
6040 "*2\r\n$1\r\n1\r\n$-1\r\n",
6041 "positional, so the field that was not there is a nil in its place"
6042 );
6043 assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
6044 assert_eq!(
6045 f.run(&[b"HGETDEL", b"nokey", b"FIELDS", b"1", b"a"]),
6046 "*1\r\n$-1\r\n"
6047 );
6048 assert_eq!(
6049 f.run(&[b"HGETDEL", b"h", b"FIELDS", b"1", b"b"]),
6050 "*1\r\n$1\r\n2\r\n"
6051 );
6052 assert_eq!(
6053 f.run(&[b"EXISTS", b"h"]),
6054 ":0\r\n",
6055 "and the last field took the key"
6056 );
6057 }
6058
6059 #[test]
6060 fn hgetex_reads_and_moves_the_deadline_in_one_command() {
6061 let mut f = Fixture::new();
6062 f.run(&[b"HSET", b"h", b"a", b"1"]);
6063 assert_eq!(
6064 f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]),
6065 "*1\r\n$1\r\n1\r\n"
6066 );
6067 assert_eq!(
6068 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
6069 "*1\r\n:-1\r\n",
6070 "no option means leave it alone, which is the one place this is not GETEX"
6071 );
6072
6073 f.run(&[b"HGETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a"]);
6074 assert_eq!(
6075 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
6076 "*1\r\n:100\r\n"
6077 );
6078 f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]);
6079 assert_eq!(
6080 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
6081 "*1\r\n:100\r\n",
6082 "and a plain read really does leave it alone"
6083 );
6084 assert_eq!(
6085 f.run(&[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"a"]),
6086 "*1\r\n$1\r\n1\r\n"
6087 );
6088 assert_eq!(
6089 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
6090 "*1\r\n:-1\r\n"
6091 );
6092
6093 assert_eq!(
6094 f.run(&[b"HGETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a"]),
6095 "*1\r\n$1\r\n1\r\n",
6096 "the value goes out before the deadline that has already gone is applied"
6097 );
6098 assert_eq!(f.run(&[b"EXISTS", b"h"]), ":0\r\n");
6099 assert_eq!(
6100 f.run(&[b"HGETEX", b"nokey", b"EX", b"100", b"FIELDS", b"1", b"a"]),
6101 "*1\r\n$-1\r\n"
6102 );
6103 }
6104
6105 #[test]
6106 fn hsetex_writes_all_of_it_or_none_of_it() {
6107 let mut f = Fixture::new();
6108 assert_eq!(
6109 f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"1"]),
6110 ":1\r\n"
6111 );
6112 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
6113 assert_eq!(
6114 f.run(&[
6115 b"HSETEX", b"h", b"FNX", b"FIELDS", b"2", b"a", b"9", b"new", b"9"
6116 ]),
6117 ":0\r\n",
6118 "FNX wants every field named to be missing"
6119 );
6120 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
6121 assert_eq!(
6122 f.run(&[b"HEXISTS", b"h", b"new"]),
6123 ":0\r\n",
6124 "and none of the list was written"
6125 );
6126 assert_eq!(
6127 f.run(&[
6128 b"HSETEX", b"h", b"FXX", b"FIELDS", b"2", b"a", b"9", b"nope", b"9"
6129 ]),
6130 ":0\r\n",
6131 "and FXX wants every one of them to be there"
6132 );
6133 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
6134 assert_eq!(
6135 f.run(&[b"HSETEX", b"h", b"FXX", b"FIELDS", b"1", b"a", b"9"]),
6136 ":1\r\n"
6137 );
6138 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
6139
6140 assert_eq!(
6141 f.run(&[b"HSETEX", b"gone", b"FXX", b"FIELDS", b"1", b"a", b"1"]),
6142 ":0\r\n"
6143 );
6144 assert_eq!(
6145 f.run(&[b"EXISTS", b"gone"]),
6146 ":0\r\n",
6147 "a key with no fields cannot meet FXX and is not created trying"
6148 );
6149 }
6150
6151 #[test]
6152 fn hsetex_clears_the_deadline_unless_it_is_told_to_keep_it() {
6153 let mut f = Fixture::new();
6154 f.run(&[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a", b"1"]);
6155 assert_eq!(
6156 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
6157 "*1\r\n:100\r\n"
6158 );
6159
6160 f.run(&[b"HSETEX", b"h", b"KEEPTTL", b"FIELDS", b"1", b"a", b"2"]);
6161 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n2\r\n");
6162 assert_eq!(
6163 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
6164 "*1\r\n:100\r\n",
6165 "KEEPTTL put back what the write cleared"
6166 );
6167
6168 f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"3"]);
6169 assert_eq!(
6170 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
6171 "*1\r\n:-1\r\n",
6172 "and without it a write clears the deadline the way HSET does"
6173 );
6174
6175 assert_eq!(
6178 f.run(&[
6179 b"HSETEX", b"h", b"PX", b"100000", b"FXX", b"FIELDS", b"1", b"a", b"4"
6180 ]),
6181 ":1\r\n"
6182 );
6183 assert_eq!(
6184 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
6185 "*1\r\n:100\r\n"
6186 );
6187
6188 assert_eq!(
6189 f.run(&[b"HSETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a", b"5"]),
6190 ":1\r\n",
6191 "written, and not the separate code the HEXPIRE family has for this"
6192 );
6193 assert_eq!(
6194 f.run(&[b"EXISTS", b"h"]),
6195 ":0\r\n",
6196 "and storing it and then removing it emptied the hash"
6197 );
6198 }
6199
6200 #[test]
6201 fn the_last_three_hash_commands_word_their_mistakes_their_own_way() {
6202 let mut f = Fixture::new();
6203 f.run(&[b"HSET", b"h", b"a", b"1"]);
6204 for (bad, want) in [
6205 (
6207 &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
6208 "-ERR Number of fields must be a positive integer",
6209 ),
6210 (
6211 &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
6212 "-ERR The `numfields` parameter must match the number of arguments",
6213 ),
6214 (
6215 &[b"HGETDEL".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
6216 "-ERR Mandatory argument FIELDS is missing or not at the right position",
6217 ),
6218 (
6220 &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
6221 "-ERR invalid number of fields",
6222 ),
6223 (
6224 &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
6225 "-ERR wrong number of arguments",
6226 ),
6227 (
6228 &[b"HGETEX".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
6229 "-ERR unknown argument: FIELD",
6230 ),
6231 (
6232 &[
6233 b"HGETEX".as_slice(),
6234 b"h",
6235 b"KEEPTTL",
6236 b"FIELDS",
6237 b"1",
6238 b"a",
6239 ][..],
6240 "-ERR unknown argument: KEEPTTL",
6241 ),
6242 (
6243 &[
6244 b"HGETEX".as_slice(),
6245 b"h",
6246 b"EX",
6247 b"100",
6248 b"PERSIST",
6249 b"FIELDS",
6250 b"1",
6251 b"a",
6252 ][..],
6253 "-ERR Only one of EX, PX, EXAT, PXAT or PERSIST arguments can be specified",
6254 ),
6255 (
6256 &[
6257 b"HSETEX".as_slice(),
6258 b"h",
6259 b"EX",
6260 b"1",
6261 b"KEEPTTL",
6262 b"FIELDS",
6263 b"1",
6264 b"a",
6265 b"1",
6266 ][..],
6267 "-ERR Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments can be specified",
6268 ),
6269 (
6270 &[
6271 b"HSETEX".as_slice(),
6272 b"h",
6273 b"FNX",
6274 b"FXX",
6275 b"FIELDS",
6276 b"1",
6277 b"a",
6278 b"1",
6279 ][..],
6280 "-ERR Only one of FXX or FNX arguments can be specified",
6281 ),
6282 (
6283 &[
6284 b"HSETEX".as_slice(),
6285 b"h",
6286 b"FIELDS",
6287 b"2",
6288 b"a",
6289 b"1",
6290 b"b",
6291 ][..],
6292 "-ERR wrong number of arguments",
6293 ),
6294 (
6295 &[
6296 b"HGETEX".as_slice(),
6297 b"h",
6298 b"EX",
6299 b"-1",
6300 b"FIELDS",
6301 b"1",
6302 b"a",
6303 ][..],
6304 "-ERR invalid expire time, must be >= 0",
6305 ),
6306 (
6307 &[
6308 b"HGETEX".as_slice(),
6309 b"h",
6310 b"PXAT",
6311 b"99999999999999",
6312 b"FIELDS",
6313 b"1",
6314 b"a",
6315 ][..],
6316 "-ERR invalid expire time in 'hgetex' command",
6317 ),
6318 (
6319 &[
6320 b"HSETEX".as_slice(),
6321 b"h",
6322 b"EX",
6323 b"abc",
6324 b"FIELDS",
6325 b"1",
6326 b"a",
6327 b"1",
6328 ][..],
6329 "-ERR value is not an integer or out of range",
6330 ),
6331 ] {
6332 let reply = f.run(bad);
6333 assert!(reply.starts_with(want), "wanted {want}, got {reply}");
6334 assert!(!reply.contains('*'), "an array header went out in front");
6335 }
6336 assert_eq!(
6337 f.run(&[b"HGET", b"h", b"a"]),
6338 "$1\r\n1\r\n",
6339 "and not one of them wrote anything"
6340 );
6341 assert_eq!(
6342 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
6343 "*1\r\n:-1\r\n"
6344 );
6345 }
6346
6347 #[test]
6348 fn the_last_three_hash_commands_say_wrongtype_and_write_nothing() {
6349 let mut f = Fixture::new();
6350 f.run(&[b"SET", b"str", b"v"]);
6351 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
6352 for cmd in [
6353 &[b"HGETDEL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
6354 &[b"HGETEX".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
6355 &[
6356 b"HGETEX".as_slice(),
6357 b"str",
6358 b"EX",
6359 b"100",
6360 b"FIELDS",
6361 b"1",
6362 b"f",
6363 ][..],
6364 &[b"HSETEX".as_slice(), b"str", b"FIELDS", b"1", b"f", b"v"][..],
6365 ] {
6366 assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
6367 }
6368 assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
6369 }
6370
6371 #[test]
6379 fn himport_writes_declared_values_into_sorted_fields() {
6380 let mut f = Fixture::new();
6381 assert_eq!(
6382 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"b", b"aa", b"a"]),
6383 "+OK\r\n"
6384 );
6385 assert_eq!(
6386 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2", b"3"]),
6387 "+OK\r\n"
6388 );
6389 assert_eq!(f.run(&[b"HKEYS", b"k"]), bulks(&["a", "b", "aa"]));
6390 assert_eq!(
6391 f.run(&[b"HGETALL", b"k"]),
6392 bulks(&["a", "3", "b", "1", "aa", "2"])
6393 );
6394 }
6395
6396 #[test]
6399 fn himport_set_replaces_the_whole_key() {
6400 let mut f = Fixture::new();
6401 f.run(&[b"HSET", b"k", b"gone", b"old", b"a", b"old"]);
6402 f.run(&[b"EXPIRE", b"k", b"100"]);
6403 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
6404 assert_eq!(
6405 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
6406 "+OK\r\n"
6407 );
6408 assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
6409 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
6410 }
6411
6412 #[test]
6415 fn himport_fieldsets_belong_to_the_connection_and_not_to_the_keyspace() {
6416 let mut f = Fixture::new();
6417 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a"]);
6418 f.run(&[b"SELECT", b"1"]);
6419 assert_eq!(
6420 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
6421 "+OK\r\n"
6422 );
6423 f.run(&[b"SELECT", b"0"]);
6424 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
6425 assert_eq!(
6426 f.run(&[b"HIMPORT", b"SET", b"k2", b"shape", b"1"]),
6427 "-ERR no such fieldset\r\n"
6428 );
6429 }
6430
6431 #[test]
6438 fn himport_complains_in_the_order_a_real_server_does() {
6439 let mut f = Fixture::new();
6440 f.run(&[b"SET", b"str", b"v"]);
6441 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
6442 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
6443 assert_eq!(
6444 f.run(&[b"HIMPORT", b"SET", b"str", b"nope", b"1"]),
6445 wrong,
6446 "the type beats a missing fieldset"
6447 );
6448 assert_eq!(
6449 f.run(&[b"HIMPORT", b"SET", b"str", b"shape", b"1"]),
6450 wrong,
6451 "and it beats a value count that does not fit"
6452 );
6453 assert_eq!(
6454 f.run(&[b"HIMPORT", b"SET", b"k", b"nope", b"1"]),
6455 "-ERR no such fieldset\r\n"
6456 );
6457 for values in [&[b"1".as_slice()][..], &[b"1".as_slice(), b"2", b"3"][..]] {
6459 let mut line: Vec<&[u8]> = vec![b"HIMPORT", b"SET", b"k", b"shape"];
6460 line.extend_from_slice(values);
6461 assert_eq!(
6462 f.run(&line),
6463 "-ERR value count does not match fieldset field count\r\n",
6464 "{} values into two fields",
6465 values.len()
6466 );
6467 }
6468 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
6469 }
6470
6471 #[test]
6473 fn himport_checks_each_subcommand_count_under_its_own_name() {
6474 let mut f = Fixture::new();
6475 assert_eq!(
6476 f.run(&[b"HIMPORT"]),
6477 "-ERR wrong number of arguments for 'himport' command\r\n"
6478 );
6479 for (rest, name) in [
6480 (&["PREPARE"][..], "prepare"),
6481 (&["PREPARE", "fs"][..], "prepare"),
6482 (&["SET"][..], "set"),
6483 (&["SET", "k"][..], "set"),
6484 (&["SET", "k", "fs"][..], "set"),
6485 (&["DISCARD"][..], "discard"),
6486 (&["DISCARD", "a", "b"][..], "discard"),
6487 (&["DISCARDALL", "x"][..], "discardall"),
6488 ] {
6489 let mut line: Vec<&[u8]> = vec![b"HIMPORT"];
6490 line.extend(rest.iter().map(|a| a.as_bytes()));
6491 assert_eq!(
6492 f.run(&line),
6493 format!("-ERR wrong number of arguments for 'himport|{name}' command\r\n"),
6494 "HIMPORT {}",
6495 rest.join(" ")
6496 );
6497 }
6498 assert_eq!(
6499 f.run(&[b"HIMPORT", b"NOPE", b"x"]),
6500 "-ERR unknown subcommand 'NOPE'. Try HIMPORT HELP.\r\n"
6501 );
6502 }
6503
6504 #[test]
6507 fn a_failed_himport_prepare_leaves_the_old_fieldset_alone() {
6508 let mut f = Fixture::new();
6509 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
6510 assert_eq!(
6511 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"c", b"c"]),
6512 "-ERR duplicate field name in fieldset\r\n"
6513 );
6514 assert_eq!(
6515 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
6516 "+OK\r\n"
6517 );
6518 assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
6519 }
6520
6521 #[test]
6524 fn himport_prepare_replaces_and_the_discards_count() {
6525 let mut f = Fixture::new();
6526 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
6527 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"z"]);
6528 assert_eq!(
6529 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
6530 "+OK\r\n"
6531 );
6532 assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["z", "1"]));
6533
6534 assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":1\r\n");
6535 assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":0\r\n");
6536 f.run(&[b"HIMPORT", b"PREPARE", b"one", b"a"]);
6537 f.run(&[b"HIMPORT", b"PREPARE", b"two", b"a"]);
6538 assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":2\r\n");
6539 assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":0\r\n");
6540 }
6541
6542 fn int(reply: &str) -> i64 {
6548 let body = reply
6549 .strip_prefix(':')
6550 .and_then(|s| s.strip_suffix("\r\n"))
6551 .unwrap_or_else(|| panic!("wanted an integer, got {reply}"));
6552 body.parse().expect("an integer")
6553 }
6554
6555 fn int_reply(reply: &str) -> i64 {
6556 let body = reply
6557 .strip_prefix("*1\r\n:")
6558 .and_then(|s| s.strip_suffix("\r\n"))
6559 .unwrap_or_else(|| panic!("wanted one integer, got {reply}"));
6560 body.parse().expect("an integer")
6561 }
6562
6563 fn scan_reply(reply: &str) -> (String, Vec<String>) {
6565 let mut lines = reply.split("\r\n");
6566 assert_eq!(lines.next(), Some("*2"), "got {reply}");
6567 lines.next().expect("the cursor header");
6568 let cursor = lines.next().expect("a cursor").to_owned();
6569 let header = lines.next().expect("an item count");
6570 let n: usize = header[1..].parse().expect("a count");
6571 let mut items = Vec::with_capacity(n);
6572 for _ in 0..n {
6573 lines.next().expect("an item header");
6574 items.push(lines.next().expect("an item").to_owned());
6575 }
6576 (cursor, items)
6577 }
6578
6579 fn sorted(reply: &str) -> Vec<String> {
6582 let mut lines = reply.split("\r\n");
6583 let header = lines.next().expect("a header");
6584 assert!(
6585 header.starts_with('*') || header.starts_with('~'),
6586 "got {reply}"
6587 );
6588 let n: usize = header[1..].parse().expect("a member count");
6589 let mut got = Vec::with_capacity(n);
6590 for _ in 0..n {
6591 lines.next().expect("a member header");
6592 got.push(lines.next().expect("a member").to_owned());
6593 }
6594 got.sort();
6595 got
6596 }
6597
6598 #[test]
6599 fn the_algebra_answers_what_the_sets_share_and_do_not() {
6600 let mut f = Fixture::new();
6601 f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
6602 f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
6603 f.run(&[b"SADD", b"c", b"3", b"4", b"5"]);
6604
6605 assert_eq!(sorted(&f.run(&[b"SINTER", b"a", b"b", b"c"])), ["3"]);
6606 assert_eq!(
6607 sorted(&f.run(&[b"SUNION", b"a", b"b", b"c"])),
6608 ["1", "2", "3", "4", "5"]
6609 );
6610 assert_eq!(sorted(&f.run(&[b"SDIFF", b"a", b"b"])), ["1"]);
6611 assert_eq!(sorted(&f.run(&[b"SINTER", b"a"])), ["1", "2", "3"]);
6612
6613 assert_eq!(f.run(&[b"SINTER", b"a", b"nope"]), "*0\r\n");
6616 assert_eq!(sorted(&f.run(&[b"SUNION", b"a", b"nope"])), ["1", "2", "3"]);
6617 assert_eq!(f.run(&[b"SDIFF", b"nope", b"a"]), "*0\r\n");
6618 assert_eq!(f.run(&[b"DBSIZE"]), ":3\r\n", "and none of it made a key");
6619 }
6620
6621 #[test]
6622 fn the_algebra_answers_a_set_on_resp3_and_an_array_on_resp2() {
6623 let mut f = Fixture::new();
6624 f.run(&[b"SADD", b"a", b"x"]);
6625 assert_eq!(f.run(&[b"SINTER", b"a"]), "*1\r\n$1\r\nx\r\n");
6626 assert_eq!(f.run(&[b"SUNION", b"a"]), "*1\r\n$1\r\nx\r\n");
6627 assert_eq!(f.run(&[b"SDIFF", b"a"]), "*1\r\n$1\r\nx\r\n");
6628
6629 f.run(&[b"HELLO", b"3"]);
6630 assert_eq!(f.run(&[b"SINTER", b"a"]), "~1\r\n$1\r\nx\r\n");
6631 assert_eq!(f.run(&[b"SUNION", b"a"]), "~1\r\n$1\r\nx\r\n");
6632 assert_eq!(f.run(&[b"SDIFF", b"a"]), "~1\r\n$1\r\nx\r\n");
6633 assert_eq!(f.run(&[b"SINTER", b"nope"]), "~0\r\n");
6634 }
6635
6636 #[test]
6637 fn a_store_form_writes_a_key_and_answers_how_big_it_is() {
6638 let mut f = Fixture::new();
6639 f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
6640 f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
6641
6642 assert_eq!(f.run(&[b"SINTERSTORE", b"d", b"a", b"b"]), ":2\r\n");
6643 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["2", "3"]);
6644 assert_eq!(f.run(&[b"SUNIONSTORE", b"d", b"a", b"b"]), ":4\r\n");
6645 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["1", "2", "3", "4"]);
6646 assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"b"]), ":1\r\n");
6647 assert_eq!(f.run(&[b"SMEMBERS", b"d"]), "*1\r\n$1\r\n1\r\n");
6648
6649 assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"a"]), ":0\r\n");
6652 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
6653 assert_eq!(f.run(&[b"SINTERSTORE", b"a", b"a", b"b"]), ":2\r\n");
6654 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"a"])), ["2", "3"]);
6655
6656 f.run(&[b"SET", b"str", b"v"]);
6659 assert_eq!(f.run(&[b"SUNIONSTORE", b"str", b"b"]), ":3\r\n");
6660 assert_eq!(f.run(&[b"TYPE", b"str"]), "+set\r\n");
6661 }
6662
6663 #[test]
6664 fn sintercard_counts_without_building_and_stops_at_a_limit() {
6665 let mut f = Fixture::new();
6666 f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
6667 f.run(&[b"SADD", b"b", b"2", b"3", b"4", b"5"]);
6668
6669 assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"b"]), ":3\r\n");
6670 assert_eq!(
6671 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
6672 ":2\r\n"
6673 );
6674 assert_eq!(
6675 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
6676 ":3\r\n",
6677 "a limit of zero is no limit"
6678 );
6679 assert_eq!(f.run(&[b"SINTERCARD", b"1", b"a"]), ":4\r\n");
6680 assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"nope"]), ":0\r\n");
6681
6682 assert_eq!(
6684 f.run(&[b"SINTERCARD", b"0", b"a"]),
6685 "-ERR numkeys should be greater than 0\r\n"
6686 );
6687 assert_eq!(
6688 f.run(&[b"SINTERCARD", b"abc", b"a"]),
6689 "-ERR numkeys should be greater than 0\r\n"
6690 );
6691 assert_eq!(
6692 f.run(&[b"SINTERCARD", b"3", b"a", b"b"]),
6693 "-ERR Number of keys can't be greater than number of args\r\n"
6694 );
6695 assert_eq!(
6696 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"-1"]),
6697 "-ERR LIMIT can't be negative\r\n"
6698 );
6699 assert_eq!(
6700 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"NOPE", b"1"]),
6701 "-ERR syntax error\r\n"
6702 );
6703 f.run(&[b"SADD", b"LIMIT", b"2"]);
6705 assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"LIMIT"]), ":1\r\n");
6706 }
6707
6708 #[test]
6711 fn sunioncard_and_sdiffcard_count_without_building() {
6712 let mut f = Fixture::new();
6713 f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
6714 f.run(&[b"SADD", b"b", b"3", b"4", b"5", b"6"]);
6715
6716 assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"b"]), ":6\r\n");
6717 assert_eq!(
6718 f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
6719 ":2\r\n"
6720 );
6721 assert_eq!(
6722 f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
6723 ":6\r\n",
6724 "a limit of zero is no limit"
6725 );
6726 assert_eq!(f.run(&[b"SUNIONCARD", b"1", b"a"]), ":4\r\n");
6727 assert_eq!(
6728 f.run(&[b"SUNIONCARD", b"2", b"a", b"nope"]),
6729 ":4\r\n",
6730 "a missing key adds nothing to a union"
6731 );
6732
6733 assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"b"]), ":2\r\n");
6734 assert_eq!(
6735 f.run(&[b"SDIFFCARD", b"2", b"a", b"b", b"LIMIT", b"1"]),
6736 ":1\r\n"
6737 );
6738 assert_eq!(
6739 f.run(&[b"SDIFFCARD", b"2", b"b", b"a"]),
6740 ":2\r\n",
6741 "a difference is not symmetric"
6742 );
6743 assert_eq!(f.run(&[b"SDIFFCARD", b"1", b"a"]), ":4\r\n");
6744 assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"nope"]), ":4\r\n");
6745 assert_eq!(
6746 f.run(&[b"SDIFFCARD", b"2", b"nope", b"a"]),
6747 ":0\r\n",
6748 "nothing taken away from nothing"
6749 );
6750
6751 for name in [b"SUNIONCARD".as_slice(), b"SDIFFCARD".as_slice()] {
6754 assert_eq!(
6755 f.run(&[name, b"0", b"a"]),
6756 "-ERR numkeys should be greater than 0\r\n"
6757 );
6758 assert_eq!(
6759 f.run(&[name, b"abc", b"a"]),
6760 "-ERR numkeys should be greater than 0\r\n"
6761 );
6762 assert_eq!(
6763 f.run(&[name, b"-1", b"a"]),
6764 "-ERR numkeys should be greater than 0\r\n"
6765 );
6766 assert_eq!(
6767 f.run(&[name, b"3", b"a", b"b"]),
6768 "-ERR Number of keys can't be greater than number of args\r\n"
6769 );
6770 assert_eq!(
6771 f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"-1"]),
6772 "-ERR LIMIT can't be negative\r\n"
6773 );
6774 assert_eq!(
6775 f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"abc"]),
6776 "-ERR LIMIT can't be negative\r\n",
6777 "a LIMIT that is not a number gets the negative message too"
6778 );
6779 assert_eq!(
6780 f.run(&[name, b"2", b"a", b"b", b"NOPE", b"1"]),
6781 "-ERR syntax error\r\n"
6782 );
6783 assert_eq!(
6784 f.run(&[name, b"2", b"a", b"b", b"LIMIT"]),
6785 "-ERR syntax error\r\n"
6786 );
6787 assert_eq!(
6788 f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"1", b"X"]),
6789 "-ERR syntax error\r\n"
6790 );
6791 }
6792
6793 f.run(&[b"SADD", b"LIMIT", b"2"]);
6795 assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"LIMIT"]), ":4\r\n");
6796 assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"LIMIT"]), ":3\r\n");
6797 }
6798
6799 #[test]
6800 fn the_algebra_answers_wrongtype_before_it_writes_anything() {
6801 let mut f = Fixture::new();
6802 f.run(&[b"SADD", b"a", b"1"]);
6803 f.run(&[b"SADD", b"d", b"old"]);
6804 f.run(&[b"SET", b"str", b"v"]);
6805
6806 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
6807 for bad in [
6808 &[b"SINTER".as_slice(), b"a", b"str"][..],
6809 &[b"SUNION".as_slice(), b"str"][..],
6810 &[b"SDIFF".as_slice(), b"a", b"str"][..],
6811 &[b"SINTERCARD".as_slice(), b"2", b"a", b"str"][..],
6812 &[b"SINTERSTORE".as_slice(), b"d", b"a", b"str"][..],
6813 &[b"SUNIONSTORE".as_slice(), b"d", b"str"][..],
6814 &[b"SDIFFSTORE".as_slice(), b"d", b"a", b"str"][..],
6815 ] {
6816 let reply = f.run(bad);
6817 assert_eq!(reply, wrong, "for {:?}", bad[0]);
6818 }
6819 assert_eq!(
6820 f.run(&[b"SMEMBERS", b"d"]),
6821 "*1\r\n$3\r\nold\r\n",
6822 "and the destination was left alone every time"
6823 );
6824 }
6825
6826 #[cfg_attr(miri, ignore = "the volume is the claim")]
6838 #[test]
6839 fn churning_sets_does_not_grow_the_server() {
6840 let mut f = Fixture::new();
6841 let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
6842 let args: Vec<&[u8]> = std::iter::once(&b"SADD"[..])
6843 .chain(std::iter::once(&b"s"[..]))
6844 .chain(members.iter().map(Vec::as_slice))
6845 .collect();
6846
6847 f.run(&args);
6848 f.run(&[b"DEL", b"s"]);
6849 f.server.compact_step();
6850 let after_first = f.server.memory_bytes();
6851
6852 for _ in 0..200 {
6853 f.run(&args);
6854 f.run(&[b"DEL", b"s"]);
6855 f.server.compact_step();
6856 }
6857 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
6858 assert!(
6859 f.server.memory_bytes() <= after_first * 2,
6860 "held {} after two hundred passes against {after_first} after one",
6861 f.server.memory_bytes()
6862 );
6863 }
6864
6865 #[test]
6873 fn a_bit_is_written_and_read_back_and_a_write_unpacks_an_int() {
6874 let mut f = Fixture::new();
6875 assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"1"]), ":0\r\n");
6876 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n\u{1}\r\n");
6877 assert_eq!(f.run(&[b"GETBIT", b"k", b"7"]), ":1\r\n");
6878 assert_eq!(f.run(&[b"GETBIT", b"k", b"6"]), ":0\r\n");
6879 assert_eq!(f.run(&[b"GETBIT", b"k", b"100"]), ":0\r\n");
6880 assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"0"]), ":1\r\n");
6881
6882 assert_eq!(f.run(&[b"SETBIT", b"nk", b"0", b"0"]), ":0\r\n");
6884 assert_eq!(f.run(&[b"STRLEN", b"nk"]), ":1\r\n");
6885 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"nk"]), "$3\r\nraw\r\n");
6886
6887 f.run(&[b"SET", b"num", b"12345"]);
6888 assert_eq!(f.run(&[b"GETBIT", b"num", b"1"]), ":0\r\n");
6889 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nint\r\n");
6890 assert_eq!(f.run(&[b"SETBIT", b"num", b"1", b"1"]), ":0\r\n");
6891 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nraw\r\n");
6892 assert_eq!(f.run(&[b"GET", b"num"]), "$5\r\nq2345\r\n");
6893 }
6894
6895 #[test]
6900 fn bits_are_counted_over_a_range_of_bytes_or_of_bits() {
6901 let mut f = Fixture::new();
6902 f.run(&[b"SET", b"mykey", b"foobar"]);
6903 assert_eq!(f.run(&[b"BITCOUNT", b"mykey"]), ":26\r\n");
6904 assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"0", b"0"]), ":4\r\n");
6905 assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"1", b"1"]), ":6\r\n");
6906 assert_eq!(
6907 f.run(&[b"BITCOUNT", b"mykey", b"1", b"1", b"BYTE"]),
6908 ":6\r\n"
6909 );
6910 assert_eq!(
6911 f.run(&[b"BITCOUNT", b"mykey", b"0", b"-5", b"BIT"]),
6912 ":25\r\n"
6913 );
6914 assert_eq!(
6915 f.run(&[b"BITCOUNT", b"mykey", b"5", b"30", b"BIT"]),
6916 ":17\r\n"
6917 );
6918 assert_eq!(f.run(&[b"BITCOUNT", b"nokey"]), ":0\r\n");
6919
6920 assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"10", b"20"]), ":0\r\n");
6923
6924 assert_eq!(
6926 f.run(&[b"BITCOUNT", b"mykey", b"0"]),
6927 "-ERR syntax error\r\n"
6928 );
6929 assert_eq!(
6930 f.run(&[b"BITCOUNT", b"mykey", b"0", b"1", b"NIB"]),
6931 "-ERR syntax error\r\n"
6932 );
6933 }
6934
6935 #[test]
6941 fn a_search_for_a_nought_past_the_end_answers_the_length_in_bits() {
6942 let mut f = Fixture::new();
6943 f.run(&[b"SET", b"ones", b"\xff\xff\xff"]);
6944 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0"]), ":24\r\n");
6945 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0"]), ":24\r\n");
6946 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"-1"]), ":-1\r\n");
6947 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"3"]), ":-1\r\n");
6948 assert_eq!(f.run(&[b"BITPOS", b"ones", b"1"]), ":0\r\n");
6949
6950 f.run(&[b"SET", b"mid", b"\x00\xff\xf0"]);
6951 assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"0"]), ":8\r\n");
6952 assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"2"]), ":16\r\n");
6953 assert_eq!(
6954 f.run(&[b"BITPOS", b"mid", b"1", b"0", b"-1", b"BIT"]),
6955 ":8\r\n"
6956 );
6957
6958 assert_eq!(f.run(&[b"BITPOS", b"gone", b"1"]), ":-1\r\n");
6961 assert_eq!(f.run(&[b"BITPOS", b"gone", b"0"]), ":0\r\n");
6962 }
6963
6964 #[test]
6966 fn the_eight_combinations_write_what_a_real_server_writes() {
6967 let mut f = Fixture::new();
6968 f.run(&[b"SET", b"a", b"abc"]);
6969 f.run(&[b"SET", b"b", b"abd"]);
6970 let cases: &[(&[u8], &str)] = &[
6971 (b"AND", "ab`"),
6972 (b"OR", "abg"),
6973 (b"XOR", "\u{0}\u{0}\u{7}"),
6974 (b"DIFF", "\u{0}\u{0}\u{3}"),
6975 (b"DIFF1", "\u{0}\u{0}\u{4}"),
6976 (b"ANDOR", "ab`"),
6977 (b"ONE", "\u{0}\u{0}\u{7}"),
6978 ];
6979 for (op, want) in cases {
6980 assert_eq!(f.run(&[b"BITOP", op, b"d", b"a", b"b"]), ":3\r\n", "{op:?}");
6981 assert_eq!(
6982 f.run(&[b"GET", b"d"]),
6983 format!("$3\r\n{want}\r\n"),
6984 "{op:?}"
6985 );
6986 }
6987 assert_eq!(f.run(&[b"BITOP", b"NOT", b"d", b"a"]), ":3\r\n");
6989 assert_eq!(f.raw(&[b"GET", b"d"]), b"$3\r\n\x9e\x9d\x9c\r\n".to_vec());
6990
6991 assert_eq!(f.run(&[b"BITOP", b"AND", b"d", b"a", b"gone"]), ":3\r\n");
6994 assert_eq!(f.run(&[b"GET", b"d"]), "$3\r\n\u{0}\u{0}\u{0}\r\n");
6995
6996 f.run(&[b"SET", b"dest", b"x"]);
6999 assert_eq!(f.run(&[b"BITOP", b"AND", b"dest", b"g1", b"g2"]), ":0\r\n");
7000 assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
7001 }
7002
7003 #[test]
7005 fn bitop_names_the_operation_in_its_own_complaints() {
7006 let mut f = Fixture::new();
7007 f.run(&[b"SET", b"a", b"abc"]);
7008 assert_eq!(
7009 f.run(&[b"BITOP", b"nope", b"d", b"a"]),
7010 "-ERR syntax error\r\n"
7011 );
7012 assert_eq!(
7013 f.run(&[b"BITOP", b"NOT", b"d", b"a", b"a"]),
7014 "-ERR BITOP NOT must be called with a single source key.\r\n"
7015 );
7016 for op in [&b"DIFF"[..], b"DIFF1", b"ANDOR"] {
7017 assert_eq!(
7018 f.run(&[b"BITOP", op, b"d", b"a"]),
7019 format!(
7020 "-ERR BITOP {} must be called with at least two source keys.\r\n",
7021 String::from_utf8_lossy(op)
7022 )
7023 );
7024 }
7025 f.run(&[b"LPUSH", b"l", b"x"]);
7026 assert_eq!(
7027 f.run(&[b"BITOP", b"AND", b"d", b"a", b"l"]),
7028 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
7029 );
7030 }
7031
7032 #[test]
7034 fn bitfield_reads_and_writes_packed_fields() {
7035 let mut f = Fixture::new();
7036 assert_eq!(f.run(&[b"BITFIELD", b"bf"]), "*0\r\n");
7037 assert_eq!(f.run(&[b"EXISTS", b"bf"]), ":0\r\n");
7038
7039 assert_eq!(
7040 f.run(&[
7041 b"BITFIELD",
7042 b"bf",
7043 b"INCRBY",
7044 b"u2",
7045 b"100",
7046 b"1",
7047 b"GET",
7048 b"u4",
7049 b"0"
7050 ]),
7051 "*2\r\n:1\r\n:0\r\n"
7052 );
7053 assert_eq!(f.run(&[b"STRLEN", b"bf"]), ":13\r\n");
7056
7057 assert_eq!(
7059 f.run(&[
7060 b"BITFIELD",
7061 b"bf",
7062 b"SET",
7063 b"u8",
7064 b"#0",
7065 b"255",
7066 b"GET",
7067 b"u8",
7068 b"#0"
7069 ]),
7070 "*2\r\n:0\r\n:255\r\n"
7071 );
7072
7073 assert_eq!(
7074 f.run(&[
7075 b"BITFIELD",
7076 b"bf",
7077 b"OVERFLOW",
7078 b"SAT",
7079 b"INCRBY",
7080 b"i8",
7081 b"0",
7082 b"120",
7083 b"INCRBY",
7084 b"i8",
7085 b"0",
7086 b"120"
7087 ]),
7088 "*2\r\n:119\r\n:127\r\n"
7089 );
7090 assert_eq!(
7091 f.run(&[
7092 b"BITFIELD",
7093 b"bf2",
7094 b"OVERFLOW",
7095 b"FAIL",
7096 b"INCRBY",
7097 b"u2",
7098 b"0",
7099 b"5"
7100 ]),
7101 "*1\r\n$-1\r\n"
7102 );
7103 assert_eq!(
7104 f.run(&[
7105 b"BITFIELD",
7106 b"bf3",
7107 b"OVERFLOW",
7108 b"WRAP",
7109 b"INCRBY",
7110 b"u2",
7111 b"0",
7112 b"5"
7113 ]),
7114 "*1\r\n:1\r\n"
7115 );
7116 assert_eq!(
7117 f.run(&[b"BITFIELD", b"bf3", b"GET", b"i64", b"0"]),
7118 "*1\r\n:4611686018427387904\r\n"
7119 );
7120 }
7121
7122 #[test]
7128 fn a_bad_bitfield_subcommand_leaves_the_key_alone() {
7129 let mut f = Fixture::new();
7130 let bad_type = "-ERR Invalid bitfield type. Use something like i16 u8. Note that u64 is not supported but i64 is.\r\n";
7131 assert_eq!(
7132 f.run(&[
7133 b"BITFIELD",
7134 b"bad",
7135 b"SET",
7136 b"u8",
7137 b"0",
7138 b"1",
7139 b"GET",
7140 b"u99",
7141 b"0"
7142 ]),
7143 bad_type
7144 );
7145 assert_eq!(f.run(&[b"EXISTS", b"bad"]), ":0\r\n");
7146 assert_eq!(
7147 f.run(&[b"BITFIELD", b"bad", b"GET", b"u64", b"0"]),
7148 bad_type
7149 );
7150 assert_eq!(
7151 f.run(&[b"BITFIELD", b"bad", b"GET"]),
7152 "-ERR syntax error\r\n"
7153 );
7154 assert_eq!(
7155 f.run(&[b"BITFIELD", b"bad", b"NOPE", b"u8", b"0"]),
7156 "-ERR syntax error\r\n"
7157 );
7158 assert_eq!(
7159 f.run(&[b"BITFIELD", b"bad", b"OVERFLOW"]),
7160 "-ERR syntax error\r\n"
7161 );
7162 assert_eq!(
7163 f.run(&[
7164 b"BITFIELD",
7165 b"bad",
7166 b"OVERFLOW",
7167 b"NOPE",
7168 b"GET",
7169 b"u8",
7170 b"0"
7171 ]),
7172 "-ERR Invalid OVERFLOW type specified\r\n"
7173 );
7174 assert_eq!(
7175 f.run(&[b"BITFIELD", b"bad", b"SET", b"u8", b"0", b"notanum"]),
7176 "-ERR value is not an integer or out of range\r\n"
7177 );
7178 for at in [&b"#-1"[..], b"abc"] {
7179 assert_eq!(
7180 f.run(&[b"BITFIELD", b"bad", b"GET", b"u8", at]),
7181 "-ERR bit offset is not an integer or out of range\r\n"
7182 );
7183 }
7184 }
7185
7186 #[test]
7188 fn bitfield_ro_answers_gets_and_refuses_the_rest() {
7189 let mut f = Fixture::new();
7190 f.run(&[b"SET", b"n", b"123"]);
7191 assert_eq!(
7192 f.run(&[b"BITFIELD_RO", b"n", b"GET", b"u8", b"0"]),
7193 "*1\r\n:49\r\n"
7194 );
7195 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"n"]), "$3\r\nint\r\n");
7197
7198 assert_eq!(
7200 f.run(&[
7201 b"BITFIELD_RO",
7202 b"n",
7203 b"OVERFLOW",
7204 b"SAT",
7205 b"GET",
7206 b"u8",
7207 b"0"
7208 ]),
7209 "*1\r\n:49\r\n"
7210 );
7211 for sub in [&b"SET"[..], b"INCRBY"] {
7212 assert_eq!(
7213 f.run(&[b"BITFIELD_RO", b"n", sub, b"u8", b"0", b"1"]),
7214 "-ERR BITFIELD_RO only supports the GET subcommand\r\n"
7215 );
7216 }
7217
7218 assert_eq!(
7219 f.run(&[b"BITFIELD_RO", b"gone", b"GET", b"u8", b"100"]),
7220 "*1\r\n:0\r\n"
7221 );
7222 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
7223 }
7224
7225 #[test]
7227 fn an_offset_off_the_end_of_the_world_is_refused() {
7228 let mut f = Fixture::new();
7229 let bad = "-ERR bit offset is not an integer or out of range\r\n";
7230 for arg in [&b"abc"[..], b"-1", b"4294967296"] {
7231 assert_eq!(f.run(&[b"SETBIT", b"k", arg, b"1"]), bad);
7232 assert_eq!(f.run(&[b"GETBIT", b"k", arg]), bad);
7233 }
7234 for arg in [&b"2"[..], b"-1"] {
7235 assert_eq!(
7236 f.run(&[b"BITPOS", b"k", arg]),
7237 "-ERR The bit argument must be 1 or 0.\r\n"
7238 );
7239 }
7240 assert_eq!(
7241 f.run(&[b"BITPOS", b"k", b"abc"]),
7242 "-ERR value is not an integer or out of range\r\n"
7243 );
7244 assert_eq!(
7245 f.run(&[b"BITPOS", b"k", b"0", b"5", b"BIT"]),
7246 "-ERR value is not an integer or out of range\r\n"
7247 );
7248 let bad_bit = "-ERR bit is not an integer or out of range\r\n";
7249 assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"2"]), bad_bit);
7250 assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"abc"]), bad_bit);
7251 }
7252
7253 #[test]
7255 fn every_bitmap_command_says_wrongtype() {
7256 let mut f = Fixture::new();
7257 f.run(&[b"LPUSH", b"l", b"x"]);
7258 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
7259 let cases: &[&[&[u8]]] = &[
7260 &[b"SETBIT", b"l", b"0", b"1"],
7261 &[b"GETBIT", b"l", b"0"],
7262 &[b"BITCOUNT", b"l"],
7263 &[b"BITPOS", b"l", b"1"],
7264 &[b"BITOP", b"AND", b"d", b"l"],
7265 &[b"BITFIELD", b"l", b"GET", b"u8", b"0"],
7266 &[b"BITFIELD_RO", b"l", b"GET", b"u8", b"0"],
7267 ];
7268 for case in cases {
7269 assert_eq!(f.run(case), wrong, "{:?}", case[0]);
7270 }
7271 }
7272
7273 #[test]
7276 fn a_sketch_is_added_to_and_counted() {
7277 let mut f = Fixture::new();
7278 assert_eq!(f.run(&[b"PFADD", b"h"]), ":1\r\n");
7280 assert_eq!(f.run(&[b"PFADD", b"h"]), ":0\r\n");
7281 assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":0\r\n");
7282 assert_eq!(f.run(&[b"STRLEN", b"h"]), ":18\r\n");
7283 assert_eq!(f.run(&[b"TYPE", b"h"]), "+string\r\n");
7286 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$3\r\nraw\r\n");
7287
7288 assert_eq!(f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]), ":1\r\n");
7289 assert_eq!(f.run(&[b"PFADD", b"h", b"a"]), ":0\r\n");
7290 assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
7291 }
7292
7293 #[test]
7294 fn the_bytes_of_a_sketch_are_the_ones_a_real_server_writes() {
7295 let mut f = Fixture::new();
7296 f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
7297 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";
7299 let mut reply = b"$27\r\n".to_vec();
7300 reply.extend_from_slice(want);
7301 reply.extend_from_slice(b"\r\n");
7302 assert_eq!(f.raw(&[b"GET", b"h"]), reply);
7303 }
7304
7305 #[test]
7306 fn counting_several_keys_counts_their_union() {
7307 let mut f = Fixture::new();
7308 f.run(&[b"PFADD", b"a", b"x", b"y"]);
7309 f.run(&[b"PFADD", b"b", b"y", b"z"]);
7310 assert_eq!(f.run(&[b"PFCOUNT", b"a"]), ":2\r\n");
7311 assert_eq!(f.run(&[b"PFCOUNT", b"a", b"b"]), ":3\r\n");
7312 assert_eq!(f.run(&[b"PFCOUNT", b"gone"]), ":0\r\n");
7315 assert_eq!(f.run(&[b"PFCOUNT", b"a", b"gone"]), ":2\r\n");
7316 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
7317 }
7318
7319 #[test]
7320 fn a_merge_keeps_what_the_destination_had() {
7321 let mut f = Fixture::new();
7322 f.run(&[b"PFADD", b"a", b"x", b"y"]);
7323 f.run(&[b"PFADD", b"b", b"z"]);
7324 assert_eq!(f.run(&[b"PFMERGE", b"d", b"a", b"b"]), "+OK\r\n");
7325 assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":3\r\n");
7326 f.run(&[b"PFADD", b"c", b"w"]);
7328 assert_eq!(f.run(&[b"PFMERGE", b"d", b"c"]), "+OK\r\n");
7329 assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":4\r\n");
7330 assert_eq!(f.run(&[b"PFMERGE", b"fresh"]), "+OK\r\n");
7333 assert_eq!(f.run(&[b"PFCOUNT", b"fresh"]), ":0\r\n");
7334 }
7335
7336 #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
7345 #[test]
7346 fn the_debug_forms_answer_four_different_shapes() {
7347 let mut f = Fixture::new();
7348 f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
7349 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+sparse\r\n");
7350 assert_eq!(
7351 f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
7352 "$44\r\nZ:8436 v:1,1 Z:4274 v:2,1 Z:3068 v:1,1 Z:603\r\n"
7353 );
7354 assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":1\r\n");
7355 assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":0\r\n");
7356 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+dense\r\n");
7357 assert_eq!(f.run(&[b"STRLEN", b"h"]), ":12304\r\n");
7358 assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
7359 assert_eq!(
7361 f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
7362 "-ERR HLL encoding is not sparse\r\n"
7363 );
7364
7365 let reply = f.run(&[b"PFDEBUG", b"GETREG", b"h"]);
7367 assert!(reply.starts_with("*16384\r\n"), "{}", &reply[..16]);
7368 assert_eq!(reply.matches(":0\r\n").count(), 16381);
7369 assert_eq!(reply.matches(":1\r\n").count(), 2);
7370 assert_eq!(reply.matches(":2\r\n").count(), 1);
7371
7372 assert_eq!(f.run(&[b"PFSELFTEST"]), "+OK\r\n");
7373 }
7374
7375 #[test]
7376 fn a_string_that_is_not_a_sketch_is_refused_with_its_own_sentence() {
7377 let mut f = Fixture::new();
7378 f.run(&[b"SET", b"plain", b"not a sketch"]);
7379 let not_hll = "-WRONGTYPE Key is not a valid HyperLogLog string value.\r\n";
7380 assert_eq!(f.run(&[b"PFADD", b"plain", b"a"]), not_hll);
7381 assert_eq!(f.run(&[b"PFCOUNT", b"plain"]), not_hll);
7382 assert_eq!(f.run(&[b"PFMERGE", b"plain"]), not_hll);
7383 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"plain"]), not_hll);
7384
7385 f.run(&[b"RPUSH", b"l", b"x"]);
7388 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
7389 assert_eq!(f.run(&[b"PFADD", b"l", b"a"]), wrong);
7390 assert_eq!(f.run(&[b"PFCOUNT", b"l"]), wrong);
7391 assert_eq!(f.run(&[b"PFMERGE", b"dest", b"l"]), wrong);
7392 assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
7393 assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"l"]), wrong);
7394 }
7395
7396 #[test]
7397 fn pfdebug_has_its_own_complaints() {
7398 let mut f = Fixture::new();
7399 f.run(&[b"PFADD", b"h", b"a"]);
7400 assert_eq!(
7403 f.run(&[b"PFDEBUG", b"NOPE", b"h"]),
7404 "-ERR Unknown PFDEBUG subcommand 'NOPE'\r\n"
7405 );
7406 let gone = "-ERR The specified key does not exist\r\n";
7408 assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"missing"]), gone);
7409 assert_eq!(f.run(&[b"PFDEBUG", b"DECODE", b"missing"]), gone);
7410 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"missing"]), gone);
7411 assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"missing"]), gone);
7412 assert_eq!(
7413 f.run(&[b"PFDEBUG"]),
7414 "-ERR wrong number of arguments for 'pfdebug' command\r\n"
7415 );
7416 assert_eq!(
7417 f.run(&[b"PFSELFTEST", b"x"]),
7418 "-ERR wrong number of arguments for 'pfselftest' command\r\n"
7419 );
7420 }
7421
7422 #[test]
7423 fn a_sketch_whose_opcodes_do_not_add_up_says_so() {
7424 let mut f = Fixture::new();
7425 f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
7426 let reply = f.raw(&[b"GET", b"h"]);
7429 let short = reply[5..reply.len() - 3].to_vec();
7430 f.run(&[b"SET", b"h", &short]);
7431 assert_eq!(
7432 f.run(&[b"PFCOUNT", b"h"]),
7433 "-INVALIDOBJ Corrupted HLL object detected\r\n"
7434 );
7435 }
7436
7437 #[test]
7438 fn a_sketch_survives_a_dump_and_a_restore_in_both_encodings() {
7439 let mut f = Fixture::new();
7440 f.run(&[b"PFADD", b"small", b"a", b"b", b"c"]);
7443 if cfg!(miri) {
7450 f.run(&[b"PFADD", b"big", b"a", b"b", b"c"]);
7451 f.run(&[b"PFDEBUG", b"TODENSE", b"big"]);
7452 } else {
7453 for i in 0..10_000u32 {
7454 let ele = format!("e{i}");
7455 f.run(&[b"PFADD", b"big", ele.as_bytes()]);
7456 }
7457 }
7458 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"small"]), "+sparse\r\n");
7459 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"big"]), "+dense\r\n");
7460
7461 for key in [&b"small"[..], b"big"] {
7462 let mut copy = key.to_vec();
7463 copy.push(b'2');
7464 let bytes = payload(&f.raw(&[b"DUMP", key]));
7465 assert_eq!(f.run(&[b"RESTORE", ©, b"0", &bytes]), "+OK\r\n");
7466 assert_eq!(f.raw(&[b"GET", ©]), f.raw(&[b"GET", key]));
7469 assert_eq!(
7470 f.run(&[b"PFDEBUG", b"ENCODING", ©]),
7471 f.run(&[b"PFDEBUG", b"ENCODING", key])
7472 );
7473 assert_eq!(f.run(&[b"PFCOUNT", ©]), f.run(&[b"PFCOUNT", key]));
7474 }
7475 assert_eq!(f.run(&[b"PFCOUNT", b"small2"]), ":3\r\n");
7476 assert_eq!(f.run(&[b"STRLEN", b"big2"]), ":12304\r\n");
7477 }
7478
7479 fn bulk(s: &str) -> String {
7483 format!("${}\r\n{s}\r\n", s.len())
7484 }
7485
7486 fn bulks(parts: &[&str]) -> String {
7489 let mut s = format!("*{}\r\n", parts.len());
7490 for p in parts {
7491 s.push_str(&format!("${}\r\n{p}\r\n", p.len()));
7492 }
7493 s
7494 }
7495
7496 #[test]
7497 fn a_list_is_pushed_from_both_ends_and_the_left_one_reverses() {
7498 let mut f = Fixture::new();
7499 assert_eq!(f.run(&[b"LPUSH", b"k", b"a", b"b", b"c"]), ":3\r\n");
7503 assert_eq!(
7504 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
7505 bulks(&["c", "b", "a"])
7506 );
7507 assert_eq!(f.run(&[b"RPUSH", b"k", b"d"]), ":4\r\n");
7508 assert_eq!(f.run(&[b"LLEN", b"k"]), ":4\r\n");
7509 assert_eq!(f.run(&[b"LPOP", b"k"]), "$1\r\nc\r\n");
7510 assert_eq!(f.run(&[b"RPOP", b"k"]), "$1\r\nd\r\n");
7511 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "a"]));
7512 assert_eq!(f.run(&[b"TYPE", b"k"]), "+list\r\n");
7513 }
7514
7515 #[test]
7516 fn the_x_pushes_refuse_to_bring_a_list_back_to_life() {
7517 let mut f = Fixture::new();
7518 assert_eq!(f.run(&[b"LPUSHX", b"k", b"a"]), ":0\r\n");
7519 assert_eq!(f.run(&[b"RPUSHX", b"k", b"a"]), ":0\r\n");
7520 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
7521 f.run(&[b"RPUSH", b"k", b"a"]);
7522 assert_eq!(f.run(&[b"LPUSHX", b"k", b"z"]), ":2\r\n");
7523 assert_eq!(f.run(&[b"RPUSHX", b"k", b"y"]), ":3\r\n");
7524 assert_eq!(
7525 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
7526 bulks(&["z", "a", "y"])
7527 );
7528 }
7529
7530 #[test]
7533 fn an_empty_pop_is_a_different_nothing_with_a_count_and_without() {
7534 let mut f = Fixture::new();
7535 assert_eq!(f.run(&[b"LPOP", b"nope"]), "$-1\r\n");
7536 assert_eq!(f.run(&[b"LPOP", b"nope", b"2"]), "*-1\r\n");
7537 assert_eq!(f.run(&[b"RPOP", b"nope"]), "$-1\r\n");
7538 assert_eq!(f.run(&[b"RPOP", b"nope", b"2"]), "*-1\r\n");
7539 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
7540 assert_eq!(f.run(&[b"LPOP", b"k", b"0"]), "*0\r\n");
7543 assert_eq!(f.run(&[b"LPOP", b"k", b"1"]), bulks(&["a"]));
7544 assert_eq!(f.run(&[b"RPOP", b"k", b"9"]), bulks(&["c", "b"]));
7546 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
7547 }
7548
7549 #[test]
7550 fn a_pop_count_has_its_own_sentence_and_a_third_argument_is_an_arity_error() {
7551 let mut f = Fixture::new();
7552 f.run(&[b"RPUSH", b"k", b"a"]);
7553 let range = "-ERR value is out of range, must be positive\r\n";
7554 assert_eq!(f.run(&[b"LPOP", b"k", b"-1"]), range);
7555 assert_eq!(f.run(&[b"LPOP", b"k", b"abc"]), range);
7556 assert_eq!(f.run(&[b"RPOP", b"k", b"-1"]), range);
7557 assert_eq!(
7560 f.run(&[b"LPOP", b"k", b"1", b"2"]),
7561 "-ERR wrong number of arguments for 'lpop' command\r\n"
7562 );
7563 assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
7564 }
7565
7566 #[test]
7567 fn a_range_takes_negative_ends_and_clamps_the_ones_that_run_off() {
7568 let mut f = Fixture::new();
7569 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
7570 assert_eq!(
7571 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
7572 bulks(&["a", "b", "c"])
7573 );
7574 assert_eq!(f.run(&[b"LRANGE", b"k", b"-2", b"-1"]), bulks(&["b", "c"]));
7575 assert_eq!(f.run(&[b"LRANGE", b"k", b"1", b"1"]), bulks(&["b"]));
7576 assert_eq!(f.run(&[b"LRANGE", b"k", b"5", b"10"]), "*0\r\n");
7577 assert_eq!(f.run(&[b"LRANGE", b"k", b"2", b"1"]), "*0\r\n");
7578 assert_eq!(
7579 f.run(&[b"LRANGE", b"k", b"-100", b"100"]),
7580 bulks(&["a", "b", "c"])
7581 );
7582 assert_eq!(f.run(&[b"LRANGE", b"nope", b"0", b"-1"]), "*0\r\n");
7585 assert_eq!(
7586 f.run(&[b"LRANGE", b"k", b"a", b"b"]),
7587 "-ERR value is not an integer or out of range\r\n"
7588 );
7589 }
7590
7591 #[test]
7592 fn an_index_reads_and_writes_from_whichever_end_is_nearer() {
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"LINDEX", b"k", b"0"]), "$1\r\na\r\n");
7596 assert_eq!(f.run(&[b"LINDEX", b"k", b"-1"]), "$1\r\nc\r\n");
7597 assert_eq!(f.run(&[b"LINDEX", b"k", b"99"]), "$-1\r\n");
7598 assert_eq!(f.run(&[b"LINDEX", b"nope", b"0"]), "$-1\r\n");
7599 assert_eq!(f.run(&[b"LSET", b"k", b"-1", b"z"]), "+OK\r\n");
7600 assert_eq!(
7601 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
7602 bulks(&["a", "b", "z"])
7603 );
7604 assert_eq!(
7607 f.run(&[b"LSET", b"k", b"99", b"z"]),
7608 "-ERR index out of range\r\n"
7609 );
7610 assert_eq!(
7611 f.run(&[b"LSET", b"nope", b"0", b"z"]),
7612 "-ERR no such key\r\n"
7613 );
7614 }
7615
7616 #[test]
7617 fn linsert_says_three_things_with_one_signed_number() {
7618 let mut f = Fixture::new();
7619 assert_eq!(
7622 f.run(&[b"LINSERT", b"nope", b"BEFORE", b"a", b"x"]),
7623 ":0\r\n"
7624 );
7625 f.run(&[b"RPUSH", b"k", b"a", b"b"]);
7626 assert_eq!(f.run(&[b"LINSERT", b"k", b"before", b"a", b"X"]), ":3\r\n");
7627 assert_eq!(f.run(&[b"LINSERT", b"k", b"AFTER", b"b", b"Y"]), ":4\r\n");
7628 assert_eq!(
7629 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
7630 bulks(&["X", "a", "b", "Y"])
7631 );
7632 assert_eq!(
7633 f.run(&[b"LINSERT", b"k", b"BEFORE", b"zz", b"x"]),
7634 ":-1\r\n"
7635 );
7636 assert_eq!(
7637 f.run(&[b"LINSERT", b"k", b"SIDEWAYS", b"a", b"x"]),
7638 "-ERR syntax error\r\n"
7639 );
7640 }
7641
7642 #[test]
7643 fn lrem_counts_in_three_directions_and_takes_the_key_when_it_empties() {
7644 let mut f = Fixture::new();
7645 f.run(&[b"RPUSH", b"k", b"a", b"b", b"a", b"c", b"a"]);
7646 assert_eq!(f.run(&[b"LREM", b"k", b"2", b"a"]), ":2\r\n");
7647 assert_eq!(
7648 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
7649 bulks(&["b", "c", "a"])
7650 );
7651 assert_eq!(f.run(&[b"LREM", b"k", b"-1", b"a"]), ":1\r\n");
7652 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
7653 assert_eq!(f.run(&[b"LREM", b"k", b"0", b"b"]), ":1\r\n");
7654 assert_eq!(f.run(&[b"LREM", b"k", b"0", b"c"]), ":1\r\n");
7655 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
7656 assert_eq!(f.run(&[b"LREM", b"nope", b"0", b"a"]), ":0\r\n");
7657 }
7658
7659 #[test]
7660 fn ltrim_keeps_a_window_and_an_empty_one_deletes_the_key() {
7661 let mut f = Fixture::new();
7662 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c", b"d"]);
7663 assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"-2"]), "+OK\r\n");
7664 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
7665 assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"0"]), "+OK\r\n");
7668 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
7669 assert_eq!(f.run(&[b"LTRIM", b"nope", b"0", b"-1"]), "+OK\r\n");
7670 }
7671
7672 #[test]
7673 fn lpos_walks_from_either_end_and_stops_where_it_is_told() {
7674 let mut f = Fixture::new();
7675 f.run(&[b"RPUSH", b"p", b"a", b"b", b"c", b"a", b"b", b"c", b"a"]);
7676 assert_eq!(f.run(&[b"LPOS", b"p", b"a"]), ":0\r\n");
7677 assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1"]), ":6\r\n");
7678 assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"2"]), ":3\r\n");
7679 assert_eq!(
7680 f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"2"]),
7681 "*2\r\n:0\r\n:3\r\n"
7682 );
7683 assert_eq!(
7684 f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1", b"COUNT", b"0"]),
7685 "*3\r\n:6\r\n:3\r\n:0\r\n"
7686 );
7687 assert_eq!(
7690 f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"0", b"MAXLEN", b"3"]),
7691 "*1\r\n:0\r\n"
7692 );
7693 assert_eq!(f.run(&[b"LPOS", b"p", b"zz"]), "$-1\r\n");
7696 assert_eq!(f.run(&[b"LPOS", b"p", b"zz", b"COUNT", b"0"]), "*0\r\n");
7697 assert_eq!(f.run(&[b"LPOS", b"nope", b"a"]), "$-1\r\n");
7698 assert_eq!(f.run(&[b"LPOS", b"nope", b"a", b"COUNT", b"2"]), "*0\r\n");
7699 }
7700
7701 #[test]
7702 fn lpos_words_its_three_mistakes_the_way_redis_does() {
7703 let mut f = Fixture::new();
7704 f.run(&[b"RPUSH", b"p", b"a"]);
7705 assert_eq!(
7708 f.run(&[b"LPOS", b"p", b"a", b"RANK", b"0"]),
7709 "-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"
7710 );
7711 assert_eq!(
7712 f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"-1"]),
7713 "-ERR COUNT can't be negative\r\n"
7714 );
7715 assert_eq!(
7716 f.run(&[b"LPOS", b"p", b"a", b"MAXLEN", b"-1"]),
7717 "-ERR MAXLEN can't be negative\r\n"
7718 );
7719 assert_eq!(
7720 f.run(&[b"LPOS", b"p", b"a", b"RANK"]),
7721 "-ERR syntax error\r\n"
7722 );
7723 assert_eq!(
7724 f.run(&[b"LPOS", b"p", b"a", b"FOO", b"1"]),
7725 "-ERR syntax error\r\n"
7726 );
7727 }
7728
7729 #[test]
7730 fn a_move_takes_from_one_end_and_gives_to_another_even_on_one_key() {
7731 let mut f = Fixture::new();
7732 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
7733 assert_eq!(f.run(&[b"RPOPLPUSH", b"k", b"d"]), "$1\r\nc\r\n");
7734 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
7735 assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c"]));
7736 assert_eq!(
7737 f.run(&[b"LMOVE", b"k", b"d", b"LEFT", b"RIGHT"]),
7738 "$1\r\na\r\n"
7739 );
7740 assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c", "a"]));
7741 f.run(&[b"DEL", b"r"]);
7744 f.run(&[b"RPUSH", b"r", b"1", b"2", b"3"]);
7745 assert_eq!(f.run(&[b"RPOPLPUSH", b"r", b"r"]), "$1\r\n3\r\n");
7746 assert_eq!(
7747 f.run(&[b"LRANGE", b"r", b"0", b"-1"]),
7748 bulks(&["3", "1", "2"])
7749 );
7750 assert_eq!(
7751 f.run(&[b"LMOVE", b"nope", b"d", b"LEFT", b"LEFT"]),
7752 "$-1\r\n"
7753 );
7754 assert_eq!(
7755 f.run(&[b"LMOVE", b"r", b"d", b"LEFT", b"SIDEWAYS"]),
7756 "-ERR syntax error\r\n"
7757 );
7758 }
7759
7760 #[test]
7761 fn a_move_checks_the_destination_before_it_takes_anything() {
7762 let mut f = Fixture::new();
7763 f.run(&[b"RPUSH", b"k", b"a", b"b"]);
7764 f.run(&[b"SET", b"str", b"v"]);
7765 assert_eq!(
7766 f.run(&[b"LMOVE", b"k", b"str", b"LEFT", b"LEFT"]),
7767 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
7768 );
7769 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
7771 }
7772
7773 #[test]
7774 fn a_block_move_orders_the_block_by_the_ends_and_the_ordering_word() {
7775 for (from, to, order, want) in [
7779 ("LEFT", "RIGHT", "OBO", ["a", "b"]),
7780 ("LEFT", "RIGHT", "BULK", ["a", "b"]),
7781 ("LEFT", "LEFT", "OBO", ["b", "a"]),
7782 ("LEFT", "LEFT", "BULK", ["a", "b"]),
7783 ("RIGHT", "LEFT", "OBO", ["d", "e"]),
7784 ("RIGHT", "LEFT", "BULK", ["d", "e"]),
7785 ("RIGHT", "RIGHT", "OBO", ["e", "d"]),
7786 ("RIGHT", "RIGHT", "BULK", ["d", "e"]),
7787 ] {
7788 let mut f = Fixture::new();
7789 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c", b"d", b"e"]);
7790 let how = format!("{from} {to} {order}");
7791 let reply = f.run(&[
7792 b"LMOVEM",
7793 b"s",
7794 b"d",
7795 from.as_bytes(),
7796 to.as_bytes(),
7797 b"COUNT",
7798 b"2",
7799 order.as_bytes(),
7800 ]);
7801 assert_eq!(reply, bulks(&want), "the reply for {how}");
7802 assert_eq!(
7803 f.run(&[b"LRANGE", b"d", b"0", b"-1"]),
7804 bulks(&want),
7805 "the destination for {how}"
7806 );
7807 }
7808 }
7809
7810 #[test]
7811 fn a_block_move_of_one_needs_no_count_at_all() {
7812 let mut f = Fixture::new();
7813 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
7814 assert_eq!(
7815 f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT"]),
7816 bulks(&["a"])
7817 );
7818 assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["b", "c"]));
7819 assert_eq!(
7822 f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT"]),
7823 "-ERR syntax error\r\n"
7824 );
7825 assert_eq!(
7826 f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"2"]),
7827 "-ERR syntax error\r\n"
7828 );
7829 }
7830
7831 #[test]
7832 fn a_block_move_with_exactly_takes_all_of_them_or_none() {
7833 let mut f = Fixture::new();
7834 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
7835 assert_eq!(
7839 f.run(&[
7840 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"EXACTLY", b"99", b"BULK"
7841 ]),
7842 "*-1\r\n"
7843 );
7844 assert_eq!(
7845 f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
7846 bulks(&["a", "b", "c"])
7847 );
7848 assert_eq!(
7850 f.run(&[
7851 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"99", b"BULK"
7852 ]),
7853 bulks(&["a", "b", "c"])
7854 );
7855 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
7856 assert_eq!(
7857 f.run(&[
7858 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
7859 ]),
7860 "*-1\r\n"
7861 );
7862 }
7863
7864 #[test]
7865 fn a_block_move_onto_itself_rotates_by_the_count() {
7866 let mut f = Fixture::new();
7867 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
7868 assert_eq!(
7869 f.run(&[
7870 b"LMOVEM", b"s", b"s", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
7871 ]),
7872 bulks(&["a", "b"])
7873 );
7874 assert_eq!(
7875 f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
7876 bulks(&["c", "a", "b"])
7877 );
7878 }
7879
7880 #[test]
7881 fn a_block_move_reads_the_count_before_the_ordering_word() {
7882 let mut f = Fixture::new();
7883 f.run(&[b"RPUSH", b"s", b"a", b"b"]);
7884 f.run(&[b"SET", b"str", b"v"]);
7885 let count = "-ERR count should be greater than 0\r\n";
7886 assert_eq!(
7887 f.run(&[
7888 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"NOPE"
7889 ]),
7890 count
7891 );
7892 assert_eq!(
7893 f.run(&[
7894 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"0", b"BULK"
7895 ]),
7896 count
7897 );
7898 assert_eq!(
7899 f.run(&[
7900 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"NOPE"
7901 ]),
7902 "-ERR syntax error\r\n"
7903 );
7904 assert_eq!(
7905 f.run(&[
7906 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"NOPE", b"abc", b"BULK"
7907 ]),
7908 "-ERR syntax error\r\n"
7909 );
7910 assert_eq!(
7913 f.run(&[
7914 b"LMOVEM", b"str", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"BULK"
7915 ]),
7916 count
7917 );
7918 assert_eq!(
7919 f.run(&[
7920 b"LMOVEM", b"s", b"str", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
7921 ]),
7922 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
7923 );
7924 assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["a", "b"]));
7925 }
7926
7927 #[test]
7928 fn lmpop_answers_from_the_first_key_that_has_anything() {
7929 let mut f = Fixture::new();
7930 f.run(&[b"RPUSH", b"b", b"1", b"2", b"3"]);
7931 assert_eq!(
7934 f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT", b"COUNT", b"2"]),
7935 "*2\r\n$1\r\nb\r\n*2\r\n$1\r\n1\r\n$1\r\n2\r\n"
7936 );
7937 assert_eq!(
7938 f.run(&[b"LMPOP", b"2", b"a", b"b", b"RIGHT"]),
7939 "*2\r\n$1\r\nb\r\n*1\r\n$1\r\n3\r\n"
7940 );
7941 assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
7942 assert_eq!(f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT"]), "*-1\r\n");
7945 }
7946
7947 #[test]
7948 fn lmpop_has_its_own_words_for_a_count_and_for_a_key_count() {
7949 let mut f = Fixture::new();
7950 f.run(&[b"RPUSH", b"k", b"a"]);
7951 assert_eq!(
7952 f.run(&[b"LMPOP", b"0", b"k", b"LEFT"]),
7953 "-ERR numkeys should be greater than 0\r\n"
7954 );
7955 assert_eq!(
7956 f.run(&[b"LMPOP", b"-1", b"k", b"LEFT"]),
7957 "-ERR numkeys should be greater than 0\r\n"
7958 );
7959 assert_eq!(
7960 f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"0"]),
7961 "-ERR count should be greater than 0\r\n"
7962 );
7963 assert_eq!(
7966 f.run(&[b"LMPOP", b"3", b"k", b"LEFT"]),
7967 "-ERR syntax error\r\n"
7968 );
7969 assert_eq!(
7970 f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"1", b"x"]),
7971 "-ERR syntax error\r\n"
7972 );
7973 assert_eq!(
7974 f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"FOO", b"1"]),
7975 "-ERR syntax error\r\n"
7976 );
7977 assert_eq!(
7978 f.run(&[b"LMPOP", b"1", b"k", b"SIDEWAYS"]),
7979 "-ERR syntax error\r\n"
7980 );
7981 assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
7982 }
7983
7984 #[test]
7985 fn every_list_command_says_wrongtype_and_writes_nothing() {
7986 let mut f = Fixture::new();
7987 f.run(&[b"SET", b"str", b"v"]);
7988 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
7989 for cmd in [
7990 &[b"LPUSH".as_slice(), b"str", b"a"][..],
7991 &[b"RPUSH", b"str", b"a"],
7992 &[b"LPUSHX", b"str", b"a"],
7993 &[b"RPUSHX", b"str", b"a"],
7994 &[b"LPOP", b"str"],
7995 &[b"LPOP", b"str", b"2"],
7996 &[b"RPOP", b"str"],
7997 &[b"LLEN", b"str"],
7998 &[b"LRANGE", b"str", b"0", b"-1"],
7999 &[b"LINDEX", b"str", b"0"],
8000 &[b"LSET", b"str", b"0", b"a"],
8001 &[b"LINSERT", b"str", b"BEFORE", b"a", b"b"],
8002 &[b"LREM", b"str", b"0", b"a"],
8003 &[b"LTRIM", b"str", b"0", b"-1"],
8004 &[b"LPOS", b"str", b"a"],
8005 &[b"LPOS", b"str", b"a", b"COUNT", b"0"],
8006 &[b"RPOPLPUSH", b"str", b"d"],
8007 &[b"LMOVE", b"str", b"d", b"LEFT", b"LEFT"],
8008 &[b"LMPOP", b"1", b"str", b"LEFT"],
8009 ] {
8010 assert_eq!(f.run(cmd), wrong, "{:?}", String::from_utf8_lossy(cmd[0]));
8011 }
8012 assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
8013 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
8014 }
8015
8016 #[test]
8020 fn a_timeout_has_three_ways_of_being_wrong() {
8021 let mut f = Fixture::new();
8022 let not_float = "-ERR timeout is not a float or out of range\r\n";
8023 let range = "-ERR timeout is out of range\r\n";
8024 for (bad, want) in [
8025 (&[b"BLPOP".as_slice(), b"k", b"abc"][..], not_float),
8026 (&[b"BLPOP", b"k", b"nan"], not_float),
8027 (&[b"BLPOP", b"k", b""], not_float),
8028 (&[b"BLPOP", b"k", b" 1"], not_float),
8031 (&[b"BLPOP", b"k", b"1 "], not_float),
8032 (&[b"BLPOP", b"k", b"-1"], "-ERR timeout is negative\r\n"),
8033 (&[b"BLPOP", b"k", b"-0.1"], "-ERR timeout is negative\r\n"),
8034 (&[b"BLPOP", b"k", b"1e400"], range),
8037 (&[b"BLPOP", b"k", b"inf"], range),
8038 (&[b"BLPOP", b"k", b"9999999999999999"], range),
8039 (&[b"BRPOP", b"k", b"abc"], not_float),
8040 (
8041 &[b"BLMOVE", b"a", b"b", b"LEFT", b"RIGHT", b"abc"],
8042 not_float,
8043 ),
8044 (
8045 &[b"BRPOPLPUSH", b"a", b"b", b"-1"],
8046 "-ERR timeout is negative\r\n",
8047 ),
8048 (&[b"BLMPOP", b"abc", b"1", b"k", b"LEFT"], not_float),
8049 ] {
8050 assert_eq!(f.run(bad), want, "for {bad:?}");
8051 }
8052 }
8053
8054 #[test]
8057 fn a_zero_timeout_waits_and_the_smallest_positive_one_does_not() {
8058 let mut f = Fixture::new();
8059 for timeout in [b"0".as_slice(), b"0.0", b"-0.0"] {
8060 let (flow, out) = f.flow(&[b"BLPOP", b"k", timeout]);
8061 assert_eq!(flow, Flow::Block, "for {timeout:?}");
8062 assert!(out.is_empty(), "for {timeout:?}");
8063 }
8064 let (flow, out) = f.flow(&[b"BLPOP", b"k", b"0.0000001"]);
8068 assert_eq!(flow, Flow::Block);
8069 assert!(out.is_empty());
8070 }
8071
8072 #[test]
8073 fn a_blocking_command_that_can_be_answered_answers_like_the_one_it_wraps() {
8074 let mut f = Fixture::new();
8075 f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
8076
8077 assert_eq!(
8080 f.flow(&[b"BLPOP", b"nope", b"L", b"0"]),
8081 (Flow::Continue, "*2\r\n$1\r\nL\r\n$1\r\na\r\n".to_owned())
8082 );
8083 assert_eq!(
8084 f.run(&[b"BRPOP", b"L", b"0"]),
8085 "*2\r\n$1\r\nL\r\n$1\r\ne\r\n"
8086 );
8087 assert_eq!(
8088 f.run(&[
8089 b"BLMPOP", b"0", b"2", b"nope", b"L", b"LEFT", b"COUNT", b"2"
8090 ]),
8091 "*2\r\n$1\r\nL\r\n*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
8092 );
8093 assert_eq!(
8094 f.run(&[b"BLMOVE", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
8095 "$1\r\nd\r\n"
8096 );
8097 assert_eq!(
8098 f.run(&[b"EXISTS", b"L"]),
8099 ":0\r\n",
8100 "and the key went with it"
8101 );
8102 assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nd\r\n");
8103 f.run(&[b"RPUSH", b"D", b"x"]);
8106 assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"D", b"0"]), "$1\r\nx\r\n");
8107 assert_eq!(
8108 f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
8109 "*2\r\n$1\r\nx\r\n$1\r\nd\r\n"
8110 );
8111 }
8112
8113 #[test]
8114 fn blmpop_reads_its_count_and_its_key_count_the_way_lmpop_does() {
8115 let mut f = Fixture::new();
8116 f.run(&[b"RPUSH", b"k", b"a"]);
8117 for (bad, want) in [
8118 (
8119 &[b"BLMPOP".as_slice(), b"0", b"0", b"k", b"LEFT"][..],
8120 "-ERR numkeys should be greater than 0\r\n",
8121 ),
8122 (
8123 &[b"BLMPOP", b"0", b"-1", b"k", b"LEFT"],
8124 "-ERR numkeys should be greater than 0\r\n",
8125 ),
8126 (
8129 &[b"BLMPOP", b"0", b"2", b"k", b"LEFT"],
8130 "-ERR syntax error\r\n",
8131 ),
8132 (
8133 &[b"BLMPOP", b"0", b"1", b"k", b"SIDEWAYS"],
8134 "-ERR syntax error\r\n",
8135 ),
8136 (
8137 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT"],
8138 "-ERR syntax error\r\n",
8139 ),
8140 (
8141 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"2", b"x"],
8142 "-ERR syntax error\r\n",
8143 ),
8144 (
8147 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"0"],
8148 "-ERR count should be greater than 0\r\n",
8149 ),
8150 (
8151 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"abc"],
8152 "-ERR count should be greater than 0\r\n",
8153 ),
8154 ] {
8155 assert_eq!(f.run(bad), want, "for {bad:?}");
8156 }
8157 assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n", "and none of them popped");
8158 }
8159
8160 #[test]
8161 fn a_blocking_move_reads_its_directions_before_its_timeout() {
8162 let mut f = Fixture::new();
8163 assert_eq!(
8166 f.run(&[b"BLMOVE", b"a", b"b", b"UP", b"DOWN", b"abc"]),
8167 "-ERR syntax error\r\n"
8168 );
8169 assert_eq!(
8170 f.run(&[b"BLMOVE", b"a", b"b", b"LEFT", b"DOWN", b"0.05"]),
8171 "-ERR syntax error\r\n"
8172 );
8173 }
8174
8175 #[test]
8179 fn a_blocking_block_move_that_can_be_answered_answers_like_lmovem() {
8180 let mut f = Fixture::new();
8181 f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
8182 assert_eq!(
8183 f.flow(&[b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
8184 (Flow::Continue, "*1\r\n$1\r\na\r\n".to_owned())
8185 );
8186 assert_eq!(
8187 f.run(&[
8188 b"BLMOVEM", b"L", b"D", b"RIGHT", b"RIGHT", b"0", b"COUNT", b"2", b"OBO"
8189 ]),
8190 bulks(&["e", "d"])
8191 );
8192 assert_eq!(
8193 f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
8194 bulks(&["a", "e", "d"])
8195 );
8196 assert_eq!(
8198 f.run(&[
8199 b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"2", b"BULK"
8200 ]),
8201 bulks(&["b", "c"])
8202 );
8203 assert_eq!(f.run(&[b"EXISTS", b"L"]), ":0\r\n", "and the key went");
8204 }
8205
8206 #[test]
8210 fn a_blocking_block_move_waits_for_the_whole_block_only_under_exactly() {
8211 let mut f = Fixture::new();
8212 f.run(&[b"RPUSH", b"L", b"a", b"b"]);
8213 assert_eq!(
8215 f.flow(&[
8216 b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"COUNT", b"3", b"BULK"
8217 ]),
8218 (Flow::Continue, bulks(&["a", "b"]))
8219 );
8220
8221 f.run(&[b"RPUSH", b"L", b"a", b"b"]);
8222 assert_eq!(
8225 f.flow(&[
8226 b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"3", b"BULK"
8227 ])
8228 .0,
8229 Flow::Block
8230 );
8231 assert_eq!(f.run(&[b"LRANGE", b"L", b"0", b"-1"]), bulks(&["a", "b"]));
8232 }
8233
8234 #[test]
8235 fn a_blocking_block_move_reads_its_directions_then_its_timeout_then_its_count() {
8236 let mut f = Fixture::new();
8237 let syntax = "-ERR syntax error\r\n";
8238 assert_eq!(
8240 f.run(&[
8241 b"BLMOVEM", b"a", b"b", b"UP", b"DOWN", b"abc", b"NOPE", b"x", b"y"
8242 ]),
8243 syntax
8244 );
8245 assert_eq!(
8247 f.run(&[
8248 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"abc", b"COUNT", b"abc", b"BULK"
8249 ]),
8250 "-ERR timeout is not a float or out of range\r\n"
8251 );
8252 assert_eq!(
8253 f.run(&[
8254 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"-1", b"COUNT", b"1", b"BULK"
8255 ]),
8256 "-ERR timeout is negative\r\n"
8257 );
8258 assert_eq!(
8260 f.run(&[
8261 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"abc", b"NOPE"
8262 ]),
8263 "-ERR count should be greater than 0\r\n"
8264 );
8265 assert_eq!(
8266 f.run(&[
8267 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"1", b"NOPE"
8268 ]),
8269 syntax
8270 );
8271 assert_eq!(
8274 f.run(&[b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT"]),
8275 syntax
8276 );
8277 assert_eq!(
8278 f.run(&[
8279 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"2"
8280 ]),
8281 syntax
8282 );
8283 }
8284
8285 #[test]
8288 fn a_blocking_command_errors_on_a_wrong_type_rather_than_waiting_on_it() {
8289 let mut f = Fixture::new();
8290 f.run(&[b"SET", b"S", b"v"]);
8291 f.run(&[b"RPUSH", b"D", b"x"]);
8292 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8293
8294 assert_eq!(f.run(&[b"BLPOP", b"S", b"0"]), wrong);
8295 assert_eq!(f.run(&[b"BLPOP", b"E", b"S", b"0"]), wrong);
8298 assert_eq!(f.run(&[b"BRPOP", b"S", b"0"]), wrong);
8299 assert_eq!(f.run(&[b"BLMPOP", b"0", b"1", b"S", b"LEFT"]), wrong);
8300 assert_eq!(f.run(&[b"BRPOPLPUSH", b"S", b"D", b"0"]), wrong);
8301 assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"S", b"0"]), wrong);
8304 assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nx\r\n");
8305 assert_eq!(
8306 f.run(&[b"BLMOVEM", b"S", b"D", b"LEFT", b"RIGHT", b"0"]),
8307 wrong
8308 );
8309 assert_eq!(
8310 f.run(&[b"BLMOVEM", b"D", b"S", b"LEFT", b"RIGHT", b"0"]),
8311 wrong
8312 );
8313
8314 assert_eq!(
8318 f.flow(&[b"BLMOVE", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
8319 .0,
8320 Flow::Block
8321 );
8322 assert_eq!(
8326 f.flow(&[b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
8327 .0,
8328 Flow::Block
8329 );
8330 f.run(&[b"RPUSH", b"E", b"1", b"2"]);
8331 assert_eq!(
8332 f.flow(&[
8333 b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1", b"EXACTLY", b"3", b"BULK"
8334 ])
8335 .0,
8336 Flow::Block
8337 );
8338 }
8339
8340 #[cfg_attr(miri, ignore = "the volume is the claim")]
8345 #[test]
8346 fn churning_lists_does_not_grow_the_server() {
8347 let mut f = Fixture::new();
8348 let vals: Vec<Vec<u8>> = (0..200).map(|i| format!("v{i}").into_bytes()).collect();
8349 let args: Vec<&[u8]> = [&b"RPUSH"[..], &b"k"[..]]
8350 .into_iter()
8351 .chain(vals.iter().map(Vec::as_slice))
8352 .collect();
8353
8354 f.run(&args);
8355 f.run(&[b"DEL", b"k"]);
8356 f.server.compact_step();
8357 let after_first = f.server.memory_bytes();
8358
8359 for _ in 0..200 {
8360 f.run(&args);
8361 f.run(&[b"LTRIM", b"k", b"1", b"0"]);
8362 f.server.compact_step();
8363 }
8364 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
8365 assert!(
8366 f.server.memory_bytes() <= after_first * 2,
8367 "held {} after two hundred passes against {after_first} after one",
8368 f.server.memory_bytes()
8369 );
8370 }
8371
8372 #[test]
8375 fn a_sorted_set_takes_scores_and_gives_them_back() {
8376 let mut f = Fixture::new();
8377 assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]), ":2\r\n");
8378 assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]), ":1\r\n");
8379 assert_eq!(f.run(&[b"ZCARD", b"z"]), ":3\r\n");
8380 assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$1\r\n2\r\n");
8381 assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "$-1\r\n");
8382 assert_eq!(f.run(&[b"ZSCORE", b"nokey", b"b"]), "$-1\r\n");
8383 assert_eq!(
8384 f.run(&[b"ZMSCORE", b"z", b"a", b"nope", b"c"]),
8385 "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n"
8386 );
8387 assert_eq!(f.run(&[b"ZREM", b"z", b"a", b"nope"]), ":1\r\n");
8388 assert_eq!(f.run(&[b"ZCARD", b"z"]), ":2\r\n");
8389 assert_eq!(f.run(&[b"ZREM", b"z", b"b", b"c"]), ":2\r\n");
8391 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
8392 }
8393
8394 #[test]
8395 fn a_score_is_a_double_on_resp3_and_digits_on_resp2() {
8396 let mut f = Fixture::new();
8397 f.run(&[b"ZADD", b"z", b"1.5", b"a", b"inf", b"b", b"-inf", b"c"]);
8398 assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$3\r\n1.5\r\n");
8399 assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$3\r\ninf\r\n");
8400 assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), "$4\r\n-inf\r\n");
8401
8402 f.out = Out::new(Proto::Resp3);
8403 assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), ",1.5\r\n");
8404 assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), ",inf\r\n");
8405 assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), ",-inf\r\n");
8406 assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "_\r\n");
8407 }
8408
8409 #[test]
8410 fn the_zadd_options_gate_what_gets_written() {
8411 let mut f = Fixture::new();
8412 f.run(&[b"ZADD", b"z", b"5", b"a"]);
8413 assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"9", b"a"]), ":0\r\n");
8415 assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$1\r\n5\r\n");
8416 assert_eq!(f.run(&[b"ZADD", b"z", b"XX", b"9", b"new"]), ":0\r\n");
8417 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":1\r\n");
8418 assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"3", b"a"]), ":0\r\n");
8420 assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"7", b"a"]), ":1\r\n");
8421 assert_eq!(f.run(&[b"ZADD", b"z", b"LT", b"CH", b"9", b"a"]), ":0\r\n");
8422 assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"1", b"b"]), ":1\r\n");
8424 assert_eq!(
8425 f.run(&[b"ZADD", b"z", b"CH", b"2", b"a", b"2", b"c"]),
8426 ":2\r\n"
8427 );
8428 }
8429
8430 #[test]
8431 fn zadd_incr_answers_a_score_or_nothing_at_all() {
8432 let mut f = Fixture::new();
8433 assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"5", b"m"]), "$1\r\n5\r\n");
8434 assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"2", b"m"]), "$1\r\n7\r\n");
8435 assert_eq!(
8438 f.run(&[b"ZADD", b"z", b"NX", b"INCR", b"2", b"m"]),
8439 "$-1\r\n"
8440 );
8441 assert_eq!(
8442 f.run(&[b"ZADD", b"z", b"XX", b"INCR", b"2", b"gone"]),
8443 "$-1\r\n"
8444 );
8445 assert_eq!(
8446 f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"-1", b"m"]),
8447 "$-1\r\n"
8448 );
8449 assert_eq!(
8450 f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"1", b"m"]),
8451 "$1\r\n8\r\n"
8452 );
8453 assert_eq!(f.run(&[b"ZINCRBY", b"z", b"2", b"m"]), "$2\r\n10\r\n");
8454 assert_eq!(f.run(&[b"ZINCRBY", b"z", b"1", b"fresh"]), "$1\r\n1\r\n");
8455 }
8456
8457 #[test]
8458 fn the_two_infinities_will_not_be_added_together() {
8459 let mut f = Fixture::new();
8460 f.run(&[b"ZADD", b"z", b"inf", b"m"]);
8461 let nan = "-ERR resulting score is not a number (NaN)\r\n";
8462 assert_eq!(f.run(&[b"ZINCRBY", b"z", b"-inf", b"m"]), nan);
8463 assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"-inf", b"m"]), nan);
8464 assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\ninf\r\n");
8465 assert_eq!(f.run(&[b"ZINCRBY", b"gone", b"1", b"m"]), "$1\r\n1\r\n");
8467 }
8468
8469 #[test]
8470 fn zadd_says_its_mistakes_the_way_redis_says_them() {
8471 let mut f = Fixture::new();
8472 assert_eq!(
8475 f.run(&[b"ZADD", b"z", b"NX", b"XX"]),
8476 "-ERR syntax error\r\n"
8477 );
8478 assert_eq!(
8479 f.run(&[b"ZADD", b"z", b"NX", b"XX", b"1", b"a"]),
8480 "-ERR XX and NX options at the same time are not compatible\r\n"
8481 );
8482 let gtlt = "-ERR GT, LT, and/or NX options at the same time are not compatible\r\n";
8483 assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"GT", b"1", b"a"]), gtlt);
8484 assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"LT", b"1", b"a"]), gtlt);
8485 assert_eq!(
8486 f.run(&[b"ZADD", b"z", b"INCR", b"1", b"a", b"2", b"b"]),
8487 "-ERR INCR option supports a single increment-element pair\r\n"
8488 );
8489 assert_eq!(
8491 f.run(&[b"ZADD", b"z", b"1", b"a", b"2"]),
8492 "-ERR syntax error\r\n"
8493 );
8494 assert_eq!(
8496 f.run(&[b"ZADD", b"z", b"1", b"a", b"nonsense", b"b"]),
8497 "-ERR value is not a valid float\r\n"
8498 );
8499 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
8500 }
8501
8502 #[test]
8503 fn a_rank_says_where_a_member_sits_from_either_end() {
8504 let mut f = Fixture::new();
8505 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8506 assert_eq!(f.run(&[b"ZRANK", b"z", b"a"]), ":0\r\n");
8507 assert_eq!(f.run(&[b"ZRANK", b"z", b"c"]), ":2\r\n");
8508 assert_eq!(f.run(&[b"ZREVRANK", b"z", b"c"]), ":0\r\n");
8509 assert_eq!(f.run(&[b"ZREVRANK", b"z", b"a"]), ":2\r\n");
8510 assert_eq!(
8512 f.run(&[b"ZRANK", b"z", b"b", b"WITHSCORE"]),
8513 "*2\r\n:1\r\n$1\r\n2\r\n"
8514 );
8515 assert_eq!(f.run(&[b"ZRANK", b"z", b"nope"]), "$-1\r\n");
8516 assert_eq!(f.run(&[b"ZRANK", b"z", b"nope", b"WITHSCORE"]), "*-1\r\n");
8517 assert_eq!(f.run(&[b"ZRANK", b"nokey", b"a", b"WITHSCORE"]), "*-1\r\n");
8518 assert_eq!(
8521 f.run(&[b"ZRANK", b"z", b"b", b"bogus"]),
8522 "-ERR syntax error\r\n"
8523 );
8524 assert_eq!(
8525 f.run(&[b"ZREVRANK", b"z", b"b", b"WITHSCORE", b"more"]),
8526 "-ERR wrong number of arguments for 'zrevrank' command\r\n"
8527 );
8528 }
8529
8530 #[test]
8531 fn the_two_counts_read_their_two_kinds_of_bound() {
8532 let mut f = Fixture::new();
8533 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8534 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"-inf", b"+inf"]), ":3\r\n");
8535 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"2", b"3"]), ":2\r\n");
8536 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"3"]), ":2\r\n");
8537 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"(3"]), ":1\r\n");
8538 assert_eq!(f.run(&[b"ZCOUNT", b"nokey", b"-inf", b"+inf"]), ":0\r\n");
8539 assert_eq!(
8540 f.run(&[b"ZCOUNT", b"z", b"bogus", b"3"]),
8541 "-ERR min or max is not a float\r\n"
8542 );
8543
8544 f.run(&[b"ZADD", b"l", b"0", b"a", b"0", b"b", b"0", b"c"]);
8545 assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"-", b"+"]), ":3\r\n");
8546 assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"[a", b"(c"]), ":2\r\n");
8547 assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"(a", b"+"]), ":2\r\n");
8548 assert_eq!(
8551 f.run(&[b"ZLEXCOUNT", b"l", b"a", b"c"]),
8552 "-ERR min or max not valid string range item\r\n"
8553 );
8554 }
8555
8556 #[test]
8562 fn one_range_command_selects_by_rank_or_score_or_name() {
8563 let mut f = Fixture::new();
8564 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8565 assert_eq!(
8566 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
8567 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
8568 );
8569 assert_eq!(
8570 f.run(&[b"ZRANGE", b"z", b"-2", b"-1"]),
8571 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
8572 );
8573 assert_eq!(f.run(&[b"ZRANGE", b"z", b"5", b"9"]), "*0\r\n");
8574 assert_eq!(f.run(&[b"ZRANGE", b"nokey", b"0", b"-1"]), "*0\r\n");
8575 assert_eq!(
8578 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"REV"]),
8579 "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
8580 );
8581 assert_eq!(
8582 f.run(&[b"ZRANGE", b"z", b"(1", b"+inf", b"BYSCORE"]),
8583 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
8584 );
8585 assert_eq!(
8588 f.run(&[b"ZRANGE", b"z", b"+inf", b"(1", b"BYSCORE", b"REV"]),
8589 "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
8590 );
8591 assert_eq!(
8592 f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX"]),
8593 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
8594 );
8595 assert_eq!(
8596 f.run(&[b"ZRANGE", b"z", b"+", b"-", b"BYLEX", b"REV"]),
8597 "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
8598 );
8599 }
8600
8601 #[test]
8604 fn the_older_range_spellings_name_their_high_end_first() {
8605 let mut f = Fixture::new();
8606 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8607 assert_eq!(
8608 f.run(&[b"ZREVRANGE", b"z", b"0", b"-1"]),
8609 "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
8610 );
8611 assert_eq!(
8612 f.run(&[b"ZREVRANGE", b"z", b"0", b"0", b"WITHSCORES"]),
8613 "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
8614 );
8615 assert_eq!(
8616 f.run(&[b"ZRANGEBYSCORE", b"z", b"(1", b"3"]),
8617 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
8618 );
8619 assert_eq!(
8620 f.run(&[b"ZREVRANGEBYSCORE", b"z", b"3", b"(1"]),
8621 "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
8622 );
8623 assert_eq!(f.run(&[b"ZREVRANGEBYSCORE", b"z", b"(1", b"3"]), "*0\r\n");
8627 assert_eq!(
8628 f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"(c"]),
8629 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
8630 );
8631 assert_eq!(
8632 f.run(&[b"ZREVRANGEBYLEX", b"z", b"(c", b"[a"]),
8633 "*2\r\n$1\r\nb\r\n$1\r\na\r\n"
8634 );
8635 for cmd in [
8638 &[b"ZREVRANGE".as_slice(), b"z", b"0", b"-1", b"BYSCORE"][..],
8639 &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"REV"],
8640 &[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"BYLEX"],
8641 ] {
8642 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{:?}", cmd[0]);
8643 }
8644 }
8645
8646 #[test]
8649 fn limit_and_withscores_are_read_by_all_of_them_and_refused_afterwards() {
8650 let mut f = Fixture::new();
8651 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8652 assert_eq!(
8653 f.run(&[
8654 b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"1", b"1"
8655 ]),
8656 "*1\r\n$1\r\nb\r\n"
8657 );
8658 assert_eq!(
8660 f.run(&[
8661 b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"-1", b"2"
8662 ]),
8663 "*0\r\n"
8664 );
8665 assert_eq!(
8666 f.run(&[
8667 b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"0", b"-1"
8668 ]),
8669 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
8670 );
8671 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";
8673 assert_eq!(
8674 f.run(&[
8675 b"ZRANGEBYSCORE",
8676 b"z",
8677 b"1",
8678 b"3",
8679 b"WITHSCORES",
8680 b"LIMIT",
8681 b"0",
8682 b"2"
8683 ]),
8684 both
8685 );
8686 assert_eq!(
8687 f.run(&[
8688 b"ZRANGEBYSCORE",
8689 b"z",
8690 b"1",
8691 b"3",
8692 b"LIMIT",
8693 b"0",
8694 b"2",
8695 b"WITHSCORES"
8696 ]),
8697 both
8698 );
8699 let needs_by = "-ERR syntax error, LIMIT is only supported in combination with either BYSCORE or BYLEX\r\n";
8702 assert_eq!(
8703 f.run(&[
8704 b"ZREVRANGE",
8705 b"z",
8706 b"0",
8707 b"-1",
8708 b"WITHSCORES",
8709 b"LIMIT",
8710 b"0",
8711 b"1"
8712 ]),
8713 needs_by
8714 );
8715 assert_eq!(
8716 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"LIMIT", b"0", b"1"]),
8717 needs_by
8718 );
8719 let not_bylex = "-ERR syntax error, WITHSCORES not supported in combination with BYLEX\r\n";
8720 assert_eq!(
8721 f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX", b"WITHSCORES"]),
8722 not_bylex
8723 );
8724 assert_eq!(
8725 f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"WITHSCORES"]),
8726 not_bylex
8727 );
8728 for cmd in [
8731 &[
8732 b"ZRANGE".as_slice(),
8733 b"z",
8734 b"0",
8735 b"-1",
8736 b"BYSCORE",
8737 b"BYLEX",
8738 ][..],
8739 &[b"ZRANGE", b"z", b"0", b"-1", b"junk"],
8740 &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"0"],
8741 ] {
8742 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
8743 }
8744 assert_eq!(
8745 f.run(&[b"ZRANGEBYSCORE", b"z", b"bad", b"3"]),
8746 "-ERR min or max is not a float\r\n"
8747 );
8748 assert_eq!(
8749 f.run(&[b"ZRANGEBYLEX", b"z", b"a", b"[c"]),
8750 "-ERR min or max not valid string range item\r\n"
8751 );
8752 assert_eq!(
8753 f.run(&[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"a", b"2"]),
8754 "-ERR value is not an integer or out of range\r\n"
8755 );
8756 }
8757
8758 #[test]
8761 fn withscores_nests_on_resp3_and_flattens_on_resp2() {
8762 let mut f = Fixture::new();
8763 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8764 assert_eq!(
8765 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
8766 "*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"
8767 );
8768 f.out = Out::new(Proto::Resp3);
8769 assert_eq!(
8770 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
8771 "*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"
8772 );
8773 assert_eq!(
8774 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
8775 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
8776 );
8777 }
8778
8779 #[test]
8781 fn a_range_store_writes_the_window_into_another_key() {
8782 let mut f = Fixture::new();
8783 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8784 assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1"]), ":3\r\n");
8785 assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"5", b"9"]), ":0\r\n");
8788 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
8789 assert_eq!(
8790 f.run(&[b"ZRANGESTORE", b"d", b"z", b"(1", b"+inf", b"BYSCORE"]),
8791 ":2\r\n"
8792 );
8793 assert_eq!(
8794 f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
8795 "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
8796 );
8797 assert_eq!(f.run(&[b"ZRANGESTORE", b"z", b"z", b"1", b"2"]), ":2\r\n");
8800 assert_eq!(
8801 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
8802 "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
8803 );
8804 assert_eq!(
8807 f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1", b"WITHSCORES"]),
8808 "-ERR syntax error\r\n"
8809 );
8810 }
8811
8812 #[test]
8815 fn the_three_removals_share_their_window_with_the_reads() {
8816 let mut f = Fixture::new();
8817 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8818 assert_eq!(f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"0"]), ":1\r\n");
8819 assert_eq!(
8820 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
8821 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
8822 );
8823 assert_eq!(
8824 f.run(&[b"ZREMRANGEBYSCORE", b"z", b"(2", b"+inf"]),
8825 ":1\r\n"
8826 );
8827 assert_eq!(f.run(&[b"ZRANGE", b"z", b"0", b"-1"]), "*1\r\n$1\r\nb\r\n");
8828 assert_eq!(f.run(&[b"ZREMRANGEBYLEX", b"z", b"-", b"+"]), ":1\r\n");
8830 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
8831 assert_eq!(
8832 f.run(&[b"ZREMRANGEBYRANK", b"nokey", b"0", b"-1"]),
8833 ":0\r\n"
8834 );
8835 assert_eq!(
8836 f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"x"]),
8837 "-ERR value is not an integer or out of range\r\n"
8838 );
8839 }
8840
8841 #[test]
8843 fn the_three_algebra_commands_combine_scores_and_order_the_answer_once() {
8844 let mut f = Fixture::new();
8845 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8846 f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
8847 assert_eq!(
8848 f.run(&[b"ZUNION", b"2", b"z", b"y"]),
8849 "*4\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\nd\r\n"
8850 );
8851 assert_eq!(
8854 f.run(&[b"ZUNION", b"2", b"z", b"y", b"WITHSCORES"]),
8855 "*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"
8856 );
8857 assert_eq!(
8858 f.run(&[
8859 b"ZUNION",
8860 b"2",
8861 b"z",
8862 b"y",
8863 b"WEIGHTS",
8864 b"2",
8865 b"3",
8866 b"WITHSCORES"
8867 ]),
8868 "*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"
8869 );
8870 assert_eq!(
8871 f.run(&[
8872 b"ZUNION",
8873 b"2",
8874 b"z",
8875 b"y",
8876 b"AGGREGATE",
8877 b"MIN",
8878 b"WITHSCORES"
8879 ]),
8880 "*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"
8881 );
8882 assert_eq!(
8883 f.run(&[
8884 b"ZUNION",
8885 b"2",
8886 b"z",
8887 b"y",
8888 b"AGGREGATE",
8889 b"MAX",
8890 b"WITHSCORES"
8891 ]),
8892 "*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"
8893 );
8894 assert_eq!(
8895 f.run(&[b"ZINTER", b"2", b"z", b"y", b"WITHSCORES"]),
8896 "*2\r\n$1\r\nb\r\n$2\r\n12\r\n"
8897 );
8898 assert_eq!(
8899 f.run(&[b"ZDIFF", b"2", b"z", b"y", b"WITHSCORES"]),
8900 "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nc\r\n$1\r\n3\r\n"
8901 );
8902 assert_eq!(f.run(&[b"ZUNION", b"1", b"nokey"]), "*0\r\n");
8903 f.run(&[b"SADD", b"p", b"a", b"d"]);
8906 assert_eq!(
8907 f.run(&[b"ZUNION", b"2", b"z", b"p", b"WITHSCORES"]),
8908 "*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"
8909 );
8910 for cmd in [
8913 &[
8914 b"ZDIFF".as_slice(),
8915 b"2",
8916 b"z",
8917 b"y",
8918 b"WEIGHTS",
8919 b"1",
8920 b"1",
8921 ][..],
8922 &[b"ZDIFF", b"2", b"z", b"y", b"AGGREGATE", b"MIN"],
8923 ] {
8924 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
8925 }
8926 }
8927
8928 #[test]
8930 fn the_algebra_counts_its_keys_and_says_so_when_the_count_is_wrong() {
8931 let mut f = Fixture::new();
8932 f.run(&[b"ZADD", b"z", b"1", b"a"]);
8933 f.run(&[b"ZADD", b"y", b"2", b"b"]);
8934 assert_eq!(
8936 f.run(&[b"ZUNION", b"0", b"z"]),
8937 "-ERR at least 1 input key is needed for 'zunion' command\r\n"
8938 );
8939 assert_eq!(
8940 f.run(&[b"ZUNION", b"-1", b"z"]),
8941 "-ERR at least 1 input key is needed for 'zunion' command\r\n"
8942 );
8943 assert_eq!(
8944 f.run(&[b"ZINTERCARD", b"0", b"z"]),
8945 "-ERR at least 1 input key is needed for 'zintercard' command\r\n"
8946 );
8947 assert_eq!(
8950 f.run(&[b"ZUNION", b"3", b"z", b"y"]),
8951 "-ERR syntax error\r\n"
8952 );
8953 assert_eq!(
8954 f.run(&[b"ZUNION", b"x", b"z"]),
8955 "-ERR value is not an integer or out of range\r\n"
8956 );
8957 assert_eq!(
8960 f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"1"]),
8961 "-ERR syntax error\r\n"
8962 );
8963 assert_eq!(
8964 f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"a", b"b"]),
8965 "-ERR weight value is not a float\r\n"
8966 );
8967 assert_eq!(
8968 f.run(&[b"ZUNION", b"2", b"z", b"y", b"AGGREGATE", b"NOPE"]),
8969 "-ERR syntax error\r\n"
8970 );
8971 }
8972
8973 #[test]
8975 fn the_algebra_stores_answer_a_count_and_delete_an_empty_destination() {
8976 let mut f = Fixture::new();
8977 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
8978 f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
8979 assert_eq!(f.run(&[b"ZUNIONSTORE", b"d", b"2", b"z", b"y"]), ":4\r\n");
8980 assert_eq!(
8981 f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
8982 "*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"
8983 );
8984 assert_eq!(f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"y"]), ":1\r\n");
8985 assert_eq!(f.run(&[b"ZDIFFSTORE", b"d", b"2", b"z", b"y"]), ":2\r\n");
8986 assert_eq!(
8989 f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"nokey"]),
8990 ":0\r\n"
8991 );
8992 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
8993 assert_eq!(f.run(&[b"ZUNIONSTORE", b"z", b"2", b"z", b"y"]), ":4\r\n");
8995 assert_eq!(f.run(&[b"ZCARD", b"z"]), ":4\r\n");
8996 for cmd in [
8997 &[
8998 b"ZUNIONSTORE".as_slice(),
8999 b"d",
9000 b"2",
9001 b"z",
9002 b"y",
9003 b"WITHSCORES",
9004 ][..],
9005 &[
9006 b"ZDIFFSTORE",
9007 b"d",
9008 b"2",
9009 b"z",
9010 b"y",
9011 b"WEIGHTS",
9012 b"1",
9013 b"1",
9014 ],
9015 ] {
9016 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
9017 }
9018 }
9019
9020 #[test]
9022 fn intercard_counts_and_stops_at_its_limit() {
9023 let mut f = Fixture::new();
9024 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
9025 f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"c", b"30", b"d"]);
9026 assert_eq!(f.run(&[b"ZINTERCARD", b"2", b"z", b"y"]), ":2\r\n");
9027 assert_eq!(
9029 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"0"]),
9030 ":2\r\n"
9031 );
9032 assert_eq!(
9033 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"1"]),
9034 ":1\r\n"
9035 );
9036 let bad = "-ERR LIMIT can't be negative\r\n";
9039 assert_eq!(
9040 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"-1"]),
9041 bad
9042 );
9043 assert_eq!(
9044 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"x"]),
9045 bad
9046 );
9047 for cmd in [
9048 &[b"ZINTERCARD".as_slice(), b"3", b"z", b"y"][..],
9049 &[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT"],
9050 &[b"ZINTERCARD", b"2", b"z", b"y", b"junk", b"1"],
9051 ] {
9052 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
9053 }
9054 }
9055
9056 #[test]
9058 fn a_draw_answers_one_member_or_an_array_of_them() {
9059 let mut f = Fixture::new();
9060 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
9061 assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "$-1\r\n");
9064 assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
9065 assert_eq!(f.run(&[b"ZRANDMEMBER", b"z", b"0"]), "*0\r\n");
9066 assert!(f.run(&[b"ZRANDMEMBER", b"z"]).starts_with("$1\r\n"));
9067 let all = f.run(&[b"ZRANDMEMBER", b"z", b"10"]);
9070 assert!(all.starts_with("*3\r\n"), "{all}");
9071 for m in ["a", "b", "c"] {
9072 assert!(all.contains(m), "{all}");
9073 }
9074 assert!(
9077 f.run(&[b"ZRANDMEMBER", b"z", b"-5"]).starts_with("*5\r\n"),
9078 "five draws with replacement"
9079 );
9080 assert!(
9081 f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"])
9082 .starts_with("*4\r\n"),
9083 "two pairs, flat on RESP2"
9084 );
9085 f.out = Out::new(Proto::Resp3);
9086 let got = f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"]);
9087 assert!(got.starts_with("*2\r\n*2\r\n"), "{got}");
9088 assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "_\r\n");
9089 f.out = Out::new(Proto::Resp2);
9090 assert_eq!(
9091 f.run(&[b"ZRANDMEMBER", b"z", b"2", b"junk"]),
9092 "-ERR syntax error\r\n"
9093 );
9094 assert_eq!(
9095 f.run(&[b"ZRANDMEMBER", b"z", b"x"]),
9096 "-ERR value is not an integer or out of range\r\n"
9097 );
9098 }
9099
9100 #[test]
9102 fn a_sorted_set_scan_answers_pairs_of_strings_on_both_protocols() {
9103 let mut f = Fixture::new();
9104 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
9105 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";
9106 assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
9107 assert_eq!(f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"10"]), all);
9108 assert_eq!(
9109 f.run(&[b"ZSCAN", b"z", b"0", b"MATCH", b"a*"]),
9110 "*2\r\n$1\r\n0\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
9111 );
9112 assert_eq!(
9113 f.run(&[b"ZSCAN", b"nokey", b"0"]),
9114 "*2\r\n$1\r\n0\r\n*0\r\n"
9115 );
9116 f.out = Out::new(Proto::Resp3);
9119 assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
9120 f.out = Out::new(Proto::Resp2);
9121 assert_eq!(
9122 f.run(&[b"ZSCAN", b"z", b"0", b"NOVALUES"]),
9123 "-ERR NOVALUES option can only be used in HSCAN\r\n"
9124 );
9125 assert_eq!(f.run(&[b"ZSCAN", b"z", b"-1"]), "-ERR invalid cursor\r\n");
9126 assert_eq!(
9127 f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"0"]),
9128 "-ERR syntax error\r\n"
9129 );
9130 }
9131
9132 #[test]
9134 fn a_sorted_set_pop_changes_shape_when_it_is_given_a_count() {
9135 let mut f = Fixture::new();
9136 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
9137 assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
9139 assert_eq!(f.run(&[b"ZPOPMAX", b"z"]), "*2\r\n$1\r\nc\r\n$1\r\n3\r\n");
9140 f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
9141 assert_eq!(
9143 f.run(&[b"ZPOPMIN", b"z", b"2"]),
9144 "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
9145 );
9146 assert_eq!(f.run(&[b"ZPOPMIN", b"nokey"]), "*0\r\n");
9149 assert_eq!(f.run(&[b"ZPOPMIN", b"nokey", b"2"]), "*0\r\n");
9150 assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"0"]), "*0\r\n");
9151 assert_eq!(
9153 f.run(&[b"ZPOPMIN", b"z", b"9"]),
9154 "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
9155 );
9156 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
9157
9158 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]);
9159 f.out = Out::new(Proto::Resp3);
9160 assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n,1\r\n");
9161 assert_eq!(
9162 f.run(&[b"ZPOPMIN", b"z", b"1"]),
9163 "*1\r\n*2\r\n$1\r\nb\r\n,2\r\n"
9164 );
9165 f.out = Out::new(Proto::Resp2);
9166 let bad = "-ERR value is out of range, must be positive\r\n";
9169 assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"x"]), bad);
9170 assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"-1"]), bad);
9171 assert_eq!(
9172 f.run(&[b"ZPOPMIN", b"z", b"1", b"2"]),
9173 "-ERR syntax error\r\n"
9174 );
9175 }
9176
9177 #[test]
9179 fn a_multi_key_pop_names_the_key_that_answered_and_nests_its_pairs() {
9180 let mut f = Fixture::new();
9181 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
9182 assert_eq!(
9183 f.run(&[b"ZMPOP", b"2", b"nokey", b"z", b"MIN"]),
9184 "*2\r\n$1\r\nz\r\n*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
9185 );
9186 assert_eq!(
9189 f.run(&[b"ZMPOP", b"1", b"z", b"MAX", b"COUNT", b"2"]),
9190 "*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"
9191 );
9192 assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "*-1\r\n");
9194 f.out = Out::new(Proto::Resp3);
9195 assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "_\r\n");
9196 f.out = Out::new(Proto::Resp2);
9197 let numkeys = "-ERR numkeys should be greater than 0\r\n";
9198 for bad in [
9199 &[b"ZMPOP".as_slice(), b"0", b"z", b"MIN"][..],
9200 &[b"ZMPOP", b"-1", b"z", b"MIN"],
9201 &[b"ZMPOP", b"x", b"z", b"MIN"],
9202 ] {
9203 assert_eq!(f.run(bad), numkeys, "{:?}", bad[1]);
9204 }
9205 let count = "-ERR count should be greater than 0\r\n";
9206 for bad in [
9207 &[b"ZMPOP".as_slice(), b"1", b"z", b"MIN", b"COUNT", b"0"][..],
9208 &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"-1"],
9209 &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"x"],
9210 ] {
9211 assert_eq!(f.run(bad), count, "{:?}", bad[5]);
9212 }
9213 let syntax = "-ERR syntax error\r\n";
9214 for bad in [
9215 &[b"ZMPOP".as_slice(), b"2", b"z", b"MIN"][..],
9218 &[b"ZMPOP", b"1", b"z", b"SIDEWAYS"],
9219 &[b"ZMPOP", b"1", b"z", b"MIN", b"junk"],
9220 &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"1", b"junk"],
9221 ] {
9222 assert_eq!(f.run(bad), syntax, "{bad:?}");
9223 }
9224 }
9225
9226 #[test]
9229 fn the_sorted_set_pops_that_wait_answer_like_the_ones_they_wrap() {
9230 let mut f = Fixture::new();
9231 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
9232 assert_eq!(
9233 f.flow(&[b"BZPOPMIN", b"nokey", b"z", b"0"]),
9234 (
9235 Flow::Continue,
9236 "*3\r\n$1\r\nz\r\n$1\r\na\r\n$1\r\n1\r\n".to_owned()
9237 )
9238 );
9239 assert_eq!(
9240 f.run(&[b"BZPOPMAX", b"z", b"0"]),
9241 "*3\r\n$1\r\nz\r\n$1\r\nc\r\n$1\r\n3\r\n"
9242 );
9243 f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
9244 assert_eq!(
9245 f.run(&[
9246 b"BZMPOP", b"0", b"2", b"nokey", b"z", b"MIN", b"COUNT", b"2"
9247 ]),
9248 "*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"
9249 );
9250 f.out = Out::new(Proto::Resp3);
9251 assert_eq!(
9252 f.run(&[b"BZPOPMIN", b"z", b"0"]),
9253 "*3\r\n$1\r\nz\r\n$1\r\nc\r\n,3\r\n"
9254 );
9255 f.out = Out::new(Proto::Resp2);
9256 assert_eq!(
9258 f.flow(&[b"BZPOPMIN", b"z", b"0"]),
9259 (Flow::Block, String::new())
9260 );
9261 assert_eq!(
9262 f.flow(&[b"BZMPOP", b"0", b"1", b"z", b"MIN"]),
9263 (Flow::Block, String::new())
9264 );
9265 assert_eq!(
9268 f.run(&[b"BZMPOP", b"abc", b"0", b"z", b"MIN"]),
9269 "-ERR timeout is not a float or out of range\r\n"
9270 );
9271 assert_eq!(
9272 f.run(&[b"BZMPOP", b"0", b"0", b"z", b"MIN"]),
9273 "-ERR numkeys should be greater than 0\r\n"
9274 );
9275 assert_eq!(
9276 f.run(&[b"BZPOPMIN", b"z", b"-1"]),
9277 "-ERR timeout is negative\r\n"
9278 );
9279 }
9280
9281 #[test]
9285 fn a_parked_sorted_set_client_waits_for_a_member_and_not_for_a_key() {
9286 let mut f = Fixture::new();
9287 assert_eq!(f.flow(&[b"BZPOPMIN", b"z", b"0"]).0, Flow::Block);
9288 assert_eq!(f.server.parked(), 1);
9289 f.run(&[b"SET", b"z", b"v"]);
9292 let mut out = Out::new(Proto::Resp2);
9293 assert!(!f.server.serve_waiter(7, 0, &mut out));
9294 assert!(out.as_slice().is_empty());
9295 f.run(&[b"DEL", b"z"]);
9296 f.run(&[b"ZADD", b"z", b"5", b"m"]);
9297 assert!(f.server.serve_waiter(7, 0, &mut out));
9298 assert_eq!(
9299 core::str::from_utf8(out.as_slice()).expect("ascii"),
9300 "*3\r\n$1\r\nz\r\n$1\r\nm\r\n$1\r\n5\r\n"
9301 );
9302 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
9305 }
9306
9307 #[test]
9308 fn every_sorted_set_command_says_wrongtype_and_writes_nothing() {
9309 let mut f = Fixture::new();
9310 f.run(&[b"SET", b"s", b"v"]);
9311 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9312 for cmd in [
9313 &[b"ZADD".as_slice(), b"s", b"1", b"a"][..],
9314 &[b"ZINCRBY", b"s", b"1", b"a"],
9315 &[b"ZCARD", b"s"],
9316 &[b"ZSCORE", b"s", b"a"],
9317 &[b"ZMSCORE", b"s", b"a"],
9318 &[b"ZREM", b"s", b"a"],
9319 &[b"ZRANK", b"s", b"a"],
9320 &[b"ZREVRANK", b"s", b"a"],
9321 &[b"ZCOUNT", b"s", b"1", b"2"],
9322 &[b"ZLEXCOUNT", b"s", b"-", b"+"],
9323 &[b"ZRANGE", b"s", b"0", b"-1"],
9324 &[b"ZREVRANGE", b"s", b"0", b"-1"],
9325 &[b"ZRANGEBYSCORE", b"s", b"1", b"2"],
9326 &[b"ZREVRANGEBYSCORE", b"s", b"2", b"1"],
9327 &[b"ZRANGEBYLEX", b"s", b"-", b"+"],
9328 &[b"ZREVRANGEBYLEX", b"s", b"+", b"-"],
9329 &[b"ZRANGESTORE", b"d", b"s", b"0", b"-1"],
9330 &[b"ZREMRANGEBYRANK", b"s", b"0", b"-1"],
9331 &[b"ZREMRANGEBYSCORE", b"s", b"1", b"2"],
9332 &[b"ZREMRANGEBYLEX", b"s", b"-", b"+"],
9333 &[b"ZUNION", b"1", b"s"],
9334 &[b"ZINTER", b"1", b"s"],
9335 &[b"ZDIFF", b"1", b"s"],
9336 &[b"ZUNIONSTORE", b"d", b"1", b"s"],
9337 &[b"ZINTERSTORE", b"d", b"1", b"s"],
9338 &[b"ZDIFFSTORE", b"d", b"1", b"s"],
9339 &[b"ZINTERCARD", b"1", b"s"],
9340 &[b"ZRANDMEMBER", b"s"],
9341 &[b"ZSCAN", b"s", b"0"],
9342 &[b"ZPOPMIN", b"s"],
9343 &[b"ZPOPMAX", b"s", b"2"],
9344 &[b"ZMPOP", b"1", b"s", b"MIN"],
9345 &[b"BZPOPMIN", b"s", b"0"],
9346 &[b"BZPOPMAX", b"s", b"0"],
9347 &[b"BZMPOP", b"0", b"1", b"s", b"MIN"],
9348 ] {
9349 assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
9350 }
9351 assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nv\r\n");
9352 }
9353
9354 #[cfg_attr(miri, ignore = "the volume is the claim")]
9359 #[test]
9360 fn churning_sorted_sets_does_not_grow_the_server() {
9361 let mut f = Fixture::new();
9362 let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
9363 let scores: Vec<Vec<u8>> = (0..200).map(|i| format!("{i}").into_bytes()).collect();
9364 let mut args: Vec<&[u8]> = vec![b"ZADD", b"z"];
9365 for i in 0..200 {
9366 args.push(&scores[i]);
9367 args.push(&members[i]);
9368 }
9369
9370 f.run(&args);
9371 f.run(&[b"DEL", b"z"]);
9372 f.server.compact_step();
9373 let after_first = f.server.memory_bytes();
9374
9375 for _ in 0..200 {
9376 f.run(&args);
9377 f.run(&[b"DEL", b"z"]);
9378 f.server.compact_step();
9379 }
9380 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
9381 assert!(
9382 f.server.memory_bytes() <= after_first * 2,
9383 "held {} after two hundred passes against {after_first} after one",
9384 f.server.memory_bytes()
9385 );
9386 }
9387
9388 fn sicily(f: &mut Fixture) {
9396 f.run(&[
9397 b"GEOADD",
9398 b"Sicily",
9399 b"13.361389",
9400 b"38.115556",
9401 b"Palermo",
9402 b"15.087269",
9403 b"37.502669",
9404 b"Catania",
9405 ]);
9406 f.run(&[
9407 b"GEOADD",
9408 b"Sicily",
9409 b"13.583333",
9410 b"37.316667",
9411 b"Agrigento",
9412 ]);
9413 }
9414
9415 #[test]
9416 fn places_go_in_as_scores_and_come_back_as_positions() {
9417 let mut f = Fixture::new();
9418 assert_eq!(
9419 f.run(&[
9420 b"GEOADD",
9421 b"Sicily",
9422 b"13.361389",
9423 b"38.115556",
9424 b"Palermo",
9425 b"15.087269",
9426 b"37.502669",
9427 b"Catania"
9428 ]),
9429 ":2\r\n"
9430 );
9431 assert_eq!(f.run(&[b"TYPE", b"Sicily"]), "+zset\r\n");
9435 assert_eq!(
9436 f.run(&[b"ZSCORE", b"Sicily", b"Palermo"]),
9437 "$16\r\n3479099956230698\r\n"
9438 );
9439 assert_eq!(
9440 f.run(&[b"GEOPOS", b"Sicily", b"Palermo", b"NonExisting"]),
9441 "*2\r\n*2\r\n$18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n*-1\r\n"
9442 );
9443 assert_eq!(
9444 f.run(&[
9445 b"GEOHASH",
9446 b"Sicily",
9447 b"Palermo",
9448 b"Catania",
9449 b"NonExisting"
9450 ]),
9451 "*3\r\n$11\r\nsqc8b49rny0\r\n$11\r\nsqdtr74hyu0\r\n$-1\r\n"
9452 );
9453 assert_eq!(f.run(&[b"GEOPOS", b"nokey", b"a"]), "*1\r\n*-1\r\n");
9457 assert_eq!(f.run(&[b"GEOHASH", b"nokey", b"a"]), "*1\r\n$-1\r\n");
9458 }
9459
9460 #[test]
9461 fn a_distance_comes_back_with_four_places_in_whatever_unit_was_asked_for() {
9462 let mut f = Fixture::new();
9463 sicily(&mut f);
9464 assert_eq!(
9465 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania"]),
9466 "$11\r\n166274.1516\r\n"
9467 );
9468 assert_eq!(
9469 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"km"]),
9470 "$8\r\n166.2742\r\n"
9471 );
9472 assert_eq!(
9473 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"mi"]),
9474 "$8\r\n103.3182\r\n"
9475 );
9476 assert_eq!(
9480 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Foo"]),
9481 "$-1\r\n"
9482 );
9483 assert_eq!(f.run(&[b"GEODIST", b"nokey", b"a", b"b"]), "$-1\r\n");
9484 assert_eq!(
9485 f.run(&[b"GEODIST", b"nokey", b"a", b"b", b"parsecs"]),
9486 "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n"
9487 );
9488 assert_eq!(
9489 f.run(&[b"GEODIST", b"Sicily", b"a", b"b", b"km", b"extra"]),
9490 "-ERR syntax error\r\n"
9491 );
9492 }
9493
9494 #[test]
9495 fn a_search_finds_what_is_inside_it_nearest_first() {
9496 let mut f = Fixture::new();
9497 sicily(&mut f);
9498 let all = "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n";
9499 assert_eq!(
9500 f.run(&[
9501 b"GEOSEARCH",
9502 b"Sicily",
9503 b"FROMLONLAT",
9504 b"15",
9505 b"37",
9506 b"BYRADIUS",
9507 b"200",
9508 b"km",
9509 b"ASC"
9510 ]),
9511 all
9512 );
9513 assert_eq!(
9516 f.run(&[b"GEORADIUS", b"Sicily", b"15", b"37", b"200", b"km", b"ASC"]),
9517 all
9518 );
9519 assert_eq!(
9520 f.run(&[
9521 b"GEORADIUS_RO",
9522 b"Sicily",
9523 b"15",
9524 b"37",
9525 b"200",
9526 b"km",
9527 b"ASC"
9528 ]),
9529 all
9530 );
9531 assert_eq!(
9534 f.run(&[
9535 b"GEORADIUS",
9536 b"Sicily",
9537 b"15",
9538 b"37",
9539 b"200",
9540 b"km",
9541 b"DESC",
9542 b"COUNT",
9543 b"1"
9544 ]),
9545 "*1\r\n$7\r\nPalermo\r\n"
9546 );
9547 assert_eq!(
9548 f.run(&[
9549 b"GEORADIUS",
9550 b"Sicily",
9551 b"15",
9552 b"37",
9553 b"200",
9554 b"km",
9555 b"COUNT",
9556 b"1"
9557 ]),
9558 "*1\r\n$7\r\nCatania\r\n"
9559 );
9560 let empty = "*0\r\n";
9563 assert_eq!(
9564 f.run(&[
9565 b"GEOSEARCH",
9566 b"Sicily",
9567 b"FROMLONLAT",
9568 b"15",
9569 b"37",
9570 b"BYRADIUS",
9571 b"1",
9572 b"km"
9573 ]),
9574 empty
9575 );
9576 assert_eq!(
9577 f.run(&[
9578 b"GEOSEARCH",
9579 b"nokey",
9580 b"FROMLONLAT",
9581 b"15",
9582 b"37",
9583 b"BYRADIUS",
9584 b"1",
9585 b"km"
9586 ]),
9587 empty
9588 );
9589 assert_eq!(
9590 f.run(&[b"GEORADIUSBYMEMBER", b"nokey", b"m", b"1", b"km"]),
9591 empty
9592 );
9593 }
9594
9595 #[test]
9596 fn a_search_centred_on_a_member_starts_from_where_that_member_is() {
9597 let mut f = Fixture::new();
9598 sicily(&mut f);
9599 assert_eq!(
9600 f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Agrigento", b"100", b"km"]),
9601 "*2\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
9602 );
9603 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";
9606 assert_eq!(
9607 f.run(&[
9608 b"GEORADIUSBYMEMBER_RO",
9609 b"Sicily",
9610 b"Agrigento",
9611 b"100",
9612 b"km",
9613 b"WITHDIST"
9614 ]),
9615 with_dist
9616 );
9617 assert_eq!(
9618 f.run(&[
9619 b"GEOSEARCH",
9620 b"Sicily",
9621 b"FROMMEMBER",
9622 b"Agrigento",
9623 b"BYRADIUS",
9624 b"100",
9625 b"km",
9626 b"ASC",
9627 b"WITHDIST"
9628 ]),
9629 with_dist
9630 );
9631 assert_eq!(
9632 f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Nowhere", b"100", b"km"]),
9633 "-ERR could not decode requested zset member\r\n"
9634 );
9635 }
9636
9637 #[test]
9638 fn a_box_search_reports_the_distance_the_hash_and_the_coordinates() {
9639 let mut f = Fixture::new();
9640 sicily(&mut f);
9641 assert_eq!(
9645 f.run(&[
9646 b"GEOSEARCH",
9647 b"Sicily",
9648 b"FROMLONLAT",
9649 b"15",
9650 b"37",
9651 b"BYBOX",
9652 b"400",
9653 b"400",
9654 b"km",
9655 b"ASC",
9656 b"WITHCOORD",
9657 b"WITHDIST",
9658 b"WITHHASH"
9659 ]),
9660 "*3\r\n*4\r\n$7\r\nCatania\r\n$7\r\n56.4413\r\n:3479447370796909\r\n*2\r\n\
9661 $18\r\n15.087267458438873\r\n$17\r\n37.50266842333162\r\n\
9662 *4\r\n$9\r\nAgrigento\r\n$8\r\n130.4235\r\n:3479030013248308\r\n*2\r\n\
9663 $18\r\n13.583331406116486\r\n$18\r\n37.316668049938166\r\n\
9664 *4\r\n$7\r\nPalermo\r\n$8\r\n190.4424\r\n:3479099956230698\r\n*2\r\n\
9665 $18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n"
9666 );
9667 }
9668
9669 #[test]
9670 fn a_store_writes_the_hashes_and_a_storedist_writes_the_distances() {
9671 let mut f = Fixture::new();
9672 sicily(&mut f);
9673 let hashes = "*6\r\n$9\r\nAgrigento\r\n$16\r\n3479030013248308\r\n\
9674 $7\r\nPalermo\r\n$16\r\n3479099956230698\r\n\
9675 $7\r\nCatania\r\n$16\r\n3479447370796909\r\n";
9676 assert_eq!(
9677 f.run(&[
9678 b"GEOSEARCHSTORE",
9679 b"dst",
9680 b"Sicily",
9681 b"FROMLONLAT",
9682 b"15",
9683 b"37",
9684 b"BYRADIUS",
9685 b"200",
9686 b"km",
9687 b"ASC"
9688 ]),
9689 ":3\r\n"
9690 );
9691 assert_eq!(
9692 f.run(&[b"ZRANGE", b"dst", b"0", b"-1", b"WITHSCORES"]),
9693 hashes
9694 );
9695 assert_eq!(
9698 f.run(&[
9699 b"GEORADIUS",
9700 b"Sicily",
9701 b"15",
9702 b"37",
9703 b"200",
9704 b"km",
9705 b"STORE",
9706 b"dst3"
9707 ]),
9708 ":3\r\n"
9709 );
9710 assert_eq!(
9711 f.run(&[b"ZRANGE", b"dst3", b"0", b"-1", b"WITHSCORES"]),
9712 hashes
9713 );
9714 assert_eq!(
9723 f.run(&[
9724 b"GEOSEARCHSTORE",
9725 b"dst2",
9726 b"Sicily",
9727 b"FROMLONLAT",
9728 b"15",
9729 b"37",
9730 b"BYRADIUS",
9731 b"200",
9732 b"km",
9733 b"ASC",
9734 b"STOREDIST"
9735 ]),
9736 ":3\r\n"
9737 );
9738 for (member, want) in [
9739 ("Catania", 56.441_257_870_158_19),
9740 ("Agrigento", 130.423_487_067_147_14),
9741 ("Palermo", 190.442_429_847_757_92),
9742 ] {
9743 let reply = f.run(&[b"ZSCORE", b"dst2", member.as_bytes()]);
9744 let got: f64 = reply
9745 .trim_start_matches(|c: char| c != '\n')
9746 .trim()
9747 .parse()
9748 .unwrap_or_else(|_| panic!("{member} scored {reply:?}"));
9749 assert!(
9750 (got - want).abs() < 1e-9,
9751 "{member} scored {got} not {want}"
9752 );
9753 }
9754 assert_eq!(
9757 f.run(&[b"ZRANGE", b"dst2", b"0", b"-1"]),
9758 "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
9759 );
9760 assert_eq!(
9764 f.run(&[
9765 b"GEOSEARCHSTORE",
9766 b"dst",
9767 b"nokey",
9768 b"FROMLONLAT",
9769 b"15",
9770 b"37",
9771 b"BYRADIUS",
9772 b"200",
9773 b"km"
9774 ]),
9775 ":0\r\n"
9776 );
9777 assert_eq!(f.run(&[b"EXISTS", b"dst"]), ":0\r\n");
9778 }
9779
9780 #[test]
9781 fn the_gates_on_geoadd_are_the_ones_zadd_has() {
9782 let mut f = Fixture::new();
9783 sicily(&mut f);
9784 assert_eq!(
9787 f.run(&[
9788 b"GEOADD",
9789 b"Sicily",
9790 b"XX",
9791 b"CH",
9792 b"13.361389",
9793 b"38.115556",
9794 b"Palermo"
9795 ]),
9796 ":0\r\n"
9797 );
9798 assert_eq!(
9799 f.run(&[
9800 b"GEOADD",
9801 b"Sicily",
9802 b"NX",
9803 b"13.361389",
9804 b"38.9",
9805 b"Palermo"
9806 ]),
9807 ":0\r\n"
9808 );
9809 assert_eq!(
9810 f.run(&[
9811 b"GEOADD",
9812 b"Sicily",
9813 b"CH",
9814 b"13.361389",
9815 b"38.9",
9816 b"Palermo"
9817 ]),
9818 ":1\r\n"
9819 );
9820 assert_eq!(
9823 f.run(&[
9824 b"GEOADD",
9825 b"new",
9826 b"13.361389",
9827 b"38.115556",
9828 b"here",
9829 b"181",
9830 b"38",
9831 b"there"
9832 ]),
9833 "-ERR invalid longitude,latitude pair 181.000000,38.000000\r\n"
9834 );
9835 assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
9836 assert_eq!(
9837 f.run(&[b"GEOADD", b"new", b"x", b"38", b"here"]),
9838 "-ERR value is not a valid float\r\n"
9839 );
9840 assert_eq!(
9843 f.run(&[b"GEOADD", b"new", b"13", b"38", b"here", b"and"]),
9844 "-ERR syntax error\r\n"
9845 );
9846 assert_eq!(
9847 f.run(&[b"GEOADD", b"new", b"NX", b"XX", b"CH"]),
9848 "-ERR syntax error\r\n"
9849 );
9850 assert_eq!(
9851 f.run(&[b"GEOADD", b"new", b"CH", b"CH", b"CH", b"CH"]),
9852 "-ERR syntax error\r\n"
9853 );
9854 assert_eq!(
9855 f.run(&[b"GEOADD", b"new", b"NX", b"CH"]),
9856 "-ERR wrong number of arguments for 'geoadd' command\r\n"
9857 );
9858 }
9859
9860 #[test]
9863 fn every_way_a_search_can_be_written_wrong_has_its_own_sentence() {
9864 let mut f = Fixture::new();
9865 sicily(&mut f);
9866 let cases: &[(&[&[u8]], &str)] = &[
9867 (
9868 &[b"GEORADIUS", b"Sicily", b"15", b"37", b"x", b"km"],
9869 "-ERR need numeric radius\r\n",
9870 ),
9871 (
9872 &[b"GEORADIUS", b"Sicily", b"15", b"37", b"-1", b"km"],
9873 "-ERR radius cannot be negative\r\n",
9874 ),
9875 (
9876 &[b"GEORADIUS", b"Sicily", b"15", b"37", b"1", b"parsecs"],
9877 "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n",
9878 ),
9879 (
9880 &[b"GEORADIUS", b"Sicily", b"181", b"37", b"1", b"km"],
9881 "-ERR invalid longitude,latitude pair 181.000000,37.000000\r\n",
9882 ),
9883 (
9884 &[
9885 b"GEOSEARCH",
9886 b"Sicily",
9887 b"FROMLONLAT",
9888 b"15",
9889 b"37",
9890 b"BYBOX",
9891 b"x",
9892 b"1",
9893 b"km",
9894 ],
9895 "-ERR need numeric width\r\n",
9896 ),
9897 (
9898 &[
9899 b"GEOSEARCH",
9900 b"Sicily",
9901 b"FROMLONLAT",
9902 b"15",
9903 b"37",
9904 b"BYBOX",
9905 b"1",
9906 b"y",
9907 b"km",
9908 ],
9909 "-ERR need numeric height\r\n",
9910 ),
9911 (
9912 &[
9913 b"GEOSEARCH",
9914 b"Sicily",
9915 b"FROMLONLAT",
9916 b"15",
9917 b"37",
9918 b"BYBOX",
9919 b"-1",
9920 b"1",
9921 b"km",
9922 ],
9923 "-ERR height or width cannot be negative\r\n",
9924 ),
9925 (
9926 &[
9927 b"GEOSEARCH",
9928 b"Sicily",
9929 b"FROMLONLAT",
9930 b"15",
9931 b"37",
9932 b"BYRADIUS",
9933 b"1",
9934 b"km",
9935 b"ANY",
9936 ],
9937 "-ERR the ANY argument requires COUNT argument\r\n",
9938 ),
9939 (
9940 &[
9941 b"GEOSEARCH",
9942 b"Sicily",
9943 b"FROMLONLAT",
9944 b"15",
9945 b"37",
9946 b"BYRADIUS",
9947 b"1",
9948 b"km",
9949 b"COUNT",
9950 b"0",
9951 ],
9952 "-ERR COUNT must be > 0\r\n",
9953 ),
9954 (
9955 &[
9956 b"GEOSEARCH",
9957 b"Sicily",
9958 b"BYRADIUS",
9959 b"1",
9960 b"km",
9961 b"BYBOX",
9962 b"1",
9963 b"1",
9964 b"km",
9965 ],
9966 "-ERR syntax error\r\n",
9967 ),
9968 (
9969 &[
9970 b"GEOSEARCH",
9971 b"Sicily",
9972 b"FROMMEMBER",
9973 b"Palermo",
9974 b"FROMLONLAT",
9975 b"1",
9976 b"2",
9977 b"BYRADIUS",
9978 b"1",
9979 b"km",
9980 ],
9981 "-ERR syntax error\r\n",
9982 ),
9983 (
9986 &[
9987 b"geosearch",
9988 b"Sicily",
9989 b"BYRADIUS",
9990 b"1",
9991 b"km",
9992 b"ASC",
9993 b"WITHDIST",
9994 ],
9995 "-ERR exactly one of FROMMEMBER or FROMLONLAT can be specified for geosearch\r\n",
9996 ),
9997 (
9998 &[
9999 b"GEOSEARCH",
10000 b"Sicily",
10001 b"FROMLONLAT",
10002 b"15",
10003 b"37",
10004 b"ASC",
10005 b"WITHDIST",
10006 ],
10007 "-ERR exactly one of BYRADIUS and BYBOX can be specified for GEOSEARCH\r\n",
10008 ),
10009 (
10012 &[
10013 b"GEOSEARCHSTORE",
10014 b"d",
10015 b"Sicily",
10016 b"FROMLONLAT",
10017 b"15",
10018 b"37",
10019 b"BYRADIUS",
10020 b"1",
10021 b"km",
10022 b"WITHCOORD",
10023 ],
10024 "-ERR GEOSEARCHSTORE is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
10025 ),
10026 (
10027 &[
10028 b"GEORADIUS",
10029 b"Sicily",
10030 b"15",
10031 b"37",
10032 b"1",
10033 b"km",
10034 b"WITHDIST",
10035 b"STORE",
10036 b"d",
10037 ],
10038 "-ERR STORE option in GEORADIUS is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
10039 ),
10040 (
10043 &[
10044 b"GEORADIUS_RO",
10045 b"Sicily",
10046 b"15",
10047 b"37",
10048 b"1",
10049 b"km",
10050 b"STORE",
10051 b"d",
10052 ],
10053 "-ERR syntax error\r\n",
10054 ),
10055 (
10056 &[
10057 b"GEOSEARCH",
10058 b"Sicily",
10059 b"FROMLONLAT",
10060 b"15",
10061 b"37",
10062 b"BYRADIUS",
10063 b"1",
10064 b"km",
10065 b"STOREDIST",
10066 ],
10067 "-ERR syntax error\r\n",
10068 ),
10069 ];
10070 for (parts, want) in cases {
10071 assert_eq!(&f.run(parts), want, "{:?}", parts[0]);
10072 }
10073 }
10074
10075 #[test]
10078 fn every_geo_command_says_wrongtype() {
10079 let mut f = Fixture::new();
10080 f.run(&[b"SET", b"s", b"v"]);
10081 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10082 let cases: &[&[&[u8]]] = &[
10083 &[b"GEOADD", b"s", b"13", b"38", b"m"],
10084 &[b"GEOPOS", b"s", b"m"],
10085 &[b"GEOHASH", b"s", b"m"],
10086 &[b"GEODIST", b"s", b"a", b"b"],
10087 &[
10088 b"GEOSEARCH",
10089 b"s",
10090 b"FROMLONLAT",
10091 b"15",
10092 b"37",
10093 b"BYRADIUS",
10094 b"1",
10095 b"km",
10096 ],
10097 &[
10098 b"GEOSEARCHSTORE",
10099 b"d",
10100 b"s",
10101 b"FROMLONLAT",
10102 b"15",
10103 b"37",
10104 b"BYRADIUS",
10105 b"1",
10106 b"km",
10107 ],
10108 &[b"GEORADIUS", b"s", b"15", b"37", b"1", b"km"],
10109 &[b"GEORADIUS_RO", b"s", b"15", b"37", b"1", b"km"],
10110 &[b"GEORADIUSBYMEMBER", b"s", b"m", b"1", b"km"],
10111 &[b"GEORADIUSBYMEMBER_RO", b"s", b"m", b"1", b"km"],
10112 ];
10113 for case in cases {
10114 assert_eq!(f.run(case), wrong, "{:?}", case[0]);
10115 }
10116 assert_eq!(
10119 f.run(&[b"GEORADIUS", b"s", b"15", b"37", b"x", b"km"]),
10120 wrong
10121 );
10122 }
10123
10124 #[test]
10127 fn an_array_writes_at_any_index_and_reads_back_what_it_sent() {
10128 let mut f = Fixture::new();
10129 assert_eq!(
10132 f.run(&[b"ARSET", b"a", b"1000", b"x", b"y", b"z"]),
10133 ":3\r\n"
10134 );
10135 assert_eq!(f.run(&[b"ARSET", b"a", b"1000", b"X", b"Y"]), ":0\r\n");
10136 assert_eq!(f.run(&[b"ARGET", b"a", b"1000"]), "$1\r\nX\r\n");
10137 assert_eq!(f.run(&[b"ARGET", b"a", b"1002"]), "$1\r\nz\r\n");
10138 assert_eq!(f.run(&[b"ARGET", b"a", b"999"]), "$-1\r\n");
10140 assert_eq!(f.run(&[b"ARGET", b"nope", b"0"]), "$-1\r\n");
10141 assert_eq!(
10142 f.run(&[b"ARMGET", b"a", b"1002", b"999", b"1000"]),
10143 "*3\r\n$1\r\nz\r\n$-1\r\n$1\r\nX\r\n"
10144 );
10145 assert_eq!(f.run(&[b"ARMSET", b"a", b"5", b"p", b"5", b"q"]), ":1\r\n");
10147 assert_eq!(f.run(&[b"ARGET", b"a", b"5"]), "$1\r\nq\r\n");
10148 }
10149
10150 #[test]
10153 fn the_length_is_the_high_water_mark_and_the_count_is_the_population() {
10154 let mut f = Fixture::new();
10155 assert_eq!(f.run(&[b"ARLEN", b"nope"]), ":0\r\n");
10156 assert_eq!(f.run(&[b"ARCOUNT", b"nope"]), ":0\r\n");
10157 f.run(&[b"ARMSET", b"a", b"0", b"x", b"9", b"y"]);
10158 assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
10159 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
10160 assert_eq!(f.run(&[b"ARDEL", b"a", b"0"]), ":1\r\n");
10162 assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
10163 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":1\r\n");
10164
10165 f.run(&[b"ARSET", b"top", b"18446744073709551614", b"z"]);
10169 assert_eq!(f.run(&[b"ARLEN", b"top"]), ":18446744073709551615\r\n");
10170 assert_eq!(f.run(&[b"ARCOUNT", b"top"]), ":1\r\n");
10171 assert_eq!(
10174 f.run(&[b"ARSET", b"over", b"18446744073709551614", b"a", b"b"]),
10175 "-ERR array index overflow\r\n"
10176 );
10177 assert_eq!(f.run(&[b"EXISTS", b"over"]), ":0\r\n");
10178 }
10179
10180 #[test]
10183 fn a_range_read_answers_for_the_holes_too_and_is_capped_at_a_million() {
10184 let mut f = Fixture::new();
10185 f.run(&[b"ARSET", b"a", b"1", b"x"]);
10186 assert_eq!(
10187 f.run(&[b"ARGETRANGE", b"a", b"0", b"3"]),
10188 "*4\r\n$-1\r\n$1\r\nx\r\n$-1\r\n$-1\r\n"
10189 );
10190 assert_eq!(
10193 f.run(&[b"ARGETRANGE", b"a", b"3", b"0"]),
10194 "*4\r\n$-1\r\n$-1\r\n$1\r\nx\r\n$-1\r\n"
10195 );
10196 assert_eq!(
10198 f.run(&[b"ARGETRANGE", b"nope", b"0", b"1"]),
10199 "*2\r\n$-1\r\n$-1\r\n"
10200 );
10201 assert_eq!(
10205 f.run(&[b"ARGETRANGE", b"nope", b"0", b"18446744073709551614"]),
10206 "-ERR range exceeds maximum of 1000000 items\r\n"
10207 );
10208 }
10209
10210 #[test]
10213 fn a_bad_index_late_in_the_line_writes_none_of_the_earlier_ones() {
10214 let mut f = Fixture::new();
10215 assert_eq!(
10216 f.run(&[b"ARMSET", b"a", b"0", b"x", b"-1", b"y"]),
10217 "-ERR invalid array index\r\n"
10218 );
10219 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
10220 f.run(&[b"ARSET", b"a", b"0", b"x", b"y", b"z"]);
10221 assert_eq!(
10222 f.run(&[b"ARDEL", b"a", b"0", b"01"]),
10223 "-ERR invalid array index\r\n"
10224 );
10225 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":3\r\n");
10226 assert_eq!(
10229 f.run(&[b"ARGET", b"a", b"-1"]),
10230 "-ERR invalid array index\r\n"
10231 );
10232 assert_eq!(
10235 f.run(&[b"ARMSET", b"a", b"0", b"x", b"1"]),
10236 "-ERR wrong number of arguments for 'armset' command\r\n"
10237 );
10238 assert_eq!(
10239 f.run(&[b"ARDELRANGE", b"a", b"0", b"1", b"2"]),
10240 "-ERR wrong number of arguments for 'ardelrange' command\r\n"
10241 );
10242 }
10243
10244 #[test]
10245 fn a_range_delete_costs_the_elements_and_takes_the_key_when_it_empties() {
10246 let mut f = Fixture::new();
10247 f.run(&[b"ARSET", b"a", b"0", b"0", b"1", b"2", b"3", b"4"]);
10248 assert_eq!(f.run(&[b"ARDELRANGE", b"a", b"3", b"1"]), ":3\r\n");
10249 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
10250 assert_eq!(
10253 f.run(&[
10254 b"ARDELRANGE",
10255 b"a",
10256 b"100",
10257 b"200",
10258 b"0",
10259 b"18446744073709551614"
10260 ]),
10261 ":2\r\n"
10262 );
10263 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
10264 assert_eq!(f.run(&[b"ARDELRANGE", b"nope", b"0", b"1"]), ":0\r\n");
10265 assert_eq!(f.run(&[b"ARDEL", b"nope", b"0"]), ":0\r\n");
10266 }
10267
10268 #[test]
10271 fn a_value_comes_back_byte_for_byte_however_it_was_packed() {
10272 let mut f = Fixture::new();
10273 let long = vec![b'v'; 200];
10274 f.run(&[
10275 b"ARMSET", b"a", b"0", b"42", b"1", b"007", b"2", b"3.5", b"3", b"3.14", b"4",
10276 b"short", b"5", &long, b"6", b"-0",
10277 ]);
10278 assert_eq!(
10282 f.run(&[b"ARGETRANGE", b"a", b"0", b"6"]),
10283 format!(
10284 "*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",
10285 String::from_utf8_lossy(&long)
10286 )
10287 );
10288 }
10289
10290 #[test]
10291 fn an_array_is_a_type_and_an_encoding_a_client_can_see() {
10292 let mut f = Fixture::new();
10293 f.run(&[b"ARSET", b"a", b"0", b"x"]);
10294 assert_eq!(f.run(&[b"TYPE", b"a"]), "+array\r\n");
10295 assert_eq!(
10296 f.run(&[b"OBJECT", b"ENCODING", b"a"]),
10297 "$12\r\nsliced-array\r\n"
10298 );
10299 assert_eq!(f.run(&[b"EXPIRE", b"a", b"100"]), ":1\r\n");
10301 assert_eq!(f.run(&[b"PERSIST", b"a"]), ":1\r\n");
10302 assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":1\r\n");
10303 assert_eq!(f.run(&[b"ARGET", b"b", b"0"]), "$1\r\nx\r\n");
10304 assert_eq!(f.run(&[b"RENAME", b"a", b"c"]), "+OK\r\n");
10305 assert_eq!(f.run(&[b"ARCOUNT", b"c"]), ":1\r\n");
10306 }
10307
10308 #[test]
10309 fn every_array_command_refuses_a_key_holding_something_else() {
10310 let mut f = Fixture::new();
10311 f.run(&[b"SET", b"s", b"v"]);
10312 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10313 for cmd in [
10314 &[b"ARSET".as_ref(), b"s", b"0", b"x"][..],
10315 &[b"ARMSET".as_ref(), b"s", b"0", b"x"][..],
10316 &[b"ARGET".as_ref(), b"s", b"0"][..],
10317 &[b"ARMGET".as_ref(), b"s", b"0"][..],
10318 &[b"ARGETRANGE".as_ref(), b"s", b"0", b"1"][..],
10319 &[b"ARLEN".as_ref(), b"s"][..],
10320 &[b"ARCOUNT".as_ref(), b"s"][..],
10321 &[b"ARDEL".as_ref(), b"s", b"0"][..],
10322 &[b"ARDELRANGE".as_ref(), b"s", b"0", b"1"][..],
10323 &[b"ARINSERT".as_ref(), b"s", b"x"][..],
10324 &[b"ARRING".as_ref(), b"s", b"4", b"x"][..],
10325 &[b"ARNEXT".as_ref(), b"s"][..],
10326 &[b"ARSEEK".as_ref(), b"s", b"1"][..],
10327 &[b"ARLASTITEMS".as_ref(), b"s", b"1"][..],
10328 &[b"ARSCAN".as_ref(), b"s", b"0", b"1"][..],
10329 &[b"ARGREP".as_ref(), b"s", b"0", b"1", b"EXACT", b"v"][..],
10330 &[b"AROP".as_ref(), b"s", b"0", b"1", b"SUM"][..],
10331 &[b"ARINFO".as_ref(), b"s"][..],
10332 ] {
10333 assert_eq!(f.run(cmd), wrong, "{}", String::from_utf8_lossy(cmd[0]));
10334 }
10335 }
10336
10337 #[test]
10341 fn a_bad_index_reports_the_type_only_where_redis_reports_it() {
10342 let mut f = Fixture::new();
10343 f.run(&[b"SET", b"s", b"v"]);
10344 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10345 let bad = "-ERR invalid array index\r\n";
10346 assert_eq!(f.run(&[b"ARGET", b"s", b"-1"]), wrong);
10347 assert_eq!(f.run(&[b"ARMGET", b"s", b"0", b"-1"]), wrong);
10348 assert_eq!(f.run(&[b"ARSET", b"s", b"-1", b"x"]), bad);
10349 assert_eq!(f.run(&[b"ARDEL", b"s", b"-1"]), bad);
10350 assert_eq!(f.run(&[b"ARSCAN", b"s", b"-1", b"0"]), bad);
10351 assert_eq!(f.run(&[b"ARGREP", b"s", b"-1", b"0", b"EXACT", b"v"]), bad);
10352 f.run(&[b"ARSET", b"a", b"0", b"x"]);
10354 assert_eq!(f.run(&[b"ARGET", b"a", b"-1"]), bad);
10355 assert_eq!(f.run(&[b"ARGET", b"nope", b"-1"]), bad);
10356 }
10357
10358 #[test]
10359 fn an_append_follows_a_cursor_the_client_can_move() {
10360 let mut f = Fixture::new();
10361 assert_eq!(f.run(&[b"ARNEXT", b"nope"]), ":0\r\n");
10362 assert_eq!(f.run(&[b"ARINSERT", b"a", b"x", b"y"]), ":1\r\n");
10363 assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":2\r\n");
10364 assert_eq!(f.run(&[b"ARINSERT", b"a", b"z"]), ":2\r\n");
10365 assert_eq!(f.run(&[b"ARGET", b"a", b"2"]), "$1\r\nz\r\n");
10366
10367 assert_eq!(f.run(&[b"ARSEEK", b"nope", b"5"]), ":0\r\n");
10370 assert_eq!(f.run(&[b"EXISTS", b"nope"]), ":0\r\n");
10371 assert_eq!(f.run(&[b"ARSEEK", b"a", b"100"]), ":1\r\n");
10372 assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":100\r\n");
10373 assert_eq!(f.run(&[b"ARINSERT", b"a", b"far"]), ":100\r\n");
10374 assert_eq!(f.run(&[b"ARSEEK", b"a", b"0"]), ":1\r\n");
10375 assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":0\r\n");
10376
10377 assert_eq!(f.run(&[b"ARSEEK", b"a", b"18446744073709551615"]), ":1\r\n");
10380 assert_eq!(f.run(&[b"ARNEXT", b"a"]), "$-1\r\n");
10381 assert_eq!(
10382 f.run(&[b"ARINSERT", b"a", b"x"]),
10383 "-ERR insert index overflow\r\n"
10384 );
10385 assert_eq!(
10386 f.run(&[b"ARSET", b"a", b"18446744073709551615", b"x"]),
10387 "-ERR invalid array index\r\n"
10388 );
10389 }
10390
10391 #[test]
10392 fn a_ring_keeps_the_newest_and_renumbers_them_when_it_is_resized() {
10393 let mut f = Fixture::new();
10394 assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"a", b"b", b"c"]), ":2\r\n");
10395 assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"d", b"e"]), ":1\r\n");
10396 assert_eq!(f.run(&[b"ARLEN", b"r"]), ":3\r\n");
10397 assert_eq!(
10398 f.run(&[b"ARGETRANGE", b"r", b"0", b"2"]),
10399 "*3\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nc\r\n"
10400 );
10401 assert_eq!(f.run(&[b"ARRING", b"r", b"5", b"f"]), ":3\r\n");
10404 assert_eq!(
10405 f.run(&[b"ARGETRANGE", b"r", b"0", b"3"]),
10406 "*4\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nf\r\n"
10407 );
10408 assert_eq!(
10411 f.run(&[b"ARRING", b"r", b"0", b"x"]),
10412 "-ERR size must be positive\r\n"
10413 );
10414 assert_eq!(
10415 f.run(&[b"ARRING", b"r", b"big", b"x"]),
10416 "-ERR invalid size\r\n"
10417 );
10418 }
10419
10420 #[test]
10421 fn the_last_items_walk_back_from_the_cursor_and_report_the_holes() {
10422 let mut f = Fixture::new();
10423 assert_eq!(f.run(&[b"ARLASTITEMS", b"nope", b"5"]), "*0\r\n");
10424 f.run(&[b"ARRING", b"r", b"4", b"a", b"b", b"c", b"d", b"e"]);
10425 assert_eq!(
10426 f.run(&[b"ARLASTITEMS", b"r", b"3"]),
10427 "*3\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n"
10428 );
10429 assert_eq!(
10430 f.run(&[b"ARLASTITEMS", b"r", b"3", b"rev"]),
10431 "*3\r\n$1\r\ne\r\n$1\r\nd\r\n$1\r\nc\r\n"
10432 );
10433 assert_eq!(
10434 f.run(&[b"ARLASTITEMS", b"r", b"99"]),
10435 "*4\r\n$1\r\nb\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n",
10436 "more than there is gets what there is"
10437 );
10438 assert_eq!(f.run(&[b"ARLASTITEMS", b"r", b"0", b"junk"]), "*0\r\n");
10441 assert_eq!(
10442 f.run(&[b"ARLASTITEMS", b"r", b"1", b"junk"]),
10443 "-ERR syntax error\r\n"
10444 );
10445 assert_eq!(
10446 f.run(&[b"ARLASTITEMS", b"r", b"nine"]),
10447 "-ERR invalid COUNT\r\n"
10448 );
10449
10450 f.run(&[b"ARMSET", b"h", b"0", b"x", b"2", b"z"]);
10453 assert_eq!(
10454 f.run(&[b"ARLASTITEMS", b"h", b"5"]),
10455 "*2\r\n$-1\r\n$1\r\nz\r\n"
10456 );
10457 }
10458
10459 #[test]
10460 fn a_scan_answers_pairs_for_what_is_there_and_skips_what_is_not() {
10461 let mut f = Fixture::new();
10462 assert_eq!(f.run(&[b"ARSCAN", b"nope", b"0", b"10"]), "*0\r\n");
10463 f.run(&[b"ARMSET", b"a", b"0", b"x", b"7", b"y", b"1000000", b"z"]);
10464 assert_eq!(
10467 f.run(&[b"ARSCAN", b"a", b"0", b"18446744073709551614"]),
10468 "*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"
10469 );
10470 assert_eq!(
10471 f.run(&[
10472 b"ARSCAN",
10473 b"a",
10474 b"18446744073709551614",
10475 b"0",
10476 b"LIMIT",
10477 b"1"
10478 ]),
10479 "*1\r\n*2\r\n:1000000\r\n$1\r\nz\r\n"
10480 );
10481 assert_eq!(f.run(&[b"ARSCAN", b"a", b"1", b"6"]), "*0\r\n");
10482 assert_eq!(
10483 f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT", b"0"]),
10484 "-ERR LIMIT must be positive\r\n"
10485 );
10486 assert_eq!(
10487 f.run(&[b"ARSCAN", b"a", b"0", b"10", b"NOPE", b"1"]),
10488 "-ERR syntax error\r\n"
10489 );
10490 assert_eq!(
10491 f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT"]),
10492 "-ERR wrong number of arguments for 'arscan' command\r\n"
10493 );
10494 }
10495
10496 #[test]
10497 fn a_grep_answers_the_indexes_whose_elements_match() {
10498 let mut f = Fixture::new();
10499 assert_eq!(
10500 f.run(&[b"ARGREP", b"nope", b"0", b"10", b"EXACT", b"x"]),
10501 "*0\r\n"
10502 );
10503 f.run(&[b"ARSET", b"a", b"0", b"alpha", b"beta", b"gamma", b"ALPHA"]);
10504
10505 assert_eq!(
10508 f.run(&[b"ARGREP", b"a", b"-", b"+", b"GLOB", b"*a"]),
10509 "*3\r\n:0\r\n:1\r\n:2\r\n"
10510 );
10511 assert_eq!(
10512 f.run(&[b"ARGREP", b"a", b"+", b"-", b"GLOB", b"*a"]),
10513 "*3\r\n:2\r\n:1\r\n:0\r\n"
10514 );
10515 assert_eq!(
10516 f.run(&[b"ARGREP", b"a", b"1", b"2", b"GLOB", b"*a"]),
10517 "*2\r\n:1\r\n:2\r\n"
10518 );
10519
10520 assert_eq!(
10523 f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha"]),
10524 "*1\r\n:0\r\n"
10525 );
10526 assert_eq!(
10527 f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha", b"NOCASE"]),
10528 "*2\r\n:0\r\n:3\r\n"
10529 );
10530 assert_eq!(
10531 f.run(&[b"ARGREP", b"a", b"-", b"+", b"MATCH", b"mm"]),
10532 "*1\r\n:2\r\n"
10533 );
10534 assert_eq!(
10535 f.run(&[b"ARGREP", b"a", b"-", b"+", b"RE", b"^[bg]"]),
10536 "*2\r\n:1\r\n:2\r\n"
10537 );
10538
10539 let both: &[&[u8]] = &[
10542 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al",
10543 ];
10544 assert_eq!(f.run(both), "*2\r\n:0\r\n:1\r\n");
10545 assert_eq!(
10546 f.run(&[
10547 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND"
10548 ]),
10549 "*0\r\n"
10550 );
10551 assert_eq!(
10552 f.run(&[
10553 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND", b"OR"
10554 ]),
10555 "*2\r\n:0\r\n:1\r\n"
10556 );
10557
10558 assert_eq!(
10561 f.run(&[
10562 b"ARGREP",
10563 b"a",
10564 b"-",
10565 b"+",
10566 b"MATCH",
10567 b"a",
10568 b"WITHVALUES",
10569 b"LIMIT",
10570 b"2"
10571 ]),
10572 "*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"
10573 );
10574 assert_eq!(
10575 f.run(&[
10576 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"ALPHA", b"LIMIT", b"1"
10577 ]),
10578 "*1\r\n:3\r\n"
10579 );
10580 }
10581
10582 #[test]
10584 fn a_grep_reports_a_broken_command_the_way_redis_does() {
10585 let mut f = Fixture::new();
10586 f.run(&[b"ARSET", b"a", b"0", b"alpha"]);
10587 let syntax = "-ERR syntax error\r\n";
10588
10589 assert_eq!(
10592 f.run(&[b"ARGREP", b"a", b"-1", b"0", b"NOPE", b"x"]),
10593 "-ERR invalid array index\r\n"
10594 );
10595 assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOPE", b"x"]), syntax);
10596 assert_eq!(
10598 f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"EXACT"]),
10599 syntax
10600 );
10601 assert_eq!(
10602 f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT"]),
10603 syntax
10604 );
10605 assert_eq!(
10606 f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"WITHVALUES"]),
10607 syntax,
10608 "a command with no predicate in it at all"
10609 );
10610 assert_eq!(
10611 f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"0"]),
10612 "-ERR LIMIT must be positive\r\n"
10613 );
10614 assert_eq!(
10615 f.run(&[
10616 b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"nine"
10617 ]),
10618 "-ERR value is not an integer or out of range\r\n"
10619 );
10620 assert_eq!(
10621 f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b""]),
10622 "-ERR regular expression is empty\r\n"
10623 );
10624 assert_eq!(
10625 f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b"(a"]),
10626 "-ERR invalid regular expression: Missing ')'\r\n"
10627 );
10628 assert_eq!(
10629 f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", br"(a)\1"]),
10630 "-ERR regular expression backreferences are not supported\r\n"
10631 );
10632 let arity = "-ERR wrong number of arguments for 'argrep' command\r\n";
10635 assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT"]), arity);
10636 assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1"]), arity);
10637 }
10638
10639 #[test]
10640 fn an_op_reduces_a_range_to_one_number() {
10641 let mut f = Fixture::new();
10642 f.run(&[b"ARSET", b"a", b"0", b"1", b"2.5", b"word", b"-4"]);
10643 assert_eq!(
10644 f.run(&[b"AROP", b"a", b"0", b"10", b"SUM"]),
10645 "$4\r\n-0.5\r\n"
10646 );
10647 assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"min"]), "$2\r\n-4\r\n");
10648 assert_eq!(
10649 f.run(&[b"AROP", b"a", b"0", b"10", b"MAX"]),
10650 "$3\r\n2.5\r\n"
10651 );
10652 assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"USED"]), ":4\r\n");
10653 assert_eq!(
10654 f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH", b"word"]),
10655 ":1\r\n"
10656 );
10657 f.run(&[b"ARSET", b"t", b"0", b"0.1", b"0.2"]);
10660 assert_eq!(
10661 f.run(&[b"AROP", b"t", b"0", b"10", b"SUM"]),
10662 "$19\r\n0.30000000000000004\r\n"
10663 );
10664 assert_eq!(f.run(&[b"ZADD", b"z", b"0.3", b"m"]), ":1\r\n");
10665 assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\n0.3\r\n");
10666
10667 f.run(&[b"ARSET", b"w", b"0", b"word"]);
10670 assert_eq!(f.run(&[b"AROP", b"w", b"0", b"10", b"SUM"]), "$-1\r\n");
10671 assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"SUM"]), "$-1\r\n");
10672 assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"USED"]), ":0\r\n");
10673
10674 assert_eq!(
10675 f.run(&[b"AROP", b"a", b"0", b"10", b"NOPE"]),
10676 "-ERR unknown operation\r\n"
10677 );
10678 assert_eq!(
10679 f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH"]),
10680 "-ERR MATCH requires a value argument\r\n"
10681 );
10682 assert_eq!(
10683 f.run(&[b"AROP", b"a", b"0", b"10", b"SUM", b"extra"]),
10684 "-ERR wrong number of arguments for 'arop' command\r\n"
10685 );
10686 }
10687
10688 #[test]
10689 fn the_info_is_a_map_and_a_missing_key_is_an_error() {
10690 let mut f = Fixture::new();
10691 assert_eq!(f.run(&[b"ARINFO", b"nope"]), "-ERR no such key\r\n");
10692 f.run(&[b"ARINSERT", b"a", b"x", b"y"]);
10693 let short = f.run(&[b"ARINFO", b"a"]);
10694 assert!(
10695 short.starts_with("*14\r\n"),
10696 "seven pairs on RESP2: {short}"
10697 );
10698 assert!(short.contains("$5\r\ncount\r\n:2\r\n"), "{short}");
10699 assert!(
10700 short.contains("$17\r\nnext-insert-index\r\n:2\r\n"),
10701 "{short}"
10702 );
10703 assert!(short.contains("$10\r\nslice-size\r\n:4096\r\n"), "{short}");
10704 let full = f.run(&[b"ARINFO", b"a", b"full"]);
10705 assert!(full.starts_with("*24\r\n"), "twelve pairs: {full}");
10706 assert!(full.contains("$12\r\ndense-slices\r\n:0\r\n"), "{full}");
10709 assert!(full.contains("$13\r\nsparse-slices\r\n:1\r\n"), "{full}");
10710 assert!(
10711 full.contains("$14\r\navg-dense-size\r\n$1\r\n0\r\n"),
10712 "{full}"
10713 );
10714 assert_eq!(f.run(&[b"ARINFO", b"a", b"nope"]), "-ERR syntax error\r\n");
10715
10716 let mut g = Fixture::new();
10718 g.run(&[b"HELLO", b"3"]);
10719 g.run(&[b"ARINSERT", b"a", b"x"]);
10720 let map = g.run(&[b"ARINFO", b"a", b"FULL"]);
10721 assert!(map.starts_with("%12\r\n"), "{map}");
10722 assert!(map.contains("$5\r\ncount\r\n:1\r\n"), "{map}");
10723 assert!(map.contains("$14\r\navg-dense-size\r\n,0\r\n"), "{map}");
10724 }
10725
10726 #[test]
10727 fn a_double_on_the_wire_is_written_the_way_redis_writes_one() {
10728 let mut f = Fixture::new();
10729 for (score, want) in [
10732 ("3", "3"),
10733 ("3.5", "3.5"),
10734 ("0.3", "0.3"),
10735 ("1e30", "1e+30"),
10736 ("1e19", "1e+19"),
10737 ("1e-7", "1e-7"),
10738 ("0.000001", "0.000001"),
10739 ("4611686018427387904", "4611686018427387904"),
10740 ("-0", "-0"),
10741 ] {
10742 f.run(&[b"ZADD", b"z", score.as_bytes(), b"m"]);
10743 assert_eq!(
10744 f.run(&[b"ZSCORE", b"z", b"m"]),
10745 format!("${}\r\n{want}\r\n", want.len()),
10746 "score {score}"
10747 );
10748 }
10749
10750 let mut g = Fixture::new();
10753 g.run(&[b"HELLO", b"3"]);
10754 g.run(&[b"ZADD", b"z", b"1e30", b"m"]);
10755 assert_eq!(g.run(&[b"ZSCORE", b"z", b"m"]), ",1e+30\r\n");
10756 assert_eq!(
10761 g.run(&[b"INCRBYFLOAT", b"s", b"1e30"]),
10762 "$31\r\n1000000000000000000000000000000\r\n"
10763 );
10764 assert_eq!(g.run(&[b"INCRBYFLOAT", b"t", b"0.1"]), "$3\r\n0.1\r\n");
10765 assert_eq!(
10766 g.run(&[b"HINCRBYFLOAT", b"h", b"f", b"1e19"]),
10767 "$20\r\n10000000000000000000\r\n"
10768 );
10769 }
10770
10771 #[test]
10774 fn a_node_comes_back_with_the_fields_it_went_in_with() {
10775 let mut f = Fixture::new();
10776 assert_eq!(
10777 f.run(&[
10778 b"G.NADD", b"social", b"ada", b"name", b"Ada", b"born", b"1815"
10779 ]),
10780 ":1\r\n"
10781 );
10782 assert_eq!(
10788 f.run(&[b"G.NGET", b"social", b"ada"]),
10789 "*4\r\n$4\r\nborn\r\n$4\r\n1815\r\n$4\r\nname\r\n$3\r\nAda\r\n"
10790 );
10791 assert_eq!(
10794 f.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada Lovelace"]),
10795 ":0\r\n"
10796 );
10797 assert_eq!(
10798 f.run(&[b"G.NGET", b"social", b"ada"]),
10799 "*2\r\n$4\r\nname\r\n$12\r\nAda Lovelace\r\n"
10800 );
10801 assert_eq!(f.run(&[b"G.NADD", b"social", b"grace"]), ":1\r\n");
10804 assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
10805 assert_eq!(f.run(&[b"G.NGET", b"social", b"nobody"]), "$-1\r\n");
10806 assert_eq!(f.run(&[b"G.NGET", b"nokey", b"ada"]), "$-1\r\n");
10807
10808 assert_eq!(
10811 f.run(&[b"G.NADD", b"fresh", b"n", b"lonely"]),
10812 "-ERR syntax error\r\n"
10813 );
10814 assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
10815
10816 let mut g = Fixture::new();
10818 g.run(&[b"HELLO", b"3"]);
10819 g.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada"]);
10820 assert_eq!(
10821 g.run(&[b"G.NGET", b"social", b"ada"]),
10822 "%1\r\n$4\r\nname\r\n$3\r\nAda\r\n"
10823 );
10824 }
10825
10826 #[test]
10827 fn an_edge_creates_the_ends_it_needs() {
10828 let mut f = Fixture::new();
10829 assert_eq!(
10830 f.run(&[
10831 b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1843"
10832 ]),
10833 ":1\r\n"
10834 );
10835 assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "*0\r\n");
10837 assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
10838 assert_eq!(
10839 f.run(&[b"G.OUT", b"social", b"ada", b"FOLLOWS"]),
10840 "*2\r\n$1\r\n0\r\n*1\r\n$5\r\ngrace\r\n"
10841 );
10842 assert_eq!(
10843 f.run(&[b"G.IN", b"social", b"grace", b"FOLLOWS"]),
10844 "*2\r\n$1\r\n0\r\n*1\r\n$3\r\nada\r\n"
10845 );
10846 assert_eq!(
10849 f.run(&[
10850 b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1844"
10851 ]),
10852 ":0\r\n"
10853 );
10854 assert_eq!(f.run(&[b"G.DEG", b"social", b"ada", b"FOLLOWS"]), ":1\r\n");
10855 assert_eq!(
10857 f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"WORKS_WITH"]),
10858 ":1\r\n"
10859 );
10860 assert_eq!(
10861 f.run(&[b"G.DEG", b"social", b"ada", b"WORKS_WITH"]),
10862 ":1\r\n"
10863 );
10864
10865 assert_eq!(
10866 f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
10867 ":1\r\n"
10868 );
10869 assert_eq!(
10870 f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
10871 ":0\r\n"
10872 );
10873 assert_eq!(
10876 f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"NEVER"]),
10877 ":0\r\n"
10878 );
10879 assert_eq!(
10880 f.run(&[b"G.EDEL", b"social", b"ada", b"nobody", b"FOLLOWS"]),
10881 ":0\r\n"
10882 );
10883 assert_eq!(
10884 f.run(&[b"G.EDEL", b"nokey", b"ada", b"grace", b"FOLLOWS"]),
10885 ":0\r\n"
10886 );
10887 }
10888
10889 #[test]
10892 fn a_hop_answers_a_cursor_and_a_page() {
10893 let mut f = Fixture::new();
10894 for i in 0..25u32 {
10895 let dst = format!("n{i}");
10896 f.run(&[b"G.EADD", b"social", b"hub", dst.as_bytes(), b"FOLLOWS"]);
10897 }
10898 let first = f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS"]);
10900 assert!(first.starts_with("*2\r\n$2\r\n10\r\n*10\r\n"), "{first}");
10901
10902 let mut seen = 0;
10903 let mut cursor = String::from("0");
10904 loop {
10905 let page = f.run(&[
10906 b"G.OUT",
10907 b"social",
10908 b"hub",
10909 b"FOLLOWS",
10910 b"COUNT",
10911 b"7",
10912 b"CURSOR",
10913 cursor.as_bytes(),
10914 ]);
10915 let (head, rest) = page.split_once("\r\n*").expect("a cursor and a page");
10916 cursor = head
10917 .rsplit("\r\n")
10918 .next()
10919 .expect("the cursor line")
10920 .to_string();
10921 seen += rest
10922 .split_once("\r\n")
10923 .expect("the page length")
10924 .0
10925 .parse::<usize>()
10926 .expect("a length");
10927 if cursor == "0" {
10928 break;
10929 }
10930 }
10931 assert_eq!(seen, 25, "every neighbour once across the pages");
10932
10933 assert_eq!(
10936 f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"CURSOR", b"900"]),
10937 "*2\r\n$1\r\n0\r\n*0\r\n"
10938 );
10939 assert_eq!(
10940 f.run(&[b"G.OUT", b"social", b"hub", b"NEVER"]),
10941 "*2\r\n$1\r\n0\r\n*0\r\n"
10942 );
10943 assert_eq!(
10944 f.run(&[b"G.OUT", b"nokey", b"hub", b"FOLLOWS"]),
10945 "*2\r\n$1\r\n0\r\n*0\r\n"
10946 );
10947 assert_eq!(
10948 f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"COUNT", b"0"]),
10949 "-ERR COUNT must be a positive integer\r\n"
10950 );
10951 assert_eq!(
10952 f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"NOPE", b"1"]),
10953 "-ERR syntax error\r\n"
10954 );
10955 }
10956
10957 #[test]
10958 fn a_degree_counts_one_way_or_both() {
10959 let mut f = Fixture::new();
10960 f.run(&[b"G.EADD", b"social", b"a", b"b", b"F"]);
10961 f.run(&[b"G.EADD", b"social", b"a", b"c", b"F"]);
10962 f.run(&[b"G.EADD", b"social", b"d", b"a", b"F"]);
10963 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F"]), ":2\r\n");
10964 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"OUT"]), ":2\r\n");
10965 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"IN"]), ":1\r\n");
10966 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"BOTH"]), ":3\r\n");
10967 assert_eq!(f.run(&[b"G.DEG", b"social", b"nobody", b"F"]), ":0\r\n");
10968 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"NEVER"]), ":0\r\n");
10969 assert_eq!(f.run(&[b"G.DEG", b"nokey", b"a", b"F"]), ":0\r\n");
10970 assert_eq!(
10971 f.run(&[b"G.DEG", b"social", b"a", b"F", b"SIDEWAYS"]),
10972 "-ERR syntax error\r\n"
10973 );
10974 }
10975
10976 #[test]
10979 fn a_walk_reaches_each_node_once_however_many_ways_there_are() {
10980 let mut f = Fixture::new();
10981 for (src, dst) in [
10982 ("ada", "grace"),
10983 ("ada", "alan"),
10984 ("grace", "edsger"),
10985 ("alan", "edsger"),
10986 ("edsger", "barbara"),
10987 ] {
10988 f.run(&[b"G.EADD", b"social", src.as_bytes(), dst.as_bytes(), b"F"]);
10989 }
10990 assert_eq!(
10993 f.run(&[b"G.NEIGH", b"social", b"ada", b"F"]),
10994 "*3\r\n$5\r\ngrace\r\n$4\r\nalan\r\n$6\r\nedsger\r\n"
10995 );
10996 assert_eq!(
10997 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"1"]),
10998 "*2\r\n$5\r\ngrace\r\n$4\r\nalan\r\n"
10999 );
11000 let deep = f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"9"]);
11001 assert!(deep.starts_with("*4\r\n"), "the whole component: {deep}");
11002 assert!(deep.contains("$7\r\nbarbara\r\n"), "{deep}");
11003 assert_eq!(
11005 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"COUNT", b"1"]),
11006 "*1\r\n$5\r\ngrace\r\n"
11007 );
11008 assert_eq!(f.run(&[b"G.NEIGH", b"social", b"barbara", b"F"]), "*0\r\n");
11010 assert_eq!(f.run(&[b"G.NEIGH", b"social", b"ada", b"NEVER"]), "*0\r\n");
11011 assert_eq!(f.run(&[b"G.NEIGH", b"nokey", b"ada", b"F"]), "*0\r\n");
11012 assert_eq!(
11013 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"0"]),
11014 "-ERR DEPTH must be a positive integer\r\n"
11015 );
11016 assert_eq!(
11017 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"NOPE", b"1"]),
11018 "-ERR syntax error\r\n"
11019 );
11020 }
11021
11022 #[test]
11025 fn a_path_is_the_shortest_one_and_goes_over_any_label() {
11026 let mut f = Fixture::new();
11027 for i in 0..6u32 {
11030 let src = format!("n{i}");
11031 let dst = format!("n{}", i + 1);
11032 f.run(&[b"G.EADD", b"road", src.as_bytes(), dst.as_bytes(), b"STEP"]);
11033 }
11034 assert_eq!(
11035 f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
11036 "*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"
11037 );
11038 f.run(&[b"G.EADD", b"road", b"n0", b"n5", b"JUMP"]);
11039 assert_eq!(
11040 f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
11041 "*3\r\n$2\r\nn0\r\n$2\r\nn5\r\n$2\r\nn6\r\n"
11042 );
11043 assert_eq!(
11046 f.run(&[b"G.PATH", b"road", b"n2", b"n2"]),
11047 "*1\r\n$2\r\nn2\r\n"
11048 );
11049 assert_eq!(
11050 f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"MAXDEPTH", b"1"]),
11051 "*0\r\n"
11052 );
11053 assert_eq!(f.run(&[b"G.PATH", b"road", b"n6", b"n0"]), "*0\r\n");
11055 f.run(&[b"G.NADD", b"road", b"island"]);
11058 assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"island"]), "*0\r\n");
11059 assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"nobody"]), "*0\r\n");
11060 assert_eq!(f.run(&[b"G.PATH", b"nokey", b"n0", b"n6"]), "*0\r\n");
11061 assert_eq!(
11062 f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"NOPE", b"3"]),
11063 "-ERR syntax error\r\n"
11064 );
11065 }
11066
11067 #[test]
11071 fn the_keyspace_sees_a_graph_key_like_any_other() {
11072 let mut f = Fixture::new();
11073 f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"F"]);
11074 assert_eq!(f.run(&[b"TYPE", b"social"]), "+graph\r\n");
11075 assert_eq!(
11076 f.run(&[b"OBJECT", b"ENCODING", b"social"]),
11077 "$9\r\nadjacency\r\n"
11078 );
11079 assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
11080 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
11081 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$6\r\nsocial\r\n");
11082 let held = f.server.memory_bytes();
11086 for i in 0..200u32 {
11087 let dst = format!("n{i}");
11088 f.run(&[b"G.EADD", b"big", b"hub", dst.as_bytes(), b"F"]);
11089 }
11090 assert!(
11091 f.server.memory_bytes() > held,
11092 "two hundred edges cost something: {held} then {}",
11093 f.server.memory_bytes()
11094 );
11095 f.run(&[b"DEL", b"big"]);
11096
11097 assert_eq!(f.run(&[b"EXPIRE", b"social", b"100"]), ":1\r\n");
11100 assert_eq!(f.run(&[b"PERSIST", b"social"]), ":1\r\n");
11101 assert_eq!(f.run(&[b"RENAME", b"social", b"net"]), "+OK\r\n");
11102 assert_eq!(f.run(&[b"MOVE", b"net", b"1"]), ":1\r\n");
11103 assert_eq!(f.run(&[b"EXISTS", b"net"]), ":0\r\n");
11104 f.run(&[b"SELECT", b"1"]);
11105 assert_eq!(f.run(&[b"G.DEG", b"net", b"ada", b"F"]), ":1\r\n");
11106
11107 assert_eq!(f.run(&[b"DEL", b"net"]), ":1\r\n");
11108 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
11109 f.run(&[b"G.NADD", b"g", b"n"]);
11110 assert_eq!(f.run(&[b"FLUSHDB"]), "+OK\r\n");
11111 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
11112 }
11113
11114 #[test]
11117 fn a_graph_cannot_be_copied_or_dumped() {
11118 let mut f = Fixture::new();
11119 f.run(&[b"G.NADD", b"social", b"ada"]);
11120 assert_eq!(
11121 f.run(&[b"COPY", b"social", b"other"]),
11122 "-ERR COPY is not supported for a graph\r\n"
11123 );
11124 assert_eq!(
11125 f.run(&[b"COPY", b"social", b"other", b"DB", b"1"]),
11126 "-ERR COPY is not supported for a graph\r\n"
11127 );
11128 assert_eq!(
11129 f.run(&[b"DUMP", b"social"]),
11130 "-ERR DUMP is not supported for a graph\r\n"
11131 );
11132 assert_eq!(f.run(&[b"EXISTS", b"social", b"other"]), ":1\r\n");
11134 }
11135
11136 #[test]
11139 fn a_graph_and_a_string_are_the_wrong_type_for_each_other() {
11140 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
11141 let mut f = Fixture::new();
11142 f.run(&[b"G.NADD", b"social", b"ada"]);
11143 assert_eq!(f.run(&[b"GET", b"social"]), wrong);
11144 assert_eq!(f.run(&[b"LPUSH", b"social", b"x"]), wrong);
11145 assert_eq!(f.run(&[b"SADD", b"social", b"x"]), wrong);
11146
11147 f.run(&[b"SET", b"str", b"v"]);
11148 for cmd in [
11149 vec![b"G.NADD".as_ref(), b"str", b"n"],
11150 vec![b"G.NGET".as_ref(), b"str", b"n"],
11151 vec![b"G.NDEL".as_ref(), b"str", b"n"],
11152 vec![b"G.EADD".as_ref(), b"str", b"a", b"b", b"F"],
11153 vec![b"G.EDEL".as_ref(), b"str", b"a", b"b", b"F"],
11154 vec![b"G.OUT".as_ref(), b"str", b"a", b"F"],
11155 vec![b"G.IN".as_ref(), b"str", b"a", b"F"],
11156 vec![b"G.DEG".as_ref(), b"str", b"a", b"F"],
11157 vec![b"G.NEIGH".as_ref(), b"str", b"a", b"F"],
11158 vec![b"G.PATH".as_ref(), b"str", b"a", b"b"],
11159 ] {
11160 assert_eq!(f.run(&cmd), wrong, "{:?}", cmd[0]);
11161 }
11162 }
11163
11164 #[test]
11167 fn a_graph_goes_when_its_last_node_does() {
11168 let mut f = Fixture::new();
11169 f.run(&[
11170 b"G.EADD", b"social", b"ada", b"grace", b"F", b"since", b"1843",
11171 ]);
11172 assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":1\r\n");
11173 assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "$-1\r\n");
11175 assert_eq!(
11176 f.run(&[b"G.DEG", b"social", b"grace", b"F", b"IN"]),
11177 ":0\r\n"
11178 );
11179 assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":0\r\n");
11180 assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
11181
11182 assert_eq!(f.run(&[b"G.NDEL", b"social", b"grace"]), ":1\r\n");
11183 assert_eq!(f.run(&[b"EXISTS", b"social"]), ":0\r\n");
11184 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
11185 assert_eq!(f.run(&[b"G.NDEL", b"nokey", b"ada"]), ":0\r\n");
11186
11187 f.run(&[b"G.NADD", b"social", b"first"]);
11190 f.run(&[b"G.NADD", b"social", b"second"]);
11191 f.run(&[b"G.NDEL", b"social", b"first"]);
11192 f.run(&[b"G.EADD", b"social", b"third", b"second", b"F"]);
11193 assert_eq!(
11194 f.run(&[b"G.OUT", b"social", b"third", b"F"]),
11195 "*2\r\n$1\r\n0\r\n*1\r\n$6\r\nsecond\r\n"
11196 );
11197 }
11198
11199 #[test]
11204 fn a_json_path_answers_a_set_and_a_legacy_path_answers_a_value() {
11205 let mut f = Fixture::new();
11206 let doc = br#"{"a":1,"b":{"c":true}}"#;
11207 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$", doc]), "+OK\r\n");
11208 assert_eq!(
11211 f.run(&[b"JSON.GET", b"doc"]),
11212 bulk(r#"{"a":1,"b":{"c":true}}"#)
11213 );
11214 assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.a"]), bulk("[1]"));
11215 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("1"));
11216 assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$..c"]), bulk("[true]"));
11217 assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.nope"]), bulk("[]"));
11220 assert_eq!(
11221 f.run(&[b"JSON.GET", b"doc", b".nope"]),
11222 "-ERR Path does not exist\r\n"
11223 );
11224 assert_eq!(f.run(&[b"JSON.GET", b"nokey"]), "$-1\r\n");
11225 assert_eq!(f.run(&[b"TYPE", b"doc"]), "+ReJSON-RL\r\n");
11228 assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":1\r\n");
11229 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"doc"]), bulk("raw"));
11230 assert_eq!(f.run(&[b"DEL", b"doc"]), ":1\r\n");
11231 assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
11232 }
11233
11234 #[test]
11240 fn the_two_json_errors_that_carry_no_prefix() {
11241 let mut f = Fixture::new();
11242 f.run(&[b"SET", b"plain", b"x"]);
11243 let wrong = "-Existing key has wrong Redis type\r\n";
11244 assert_eq!(f.run(&[b"JSON.GET", b"plain"]), wrong);
11245 assert_eq!(f.run(&[b"JSON.SET", b"plain", b"$", b"1"]), wrong);
11246 assert_eq!(f.run(&[b"JSON.DEL", b"plain"]), wrong);
11247 assert_eq!(f.run(&[b"JSON.TYPE", b"plain"]), wrong);
11248 assert_eq!(f.run(&[b"JSON.CLEAR", b"plain"]), wrong);
11249
11250 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"z":1},"b":{"z":2}}"#]);
11251 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.*.z", b"9"]), "+OK\r\n");
11255 assert_eq!(
11256 f.run(&[b"JSON.GET", b"doc"]),
11257 bulk(r#"{"a":{"z":9},"b":{"z":9}}"#)
11258 );
11259 assert_eq!(
11260 f.run(&[b"JSON.SET", b"doc", b"$.*.y", b"9"]),
11261 "-Err wrong static path\r\n"
11262 );
11263 }
11264
11265 #[test]
11267 fn json_set_creates_one_field_and_refuses_to_invent_the_rest() {
11268 let mut f = Fixture::new();
11269 assert_eq!(
11271 f.run(&[b"JSON.SET", b"new", b".a", b"1"]),
11272 "-ERR new objects must be created at the root\r\n"
11273 );
11274 assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
11275 assert_eq!(
11278 f.run(&[b"JSON.SET", b"new", b".a", b"1", b"NX"]),
11279 "-ERR new objects must be created at the root\r\n"
11280 );
11281 assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"XX"]), "$-1\r\n");
11282 assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"NX"]), "+OK\r\n");
11283
11284 f.run(&[
11285 b"JSON.SET",
11286 b"doc",
11287 b"$",
11288 br#"{"o":{},"arr":[1,2],"s":"x"}"#,
11289 ]);
11290 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"1"]), "+OK\r\n");
11292 assert_eq!(
11295 f.run(&[b"JSON.SET", b"doc", b"$.nope.made", b"1"]),
11296 "$-1\r\n"
11297 );
11298 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.s.made", b"1"]), "$-1\r\n");
11299 assert_eq!(
11301 f.run(&[b"JSON.SET", b"doc", b"$.arr[5]", b"9"]),
11302 "-ERR array index out of range\r\n"
11303 );
11304 assert_eq!(
11305 f.run(&[b"JSON.SET", b"doc", b"$.arr[2]", b"9"]),
11306 "-ERR array index out of range\r\n"
11307 );
11308 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.arr[1]", b"9"]), "+OK\r\n");
11309 assert_eq!(
11312 f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"2", b"NX"]),
11313 "$-1\r\n"
11314 );
11315 assert_eq!(
11316 f.run(&[b"JSON.SET", b"doc", b"$.gone", b"2", b"XX"]),
11317 "$-1\r\n"
11318 );
11319 assert_eq!(
11320 f.run(&[b"JSON.GET", b"doc"]),
11321 bulk(r#"{"o":{"made":1},"s":"x","arr":[1,9]}"#)
11322 );
11323 assert!(
11327 f.run(&[b"JSON.SET", b"doc", b"$.s", b"nope"])
11328 .starts_with("-this is not the start of a value")
11329 );
11330 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".s"]), bulk("\"x\""));
11331 }
11332
11333 #[test]
11336 fn the_json_commands_that_do_not_answer_text() {
11337 let mut f = Fixture::new();
11338 let doc = br#"{"a":1,"t":true,"o":{"x":1},"arr":[1,2],"f":1.5,"s":"x","n":null}"#;
11339 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
11340
11341 assert_eq!(f.run(&[b"JSON.TYPE", b"doc"]), bulk("object"));
11342 assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".a"]), bulk("integer"));
11343 assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".f"]), bulk("number"));
11344 assert_eq!(
11345 f.run(&[b"JSON.TYPE", b"doc", b"$.a"]),
11346 format!("*1\r\n{}", bulk("integer"))
11347 );
11348 assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".nope"]), "$-1\r\n");
11351 assert_eq!(f.run(&[b"JSON.TYPE", b"nokey"]), "$-1\r\n");
11352
11353 assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.t"]), "*1\r\n:0\r\n");
11356 assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b".t"]), bulk("true"));
11357 assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.a"]), "*1\r\n$-1\r\n");
11360 assert_eq!(
11361 f.run(&[b"JSON.TOGGLE", b"doc", b".a"]),
11362 "-ERR Path does not exist or not a bool\r\n"
11363 );
11364 assert_eq!(
11365 f.run(&[b"JSON.TOGGLE", b"doc", b".nope"]),
11366 "-ERR Path does not exist or not a bool\r\n"
11367 );
11368 assert_eq!(
11369 f.run(&[b"JSON.TOGGLE", b"nokey", b"$.a"]),
11370 "-ERR could not perform this operation on a key that doesn't exist\r\n"
11371 );
11372
11373 assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.s"]), ":0\r\n");
11376 assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":4\r\n");
11377 assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":0\r\n");
11378 assert_eq!(
11379 f.run(&[b"JSON.GET", b"doc"]),
11380 bulk(r#"{"a":0,"f":0,"n":null,"o":{},"s":"x","t":true,"arr":[]}"#)
11381 );
11382
11383 assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.nope"]), ":0\r\n");
11386 assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.a"]), ":1\r\n");
11387 assert_eq!(f.run(&[b"JSON.FORGET", b"doc", b"$.*"]), ":6\r\n");
11392 assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":0\r\n");
11393 assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
11394 assert_eq!(f.run(&[b"JSON.DEL", b"doc"]), ":0\r\n");
11395 assert_eq!(f.run(&[b"JSON.SET", b"empty", b"$", b"{}"]), "+OK\r\n");
11396 assert_eq!(f.run(&[b"EXISTS", b"empty"]), ":1\r\n");
11397 assert_eq!(f.run(&[b"JSON.GET", b"empty"]), bulk("{}"));
11398 assert_eq!(f.run(&[b"JSON.DEL", b"nokey"]), ":0\r\n");
11399 }
11400
11401 #[test]
11408 fn json_get_lays_out_the_wrapper_it_builds() {
11409 let mut f = Fixture::new();
11410 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[1,{"c":2}]}"#]);
11411
11412 assert_eq!(
11413 f.run(&[b"JSON.GET", b"doc", b"$.a", b"$.b"]),
11414 bulk(r#"{"$.a":[1],"$.b":[[1,{"c":2}]]}"#)
11415 );
11416 assert_eq!(
11418 f.run(&[b"JSON.GET", b"doc", b".a", b".b"]),
11419 bulk(r#"{".a":1,".b":[1,{"c":2}]}"#)
11420 );
11421 let fmt: &[&[u8]] = &[b"INDENT", b" ", b"NEWLINE", b"\n", b"SPACE", b" "];
11422 let mut one = vec![b"JSON.GET".as_slice(), b"doc"];
11423 one.extend_from_slice(fmt);
11424 one.push(b"$.b");
11425 assert_eq!(
11426 f.run(&one),
11427 bulk("[\n [\n 1,\n {\n \"c\": 2\n }\n ]\n]")
11428 );
11429 let mut two = vec![b"JSON.GET".as_slice(), b"doc"];
11430 two.extend_from_slice(fmt);
11431 two.push(b"$.a");
11432 two.push(b"$.nope");
11433 assert_eq!(
11434 f.run(&two),
11435 bulk("{\n \"$.a\": [\n 1\n ],\n \"$.nope\": []\n}")
11436 );
11437 let mut root = vec![b"JSON.GET".as_slice(), b"doc", b"SPACE", b" "];
11440 root.push(b".a");
11441 assert_eq!(f.run(&root), bulk("1"));
11442 }
11443
11444 #[test]
11447 fn json_mget_answers_once_per_key_whatever_is_under_them() {
11448 let mut f = Fixture::new();
11449 f.run(&[b"JSON.SET", b"one", b"$", br#"{"a":1}"#]);
11450 f.run(&[b"JSON.SET", b"two", b"$", br#"{"a":2}"#]);
11451 f.run(&[b"SET", b"plain", b"x"]);
11452 assert_eq!(
11453 f.run(&[b"JSON.MGET", b"one", b"two", b"$.a"]),
11454 format!("*2\r\n{}{}", bulk("[1]"), bulk("[2]"))
11455 );
11456 assert_eq!(
11459 f.run(&[b"JSON.MGET", b"one", b"nokey", b"plain", b".a"]),
11460 format!("*3\r\n{}$-1\r\n$-1\r\n", bulk("1"))
11461 );
11462 assert_eq!(f.run(&[b"JSON.MGET", b"one", b".nope"]), "*1\r\n$-1\r\n");
11465 }
11466
11467 #[test]
11475 fn the_json_commands_that_answer_a_size_disagree_about_every_failure() {
11476 let mut f = Fixture::new();
11477 let doc = br#"{"a":[1,2,3],"o":{"x":1,"y":2},"s":"hello","n":7}"#;
11478 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
11479
11480 assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b".a"]), ":3\r\n");
11481 assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b"$.a"]), "*1\r\n:3\r\n");
11482 assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".o"]), ":2\r\n");
11483 assert_eq!(f.run(&[b"JSON.STRLEN", b"doc", b".s"]), ":5\r\n");
11484 assert_eq!(
11485 f.run(&[b"JSON.OBJKEYS", b"doc", b".o"]),
11486 format!("*2\r\n{}{}", bulk("x"), bulk("y"))
11487 );
11488 assert_eq!(
11491 f.run(&[b"JSON.ARRLEN", b"doc", b"$.*"]),
11492 "*4\r\n:3\r\n$-1\r\n$-1\r\n$-1\r\n"
11493 );
11494
11495 assert_eq!(
11498 f.run(&[b"JSON.ARRLEN", b"doc", b".nope"]),
11499 "-ERR Path does not exist\r\n"
11500 );
11501 assert_eq!(
11502 f.run(&[b"JSON.STRLEN", b"doc", b".nope"]),
11503 "-ERR Path does not exist\r\n"
11504 );
11505 assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".nope"]), "$-1\r\n");
11506 assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b".nope"]), "$-1\r\n");
11509 assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b"$.nope"]), "*0\r\n");
11512
11513 assert_eq!(
11517 f.run(&[b"JSON.ARRLEN", b"doc", b".n"]),
11518 "-ERR Path does not exist or not an array\r\n"
11519 );
11520 assert_eq!(
11521 f.run(&[b"JSON.OBJKEYS", b"doc", b".n"]),
11522 "-ERR Path does not exist or not an object\r\n"
11523 );
11524 assert_eq!(
11525 f.run(&[b"JSON.OBJLEN", b"doc", b".n"]),
11526 "-WRONGTYPE wrong type of path value - expected object\r\n"
11527 );
11528 assert_eq!(
11529 f.run(&[b"JSON.STRLEN", b"doc", b".n"]),
11530 "-WRONGTYPE wrong type of path value - expected string\r\n"
11531 );
11532
11533 assert_eq!(f.run(&[b"JSON.ARRLEN", b"nokey", b".a"]), "$-1\r\n");
11536 assert_eq!(f.run(&[b"JSON.OBJLEN", b"nokey", b".a"]), "$-1\r\n");
11537 assert_eq!(f.run(&[b"JSON.STRLEN", b"nokey", b".a"]), "$-1\r\n");
11538 assert_eq!(f.run(&[b"JSON.OBJKEYS", b"nokey", b".a"]), "$-1\r\n");
11539 assert_eq!(
11540 f.run(&[b"JSON.ARRLEN", b"nokey", b"$.a"]),
11541 "-ERR could not perform this operation on a key that doesn't exist\r\n"
11542 );
11543 assert_eq!(
11545 f.run(&[b"JSON.OBJLEN", b"nokey", b"$.a"]),
11546 "-ERR Path does not exist or not an object\r\n"
11547 );
11548 }
11549
11550 #[test]
11556 fn the_json_array_writes_agree_on_the_errors_and_not_on_the_indexes() {
11557 let mut f = Fixture::new();
11558 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3],"n":7}"#]);
11559
11560 assert_eq!(f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"4"]), ":4\r\n");
11561 assert_eq!(
11562 f.run(&[b"JSON.ARRAPPEND", b"doc", b"$.a", b"5", b"6"]),
11563 "*1\r\n:6\r\n"
11564 );
11565 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[1,2,3,4,5,6]"));
11566
11567 assert_eq!(
11570 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-1", b"0"]),
11571 ":7\r\n"
11572 );
11573 assert_eq!(
11574 f.run(&[b"JSON.GET", b"doc", b".a"]),
11575 bulk("[1,2,3,4,5,0,6]")
11576 );
11577 assert_eq!(
11578 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"7", b"9"]),
11579 ":8\r\n"
11580 );
11581 assert_eq!(
11583 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"9", b"9"]),
11584 "-ERR index out of bounds\r\n"
11585 );
11586 assert_eq!(
11587 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-9", b"9"]),
11588 "-ERR index out of bounds\r\n"
11589 );
11590
11591 f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3,4,5]"]);
11594 assert_eq!(
11595 f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"1", b"3"]),
11596 ":3\r\n"
11597 );
11598 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[2,3,4]"));
11599 assert_eq!(
11600 f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"-2", b"99"]),
11601 ":2\r\n"
11602 );
11603 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[3,4]"));
11604 assert_eq!(
11605 f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"9", b"9"]),
11606 ":0\r\n"
11607 );
11608 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
11609
11610 f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3]"]);
11613 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), bulk("3"));
11614 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"0"]), bulk("1"));
11615 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"99"]), bulk("2"));
11616 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), "$-1\r\n");
11617
11618 for call in [
11621 &[&b"JSON.ARRAPPEND"[..], b"doc", b"PATH", b"1"][..],
11622 &[&b"JSON.ARRTRIM"[..], b"doc", b"PATH", b"1", b"1"][..],
11623 &[&b"JSON.ARRPOP"[..], b"doc", b"PATH", b"1"][..],
11624 &[&b"JSON.ARRINSERT"[..], b"doc", b"PATH", b"0", b"1"][..],
11625 ] {
11626 for path in [&b".n"[..], &b".nope"[..]] {
11627 let args: Vec<&[u8]> = call
11628 .iter()
11629 .map(|a| if *a == b"PATH" { path } else { *a })
11630 .collect();
11631 assert_eq!(
11632 f.run(&args),
11633 "-ERR Path does not exist or not an array\r\n",
11634 "{} {}",
11635 String::from_utf8_lossy(call[0]),
11636 String::from_utf8_lossy(path)
11637 );
11638 }
11639 }
11640
11641 assert_eq!(
11644 f.run(&[b"JSON.ARRAPPEND", b"nokey", b".a", b"1"]),
11645 "-ERR could not perform this operation on a key that doesn't exist\r\n"
11646 );
11647 assert_eq!(
11648 f.run(&[b"JSON.ARRPOP", b"nokey", b"$.a"]),
11649 "-ERR could not perform this operation on a key that doesn't exist\r\n"
11650 );
11651
11652 assert!(
11657 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"nope"])
11658 .starts_with("-this is not the start of a value")
11659 );
11660 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
11661 }
11662
11663 #[test]
11671 fn json_arrinsert_is_all_or_nothing_across_the_matches() {
11672 let mut f = Fixture::new();
11673 let doc = br#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#;
11674 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
11675 assert_eq!(
11676 f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"-2", b"0"]),
11677 "-ERR index out of bounds\r\n"
11678 );
11679 assert_eq!(
11680 f.run(&[b"JSON.GET", b"doc"]),
11681 bulk(r#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#)
11682 );
11683 assert_eq!(
11685 f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"0", b"0"]),
11686 "*3\r\n:4\r\n:3\r\n:2\r\n"
11687 );
11688 assert_eq!(
11689 f.run(&[b"JSON.GET", b"doc"]),
11690 bulk(r#"{"a":[0,1,2,3],"n":{"a":[0,9,8],"in":{"a":[0,1]}}}"#)
11691 );
11692 }
11693
11694 #[test]
11702 fn json_arrindex_has_an_exclusive_stop_and_a_start_that_cannot_run_off_the_end() {
11703 let mut f = Fixture::new();
11704 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3,1],"n":7}"#]);
11705
11706 assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"2"]), ":1\r\n");
11707 assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"9"]), ":-1\r\n");
11708 assert_eq!(
11709 f.run(&[b"JSON.ARRINDEX", b"doc", b"$.a", b"2"]),
11710 "*1\r\n:1\r\n"
11711 );
11712
11713 assert_eq!(
11716 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"0"]),
11717 ":3\r\n"
11718 );
11719 assert_eq!(
11722 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"3"]),
11723 ":-1\r\n"
11724 );
11725
11726 for start in [&b"4"[..], &b"5"[..], &b"-1"[..]] {
11729 assert_eq!(
11730 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", start]),
11731 ":3\r\n",
11732 "{}",
11733 String::from_utf8_lossy(start)
11734 );
11735 }
11736 assert_eq!(
11737 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"-100"]),
11738 ":0\r\n"
11739 );
11740 f.run(&[b"JSON.SET", b"doc", b"$.a", b"[]"]);
11743 assert_eq!(
11744 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1"]),
11745 ":-1\r\n"
11746 );
11747
11748 f.run(&[b"JSON.SET", b"doc", b"$.a", br#"[{"k":1},[1,2],"s"]"#]);
11752 assert_eq!(
11753 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", br#"{"k":1}"#]),
11754 ":0\r\n"
11755 );
11756 assert_eq!(
11757 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[1,2]"]),
11758 ":1\r\n"
11759 );
11760 assert_eq!(
11761 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[2,1]"]),
11762 ":-1\r\n"
11763 );
11764
11765 assert_eq!(
11769 f.run(&[b"JSON.ARRINDEX", b"doc", b".nope", b"1"]),
11770 "-ERR Path does not exist\r\n"
11771 );
11772 assert_eq!(
11773 f.run(&[b"JSON.ARRINDEX", b"doc", b".n", b"1"]),
11774 "-WRONGTYPE wrong type of path value - expected array\r\n"
11775 );
11776 assert_eq!(
11777 f.run(&[b"JSON.ARRINDEX", b"nokey", b".a", b"1"]),
11778 "-ERR Path does not exist\r\n"
11779 );
11780 assert_eq!(
11781 f.run(&[b"JSON.ARRINDEX", b"nokey", b"$.a", b"1"]),
11782 "-ERR Path does not exist\r\n"
11783 );
11784 }
11785
11786 #[test]
11789 fn the_json_number_family_answers_json_text_and_keeps_its_integers() {
11790 let mut f = Fixture::new();
11791 let doc = br#"{"i":7,"f":1.5,"neg":-2,"s":"ab"}"#;
11792 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
11793
11794 assert_eq!(
11797 f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2"]),
11798 bulk("9").as_str()
11799 );
11800 assert_eq!(
11802 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.i", b"2"]),
11803 bulk("[11]").as_str()
11804 );
11805 assert_eq!(
11808 f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2.0"]),
11809 bulk("13.0").as_str()
11810 );
11811 assert_eq!(
11812 f.run(&[b"JSON.TYPE", b"doc", b".i"]),
11813 bulk("number").as_str()
11814 );
11815 assert_eq!(
11816 f.run(&[b"JSON.NUMMULTBY", b"doc", b".f", b"2"]),
11817 bulk("3.0").as_str()
11818 );
11819 assert_eq!(
11820 f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"3"]),
11821 bulk("-8").as_str()
11822 );
11823 f.run(&[b"JSON.SET", b"doc", b"$.f", b"1.5"]);
11826 assert_eq!(
11827 f.run(&[b"JSON.NUMPOWBY", b"doc", b".f", b"0.5"]),
11828 bulk("1.224744871391589").as_str()
11829 );
11830 assert_eq!(
11831 f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"0.5"]),
11832 "-ERR result is not a number\r\n"
11833 );
11834 f.run(&[b"JSON.SET", b"doc", b"$.big", b"9223372036854775807"]);
11838 assert_eq!(
11839 f.run(&[b"JSON.NUMINCRBY", b"doc", b".big", b"1"]),
11840 "-ERR numeric overflow\r\n"
11841 );
11842 f.run(&[b"JSON.SET", b"doc", b"$.p", b"2"]);
11843 assert_eq!(
11844 f.run(&[b"JSON.NUMPOWBY", b"doc", b".p", b"-1"]),
11845 "-ERR numeric overflow\r\n"
11846 );
11847 f.run(&[b"JSON.SET", b"doc", b"$.huge", b"1e308"]);
11849 assert_eq!(
11850 f.run(&[b"JSON.NUMMULTBY", b"doc", b".huge", b"1e10"]),
11851 "-ERR result is not a number\r\n"
11852 );
11853
11854 assert_eq!(
11858 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"1"]),
11859 bulk("[null]").as_str()
11860 );
11861 assert_eq!(
11862 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.nope", b"1"]),
11863 bulk("[]").as_str()
11864 );
11865 assert_eq!(
11866 f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", b"1"]),
11867 "-ERR Path does not exist or does not contains a number\r\n"
11868 );
11869 assert_eq!(
11870 f.run(&[b"JSON.NUMINCRBY", b"doc", b".nope", b"1"]),
11871 "-ERR Path does not exist or does not contains a number\r\n"
11872 );
11873 assert_eq!(
11876 f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"true"]),
11877 "-bad input number\r\n"
11878 );
11879 assert_eq!(
11880 f.run(&[b"JSON.NUMINCRBY", b"nokey", b".i", b"1"]),
11881 "-ERR could not perform this operation on a key that doesn't exist\r\n"
11882 );
11883 assert_eq!(
11884 f.run(&[b"JSON.NUMINCRBY", b"nokey", b"$.i", b"1"]),
11885 "-ERR could not perform this operation on a key that doesn't exist\r\n"
11886 );
11887 }
11888
11889 #[test]
11892 fn json_strappend_reads_its_shape_off_the_argument_count() {
11893 let mut f = Fixture::new();
11894 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"s":"ab","n":1}"#]);
11895
11896 assert_eq!(
11897 f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""c""#]),
11898 ":3\r\n"
11899 );
11900 assert_eq!(
11901 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", br#""d""#]),
11902 "*1\r\n:4\r\n"
11903 );
11904 assert_eq!(
11907 f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""\u00e9""#]),
11908 ":6\r\n"
11909 );
11910 f.run(&[b"JSON.SET", b"str", b"$", br#""ab""#]);
11913 assert_eq!(f.run(&[b"JSON.STRAPPEND", b"str", br#""c""#]), ":3\r\n");
11914 assert_eq!(f.run(&[b"JSON.GET", b"str"]), bulk("\"abc\"").as_str());
11915
11916 assert_eq!(
11920 f.run(&[b"JSON.STRAPPEND", b"doc", b".s", b"5"]),
11921 "-WRONGTYPE wrong type of path value - expected string\r\n"
11922 );
11923 assert_eq!(
11924 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", br#""c""#]),
11925 "*1\r\n$-1\r\n"
11926 );
11927 assert_eq!(
11928 f.run(&[b"JSON.STRAPPEND", b"doc", b".n", br#""c""#]),
11929 "-ERR Path does not exist or not a string\r\n"
11930 );
11931 assert_eq!(
11932 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.nope", br#""c""#]),
11933 "*0\r\n"
11934 );
11935 assert_eq!(
11936 f.run(&[b"JSON.STRAPPEND", b"nokey", br#""c""#]),
11937 "-ERR could not perform this operation on a key that doesn't exist\r\n"
11938 );
11939 }
11940
11941 #[test]
11944 fn a_legacy_wildcard_write_touches_every_match_and_answers_only_one() {
11945 let mut f = Fixture::new();
11946 let three = br#"{"a":[[7],[7,7],[7,7,7]]}"#;
11949
11950 f.run(&[b"JSON.SET", b"doc", b"$", three]);
11951 assert_eq!(
11952 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
11953 ":4\r\n"
11954 );
11955 f.run(&[b"JSON.SET", b"doc", b"$", three]);
11956 assert_eq!(
11957 f.run(&[b"JSON.ARRINSERT", b"doc", b".a[*]", b"0", b"9"]),
11958 ":2\r\n"
11959 );
11960 f.run(&[b"JSON.SET", b"doc", b"$", three]);
11961 assert_eq!(
11962 f.run(&[b"JSON.ARRTRIM", b"doc", b".a[*]", b"0", b"1"]),
11963 ":1\r\n"
11964 );
11965 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[1,2,3],[4,5,6]]}"#]);
11966 assert_eq!(
11967 f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]", b"0"]),
11968 bulk("1").as_str()
11969 );
11970 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3]}"#]);
11971 assert_eq!(
11972 f.run(&[b"JSON.NUMINCRBY", b"doc", b".a[*]", b"10"]),
11973 bulk("13").as_str()
11974 );
11975 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["p","qq","rrr"]}"#]);
11976 assert_eq!(
11977 f.run(&[b"JSON.STRAPPEND", b"doc", b".a[*]", br#""z""#]),
11978 ":4\r\n"
11979 );
11980 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[true,false,true]}"#]);
11981 assert_eq!(
11982 f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
11983 bulk("false").as_str()
11984 );
11985 assert_eq!(
11988 f.run(&[b"JSON.GET", b"doc", b".a"]),
11989 bulk("[false,true,false]").as_str()
11990 );
11991
11992 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x",[1],[1,2]]}"#]);
11995 assert_eq!(
11996 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
11997 ":3\r\n"
11998 );
11999 assert_eq!(
12000 f.run(&[b"JSON.GET", b"doc", b".a"]),
12001 bulk(r#"["x",[1,9],[1,2,9]]"#).as_str()
12002 );
12003 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x","y"]}"#]);
12006 assert_eq!(
12007 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
12008 "-ERR Path does not exist or not an array\r\n"
12009 );
12010 assert_eq!(
12011 f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
12012 "-ERR Path does not exist or not a bool\r\n"
12013 );
12014 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[],[2,3]]}"#]);
12018 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]"]), "$-1\r\n");
12019 assert_eq!(
12020 f.run(&[b"JSON.GET", b"doc", b".a"]),
12021 bulk("[[],[2]]").as_str()
12022 );
12023 }
12024
12025 #[test]
12028 fn a_write_reaches_a_match_that_sits_inside_another_match() {
12029 let mut f = Fixture::new();
12030 let nested = br#"{"a":[{"a":[7]},{"a":[7,7]}]}"#;
12031
12032 f.run(&[b"JSON.SET", b"doc", b"$", nested]);
12033 assert_eq!(
12034 f.run(&[b"JSON.ARRAPPEND", b"doc", b"$..a", b"9"]),
12035 "*3\r\n:3\r\n:2\r\n:3\r\n"
12036 );
12037 assert_eq!(
12038 f.run(&[b"JSON.GET", b"doc", b"$"]),
12039 bulk(r#"[{"a":[{"a":[7,9]},{"a":[7,7,9]},9]}]"#).as_str()
12040 );
12041
12042 f.run(&[b"JSON.SET", b"doc", b"$", nested]);
12045 assert_eq!(
12046 f.run(&[b"JSON.ARRTRIM", b"doc", b"$..a", b"0", b"0"]),
12047 "*3\r\n:1\r\n:1\r\n:1\r\n"
12048 );
12049 assert_eq!(
12050 f.run(&[b"JSON.GET", b"doc", b"$"]),
12051 bulk(r#"[{"a":[{"a":[7]}]}]"#).as_str()
12052 );
12053
12054 f.run(&[b"JSON.SET", b"doc", b"$", nested]);
12057 assert_eq!(
12058 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$..a[0]", b"1"]),
12059 bulk("[null,8,8]").as_str()
12060 );
12061 }
12062
12063 #[test]
12066 fn a_bad_operand_is_not_the_answer_when_the_path_found_nothing_to_use_it_on() {
12067 let mut f = Fixture::new();
12068 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"n":7,"s":"t"}"#]);
12069
12070 assert_eq!(
12073 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", br#""x""#]),
12074 bulk("[null]").as_str()
12075 );
12076 assert_eq!(
12077 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"notjson"]),
12078 bulk("[null]").as_str()
12079 );
12080 assert_eq!(
12081 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.missing", b"notjson"]),
12082 bulk("[]").as_str()
12083 );
12084 assert_eq!(
12085 f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", br#""x""#]),
12086 "-ERR Path does not exist or does not contains a number\r\n"
12087 );
12088 assert_eq!(
12090 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.n", br#""x""#]),
12091 "-bad input number\r\n"
12092 );
12093
12094 assert_eq!(
12096 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", b"1"]),
12097 "*1\r\n$-1\r\n"
12098 );
12099 assert_eq!(
12100 f.run(&[b"JSON.STRAPPEND", b"doc", b".n", b"1"]),
12101 "-ERR Path does not exist or not a string\r\n"
12102 );
12103 assert_eq!(
12104 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", b"1"]),
12105 "-WRONGTYPE wrong type of path value - expected string\r\n"
12106 );
12107
12108 assert_eq!(
12110 f.run(&[b"JSON.NUMINCRBY", b"nope", b"$.a", br#""x""#]),
12111 "-ERR could not perform this operation on a key that doesn't exist\r\n"
12112 );
12113 assert_eq!(
12114 f.run(&[b"JSON.STRAPPEND", b"nope", b"$.a", b"1"]),
12115 "-ERR could not perform this operation on a key that doesn't exist\r\n"
12116 );
12117 }
12118
12119 #[test]
12122 fn a_merge_patch_adds_replaces_and_deletes_in_one_write() {
12123 let mut f = Fixture::new();
12124
12125 assert_eq!(
12129 f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"x":null,"y":1}"#]),
12130 "+OK\r\n"
12131 );
12132 assert_eq!(
12133 f.run(&[b"JSON.GET", b"doc", b"$"]),
12134 bulk(r#"[{"x":null,"y":1}]"#).as_str()
12135 );
12136
12137 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1,"c":2},"d":3}"#]);
12140 assert_eq!(
12141 f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"a":{"b":null,"e":4}}"#]),
12142 "+OK\r\n"
12143 );
12144 assert_eq!(
12145 f.run(&[b"JSON.GET", b"doc", b"$"]),
12146 bulk(r#"[{"a":{"c":2,"e":4},"d":3}]"#).as_str()
12147 );
12148
12149 assert_eq!(f.run(&[b"JSON.MERGE", b"doc", b"$.a", b"[1,2]"]), "+OK\r\n");
12151 assert_eq!(
12152 f.run(&[b"JSON.GET", b"doc", b"$"]),
12153 bulk(r#"[{"a":[1,2],"d":3}]"#).as_str()
12154 );
12155
12156 assert_eq!(
12160 f.run(&[b"JSON.MERGE", b"doc", b"$.d", br#"{"p":null,"q":9}"#]),
12161 "+OK\r\n"
12162 );
12163 assert_eq!(
12164 f.run(&[b"JSON.GET", b"doc", b"$"]),
12165 bulk(r#"[{"a":[1,2],"d":{"q":9}}]"#).as_str()
12166 );
12167
12168 assert_eq!(
12172 f.run(&[b"JSON.MERGE", b"doc", b"$.new", br#"{"z":null}"#]),
12173 "+OK\r\n"
12174 );
12175 assert_eq!(
12176 f.run(&[b"JSON.GET", b"doc", b"$.new"]),
12177 bulk(r#"[{"z":null}]"#).as_str()
12178 );
12179 assert_eq!(
12180 f.run(&[b"JSON.MERGE", b"doc", b"$.no.deep", b"1"]),
12181 "$-1\r\n"
12182 );
12183 assert_eq!(
12184 f.run(&[b"JSON.MERGE", b"doc", b"$.no.*", b"1"]),
12185 "-Err wrong static path\r\n"
12186 );
12187
12188 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"n":1},"b":{"n":2}}"#]);
12190 assert_eq!(
12191 f.run(&[b"JSON.MERGE", b"doc", b"$.*", br#"{"m":0}"#]),
12192 "+OK\r\n"
12193 );
12194 assert_eq!(
12195 f.run(&[b"JSON.GET", b"doc", b"$"]),
12196 bulk(r#"[{"a":{"m":0,"n":1},"b":{"m":0,"n":2}}]"#).as_str()
12197 );
12198
12199 assert_eq!(
12201 f.run(&[b"JSON.MERGE", b"doc", b"$", b"{}", b"more"]),
12202 "-ERR syntax error\r\n"
12203 );
12204 assert_eq!(
12205 f.run(&[b"JSON.MERGE", b"gone", b"$.a", b"1"]),
12206 "-ERR new objects must be created at the root\r\n"
12207 );
12208 f.run(&[b"SET", b"str", b"x"]);
12209 assert_eq!(
12210 f.run(&[b"JSON.MERGE", b"str", b"$", b"1"]),
12211 "-Existing key has wrong Redis type\r\n"
12212 );
12213 }
12214
12215 #[test]
12218 fn a_merge_down_a_descent_keeps_what_the_inner_match_did() {
12219 let mut f = Fixture::new();
12220 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
12221 assert_eq!(
12222 f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"m":1}"#]),
12223 "+OK\r\n"
12224 );
12225 assert_eq!(
12229 f.run(&[b"JSON.GET", b"doc", b"$"]),
12230 bulk(r#"[{"a":{"b":{"m":1},"m":1},"c":{"m":1}}]"#).as_str()
12231 );
12232
12233 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
12236 assert_eq!(
12237 f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"a":null}"#]),
12238 "+OK\r\n"
12239 );
12240 assert_eq!(
12241 f.run(&[b"JSON.GET", b"doc", b"$"]),
12242 bulk(r#"[{"a":{"b":{}},"c":{}}]"#).as_str()
12243 );
12244 }
12245
12246 #[test]
12249 fn a_filter_path_reads_and_writes_the_members_it_keeps() {
12250 let mut f = Fixture::new();
12251 let doc = br#"{"book":[{"t":"a","p":8},{"t":"b","p":13},{"t":"c","p":9}],"cap":10}"#;
12252 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
12253
12254 assert_eq!(
12255 f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < 10)].t"]),
12256 bulk(r#"["a","c"]"#).as_str()
12257 );
12258 assert_eq!(
12261 f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < $.cap)].t"]),
12262 bulk(r#"["a","c"]"#).as_str()
12263 );
12264 assert_eq!(
12266 f.run(&[b"JSON.GET", b"doc", b"book[?(@.p < 10)].t"]),
12267 bulk(r#""a""#).as_str()
12268 );
12269 assert_eq!(
12270 f.run(&[b"JSON.TYPE", b"doc", b"$.book[?(@.p > 10)]"]),
12271 "*1\r\n$6\r\nobject\r\n"
12272 );
12273
12274 assert_eq!(
12278 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.book[?(@.p < 10)].p", b"1"]),
12279 bulk("[9,10]").as_str()
12280 );
12281 assert_eq!(
12282 f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].t", br#""B""#]),
12283 "+OK\r\n"
12284 );
12285 assert_eq!(
12286 f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].n", b"1"]),
12287 "-Err wrong static path\r\n"
12288 );
12289 assert_eq!(
12290 f.run(&[b"JSON.DEL", b"doc", b"$.book[?(@.p > 9)]"]),
12291 ":2\r\n"
12292 );
12293 assert_eq!(
12294 f.run(&[b"JSON.GET", b"doc", b"$"]),
12295 bulk(r#"[{"cap":10,"book":[{"p":9,"t":"a"}]}]"#).as_str()
12296 );
12297
12298 assert!(
12301 f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p <)]"])
12302 .starts_with("-ERR")
12303 );
12304 assert!(
12305 f.run(&[b"JSON.GET", b"nokey", b"$.book[?(@.p <)]"])
12306 .starts_with("-ERR")
12307 );
12308 }
12309
12310 #[test]
12313 fn a_filter_takes_the_membership_operators_and_the_methods_too() {
12314 let mut f = Fixture::new();
12315 let doc = br#"{"box":[{"t":"a","n":[1,2],"g":"x"},{"t":"b","n":[9],"g":"y"}]}"#;
12316 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
12317
12318 for (path, want) in [
12319 (&b"$.box[?(@.g in [\"x\"])].t"[..], r#"["a"]"#),
12320 (b"$.box[?(@.g nin [\"x\"])].t", r#"["b"]"#),
12321 (b"$.box[?(@.n anyof [2,3])].t", r#"["a"]"#),
12322 (b"$.box[?(@.n subsetof [1,2,3])].t", r#"["a"]"#),
12323 (b"$.box[?(@.n size 2)].t", r#"["a"]"#),
12324 (b"$.box[?(@.n empty false)].t", r#"["a","b"]"#),
12325 (b"$.box[?(@.n.length() == 1)].t", r#"["b"]"#),
12326 (b"$.box[?(@.n.sum() > 5)].t", r#"["b"]"#),
12327 (b"$.box[?(@.n[0] + 1 == 2)].t", r#"["a"]"#),
12328 (b"$.box[?(@~ size 3)].t", r#"["a","b"]"#),
12329 (b"$.box[?(@.n~)].t", "[]"),
12330 (b"$.box[?(@.n sizeof 2)].t", r#"["a"]"#),
12331 (b"$.box[?(-@.n[0] == -9)].t", r#"["b"]"#),
12332 (b"$.box[?(1 in @.n)].t", r#"["a"]"#),
12333 (b"$.box[?(\"g\" in @~)].t", r#"["a","b"]"#),
12334 ] {
12335 assert_eq!(f.run(&[b"JSON.GET", b"doc", path]), bulk(want).as_str());
12336 }
12337
12338 assert_eq!(
12341 f.run(&[b"JSON.SET", b"doc", b"$.box[?(@.n size 1)].g", br#""z""#]),
12342 "+OK\r\n"
12343 );
12344 assert_eq!(
12345 f.run(&[b"JSON.GET", b"doc", b"$.box[?(@.g == \"z\")].t"]),
12346 bulk(r#"["b"]"#).as_str()
12347 );
12348 }
12349
12350 #[test]
12353 fn a_merge_onto_a_number_inside_an_array_is_a_merge_and_not_an_error() {
12354 let mut f = Fixture::new();
12355 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2]}"#]);
12356 assert_eq!(
12357 f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
12358 "+OK\r\n"
12359 );
12360 assert_eq!(
12361 f.run(&[b"JSON.GET", b"doc", b"$"]),
12362 bulk(r#"[{"a":[{"x":1},2]}]"#).as_str()
12363 );
12364 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,"s"]}"#]);
12367 assert_eq!(
12368 f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
12369 "+OK\r\n"
12370 );
12371 assert_eq!(
12372 f.run(&[b"JSON.GET", b"doc", b"$"]),
12373 bulk(r#"[{"a":[{"x":1},"s"]}]"#).as_str()
12374 );
12375 }
12376
12377 #[test]
12380 fn an_mset_writes_every_triple_it_can_and_checks_the_rest_up_front() {
12381 let mut f = Fixture::new();
12382 assert_eq!(
12383 f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b", b"$", b"2"]),
12384 "+OK\r\n"
12385 );
12386 assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[1]").as_str());
12387 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[2]").as_str());
12388
12389 assert_eq!(
12391 f.run(&[b"JSON.MSET", b"a", b"$", b"3", b"a", b"$", b"4"]),
12392 "+OK\r\n"
12393 );
12394 assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[4]").as_str());
12395
12396 f.run(&[b"JSON.SET", b"a", b"$", br#"{"n":1}"#]);
12401 assert_eq!(
12402 f.run(&[b"JSON.MSET", b"a", b"$.no.deep", b"9", b"b", b"$", b"5"]),
12403 "$-1\r\n"
12404 );
12405 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[5]").as_str());
12406 assert_eq!(
12407 f.run(&[b"JSON.MSET", b"b", b"$", b"6", b"a", b"$.no.deep", b"9"]),
12408 "$-1\r\n"
12409 );
12410 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
12411
12412 f.run(&[b"SET", b"str", b"x"]);
12417 assert_eq!(
12418 f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"b", b"$", b"notjson"]),
12419 "-this is not the start of a value, at byte 0 of the JSON text\r\n"
12420 );
12421 assert_eq!(
12422 f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"str", b"$", b"1"]),
12423 "-Existing key has wrong Redis type\r\n"
12424 );
12425 assert_eq!(
12426 f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"gone", b"$.x", b"1"]),
12427 "-ERR new objects must be created at the root\r\n"
12428 );
12429 assert_eq!(f.run(&[b"JSON.GET", b"a", b"$.n"]), bulk("[1]").as_str());
12430
12431 assert_eq!(
12436 f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$.no.*", b"9"]),
12437 "-Err wrong static path\r\n"
12438 );
12439 assert_eq!(
12440 f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$[0]", b"9"]),
12441 "-ERR array index out of range\r\n"
12442 );
12443 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
12444
12445 f.run(&[b"JSON.SET", b"c", b"$", br#"{"n":1}"#]);
12449 assert_eq!(
12450 f.run(&[b"JSON.MSET", b"c", b"$", br#"{"n":2}"#, b"c", b"$.n", b"3"]),
12451 "+OK\r\n"
12452 );
12453 assert_eq!(
12454 f.run(&[b"JSON.GET", b"c", b"$"]),
12455 bulk(r#"[{"n":3}]"#).as_str()
12456 );
12457
12458 assert_eq!(
12461 f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b"]),
12462 "-ERR wrong number of arguments for 'json.mset' command\r\n"
12463 );
12464 }
12465
12466 #[test]
12469 fn json_resp_answers_the_document_as_resp_types() {
12470 let mut f = Fixture::new();
12471 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[2,"c"]}"#]);
12472 assert_eq!(
12473 f.run(&[b"JSON.RESP", b"doc"]),
12474 "*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"
12475 );
12476 assert_eq!(
12478 f.run(&[b"JSON.RESP", b"doc", b"$.b"]),
12479 "*1\r\n*3\r\n+[\r\n:2\r\n$1\r\nc\r\n"
12480 );
12481
12482 f.run(&[
12483 b"JSON.SET",
12484 b"doc",
12485 b"$",
12486 br#"{"f":2.5,"t":true,"z":null,"e":[],"o":{}}"#,
12487 ]);
12488 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".e"]), "*1\r\n+[\r\n");
12489 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".o"]), "*1\r\n+{\r\n");
12490 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".f"]), bulk("2.5").as_str());
12493 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".t"]), "+true\r\n");
12494 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".z"]), "$-1\r\n");
12495
12496 assert_eq!(
12499 f.run(&[b"JSON.RESP", b"doc", b".nope"]),
12500 "-ERR Path does not exist\r\n"
12501 );
12502 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b"$.nope"]), "*0\r\n");
12503 assert_eq!(f.run(&[b"JSON.RESP", b"gone"]), "$-1\r\n");
12504 assert_eq!(f.run(&[b"JSON.RESP", b"gone", b"$"]), "$-1\r\n");
12505 }
12506
12507 #[test]
12511 fn json_debug_answers_a_byte_count_and_its_own_help() {
12512 let mut f = Fixture::new();
12513 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2],"s":"hello"}"#]);
12514 let one = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".s"]);
12515 assert!(one.starts_with(':'), "{one}");
12516 assert_eq!(
12517 f.run(&[b"JSON.DEBUG", b"memory", b"doc", b"$.s"]),
12518 format!("*1\r\n{one}")
12519 );
12520 let whole = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc"]);
12521 assert!(whole.starts_with(':') && whole.len() > one.len(), "{whole}");
12522
12523 assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone"]), ":0\r\n");
12527 assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone", b"$"]), "*0\r\n");
12528 assert_eq!(
12529 f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".nope"]),
12530 "-ERR Path does not exist\r\n"
12531 );
12532 assert_eq!(
12533 f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b"$.nope"]),
12534 "*0\r\n"
12535 );
12536
12537 assert_eq!(
12538 f.run(&[b"JSON.DEBUG", b"HELP"]),
12539 "*2\r\n$42\r\nMEMORY <key> [path] - reports memory usage\r\n\
12540 $34\r\nHELP - this message\r\n"
12541 );
12542 assert_eq!(
12543 f.run(&[b"JSON.DEBUG", b"NOPE"]),
12544 "-ERR unknown subcommand - try `JSON.DEBUG HELP`\r\n"
12545 );
12546 assert_eq!(
12547 f.run(&[b"JSON.DEBUG", b"MEMORY"]),
12548 "-ERR wrong number of arguments for 'json.debug' command\r\n"
12549 );
12550 }
12551
12552 #[test]
12557 fn the_first_vadd_decides_how_wide_the_set_is() {
12558 let mut f = Fixture::new();
12559 assert_eq!(
12560 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]),
12561 ":1\r\n"
12562 );
12563 assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
12564 assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
12565 assert_eq!(
12568 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"east"]),
12569 ":0\r\n"
12570 );
12571 assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
12572 assert_eq!(
12575 f.run(&[b"VADD", b"v", b"VALUES", b"3", b"1", b"0", b"0", b"up"]),
12576 "-ERR Vector dimension mismatch - got 3 but set has 2\r\n"
12577 );
12578 assert_eq!(
12581 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"0", b"nowhere"]),
12582 ":1\r\n"
12583 );
12584 assert_eq!(
12585 f.run(&[b"VEMB", b"v", b"nowhere"]),
12586 "*2\r\n$1\r\n0\r\n$1\r\n0\r\n"
12587 );
12588 assert_eq!(
12592 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"other", b"BIN"]),
12593 "-ERR asked quantization mismatch with existing vector set\r\n"
12594 );
12595 assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
12598 assert_eq!(f.run(&[b"VDIM", b"fresh"]), "-ERR key does not exist\r\n");
12599 assert_eq!(f.run(&[b"VCARD", b"fresh"]), ":0\r\n");
12600 }
12601
12602 #[test]
12605 fn vemb_gives_back_the_vector_and_vsim_gives_back_a_similarity() {
12606 let mut f = Fixture::new();
12607 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"4", b"a"]);
12608 assert_eq!(
12614 f.run(&[b"VEMB", b"v", b"a"]),
12615 "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
12616 );
12617 f.run(&[b"VADD", b"n", b"VALUES", b"2", b"3", b"4", b"a", b"NOQUANT"]);
12619 assert_eq!(
12620 f.run(&[b"VEMB", b"n", b"a"]),
12621 "*2\r\n$1\r\n3\r\n$1\r\n4\r\n"
12622 );
12623 f.run(&[b"VADD", b"b", b"VALUES", b"2", b"3", b"-4", b"a", b"BIN"]);
12626 assert_eq!(
12627 f.run(&[b"VEMB", b"b", b"a"]),
12628 "*2\r\n$1\r\n1\r\n$2\r\n-1\r\n"
12629 );
12630 assert_eq!(f.run(&[b"VEMB", b"v", b"nobody"]), "*-1\r\n");
12631 assert_eq!(f.run(&[b"VEMB", b"nokey", b"a"]), "*-1\r\n");
12632
12633 let mut f = Fixture::new();
12637 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"0", b"a"]);
12638 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"-1", b"0", b"opposite"]);
12639 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"7", b"across"]);
12640 assert_eq!(
12641 f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"2", b"0", b"WITHSCORES"]),
12642 "*6\r\n$1\r\na\r\n$1\r\n1\r\n$6\r\nacross\r\n$3\r\n0.5\r\n\
12643 $8\r\nopposite\r\n$1\r\n0\r\n"
12644 );
12645 assert_eq!(
12648 f.run(&[b"VSIM", b"v", b"ELE", b"a"]),
12649 "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
12650 );
12651 assert_eq!(f.run(&[b"VSIM", b"v", b"ELE", b"nobody"]), "*0\r\n");
12654 assert_eq!(f.run(&[b"VSIM", b"nokey", b"ELE", b"a"]), "*0\r\n");
12655 assert_eq!(
12658 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1"]),
12659 "*1\r\n$6\r\nacross\r\n"
12660 );
12661 assert_eq!(
12662 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"TRUTH"]),
12663 "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
12664 );
12665 assert_eq!(
12669 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1", b"EF", b"500"]),
12670 "*1\r\n$6\r\nacross\r\n"
12671 );
12672
12673 let mut g = Fixture::new();
12676 g.run(&[b"HELLO", b"3"]);
12677 g.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
12678 assert_eq!(
12679 g.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHSCORES"]),
12680 "%1\r\n$4\r\neast\r\n,1\r\n"
12681 );
12682 }
12683
12684 #[test]
12686 fn an_attribute_is_bytes_and_an_empty_one_takes_it_off() {
12687 let mut f = Fixture::new();
12688 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
12689 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
12690 assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]), ":1\r\n");
12691 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$7\r\n{\"k\":1}\r\n");
12692 assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"not json"]), ":1\r\n");
12695 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$8\r\nnot json\r\n");
12696 assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b""]), ":1\r\n");
12698 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
12699 assert_eq!(f.run(&[b"VSETATTR", b"v", b"nobody", b"{}"]), ":0\r\n");
12702 assert_eq!(f.run(&[b"VSETATTR", b"nokey", b"east", b"{}"]), ":0\r\n");
12703 assert_eq!(f.run(&[b"EXISTS", b"nokey"]), ":0\r\n");
12704 assert_eq!(f.run(&[b"VGETATTR", b"v", b"nobody"]), "$-1\r\n");
12707 assert_eq!(f.run(&[b"VISMEMBER", b"v", b"east"]), ":1\r\n");
12708 assert_eq!(f.run(&[b"VISMEMBER", b"v", b"nobody"]), ":0\r\n");
12709 assert_eq!(f.run(&[b"VISMEMBER", b"nokey", b"east"]), ":0\r\n");
12710
12711 f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
12713 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
12714 assert_eq!(
12715 f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHATTRIBS"]),
12716 "*4\r\n$4\r\neast\r\n$7\r\n{\"k\":1}\r\n$5\r\nnorth\r\n$-1\r\n"
12717 );
12718 }
12719
12720 #[test]
12723 fn vrem_takes_the_attribute_with_it() {
12724 let mut f = Fixture::new();
12725 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
12726 f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
12727 assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":1\r\n");
12728 assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":0\r\n");
12729 assert_eq!(f.run(&[b"VREM", b"nokey", b"east"]), ":0\r\n");
12730 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
12733
12734 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
12737 f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
12738 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
12739 f.run(&[b"VREM", b"v", b"east"]);
12740 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"between"]);
12741 assert_eq!(f.run(&[b"VGETATTR", b"v", b"between"]), "$-1\r\n");
12742 }
12743
12744 #[test]
12747 fn vinfo_says_partition_first() {
12748 let mut f = Fixture::new();
12749 f.run(&[
12750 b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east", b"M", b"32",
12751 ]);
12752 f.run(&[b"VSETATTR", b"v", b"east", b"{}"]);
12753 let info = f.run(&[b"VINFO", b"v"]);
12754 assert!(info.starts_with("*24\r\n$10\r\nindex-type\r\n$9\r\npartition\r\n"));
12755 assert!(info.contains("$6\r\nhnsw-m\r\n:32\r\n"), "{info}");
12758 assert!(info.contains("$10\r\nvector-dim\r\n:2\r\n"), "{info}");
12759 assert!(info.contains("$16\r\nattributes-count\r\n:1\r\n"), "{info}");
12760 assert!(
12763 info.contains("$10\r\nquant-type\r\n$4\r\nint8\r\n"),
12764 "{info}"
12765 );
12766 let mut f = Fixture::new();
12767 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north", b"BIN"]);
12768 assert!(
12769 f.run(&[b"VINFO", b"v"])
12770 .contains("$10\r\nquant-type\r\n$3\r\nbin\r\n")
12771 );
12772 assert_eq!(f.run(&[b"VINFO", b"nokey"]), "$-1\r\n");
12773 }
12774
12775 fn named() -> Fixture {
12777 let mut f = Fixture::new();
12778 for (i, name) in ["alpha", "beta", "gamma", "delta", "epsilon"]
12779 .iter()
12780 .enumerate()
12781 {
12782 let x = (i + 1).to_string();
12783 f.run(&[
12784 b"VADD",
12785 b"r",
12786 b"VALUES",
12787 b"2",
12788 x.as_bytes(),
12789 b"1",
12790 name.as_bytes(),
12791 ]);
12792 }
12793 f
12794 }
12795
12796 #[test]
12799 fn vrange_walks_the_names_and_not_the_vectors() {
12800 let mut f = named();
12801 assert_eq!(
12802 f.run(&[b"VRANGE", b"r", b"-", b"+"]),
12803 "*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"
12804 );
12805 assert_eq!(
12806 f.run(&[b"VRANGE", b"r", b"[a", b"[d"]),
12807 "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n",
12808 "the high end is a name and not a prefix, so delta is past it"
12809 );
12810 assert_eq!(
12811 f.run(&[b"VRANGE", b"r", b"(alpha", b"(gamma"]),
12812 "*3\r\n$4\r\nbeta\r\n$5\r\ndelta\r\n$7\r\nepsilon\r\n"
12813 );
12814 assert_eq!(
12815 f.run(&[b"VRANGE", b"r", b"[beta", b"[beta"]),
12816 "*1\r\n$4\r\nbeta\r\n"
12817 );
12818 assert_eq!(f.run(&[b"VRANGE", b"r", b"[z", b"+"]), "*0\r\n");
12819 f.run(&[b"VADD", b"r", b"VALUES", b"2", b"1", b"1", b"Beta"]);
12822 assert_eq!(
12823 f.run(&[b"VRANGE", b"r", b"-", b"[beta"]),
12824 "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
12825 );
12826 assert_eq!(f.run(&[b"VRANGE", b"nokey", b"-", b"+"]), "*0\r\n");
12827 }
12828
12829 #[test]
12832 fn a_vrange_count_of_zero_asks_for_nothing() {
12833 let mut f = named();
12834 assert_eq!(
12835 f.run(&[b"VRANGE", b"r", b"-", b"+", b"2"]),
12836 "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
12837 );
12838 assert_eq!(f.run(&[b"VRANGE", b"r", b"-", b"+", b"0"]), "*0\r\n");
12839 assert!(
12840 f.run(&[b"VRANGE", b"r", b"-", b"+", b"-1"])
12841 .starts_with("*5\r\n"),
12842 "a negative count is no limit at all"
12843 );
12844 }
12845
12846 #[test]
12849 fn vrange_says_which_end_it_could_not_read() {
12850 let mut f = named();
12851 assert_eq!(
12852 f.run(&[b"VRANGE", b"r", b"x", b"y"]),
12853 "-ERR invalid start range format\r\n"
12854 );
12855 assert_eq!(
12856 f.run(&[b"VRANGE", b"r", b"+", b"x"]),
12857 "-ERR invalid end range format\r\n",
12858 "the high end is spelled wrong, which is worth saying before the \
12859 low end being on the wrong side"
12860 );
12861 assert_eq!(
12862 f.run(&[b"VRANGE", b"r", b"+", b"-"]),
12863 "-ERR '-' can only be used as first argument, '+' only as second\r\n"
12864 );
12865 assert_eq!(
12868 f.run(&[b"VRANGE", b"r", b"[", b"+"]),
12869 "-ERR invalid start range format\r\n"
12870 );
12871 assert_eq!(
12872 f.run(&[b"VRANGE", b"r", b"x", b"+", b"z"]),
12873 "-ERR invalid COUNT value\r\n"
12874 );
12875 assert_eq!(
12876 f.run(&[b"VRANGE", b"r", b"-", b"+", b"2", b"extra"]),
12877 "-ERR wrong number of arguments for 'VRANGE' command\r\n"
12878 );
12879 f.run(&[b"SET", b"s", b"x"]);
12880 assert!(
12881 f.run(&[b"VRANGE", b"s", b"-", b"+"])
12882 .starts_with("-WRONGTYPE")
12883 );
12884 }
12885
12886 #[test]
12889 fn reduce_is_refused_and_not_ignored() {
12890 let mut f = Fixture::new();
12891 let reduce = f.run(&[
12892 b"VADD", b"v", b"REDUCE", b"1", b"VALUES", b"2", b"1", b"0", b"east",
12893 ]);
12894 assert!(
12895 reduce.starts_with("-ERR REDUCE is not supported."),
12896 "{reduce}"
12897 );
12898 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
12899 }
12900
12901 #[test]
12904 fn vsim_filter_reads_the_attributes() {
12905 let mut f = Fixture::new();
12906 for (name, x, y, attr) in [
12907 ("a", "1", "0", r#"{"lang":"en","year":1999}"#),
12908 ("b", "9", "1", r#"{"lang":"fr","year":2005}"#),
12909 ("c", "8", "2", r#"{"lang":"en","year":1970}"#),
12910 ("d", "7", "3", r#"{"lang":"en","year":2020}"#),
12911 ] {
12912 f.run(&[
12913 b"VADD",
12914 b"v",
12915 b"VALUES",
12916 b"2",
12917 x.as_bytes(),
12918 y.as_bytes(),
12919 name.as_bytes(),
12920 b"SETATTR",
12921 attr.as_bytes(),
12922 ]);
12923 }
12924 assert_eq!(
12927 f.run(&[
12928 b"VSIM",
12929 b"v",
12930 b"VALUES",
12931 b"2",
12932 b"9",
12933 b"1",
12934 b"COUNT",
12935 b"2",
12936 b"FILTER",
12937 b".lang == \"en\"",
12938 ]),
12939 "*2\r\n$1\r\na\r\n$1\r\nc\r\n"
12940 );
12941 assert_eq!(
12944 f.run(&[
12945 b"VSIM",
12946 b"v",
12947 b"VALUES",
12948 b"2",
12949 b"9",
12950 b"1",
12951 b"FILTER",
12952 b".lang == 'en' and .year > 1980",
12953 ]),
12954 "*2\r\n$1\r\na\r\n$1\r\nd\r\n"
12955 );
12956 assert_eq!(
12958 f.run(&[
12959 b"VSIM",
12960 b"v",
12961 b"VALUES",
12962 b"2",
12963 b"9",
12964 b"1",
12965 b"FILTER",
12966 b".lang in ['fr', 'de']",
12967 ]),
12968 "*1\r\n$1\r\nb\r\n"
12969 );
12970 assert_eq!(
12971 f.run(&[
12972 b"VSIM",
12973 b"v",
12974 b"VALUES",
12975 b"2",
12976 b"9",
12977 b"1",
12978 b"FILTER",
12979 b".rating > 3"
12980 ]),
12981 "*0\r\n"
12982 );
12983 assert_eq!(
12986 f.run(&[
12987 b"VSIM",
12988 b"v",
12989 b"VALUES",
12990 b"2",
12991 b"9",
12992 b"1",
12993 b"TRUTH",
12994 b"FILTER",
12995 b".year < 1980",
12996 ]),
12997 "*1\r\n$1\r\nc\r\n"
12998 );
12999 f.run(&[b"VSETATTR", b"v", b"b", r#"{"lang":"en"}"#.as_bytes()]);
13002 assert_eq!(
13003 f.run(&[
13004 b"VSIM",
13005 b"v",
13006 b"VALUES",
13007 b"2",
13008 b"9",
13009 b"1",
13010 b"COUNT",
13011 b"1",
13012 b"FILTER",
13013 b".lang == \"en\"",
13014 ]),
13015 "*1\r\n$1\r\nb\r\n"
13016 );
13017 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"9", b"2", b"b"]);
13020 assert_eq!(
13021 f.run(&[
13022 b"VSIM",
13023 b"v",
13024 b"VALUES",
13025 b"2",
13026 b"9",
13027 b"1",
13028 b"COUNT",
13029 b"1",
13030 b"FILTER",
13031 b".lang == \"en\"",
13032 ]),
13033 "*1\r\n$1\r\nb\r\n"
13034 );
13035
13036 let bad = f.run(&[b"VSIM", b"nokey", b"ELE", b"e", b"FILTER", b".k =="]);
13039 assert_eq!(bad, "-ERR invalid FILTER expression\r\n");
13040 assert_eq!(
13041 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"FILTER", b"junk"]),
13042 "-ERR invalid FILTER expression\r\n"
13043 );
13044 assert_eq!(
13047 f.run(&[
13048 b"VSIM",
13049 b"v",
13050 b"VALUES",
13051 b"2",
13052 b"9",
13053 b"1",
13054 b"COUNT",
13055 b"1",
13056 b"FILTER-EF",
13057 b"500",
13058 b"FILTER",
13059 b".lang == 'en'",
13060 ]),
13061 "*1\r\n$1\r\nb\r\n"
13062 );
13063 assert_eq!(
13064 f.run(&[
13065 b"VSIM",
13066 b"v",
13067 b"VALUES",
13068 b"2",
13069 b"9",
13070 b"1",
13071 b"COUNT",
13072 b"1",
13073 b"FILTER-EF",
13074 b"0"
13075 ]),
13076 "*1\r\n$1\r\nb\r\n"
13077 );
13078 assert_eq!(
13079 f.run(&[
13080 b"VSIM",
13081 b"v",
13082 b"VALUES",
13083 b"2",
13084 b"9",
13085 b"1",
13086 b"FILTER-EF",
13087 b"lots"
13088 ]),
13089 "-ERR EF must be a positive integer\r\n"
13090 );
13091 }
13092
13093 #[test]
13096 fn the_keyspace_sees_a_vector_set_key_like_any_other() {
13097 let mut f = Fixture::new();
13098 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
13099 assert_eq!(f.run(&[b"TYPE", b"v"]), "+vectorset\r\n");
13100 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":1\r\n");
13101 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"v"]), "$6\r\nrabitq\r\n");
13102 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\nv\r\n");
13103 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
13104 assert_eq!(f.run(&[b"EXPIRE", b"v", b"100"]), ":1\r\n");
13105 assert_eq!(f.run(&[b"TTL", b"v"]), ":100\r\n");
13106 assert_eq!(f.run(&[b"PERSIST", b"v"]), ":1\r\n");
13107 assert_eq!(f.run(&[b"DEL", b"v"]), ":1\r\n");
13108 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
13109
13110 f.run(&[b"SET", b"s", b"1"]);
13112 assert_eq!(
13113 f.run(&[b"VADD", b"s", b"VALUES", b"2", b"1", b"0", b"east"]),
13114 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
13115 );
13116 assert_eq!(
13117 f.run(&[b"VCARD", b"s"]),
13118 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
13119 );
13120 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
13121 assert_eq!(
13122 f.run(&[b"GET", b"v"]),
13123 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
13124 );
13125 f.run(&[b"G.NADD", b"social", b"ada"]);
13129 assert_eq!(
13130 f.run(&[b"VCARD", b"social"]),
13131 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
13132 );
13133 assert_eq!(
13134 f.run(&[b"G.NGET", b"v", b"ada"]),
13135 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
13136 );
13137 }
13138
13139 #[test]
13142 fn vrandmember_has_the_two_shapes_srandmember_has() {
13143 let mut f = Fixture::new();
13144 for (i, name) in [&b"a"[..], b"b", b"c"].iter().enumerate() {
13145 let x = (i + 1).to_string();
13146 f.run(&[b"VADD", b"v", b"VALUES", b"2", x.as_bytes(), b"1", name]);
13147 }
13148 let one = f.run(&[b"VRANDMEMBER", b"v"]);
13150 assert!(one.starts_with("$1\r\n"), "{one}");
13151 let mut all = f.run(&[b"VRANDMEMBER", b"v", b"9"]);
13153 assert!(all.starts_with("*3\r\n"), "{all}");
13154 for name in ["a", "b", "c"] {
13155 assert!(all.contains(name), "{all} is missing {name}");
13156 }
13157 all = f.run(&[b"VRANDMEMBER", b"v", b"2"]);
13158 assert!(all.starts_with("*2\r\n"), "{all}");
13159 let many = f.run(&[b"VRANDMEMBER", b"v", b"-5"]);
13161 assert!(many.starts_with("*5\r\n"), "{many}");
13162 assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey"]), "$-1\r\n");
13164 assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
13165 }
13166
13167 #[test]
13170 fn vlinks_reports_one_layer_of_partition_neighbours() {
13171 let mut f = Fixture::new();
13172 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
13173 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
13174 assert_eq!(
13177 f.run(&[b"VLINKS", b"v", b"east"]),
13178 "*1\r\n*1\r\n$5\r\nnorth\r\n"
13179 );
13180 assert_eq!(
13181 f.run(&[b"VLINKS", b"v", b"east", b"WITHSCORES"]),
13182 "*1\r\n*2\r\n$5\r\nnorth\r\n$3\r\n0.5\r\n"
13183 );
13184 assert_eq!(f.run(&[b"VLINKS", b"v", b"nobody"]), "*-1\r\n");
13185 assert_eq!(f.run(&[b"VLINKS", b"nokey", b"east"]), "*-1\r\n");
13186 }
13187
13188 #[test]
13191 fn fp32_and_values_are_the_same_vector() {
13192 let mut f = Fixture::new();
13193 let mut blob = Vec::new();
13194 for x in [3.0f32, 4.0] {
13195 blob.extend_from_slice(&x.to_le_bytes());
13196 }
13197 assert_eq!(f.run(&[b"VADD", b"v", b"FP32", &blob, b"a"]), ":1\r\n");
13198 assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
13199 assert_eq!(
13200 f.run(&[b"VEMB", b"v", b"a"]),
13201 "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
13202 );
13203 assert_eq!(
13209 f.run(&[b"VEMB", b"v", b"a", b"RAW"]),
13210 "*4\r\n+int8\r\n$2\r\n_\x7f\r\n$1\r\n5\r\n$17\r\n0.800000011920929\r\n"
13211 );
13212 assert_eq!(
13214 f.run(&[b"VADD", b"w", b"FP32", b"abc", b"a"]),
13215 "-ERR invalid vector specification\r\n"
13216 );
13217 assert_eq!(
13219 f.run(&[b"VADD", b"w", b"VALUES", b"4", b"1", b"0", b"a"]),
13220 "-ERR syntax error\r\n"
13221 );
13222 assert_eq!(f.run(&[b"EXISTS", b"w"]), ":0\r\n");
13223 }
13224
13225 #[test]
13230 fn bf_add_makes_the_filter_and_says_whether_it_was_new() {
13231 let mut f = Fixture::new();
13232 assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":1\r\n");
13233 assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":0\r\n");
13234 assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"hello"]), ":1\r\n");
13235 assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"never"]), ":0\r\n");
13236 assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":1\r\n");
13237 assert_eq!(
13240 f.run(&[b"BF.INFO", b"b"]),
13241 "*10\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
13242 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
13243 +Expansion rate\r\n:2\r\n"
13244 );
13245 assert_eq!(f.run(&[b"TYPE", b"b"]), "+MBbloom--\r\n");
13246 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"b"]), "$3\r\nraw\r\n");
13247 assert_eq!(f.run(&[b"BF.CARD", b"gone"]), ":0\r\n");
13250 assert_eq!(f.run(&[b"BF.INFO", b"gone"]), "-ERR not found\r\n");
13251 }
13252
13253 #[test]
13259 fn a_wrong_type_is_a_miss_to_the_two_that_only_read_bits() {
13260 let mut f = Fixture::new();
13261 f.run(&[b"SET", b"s", b"text"]);
13262 assert_eq!(f.run(&[b"BF.EXISTS", b"s", b"x"]), ":0\r\n");
13263 assert_eq!(f.run(&[b"BF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
13264 for cmd in [
13265 vec![&b"BF.ADD"[..], b"s", b"x"],
13266 vec![&b"BF.MADD"[..], b"s", b"x"],
13267 vec![&b"BF.CARD"[..], b"s"],
13268 vec![&b"BF.INFO"[..], b"s"],
13269 vec![&b"BF.DEBUG"[..], b"s"],
13270 vec![&b"BF.SCANDUMP"[..], b"s", b"0"],
13271 ] {
13272 let name = String::from_utf8_lossy(cmd[0]).into_owned();
13273 assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
13274 }
13275 assert_eq!(
13278 f.run(&[b"BF.RESERVE", b"s", b"abc", b"10"]),
13279 "-ERR bad error rate\r\n"
13280 );
13281 assert!(
13282 f.run(&[b"BF.RESERVE", b"s", b"0.01", b"10"])
13283 .starts_with("-WRONGTYPE")
13284 );
13285 }
13286
13287 #[test]
13290 fn a_full_filter_grows_a_link_and_a_fixed_one_says_no() {
13291 let mut f = Fixture::new();
13292 assert_eq!(f.run(&[b"BF.RESERVE", b"g", b"0.01", b"10"]), "+OK\r\n");
13293 for i in 0..10u32 {
13294 assert_eq!(
13295 f.run(&[b"BF.ADD", b"g", i.to_string().as_bytes()]),
13296 ":1\r\n"
13297 );
13298 }
13299 assert_eq!(f.run(&[b"BF.INFO", b"g", b"FILTERS"]), "*1\r\n:1\r\n");
13300 assert_eq!(f.run(&[b"BF.ADD", b"g", b"11"]), ":1\r\n");
13301 assert_eq!(f.run(&[b"BF.INFO", b"g", b"filters"]), "*1\r\n:2\r\n");
13302 assert_eq!(f.run(&[b"BF.INFO", b"g", b"CAPACITY"]), "*1\r\n:30\r\n");
13305 assert_eq!(
13306 f.run(&[b"BF.DEBUG", b"g"]),
13307 "*3\r\n$7\r\nsize:11\r\n\
13308 $71\r\nbytes:16 bits:128 hashes:8 hashwidth:64 capacity:10 size:10 ratio:0.005\r\n\
13309 $71\r\nbytes:32 bits:256 hashes:9 hashwidth:64 capacity:20 size:1 ratio:0.0025\r\n"
13310 );
13311
13312 assert_eq!(
13314 f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]),
13315 "+OK\r\n"
13316 );
13317 assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":1\r\n");
13318 assert_eq!(f.run(&[b"BF.ADD", b"n", b"b"]), ":1\r\n");
13319 assert_eq!(
13320 f.run(&[b"BF.ADD", b"n", b"c"]),
13321 "-ERR non scaling filter is full\r\n"
13322 );
13323 assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":0\r\n");
13326 assert_eq!(f.run(&[b"BF.INFO", b"n", b"EXPANSION"]), "*1\r\n$-1\r\n");
13329 f.run(&[b"BF.RESERVE", b"z", b"0.01", b"2", b"EXPANSION", b"0"]);
13330 assert_eq!(f.run(&[b"BF.INFO", b"z", b"EXPANSION"]), "*1\r\n$-1\r\n");
13331 assert_eq!(
13334 f.run(&[
13335 b"BF.RESERVE",
13336 b"q",
13337 b"0.01",
13338 b"2",
13339 b"NONSCALING",
13340 b"EXPANSION",
13341 b"2"
13342 ]),
13343 "-Nonscaling filters cannot expand\r\n"
13344 );
13345 }
13346
13347 #[test]
13350 fn madd_truncates_its_reply_at_the_item_that_did_not_fit() {
13351 let mut f = Fixture::new();
13352 f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]);
13353 assert_eq!(
13354 f.run(&[b"BF.MADD", b"n", b"a", b"b", b"c", b"d"]),
13355 "*3\r\n:1\r\n:1\r\n-ERR non scaling filter is full\r\n"
13356 );
13357 assert_eq!(
13358 f.run(&[b"BF.MEXISTS", b"n", b"a", b"c"]),
13359 "*2\r\n:1\r\n:0\r\n"
13360 );
13361 }
13362
13363 #[test]
13366 fn insert_is_a_reserve_and_a_madd_with_different_errors() {
13367 let mut f = Fixture::new();
13368 assert_eq!(
13369 f.run(&[
13370 b"BF.INSERT",
13371 b"i",
13372 b"CAPACITY",
13373 b"50",
13374 b"ERROR",
13375 b"0.001",
13376 b"ITEMS",
13377 b"a",
13378 b"b"
13379 ]),
13380 "*2\r\n:1\r\n:1\r\n"
13381 );
13382 assert_eq!(f.run(&[b"BF.INFO", b"i", b"CAPACITY"]), "*1\r\n:50\r\n");
13383 assert_eq!(
13385 f.run(&[b"BF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
13386 "-ERR not found\r\n"
13387 );
13388 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
13389 assert_eq!(
13392 f.run(&[b"BF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
13393 "-Bad capacity\r\n"
13394 );
13395 assert_eq!(
13396 f.run(&[b"BF.INSERT", b"i", b"ERROR", b"2", b"ITEMS", b"a"]),
13397 "-Bad error rate\r\n"
13398 );
13399 assert_eq!(
13400 f.run(&[b"BF.INSERT", b"i", b"EXPANSION", b"99999", b"ITEMS", b"a"]),
13401 "-Bad expansion\r\n"
13402 );
13403 assert_eq!(
13408 f.run(&[b"BF.INSERT", b"ns", b"NOSUCHTHING", b"ITEMS", b"a"]),
13409 "*1\r\n:1\r\n"
13410 );
13411 assert_eq!(f.run(&[b"BF.INFO", b"ns", b"EXPANSION"]), "*1\r\n$-1\r\n");
13412 assert_eq!(
13416 f.run(&[b"BF.INSERT", b"e1", b"E", b"4", b"ITEMS", b"a"]),
13417 "*1\r\n:1\r\n"
13418 );
13419 assert_eq!(f.run(&[b"BF.INFO", b"e1", b"EXPANSION"]), "*1\r\n:4\r\n");
13420 assert_eq!(
13421 f.run(&[b"BF.INSERT", b"e2", b"ER", b"0.5", b"ITEMS", b"a"]),
13422 "*1\r\n:1\r\n"
13423 );
13424 assert_eq!(
13425 f.run(&[b"BF.INSERT", b"gone", b"NOC", b"ITEMS", b"a"]),
13426 "-ERR not found\r\n"
13427 );
13428 assert_eq!(
13430 f.run(&[b"BF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
13431 "-Unknown argument received\r\n"
13432 );
13433 assert_eq!(
13435 f.run(&[b"BF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
13436 "*1\r\n:1\r\n"
13437 );
13438 assert!(
13440 f.run(&[b"BF.INSERT", b"i", b"ITEMS"])
13441 .contains("wrong number of arguments")
13442 );
13443 }
13444
13445 #[test]
13448 fn a_dump_replays_into_a_filter_that_answers_the_same() {
13449 let mut f = Fixture::new();
13450 f.run(&[b"BF.RESERVE", b"src", b"0.01", b"10"]);
13451 for i in 0..25u32 {
13452 f.run(&[b"BF.ADD", b"src", i.to_string().as_bytes()]);
13453 }
13454 assert_eq!(f.run(&[b"BF.INFO", b"src", b"FILTERS"]), "*1\r\n:2\r\n");
13455
13456 let mut iter = b"0".to_vec();
13459 let mut chunks = 0;
13460 loop {
13461 let raw = f.raw(&[b"BF.SCANDUMP", b"src", &iter]);
13462 let text = String::from_utf8_lossy(&raw).into_owned();
13463 let next = text
13464 .split("\r\n")
13465 .nth(1)
13466 .and_then(|n| n.strip_prefix(':'))
13467 .expect("a two element reply of an iterator and a chunk")
13468 .to_owned();
13469 let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
13470 let data = &body[body
13471 .windows(2)
13472 .position(|w| w == b"\r\n")
13473 .expect("a length line")
13474 + 2..body.len() - 2];
13475 if next == "0" {
13476 assert!(data.is_empty(), "the last chunk is empty");
13477 break;
13478 }
13479 let put = f.run(&[b"BF.LOADCHUNK", b"dst", next.as_bytes(), data]);
13480 assert_eq!(put, "+OK\r\n", "loading chunk {chunks}");
13481 iter = next.into_bytes();
13482 chunks += 1;
13483 }
13484 assert_eq!(chunks, 3, "a header and one chunk per link");
13485
13486 assert_eq!(f.run(&[b"BF.INFO", b"dst"]), f.run(&[b"BF.INFO", b"src"]));
13487 assert_eq!(f.run(&[b"BF.DEBUG", b"dst"]), f.run(&[b"BF.DEBUG", b"src"]));
13488 for i in 0..25u32 {
13489 assert_eq!(
13490 f.run(&[b"BF.EXISTS", b"dst", i.to_string().as_bytes()]),
13491 ":1\r\n"
13492 );
13493 }
13494
13495 assert_eq!(
13498 f.run(&[b"BF.LOADCHUNK", b"dst", b"1", b"anything"]),
13499 "-ERR received bad data\r\n"
13500 );
13501 assert_eq!(
13502 f.run(&[b"BF.LOADCHUNK", b"fresh", b"1", b"anything"]),
13503 "-ERR received bad data\r\n"
13504 );
13505 assert_eq!(
13507 f.run(&[b"BF.LOADCHUNK", b"dst", b"99999", b"x"]),
13508 "-ERR invalid offset - no link found\r\n"
13509 );
13510 assert_eq!(
13511 f.run(&[b"BF.LOADCHUNK", b"dst", b"nope", b"x"]),
13512 "-ERR Second argument must be numeric\r\n"
13513 );
13514 assert_eq!(
13517 f.run(&[b"BF.SCANDUMP", b"src", b"nope"]),
13518 "-Second argument must be numeric\r\n"
13519 );
13520 }
13521
13522 #[test]
13525 fn reserve_reads_its_numbers_the_way_string2ll_does() {
13526 let mut f = Fixture::new();
13527 for (args, want) in [
13528 (vec![&b"abc"[..], b"10"], "-ERR bad error rate\r\n"),
13529 (vec![&b"nan"[..], b"10"], "-ERR bad error rate\r\n"),
13530 (
13531 vec![&b"0"[..], b"10"],
13532 "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
13533 ),
13534 (
13535 vec![&b"1"[..], b"10"],
13536 "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
13537 ),
13538 (
13539 vec![&b"inf"[..], b"10"],
13540 "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
13541 ),
13542 (vec![&b"0.01"[..], b"+10"], "-ERR bad capacity\r\n"),
13543 (vec![&b"0.01"[..], b"1e2"], "-ERR bad capacity\r\n"),
13544 (vec![&b"0.01"[..], b"007"], "-ERR bad capacity\r\n"),
13545 (
13546 vec![&b"0.01"[..], b"0"],
13547 "-ERR capacity must be in the range [1, 1073741824]\r\n",
13548 ),
13549 (
13550 vec![&b"0.01"[..], b"1073741825"],
13551 "-ERR capacity must be in the range [1, 1073741824]\r\n",
13552 ),
13553 ] {
13554 let mut cmd = vec![&b"BF.RESERVE"[..], b"k"];
13555 cmd.extend(args.iter().copied());
13556 assert_eq!(f.run(&cmd), want, "{}", String::from_utf8_lossy(args[0]));
13557 }
13558 assert_eq!(
13559 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION"]),
13560 "-ERR no expansion\r\n"
13561 );
13562 assert_eq!(
13563 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"abc"]),
13564 "-ERR bad expansion\r\n"
13565 );
13566 assert_eq!(
13567 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"32769"]),
13568 "-ERR expansion must be in the range [0, 32768]\r\n"
13569 );
13570 assert_eq!(
13572 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"junk"]),
13573 "+OK\r\n"
13574 );
13575 assert_eq!(
13576 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10"]),
13577 "-ERR item exists\r\n"
13578 );
13579 assert_eq!(
13580 f.run(&[b"BF.INFO", b"k", b"nosuchfield"]),
13581 "-Invalid information value\r\n"
13582 );
13583 assert!(
13584 f.run(&[b"BF.INFO", b"k", b"CAPACITY", b"SIZE"])
13585 .contains("wrong number of arguments")
13586 );
13587 }
13588
13589 #[test]
13591 fn the_bloom_family_answers_in_resp3_spelling_too() {
13592 let mut f = Fixture::new();
13593 f.out.set_proto(Proto::Resp3);
13594 assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#t\r\n");
13595 assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#f\r\n");
13596 assert_eq!(f.run(&[b"BF.MADD", b"b", b"a", b"c"]), "*2\r\n#f\r\n#t\r\n");
13597 assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"a"]), "#t\r\n");
13598 assert_eq!(
13599 f.run(&[b"BF.MEXISTS", b"b", b"a", b"z"]),
13600 "*2\r\n#t\r\n#f\r\n"
13601 );
13602 assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":2\r\n");
13604 assert_eq!(
13605 f.run(&[b"BF.INFO", b"b"]),
13606 "%5\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
13607 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:2\r\n\
13608 +Expansion rate\r\n:2\r\n"
13609 );
13610 assert_eq!(
13613 f.run(&[b"BF.INFO", b"b", b"CAPACITY"]),
13614 "%1\r\n+Capacity\r\n:100\r\n"
13615 );
13616 }
13617
13618 fn cf_header(
13627 items: u64,
13628 buckets: u64,
13629 deletes: u64,
13630 filters: u64,
13631 geometry: [u16; 3],
13632 ) -> Vec<u8> {
13633 let mut out = Vec::with_capacity(38);
13634 for n in [items, buckets, deletes, filters] {
13635 out.extend_from_slice(&n.to_le_bytes());
13636 }
13637 for n in geometry {
13638 out.extend_from_slice(&n.to_le_bytes());
13639 }
13640 out
13641 }
13642
13643 #[test]
13647 fn cf_add_makes_the_filter_and_counts_the_copies() {
13648 let mut f = Fixture::new();
13649 assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
13650 assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
13651 assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":2\r\n");
13652 assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"hello"]), ":0\r\n");
13655 assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"other"]), ":1\r\n");
13656 assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"hello"]), ":1\r\n");
13657 assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"no"]), ":0\r\n");
13658 assert_eq!(
13659 f.run(&[b"CF.MEXISTS", b"d", b"hello", b"no"]),
13660 "*2\r\n:1\r\n:0\r\n"
13661 );
13662 assert_eq!(
13665 f.run(&[b"CF.INFO", b"d"]),
13666 "*16\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
13667 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:3\r\n\
13668 +Number of items deleted\r\n:0\r\n+Bucket size\r\n:2\r\n\
13669 +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
13670 );
13671 assert_eq!(
13672 f.run(&[b"CF.DEBUG", b"d"]),
13673 "$79\r\nbktsize:2 buckets:512 items:3 deletes:0 filters:1 \
13674 max_iterations:20 expansion:1\r\n"
13675 );
13676 assert_eq!(f.run(&[b"TYPE", b"d"]), "+MBbloomCF\r\n");
13677 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
13678
13679 assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
13681 assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":1\r\n");
13682 assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
13683 assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":0\r\n");
13684 assert_eq!(f.run(&[b"CF.COMPACT", b"d"]), "+OK\r\n");
13685
13686 assert_eq!(f.run(&[b"CF.INFO", b"gone"]), "-ERR not found\r\n");
13689 assert_eq!(f.run(&[b"CF.DEL", b"gone", b"x"]), "-Not found\r\n");
13690 assert_eq!(
13691 f.run(&[b"CF.COMPACT", b"gone"]),
13692 "-Cuckoo filter was not found\r\n"
13693 );
13694 assert_eq!(f.run(&[b"CF.EXISTS", b"gone", b"x"]), ":0\r\n");
13695 assert!(
13699 f.run(&[b"CF.COMPACT", b"a", b"b"])
13700 .contains("wrong number of arguments")
13701 );
13702 }
13703
13704 #[test]
13707 fn a_wrong_type_is_a_miss_to_the_four_that_only_read_fingerprints() {
13708 let mut f = Fixture::new();
13709 f.run(&[b"SET", b"s", b"text"]);
13710 assert_eq!(f.run(&[b"CF.EXISTS", b"s", b"x"]), ":0\r\n");
13711 assert_eq!(f.run(&[b"CF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
13712 assert_eq!(f.run(&[b"CF.COUNT", b"s", b"x"]), ":0\r\n");
13713 assert_eq!(f.run(&[b"CF.DEL", b"s", b"x"]), "-Not found\r\n");
13717 assert_eq!(
13718 f.run(&[b"CF.COMPACT", b"s"]),
13719 "-Cuckoo filter was not found\r\n"
13720 );
13721 for cmd in [
13722 vec![&b"CF.ADD"[..], b"s", b"x"],
13723 vec![&b"CF.ADDNX"[..], b"s", b"x"],
13724 vec![&b"CF.INSERT"[..], b"s", b"ITEMS", b"x"],
13725 vec![&b"CF.INSERTNX"[..], b"s", b"ITEMS", b"x"],
13726 vec![&b"CF.INFO"[..], b"s"],
13727 vec![&b"CF.DEBUG"[..], b"s"],
13728 vec![&b"CF.SCANDUMP"[..], b"s", b"0"],
13729 vec![&b"CF.LOADCHUNK"[..], b"s", b"2", b"x"],
13730 vec![&b"CF.RESERVE"[..], b"s", b"64"],
13731 ] {
13732 let name = String::from_utf8_lossy(cmd[0]).into_owned();
13733 assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
13734 }
13735 }
13736
13737 #[test]
13740 fn reserve_complains_about_its_options_in_the_order_it_looks_for_them() {
13741 let mut f = Fixture::new();
13742 assert_eq!(
13743 f.run(&[
13744 b"CF.RESERVE",
13745 b"r",
13746 b"64",
13747 b"BUCKETSIZE",
13748 b"1",
13749 b"MAXITERATIONS",
13750 b"7",
13751 b"EXPANSION",
13752 b"4"
13753 ]),
13754 "+OK\r\n"
13755 );
13756 assert_eq!(
13757 f.run(&[b"CF.DEBUG", b"r"]),
13758 "$77\r\nbktsize:1 buckets:64 items:0 deletes:0 filters:1 \
13759 max_iterations:7 expansion:4\r\n"
13760 );
13761 assert_eq!(f.run(&[b"CF.RESERVE", b"r", b"64"]), "-ERR item exists\r\n");
13762
13763 assert_eq!(f.run(&[b"CF.RESERVE", b"q", b"abc"]), "-Bad capacity\r\n");
13764 assert_eq!(
13765 f.run(&[b"CF.RESERVE", b"q", b"1"]),
13766 "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
13767 );
13768 assert_eq!(
13771 f.run(&[b"CF.RESERVE", b"q", b"7", b"BUCKETSIZE", b"4"]),
13772 "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
13773 );
13774 assert_eq!(
13775 f.run(&[b"CF.RESERVE", b"q", b"8", b"BUCKETSIZE", b"4"]),
13776 "+OK\r\n"
13777 );
13778
13779 assert_eq!(
13784 f.run(&[b"CF.RESERVE", b"q2", b"64", b"BUCKETSIZE", b"0"]),
13785 "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
13786 );
13787 assert_eq!(
13788 f.run(&[
13789 b"CF.RESERVE",
13790 b"q2",
13791 b"64",
13792 b"EXPANSION",
13793 b"xx",
13794 b"BUCKETSIZE",
13795 b"0"
13796 ]),
13797 "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
13798 );
13799 assert_eq!(
13800 f.run(&[
13801 b"CF.RESERVE",
13802 b"q2",
13803 b"64",
13804 b"MAXITERATIONS",
13805 b"0",
13806 b"BUCKETSIZE",
13807 b"0"
13808 ]),
13809 "-MAXITERATIONS: value must be in the range [1, 65535]\r\n"
13810 );
13811 assert_eq!(
13815 f.run(&[
13816 b"CF.RESERVE",
13817 b"q3",
13818 b"64",
13819 b"BUCKETSIZE",
13820 b"1",
13821 b"BUCKETSIZE",
13822 b"0"
13823 ]),
13824 "+OK\r\n"
13825 );
13826 assert_eq!(
13829 f.run(&[b"CF.RESERVE", b"q4", b"64", b"NOSUCH", b"9"]),
13830 "+OK\r\n"
13831 );
13832 assert_eq!(
13833 f.run(&[b"CF.DEBUG", b"q4"]),
13834 "$78\r\nbktsize:2 buckets:32 items:0 deletes:0 filters:1 \
13835 max_iterations:20 expansion:1\r\n"
13836 );
13837 assert!(
13840 f.run(&[b"CF.RESERVE", b"q5", b"64", b"BUCKETSIZE"])
13841 .contains("wrong number of arguments")
13842 );
13843 }
13844
13845 #[test]
13848 fn insert_checks_every_occurrence_and_matches_on_the_first_letter() {
13849 let mut f = Fixture::new();
13850 assert_eq!(
13851 f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"64", b"ITEMS", b"a", b"b"]),
13852 "*2\r\n:1\r\n:1\r\n"
13853 );
13854 assert_eq!(
13855 f.run(&[b"CF.DEBUG", b"i"]),
13856 "$78\r\nbktsize:2 buckets:32 items:2 deletes:0 filters:1 \
13857 max_iterations:20 expansion:1\r\n"
13858 );
13859 assert_eq!(
13862 f.run(&[b"CF.INSERTNX", b"i", b"ITEMS", b"a", b"c"]),
13863 "*2\r\n:0\r\n:1\r\n"
13864 );
13865 assert_eq!(
13866 f.run(&[b"CF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
13867 "-ERR not found\r\n"
13868 );
13869 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
13870
13871 assert_eq!(
13872 f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
13873 "-Bad capacity\r\n"
13874 );
13875 assert_eq!(
13878 f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"2", b"ITEMS", b"a"]),
13879 "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
13880 );
13881 assert_eq!(
13885 f.run(&[
13886 b"CF.INSERT",
13887 b"i",
13888 b"CAPACITY",
13889 b"8",
13890 b"CAPACITY",
13891 b"2",
13892 b"ITEMS",
13893 b"a"
13894 ]),
13895 "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
13896 );
13897 assert_eq!(
13901 f.run(&[b"CF.INSERT", b"i", b"NOSUCH", b"ITEMS", b"a"]),
13902 "*1\r\n:1\r\n"
13903 );
13904 assert_eq!(
13905 f.run(&[b"CF.INSERT", b"i", b"ITEMSXYZ", b"a"]),
13906 "*1\r\n:1\r\n"
13907 );
13908 assert_eq!(
13909 f.run(&[b"CF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
13910 "-Unknown argument received\r\n"
13911 );
13912 assert_eq!(
13914 f.run(&[b"CF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
13915 "*1\r\n:1\r\n"
13916 );
13917 assert!(
13919 f.run(&[b"CF.INSERT", b"i", b"ITEMS"])
13920 .contains("wrong number of arguments")
13921 );
13922 assert!(
13923 f.run(&[b"CF.INSERT", b"i", b"CAPACITY"])
13924 .contains("wrong number of arguments")
13925 );
13926 }
13927
13928 #[test]
13931 fn a_full_filter_and_one_that_ran_out_of_filters_answer_differently() {
13932 let mut f = Fixture::new();
13933 f.run(&[
13934 b"CF.RESERVE",
13935 b"s",
13936 b"4",
13937 b"BUCKETSIZE",
13938 b"1",
13939 b"EXPANSION",
13940 b"0",
13941 ]);
13942 for i in 0..4u32 {
13943 assert_eq!(
13944 f.run(&[b"CF.ADD", b"s", i.to_string().as_bytes()]),
13945 ":1\r\n"
13946 );
13947 }
13948 assert_eq!(f.run(&[b"CF.ADD", b"s", b"4"]), "-Filter is full\r\n");
13949 assert_eq!(f.run(&[b"CF.ADDNX", b"s", b"zz"]), "-Filter is full\r\n");
13950 assert_eq!(
13953 f.run(&[b"CF.INSERT", b"s", b"ITEMS", b"p", b"q"]),
13954 "*2\r\n:-1\r\n:-1\r\n"
13955 );
13956 assert_eq!(
13957 f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"0", b"q"]),
13958 "*2\r\n:0\r\n:-1\r\n"
13959 );
13960
13961 let full = cf_header(0, 4, 0, 32, [1, 20, 1]);
13967 assert_eq!(f.run(&[b"CF.LOADCHUNK", b"g", b"1", &full]), "+OK\r\n");
13968 assert_eq!(
13969 f.run(&[b"CF.ADD", b"g", b"q"]),
13970 "-Maximum expansions reached\r\n"
13971 );
13972 assert_eq!(
13973 f.run(&[b"CF.INFO", b"g"]),
13974 "*16\r\n+Size\r\n:680\r\n+Number of buckets\r\n:4\r\n\
13975 +Number of filters\r\n:32\r\n+Number of items inserted\r\n:0\r\n\
13976 +Number of items deleted\r\n:0\r\n+Bucket size\r\n:1\r\n\
13977 +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
13978 );
13979 }
13980
13981 #[test]
13985 fn a_cuckoo_dump_replays_into_a_filter_that_answers_the_same() {
13986 let mut f = Fixture::new();
13987 f.run(&[
13988 b"CF.RESERVE",
13989 b"src",
13990 b"8",
13991 b"BUCKETSIZE",
13992 b"2",
13993 b"EXPANSION",
13994 b"2",
13995 ]);
13996 for i in 0..40u32 {
13997 f.run(&[b"CF.ADD", b"src", i.to_string().as_bytes()]);
13998 }
13999 let mut pos = b"0".to_vec();
14003 let mut chunks = 0;
14004 loop {
14005 let raw = f.raw(&[b"CF.SCANDUMP", b"src", &pos]);
14006 let head = String::from_utf8_lossy(&raw[..raw.len().min(24)]).into_owned();
14007 let next = head
14008 .split("\r\n")
14009 .nth(1)
14010 .and_then(|n| n.strip_prefix(':'))
14011 .expect("a two element reply of a position and a chunk")
14012 .to_owned();
14013 if next == "0" {
14014 assert!(raw.ends_with(b"$-1\r\n"), "the walk ends on a nil");
14015 break;
14016 }
14017 let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
14018 let at = body
14019 .windows(2)
14020 .position(|w| w == b"\r\n")
14021 .expect("a length line")
14022 + 2;
14023 let data = &body[at..body.len() - 2];
14024 assert_eq!(
14025 f.run(&[b"CF.LOADCHUNK", b"dst", next.as_bytes(), data]),
14026 "+OK\r\n",
14027 "loading chunk {chunks}"
14028 );
14029 pos = next.into_bytes();
14030 chunks += 1;
14031 }
14032 assert!(chunks >= 2, "a header and at least one chunk");
14033
14034 assert_eq!(f.run(&[b"CF.INFO", b"dst"]), f.run(&[b"CF.INFO", b"src"]));
14035 assert_eq!(f.run(&[b"CF.DEBUG", b"dst"]), f.run(&[b"CF.DEBUG", b"src"]));
14036 for i in 0..40u32 {
14037 assert_eq!(
14038 f.run(&[b"CF.EXISTS", b"dst", i.to_string().as_bytes()]),
14039 ":1\r\n"
14040 );
14041 }
14042
14043 f.run(&[b"CF.RESERVE", b"empty", b"4", b"BUCKETSIZE", b"1"]);
14046 assert_eq!(
14047 f.run(&[b"CF.SCANDUMP", b"empty", b"0"]),
14048 "*2\r\n:0\r\n$-1\r\n"
14049 );
14050
14051 assert_eq!(
14055 f.run(&[b"CF.SCANDUMP", b"src", b"nope"]),
14056 "-Invalid position\r\n"
14057 );
14058 assert_eq!(
14059 f.run(&[b"CF.SCANDUMP", b"src", b"-1"]),
14060 "-Invalid position\r\n"
14061 );
14062 assert_eq!(
14063 f.run(&[b"CF.LOADCHUNK", b"dst", b"0", b"x"]),
14064 "-Invalid position\r\n"
14065 );
14066 assert_eq!(
14067 f.run(&[b"CF.LOADCHUNK", b"dst", b"99999", b"x"]),
14068 "-Couldn't load chunk!\r\n"
14069 );
14070 let good = cf_header(0, 8, 0, 1, [2, 20, 1]);
14072 assert_eq!(
14073 f.run(&[b"CF.LOADCHUNK", b"dst", b"1", &good]),
14074 "-ERR item exists\r\n"
14075 );
14076 assert_eq!(
14080 f.run(&[b"CF.LOADCHUNK", b"n1", b"1", b"short"]),
14081 "-Invalid header\r\n"
14082 );
14083 for (why, bad) in [
14084 ("no filters at all", cf_header(0, 8, 0, 0, [2, 20, 1])),
14085 ("no buckets", cf_header(0, 0, 0, 1, [2, 20, 1])),
14086 (
14087 "a bucket count that is not a power of two",
14088 cf_header(0, 3, 0, 1, [2, 20, 1]),
14089 ),
14090 ("an empty bucket", cf_header(0, 8, 0, 1, [0, 20, 1])),
14091 ("no kicks", cf_header(0, 8, 0, 1, [2, 0, 1])),
14092 (
14093 "a growth nobody could reach",
14094 cf_header(0, 8, 0, 1, [2, 20, 32769]),
14095 ),
14096 (
14097 "a chain that cannot grow and did",
14098 cf_header(0, 8, 0, 2, [2, 20, 0]),
14099 ),
14100 (
14103 "a filter count that wraps",
14104 cf_header(0, 8, 0, 65_536, [2, 20, 1]),
14105 ),
14106 ] {
14107 assert_eq!(
14108 f.run(&[b"CF.LOADCHUNK", b"bad", b"1", &bad]),
14109 "-Couldn't create filter!\r\n",
14110 "{why}"
14111 );
14112 }
14113 }
14114
14115 #[test]
14118 fn the_cuckoo_family_answers_in_resp3_spelling_too() {
14119 let mut f = Fixture::new();
14120 f.out.set_proto(Proto::Resp3);
14121 assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
14122 assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
14123 assert_eq!(f.run(&[b"CF.ADDNX", b"c", b"a"]), "#f\r\n");
14124 assert_eq!(f.run(&[b"CF.EXISTS", b"c", b"a"]), "#t\r\n");
14125 assert_eq!(
14126 f.run(&[b"CF.MEXISTS", b"c", b"a", b"z"]),
14127 "*2\r\n#t\r\n#f\r\n"
14128 );
14129 assert_eq!(f.run(&[b"CF.DEL", b"c", b"a"]), "#t\r\n");
14130 assert_eq!(f.run(&[b"CF.DEL", b"c", b"z"]), "#f\r\n");
14131 assert_eq!(f.run(&[b"CF.COUNT", b"c", b"a"]), ":1\r\n");
14133 assert_eq!(
14134 f.run(&[b"CF.INFO", b"c"]),
14135 "%8\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
14136 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
14137 +Number of items deleted\r\n:1\r\n+Bucket size\r\n:2\r\n\
14138 +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
14139 );
14140
14141 f.run(&[
14146 b"CF.RESERVE",
14147 b"s",
14148 b"4",
14149 b"BUCKETSIZE",
14150 b"1",
14151 b"EXPANSION",
14152 b"0",
14153 ]);
14154 assert_eq!(
14155 f.run(&[
14156 b"CF.INSERT",
14157 b"s",
14158 b"ITEMS",
14159 b"a",
14160 b"b",
14161 b"c",
14162 b"d",
14163 b"e",
14164 b"f"
14165 ]),
14166 "*6\r\n#t\r\n#t\r\n#t\r\n#f\r\n#f\r\n#f\r\n"
14167 );
14168 assert_eq!(
14169 f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"a", b"zz"]),
14170 "*2\r\n:0\r\n:-1\r\n"
14171 );
14172 assert_eq!(f.run(&[b"CF.ADD", b"s", b"zzz"]), "-Filter is full\r\n");
14173 assert_eq!(f.run(&[b"CF.SCANDUMP", b"c", b"9999"]), "*2\r\n:0\r\n_\r\n");
14176 }
14177
14178 #[test]
14183 fn a_sketch_is_made_from_a_size_or_from_an_error_rate() {
14184 let mut f = Fixture::new();
14185 assert_eq!(f.run(&[b"CMS.INITBYDIM", b"d", b"100", b"5"]), "+OK\r\n");
14186 assert_eq!(
14187 f.run(&[b"CMS.INFO", b"d"]),
14188 "*6\r\n+width\r\n:100\r\n+depth\r\n:5\r\n+count\r\n:0\r\n"
14189 );
14190 assert_eq!(f.run(&[b"TYPE", b"d"]), "+CMSk-TYPE\r\n");
14191 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
14192 assert_eq!(
14195 f.run(&[b"CMS.INITBYPROB", b"p", b"0.01", b"0.03"]),
14196 "+OK\r\n"
14197 );
14198 assert_eq!(
14199 f.run(&[b"CMS.INFO", b"p"]),
14200 "*6\r\n+width\r\n:200\r\n+depth\r\n:6\r\n+count\r\n:0\r\n"
14201 );
14202 assert_eq!(
14205 f.run(&[b"CMS.INITBYDIM", b"d", b"0", b"2"]),
14206 "-CMS: key already exists\r\n"
14207 );
14208 assert_eq!(
14209 f.run(&[b"CMS.INITBYDIM", b"new", b"0", b"2"]),
14210 "-CMS: invalid width\r\n"
14211 );
14212 assert_eq!(
14213 f.run(&[b"CMS.INITBYDIM", b"new", b"2", b"0"]),
14214 "-CMS: invalid depth\r\n"
14215 );
14216 assert_eq!(
14217 f.run(&[b"CMS.INITBYPROB", b"new", b"0", b"0.5"]),
14218 "-CMS: invalid overestimation value\r\n"
14219 );
14220 assert_eq!(
14221 f.run(&[b"CMS.INITBYPROB", b"new", b"0.1", b"1"]),
14222 "-CMS: invalid prob value\r\n"
14223 );
14224 assert_eq!(
14228 f.run(&[b"CMS.INITBYPROB", b"new", b"0.5", b"1e-46"]),
14229 "-CMS: invalid init arguments\r\n"
14230 );
14231 assert_eq!(
14235 f.run(&[b"CMS.INITBYDIM", b"new", b"268435457", b"1"]),
14236 "-CMS: Insufficient memory to create the key\r\n"
14237 );
14238 assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
14239 }
14240
14241 #[test]
14244 fn increments_are_parsed_whole_and_the_counters_saturate() {
14245 let mut f = Fixture::new();
14246 f.run(&[b"CMS.INITBYDIM", b"c", b"100", b"4"]);
14247 assert_eq!(
14248 f.run(&[b"CMS.INCRBY", b"c", b"a", b"3", b"b", b"4"]),
14249 "*2\r\n:3\r\n:4\r\n"
14250 );
14251 assert_eq!(
14254 f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"a", b"1"]),
14255 "*2\r\n:4\r\n:5\r\n"
14256 );
14257 assert_eq!(
14259 f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"x"]),
14260 "-CMS: Cannot parse number\r\n"
14261 );
14262 assert_eq!(
14263 f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"-1"]),
14264 "-CMS: Number cannot be negative\r\n"
14265 );
14266 assert_eq!(
14267 f.run(&[b"CMS.QUERY", b"c", b"a", b"b"]),
14268 "*2\r\n:5\r\n:4\r\n"
14269 );
14270 f.run(&[b"CMS.INCRBY", b"c", b"a", b"4294967295"]);
14273 assert_eq!(
14274 f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b", b"1"]),
14275 "*2\r\n-CMS: INCRBY overflow\r\n:5\r\n"
14276 );
14277 assert_eq!(f.run(&[b"CMS.QUERY", b"c", b"a"]), "*1\r\n:4294967295\r\n");
14278 f.run(&[b"CMS.INITBYDIM", b"w", b"4", b"1"]);
14281 f.run(&[b"CMS.INCRBY", b"w", b"x", b"9223372036854775807"]);
14282 f.run(&[b"CMS.INCRBY", b"w", b"x", b"1"]);
14283 assert_eq!(
14284 f.run(&[b"CMS.INFO", b"w"]),
14285 "*6\r\n+width\r\n:4\r\n+depth\r\n:1\r\n+count\r\n:-9223372036854775808\r\n"
14286 );
14287 assert!(
14290 f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b"])
14291 .contains("wrong number of arguments")
14292 );
14293 assert_eq!(
14294 f.run(&[b"CMS.INCRBY", b"nope", b"a", b"1"]),
14295 "-CMS: key does not exist\r\n"
14296 );
14297 assert_eq!(
14298 f.run(&[b"CMS.QUERY", b"nope", b"a"]),
14299 "-CMS: key does not exist\r\n"
14300 );
14301 }
14302
14303 #[test]
14306 fn a_merge_lands_whole_or_not_at_all() {
14307 let mut f = Fixture::new();
14308 for name in [&b"m1"[..], b"m2", b"dst"] {
14309 f.run(&[b"CMS.INITBYDIM", name, b"64", b"3"]);
14310 }
14311 f.run(&[b"CMS.INCRBY", b"m1", b"a", b"5"]);
14312 f.run(&[b"CMS.INCRBY", b"m2", b"a", b"7"]);
14313 assert_eq!(
14314 f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
14315 "+OK\r\n"
14316 );
14317 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
14318 assert_eq!(
14321 f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
14322 "+OK\r\n"
14323 );
14324 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
14325 assert_eq!(
14326 f.run(&[
14327 b"CMS.MERGE",
14328 b"dst",
14329 b"2",
14330 b"m1",
14331 b"m2",
14332 b"WEIGHTS",
14333 b"2",
14334 b"3"
14335 ]),
14336 "+OK\r\n"
14337 );
14338 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
14339 assert_eq!(
14342 f.run(&[
14343 b"CMS.MERGE",
14344 b"dst",
14345 b"1",
14346 b"m1",
14347 b"WEIGHTS",
14348 b"4611686018427387904"
14349 ]),
14350 "-CMS: MERGE overflow\r\n"
14351 );
14352 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
14353 f.run(&[b"CMS.INITBYDIM", b"wide", b"128", b"3"]);
14356 assert_eq!(
14357 f.run(&[b"CMS.MERGE", b"gone", b"1", b"m1"]),
14358 "-CMS: key does not exist\r\n"
14359 );
14360 assert_eq!(
14361 f.run(&[b"CMS.MERGE", b"dst", b"0", b"m1"]),
14362 "-CMS: Number of keys must be positive\r\n"
14363 );
14364 assert_eq!(
14365 f.run(&[b"CMS.MERGE", b"dst", b"3", b"m1"]),
14366 "-CMS: wrong number of keys\r\n"
14367 );
14368 assert_eq!(
14369 f.run(&[b"CMS.MERGE", b"dst", b"1", b"m1", b"WEIGHTS", b"1", b"2"]),
14370 "-CMS: wrong number of keys/weights\r\n"
14371 );
14372 assert_eq!(
14373 f.run(&[b"CMS.MERGE", b"dst", b"1", b"wide"]),
14374 "-CMS: width/depth is not equal\r\n"
14375 );
14376 assert_eq!(
14377 f.run(&[b"CMS.MERGE", b"dst", b"1", b"gone"]),
14378 "-CMS: key does not exist\r\n"
14379 );
14380 }
14381
14382 #[test]
14385 fn a_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
14386 let mut f = Fixture::new();
14387 f.run(&[b"SET", b"s", b"text"]);
14388 for cmd in [
14389 vec![&b"CMS.INITBYDIM"[..], b"s", b"8", b"2"],
14390 vec![&b"CMS.INCRBY"[..], b"s", b"a", b"1"],
14391 vec![&b"CMS.QUERY"[..], b"s", b"a"],
14392 vec![&b"CMS.INFO"[..], b"s"],
14393 vec![&b"CMS.MERGE"[..], b"s", b"1", b"s"],
14394 ] {
14395 let name = String::from_utf8_lossy(cmd[0]).into_owned();
14396 let reply = f.run(&cmd);
14397 assert!(
14400 reply.starts_with("-WRONGTYPE") || reply == "-CMS: key already exists\r\n",
14401 "{name}: {reply}"
14402 );
14403 }
14404 f.run(&[b"CMS.INITBYDIM", b"c", b"64", b"2"]);
14405 assert_eq!(
14409 f.run(&[b"COPY", b"c", b"c2"]),
14410 "-ERR not supported for this module key\r\n"
14411 );
14412 assert_eq!(
14413 f.run(&[b"DUMP", b"c"]),
14414 "-ERR DUMP is not supported for this module key\r\n"
14415 );
14416 f.run(&[b"G.NADD", b"g", b"a"]);
14418 assert_eq!(
14419 f.run(&[b"COPY", b"g", b"g2"]),
14420 "-ERR COPY is not supported for a graph\r\n"
14421 );
14422 assert_eq!(
14423 f.run(&[b"DUMP", b"g"]),
14424 "-ERR DUMP is not supported for a graph\r\n"
14425 );
14426 assert_eq!(f.run(&[b"EXPIRE", b"c", b"100"]), ":1\r\n");
14429 assert_eq!(f.run(&[b"PERSIST", b"c"]), ":1\r\n");
14430 assert_eq!(f.run(&[b"RENAME", b"c", b"c3"]), "+OK\r\n");
14431 assert_eq!(f.run(&[b"TYPE", b"c3"]), "+CMSk-TYPE\r\n");
14432 assert_eq!(f.run(&[b"DEL", b"c3"]), ":1\r\n");
14433 }
14434
14435 #[test]
14440 fn a_reserve_takes_three_arguments_or_six() {
14441 let mut f = Fixture::new();
14442 assert_eq!(f.run(&[b"TOPK.RESERVE", b"t", b"5"]), "+OK\r\n");
14443 assert_eq!(
14444 f.run(&[b"TOPK.INFO", b"t"]),
14445 "*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"
14446 );
14447 for cmd in [
14450 vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8"],
14451 vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8", b"7"],
14452 ] {
14453 assert!(f.run(&cmd).contains("wrong number of arguments"));
14454 }
14455 assert_eq!(
14456 f.run(&[b"TOPK.RESERVE", b"u", b"5", b"8", b"7", b"0.5"]),
14457 "+OK\r\n"
14458 );
14459 assert_eq!(
14462 f.run(&[b"TOPK.RESERVE", b"u", b"0", b"0", b"0", b"9"]),
14463 "-TopK: key already exists\r\n"
14464 );
14465 assert_eq!(
14466 f.run(&[b"TOPK.RESERVE", b"v", b"0"]),
14467 "-TopK: invalid k\r\n"
14468 );
14469 assert_eq!(
14470 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"0", b"7", b"0.9"]),
14471 "-TopK: invalid width\r\n"
14472 );
14473 assert_eq!(
14474 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"x", b"0.9"]),
14475 "-TopK: invalid depth\r\n"
14476 );
14477 assert_eq!(
14479 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"0"]),
14480 "-TopK: invalid decay value. must be '<= 1' & '> 0'\r\n"
14481 );
14482 assert_eq!(
14483 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"1"]),
14484 "+OK\r\n"
14485 );
14486 assert_eq!(
14488 f.run(&[
14489 b"TOPK.RESERVE",
14490 b"w",
14491 b"1",
14492 b"4294967295",
14493 b"4294967295",
14494 b"0.9"
14495 ]),
14496 "-ERR Insufficient memory to create topk data structure\r\n"
14497 );
14498 }
14499
14500 #[test]
14502 fn the_kept_set_is_what_query_and_list_answer_from() {
14503 let mut f = Fixture::new();
14504 f.run(&[b"TOPK.RESERVE", b"t", b"2", b"1000", b"5", b"0.9"]);
14505 assert_eq!(
14508 f.run(&[b"TOPK.ADD", b"t", b"a", b"b"]),
14509 "*2\r\n$-1\r\n$-1\r\n"
14510 );
14511 assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"a", b"10"]), "*1\r\n$-1\r\n");
14512 assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"c"]), "*1\r\n$1\r\nb\r\n");
14515 assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"c", b"5"]), "*1\r\n$-1\r\n");
14516 assert_eq!(
14517 f.run(&[b"TOPK.QUERY", b"t", b"a", b"b", b"c"]),
14518 "*3\r\n:1\r\n:0\r\n:1\r\n"
14519 );
14520 assert_eq!(
14522 f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
14523 "*3\r\n:11\r\n:1\r\n:6\r\n"
14524 );
14525 assert_eq!(f.run(&[b"TOPK.LIST", b"t"]), "*2\r\n$1\r\na\r\n$1\r\nc\r\n");
14526 assert_eq!(
14527 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"]),
14528 "*4\r\n$1\r\na\r\n:11\r\n$1\r\nc\r\n:6\r\n"
14529 );
14530 assert_eq!(
14533 f.run(&[b"TOPK.LIST", b"t", b"w"]),
14534 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
14535 );
14536 assert_eq!(
14537 f.run(&[b"TOPK.LIST", b"t", b""]),
14538 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
14539 );
14540 assert_eq!(
14541 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNTS"]),
14542 "-WITHCOUNT keyword expected\r\n"
14543 );
14544 assert_eq!(
14547 f.run(&[b"TOPK.LIST", b"missing", b"nope"]),
14548 "-WITHCOUNT keyword expected\r\n"
14549 );
14550 assert_eq!(
14551 f.run(&[b"TOPK.LIST", b"missing"]),
14552 "-TopK: key does not exist\r\n"
14553 );
14554 f.run(&[b"TOPK.RESERVE", b"z", b"3"]);
14556 assert_eq!(
14557 f.run(&[b"TOPK.INCRBY", b"z", b"nothing", b"0"]),
14558 "*1\r\n$-1\r\n"
14559 );
14560 assert_eq!(f.run(&[b"TOPK.QUERY", b"z", b"nothing"]), "*1\r\n:1\r\n");
14561 assert_eq!(f.run(&[b"TOPK.LIST", b"z"]), "*0\r\n");
14562 }
14563
14564 #[test]
14567 fn an_increment_is_applied_as_it_goes_and_stops_at_a_bad_one() {
14568 let mut f = Fixture::new();
14569 f.run(&[b"TOPK.RESERVE", b"t", b"5", b"1000", b"5", b"0.9"]);
14570 assert_eq!(
14574 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"3", b"b", b"-1", b"c", b"4"]),
14575 format!(
14576 "*2\r\n$-1\r\n-{}\r\n",
14577 "TopK: increment must be an integer greater or equal to 0 and smaller or equal to 100,000"
14578 )
14579 );
14580 assert_eq!(
14581 f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
14582 "*3\r\n:3\r\n:0\r\n:0\r\n"
14583 );
14584 assert_eq!(
14586 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100000"]),
14587 "*1\r\n$-1\r\n"
14588 );
14589 assert!(
14590 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100001"])
14591 .contains("smaller or equal to 100,000")
14592 );
14593 assert!(
14595 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"1", b"b"])
14596 .contains("wrong number of arguments")
14597 );
14598 assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:100003\r\n");
14599 }
14600
14601 #[test]
14603 fn a_query_is_a_bool_and_info_is_a_map_on_resp3() {
14604 let mut f = Fixture::new();
14605 f.run(&[b"HELLO", b"3"]);
14606 f.run(&[b"TOPK.RESERVE", b"t", b"2", b"8", b"7", b"0.5"]);
14607 f.run(&[b"TOPK.ADD", b"t", b"a"]);
14608 assert_eq!(
14609 f.run(&[b"TOPK.QUERY", b"t", b"a", b"b"]),
14610 "*2\r\n#t\r\n#f\r\n"
14611 );
14612 assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:1\r\n");
14614 assert_eq!(
14615 f.run(&[b"TOPK.INFO", b"t"]),
14616 "%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"
14617 );
14618 assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"a"]), "*1\r\n_\r\n");
14619 }
14620
14621 #[test]
14624 fn a_top_k_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
14625 let mut f = Fixture::new();
14626 f.run(&[b"SET", b"s", b"text"]);
14627 for cmd in [
14628 vec![&b"TOPK.RESERVE"[..], b"s", b"5"],
14629 vec![&b"TOPK.ADD"[..], b"s", b"a"],
14630 vec![&b"TOPK.INCRBY"[..], b"s", b"a", b"1"],
14631 vec![&b"TOPK.QUERY"[..], b"s", b"a"],
14632 vec![&b"TOPK.COUNT"[..], b"s", b"a"],
14633 vec![&b"TOPK.LIST"[..], b"s"],
14634 vec![&b"TOPK.INFO"[..], b"s"],
14635 ] {
14636 let name = String::from_utf8_lossy(cmd[0]).into_owned();
14637 let reply = f.run(&cmd);
14638 assert!(
14639 reply.starts_with("-WRONGTYPE") || reply == "-TopK: key already exists\r\n",
14640 "{name}: {reply}"
14641 );
14642 }
14643 f.run(&[b"TOPK.RESERVE", b"t", b"5"]);
14644 assert_eq!(
14645 f.run(&[b"COPY", b"t", b"t2"]),
14646 "-ERR not supported for this module key\r\n"
14647 );
14648 assert_eq!(
14649 f.run(&[b"DUMP", b"t"]),
14650 "-ERR DUMP is not supported for this module key\r\n"
14651 );
14652 assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
14653 assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
14654 assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
14655 assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TopK-TYPE\r\n");
14656 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
14657 assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
14658 assert_eq!(
14661 f.run(&[b"TOPK.INFO", b"t3"]),
14662 "-TopK: key does not exist\r\n"
14663 );
14664 }
14665
14666 #[test]
14671 fn a_create_takes_two_arguments_or_four_and_reads_the_last_one() {
14672 let mut f = Fixture::new();
14673 assert_eq!(f.run(&[b"TDIGEST.CREATE", b"t"]), "+OK\r\n");
14674 assert_eq!(
14676 f.run(&[b"TDIGEST.INFO", b"t"]),
14677 "*18\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:0\r\n\
14678 +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:0\r\n+Unmerged weight\r\n:0\r\n\
14679 +Observations\r\n:0\r\n+Total compressions\r\n:0\r\n+Memory usage\r\n:9840\r\n"
14680 );
14681 assert_eq!(
14682 f.run(&[b"TDIGEST.CREATE", b"t"]),
14683 "-ERR T-Digest: key already exists\r\n"
14684 );
14685 assert!(
14687 f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION"])
14688 .contains("wrong number of arguments")
14689 );
14690 assert_eq!(
14691 f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION", b"1000"]),
14692 "+OK\r\n"
14693 );
14694 assert_eq!(
14695 f.run(&[b"TDIGEST.CREATE", b"v", b"compression", b"1"]),
14696 "+OK\r\n"
14697 );
14698 assert_eq!(
14702 f.run(&[b"TDIGEST.CREATE", b"w", b"100", b"COMPRESSION"]),
14703 "-ERR T-Digest: error parsing compression parameter\r\n"
14704 );
14705 assert_eq!(
14706 f.run(&[b"TDIGEST.CREATE", b"w", b"NOPE", b"100"]),
14707 "-ERR T-Digest: wrong keyword\r\n"
14708 );
14709 assert_eq!(
14710 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"1.5"]),
14711 "-ERR T-Digest: error parsing compression parameter\r\n"
14712 );
14713 assert_eq!(
14714 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"0"]),
14715 "-ERR T-Digest: compression parameter needs to be a positive integer\r\n"
14716 );
14717 assert_eq!(
14720 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"357913942"]),
14721 "-ERR T-Digest: allocation failed\r\n"
14722 );
14723 assert_eq!(
14725 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"100000000"]),
14726 "-ERR T-Digest: allocation failed\r\n"
14727 );
14728 assert_eq!(
14731 f.run(&[b"TDIGEST.CREATE", b"t", b"COMPRESSION", b"0"]),
14732 "-ERR T-Digest: key already exists\r\n"
14733 );
14734 }
14735
14736 #[test]
14739 fn the_quantile_family_answers_what_the_module_answers() {
14740 let mut f = Fixture::new();
14741 f.run(&[b"TDIGEST.CREATE", b"s"]);
14742 assert_eq!(
14743 f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]),
14744 "+OK\r\n"
14745 );
14746 assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), "$1\r\n1\r\n");
14747 assert_eq!(f.run(&[b"TDIGEST.MAX", b"s"]), "$1\r\n4\r\n");
14748 assert_eq!(
14750 f.run(&[b"TDIGEST.CDF", b"s", b"1", b"2", b"3", b"4"]),
14751 "*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"
14752 );
14753 assert_eq!(
14754 f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"0.5", b"1"]),
14755 "*3\r\n$1\r\n1\r\n$1\r\n3\r\n$1\r\n4\r\n"
14756 );
14757 assert_eq!(
14760 f.run(&[b"TDIGEST.QUANTILE", b"s", b"0.5", b"0.1", b"0.9"]),
14761 "*3\r\n$1\r\n3\r\n$1\r\n1\r\n$1\r\n4\r\n"
14762 );
14763 assert_eq!(
14764 f.run(&[b"TDIGEST.RANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
14765 "*5\r\n:-1\r\n:0\r\n:2\r\n:3\r\n:4\r\n"
14766 );
14767 assert_eq!(
14768 f.run(&[b"TDIGEST.REVRANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
14769 "*5\r\n:4\r\n:3\r\n:1\r\n:0\r\n:-1\r\n"
14770 );
14771 assert_eq!(
14772 f.run(&[b"TDIGEST.BYRANK", b"s", b"0", b"1", b"3", b"4"]),
14773 "*4\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n4\r\n$3\r\ninf\r\n"
14774 );
14775 assert_eq!(
14776 f.run(&[b"TDIGEST.BYREVRANK", b"s", b"0", b"1", b"3", b"4"]),
14777 "*4\r\n$1\r\n4\r\n$1\r\n3\r\n$1\r\n1\r\n$4\r\n-inf\r\n"
14778 );
14779 assert_eq!(
14780 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0", b"1"]),
14781 "$3\r\n2.5\r\n"
14782 );
14783 assert_eq!(
14784 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.25", b"0.75"]),
14785 "$3\r\n2.5\r\n"
14786 );
14787 assert_eq!(
14789 f.run(&[b"TDIGEST.QUANTILE", b"s", b"1.1"]),
14790 "-ERR T-Digest: quantile should be in [0,1]\r\n"
14791 );
14792 assert_eq!(
14793 f.run(&[b"TDIGEST.QUANTILE", b"s", b"zzz"]),
14794 "-ERR T-Digest: error parsing quantile\r\n"
14795 );
14796 assert_eq!(
14797 f.run(&[b"TDIGEST.CDF", b"s", b"zzz"]),
14798 "-ERR T-Digest: error parsing cdf\r\n"
14799 );
14800 assert_eq!(
14801 f.run(&[b"TDIGEST.RANK", b"s", b"zzz"]),
14802 "-ERR T-Digest: error parsing value\r\n"
14803 );
14804 assert_eq!(
14805 f.run(&[b"TDIGEST.BYRANK", b"s", b"-1"]),
14806 "-ERR T-Digest: rank needs to be non negative\r\n"
14807 );
14808 assert_eq!(
14809 f.run(&[b"TDIGEST.BYRANK", b"s", b"1.5"]),
14810 "-ERR T-Digest: error parsing rank\r\n"
14811 );
14812 assert_eq!(
14815 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"zzz", b"0.9"]),
14816 "-ERR T-Digest: error parsing low_cut_percentile\r\n"
14817 );
14818 assert_eq!(
14819 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"zzz"]),
14820 "-ERR T-Digest: error parsing high_cut_percentile\r\n"
14821 );
14822 assert_eq!(
14823 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"1.1"]),
14824 "-ERR T-Digest: low_cut_percentile and high_cut_percentile should be in [0,1]\r\n"
14825 );
14826 assert_eq!(
14827 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.5", b"0.5"]),
14828 "-ERR T-Digest: low_cut_percentile should be lower than high_cut_percentile\r\n"
14829 );
14830 }
14831
14832 #[test]
14835 fn an_empty_digest_has_an_answer_for_everything() {
14836 let mut f = Fixture::new();
14837 f.run(&[b"TDIGEST.CREATE", b"e"]);
14838 assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
14839 assert_eq!(f.run(&[b"TDIGEST.MAX", b"e"]), "$3\r\nnan\r\n");
14840 assert_eq!(
14841 f.run(&[b"TDIGEST.QUANTILE", b"e", b"0", b"1"]),
14842 "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
14843 );
14844 assert_eq!(f.run(&[b"TDIGEST.CDF", b"e", b"0"]), "*1\r\n$3\r\nnan\r\n");
14845 assert_eq!(
14846 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"e", b"0.1", b"0.9"]),
14847 "$3\r\nnan\r\n"
14848 );
14849 assert_eq!(
14852 f.run(&[b"TDIGEST.RANK", b"e", b"0", b"1"]),
14853 "*2\r\n:-2\r\n:-2\r\n"
14854 );
14855 assert_eq!(
14856 f.run(&[b"TDIGEST.REVRANK", b"e", b"0", b"1"]),
14857 "*2\r\n:-2\r\n:-2\r\n"
14858 );
14859 assert_eq!(
14860 f.run(&[b"TDIGEST.BYRANK", b"e", b"0", b"5"]),
14861 "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
14862 );
14863 f.run(&[b"TDIGEST.ADD", b"e", b"1", b"2", b"3"]);
14865 assert_eq!(f.run(&[b"TDIGEST.RESET", b"e"]), "+OK\r\n");
14866 assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
14867 f.run(&[b"TDIGEST.CREATE", b"e2"]);
14870 assert_eq!(
14871 f.run(&[b"TDIGEST.INFO", b"e"]),
14872 f.run(&[b"TDIGEST.INFO", b"e2"])
14873 );
14874 }
14875
14876 #[test]
14879 fn a_sample_is_read_the_way_redis_reads_a_double() {
14880 let mut f = Fixture::new();
14881 f.run(&[b"TDIGEST.CREATE", b"a"]);
14882 for bad in [
14885 &b"nan"[..],
14886 b"1e400",
14887 b"-1e400",
14888 b"1e309",
14889 b"1e-400",
14890 b"",
14891 b" 1",
14892 b"1 ",
14893 b"1e",
14894 b"--1",
14895 ] {
14896 assert_eq!(
14897 f.run(&[b"TDIGEST.ADD", b"a", bad]),
14898 "-ERR T-Digest: error parsing val parameter\r\n",
14899 "{}",
14900 String::from_utf8_lossy(bad)
14901 );
14902 }
14903 for word in [&b"inf"[..], b"-inf", b"+INF", b"Infinity"] {
14906 assert_eq!(
14907 f.run(&[b"TDIGEST.ADD", b"a", word]),
14908 "-ERR T-Digest: val parameter needs to be a finite number\r\n",
14909 "{}",
14910 String::from_utf8_lossy(word)
14911 );
14912 }
14913 for good in [&b"0x10"[..], b".5", b"1.", b"1e-320", b"-0", b"0"] {
14916 assert_eq!(
14917 f.run(&[b"TDIGEST.ADD", b"a", good]),
14918 "+OK\r\n",
14919 "{}",
14920 String::from_utf8_lossy(good)
14921 );
14922 }
14923 assert!(
14925 f.run(&[b"TDIGEST.INFO", b"a"])
14926 .contains("Observations\r\n:6\r\n")
14927 );
14928 assert_eq!(
14931 f.run(&[b"TDIGEST.ADD", b"a", b"1", b"zzz"]),
14932 "-ERR T-Digest: error parsing val parameter\r\n"
14933 );
14934 assert!(
14935 f.run(&[b"TDIGEST.INFO", b"a"])
14936 .contains("Observations\r\n:6\r\n")
14937 );
14938 }
14939
14940 #[test]
14943 fn a_merge_sweeps_the_destination_between_its_inputs() {
14944 let mut f = Fixture::new();
14945 f.run(&[b"TDIGEST.CREATE", b"m1", b"COMPRESSION", b"100"]);
14946 f.run(&[b"TDIGEST.ADD", b"m1", b"1", b"2", b"3"]);
14947 f.run(&[b"TDIGEST.CREATE", b"m2", b"COMPRESSION", b"200"]);
14948 f.run(&[b"TDIGEST.ADD", b"m2", b"4", b"5", b"6"]);
14949 assert_eq!(
14950 f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"m2"]),
14951 "+OK\r\n"
14952 );
14953 let info = f.run(&[b"TDIGEST.INFO", b"d"]);
14958 assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
14959 assert!(info.contains("Merged nodes\r\n:3\r\n"), "{info}");
14960 assert!(info.contains("Unmerged nodes\r\n:3\r\n"), "{info}");
14961 assert!(info.contains("Total compressions\r\n:1\r\n"), "{info}");
14962 assert_eq!(f.run(&[b"TDIGEST.MIN", b"d"]), "$1\r\n1\r\n");
14963 assert_eq!(f.run(&[b"TDIGEST.MAX", b"d"]), "$1\r\n6\r\n");
14964 assert!(
14967 f.run(&[b"TDIGEST.INFO", b"m1"])
14968 .contains("Merged nodes\r\n:3\r\n")
14969 );
14970 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1"]);
14973 let info = f.run(&[b"TDIGEST.INFO", b"d"]);
14974 assert!(info.contains("Observations\r\n:9\r\n"), "{info}");
14975 assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
14976 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"OVERRIDE"]);
14979 let info = f.run(&[b"TDIGEST.INFO", b"d"]);
14980 assert!(info.contains("Observations\r\n:3\r\n"), "{info}");
14981 assert!(info.contains("Compression\r\n:100\r\n"), "{info}");
14982 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION", b"500"]);
14984 assert!(
14985 f.run(&[b"TDIGEST.INFO", b"d"])
14986 .contains("Compression\r\n:500\r\n")
14987 );
14988 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"d"]);
14990 assert!(
14991 f.run(&[b"TDIGEST.INFO", b"d"])
14992 .contains("Observations\r\n:12\r\n")
14993 );
14994 assert_eq!(
14996 f.run(&[b"TDIGEST.MERGE", b"d", b"zzz", b"m1"]),
14997 "-ERR T-Digest: error parsing numkeys\r\n"
14998 );
14999 assert_eq!(
15000 f.run(&[b"TDIGEST.MERGE", b"d", b"0", b"m1"]),
15001 "-ERR T-Digest: numkeys needs to be a positive integer\r\n"
15002 );
15003 assert!(
15004 f.run(&[b"TDIGEST.MERGE", b"d", b"3", b"m1", b"m2"])
15005 .contains("wrong number of arguments")
15006 );
15007 assert!(
15008 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION"])
15009 .contains("wrong number of arguments")
15010 );
15011 assert_eq!(
15012 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"NOPE"]),
15013 "-ERR T-Digest: wrong keyword\r\n"
15014 );
15015 assert_eq!(
15018 f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"gone"]),
15019 "-ERR T-Digest: key does not exist\r\n"
15020 );
15021 assert!(
15022 f.run(&[b"TDIGEST.INFO", b"d"])
15023 .contains("Observations\r\n:12\r\n")
15024 );
15025 assert_eq!(
15028 f.run(&[b"TDIGEST.MERGE", b"gone", b"1", b"gone"]),
15029 "-ERR T-Digest: key does not exist\r\n"
15030 );
15031 }
15032
15033 #[test]
15035 fn a_digest_answers_doubles_and_a_map_on_resp3() {
15036 let mut f = Fixture::new();
15037 f.run(&[b"HELLO", b"3"]);
15038 f.run(&[b"TDIGEST.CREATE", b"s"]);
15039 f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]);
15040 assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), ",1\r\n");
15041 assert_eq!(
15042 f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"1"]),
15043 "*2\r\n,1\r\n,4\r\n"
15044 );
15045 assert_eq!(f.run(&[b"TDIGEST.CDF", b"s", b"1"]), "*1\r\n,0.125\r\n");
15046 assert_eq!(f.run(&[b"TDIGEST.BYRANK", b"s", b"4"]), "*1\r\n,inf\r\n");
15048 assert_eq!(
15049 f.run(&[b"TDIGEST.BYREVRANK", b"s", b"4"]),
15050 "*1\r\n,-inf\r\n"
15051 );
15052 f.run(&[b"TDIGEST.CREATE", b"e"]);
15053 assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), ",nan\r\n");
15054 assert_eq!(f.run(&[b"TDIGEST.RANK", b"s", b"1"]), "*1\r\n:0\r\n");
15056 assert_eq!(
15059 f.run(&[b"TDIGEST.INFO", b"s"]),
15060 "%9\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:4\r\n\
15061 +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:4\r\n+Unmerged weight\r\n:0\r\n\
15062 +Observations\r\n:4\r\n+Total compressions\r\n:1\r\n+Memory usage\r\n:9840\r\n"
15063 );
15064 }
15065
15066 #[test]
15069 fn a_t_digest_is_a_module_key_to_the_rest_of_the_keyspace() {
15070 let mut f = Fixture::new();
15071 f.run(&[b"SET", b"s", b"text"]);
15072 for cmd in [
15073 vec![&b"TDIGEST.CREATE"[..], b"s"],
15074 vec![&b"TDIGEST.RESET"[..], b"s"],
15075 vec![&b"TDIGEST.ADD"[..], b"s", b"1"],
15076 vec![&b"TDIGEST.MIN"[..], b"s"],
15077 vec![&b"TDIGEST.MAX"[..], b"s"],
15078 vec![&b"TDIGEST.QUANTILE"[..], b"s", b"0.5"],
15079 vec![&b"TDIGEST.CDF"[..], b"s", b"1"],
15080 vec![&b"TDIGEST.TRIMMED_MEAN"[..], b"s", b"0.1", b"0.9"],
15081 vec![&b"TDIGEST.RANK"[..], b"s", b"1"],
15082 vec![&b"TDIGEST.REVRANK"[..], b"s", b"1"],
15083 vec![&b"TDIGEST.BYRANK"[..], b"s", b"0"],
15084 vec![&b"TDIGEST.BYREVRANK"[..], b"s", b"0"],
15085 vec![&b"TDIGEST.INFO"[..], b"s"],
15086 ] {
15087 let name = String::from_utf8_lossy(cmd[0]).into_owned();
15088 let reply = f.run(&cmd);
15089 assert!(reply.starts_with("-WRONGTYPE"), "{name}: {reply}");
15090 }
15091 f.run(&[b"TDIGEST.CREATE", b"t"]);
15093 assert!(
15094 f.run(&[b"TDIGEST.MERGE", b"s", b"1", b"t"])
15095 .starts_with("-WRONGTYPE")
15096 );
15097 assert!(
15098 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"s"])
15099 .starts_with("-WRONGTYPE")
15100 );
15101 assert_eq!(
15102 f.run(&[b"COPY", b"t", b"t2"]),
15103 "-ERR not supported for this module key\r\n"
15104 );
15105 assert_eq!(
15106 f.run(&[b"DUMP", b"t"]),
15107 "-ERR DUMP is not supported for this module key\r\n"
15108 );
15109 assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
15110 assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
15111 assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
15112 assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TDIS-TYPE\r\n");
15113 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
15114 assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
15115 assert_eq!(
15118 f.run(&[b"TDIGEST.INFO", b"t3"]),
15119 "-ERR T-Digest: key does not exist\r\n"
15120 );
15121 assert_eq!(
15124 f.run(&[b"TDIGEST.QUANTILE", b"t3", b"zzz"]),
15125 "-ERR T-Digest: key does not exist\r\n"
15126 );
15127 }
15128
15129 fn without_memory(reply: &str) -> String {
15139 let head = "+memoryUsage\r\n:";
15140 let at = reply.find(head).expect("every TS.INFO reports memory");
15141 let rest = &reply[at + head.len()..];
15142 let end = rest.find("\r\n").expect("and it is a whole number");
15143 format!("{}{}", &reply[..at + head.len()], &rest[end..])
15144 }
15145
15146 #[test]
15149 fn a_series_is_made_empty_and_reports_on_itself() {
15150 let mut f = Fixture::new();
15151 assert_eq!(f.run(&[b"TS.CREATE", b"t"]), "+OK\r\n");
15152 assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
15153 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t"]), "$3\r\nraw\r\n");
15154 assert_eq!(
15158 without_memory(&f.run(&[b"TS.INFO", b"t"])),
15159 "*28\r\n\
15160 +totalSamples\r\n:0\r\n\
15161 +memoryUsage\r\n:\r\n\
15162 +firstTimestamp\r\n:0\r\n\
15163 +lastTimestamp\r\n:0\r\n\
15164 +retentionTime\r\n:0\r\n\
15165 +chunkCount\r\n:1\r\n\
15166 +chunkSize\r\n:4096\r\n\
15167 +chunkType\r\n+compressed\r\n\
15168 +duplicatePolicy\r\n+block\r\n\
15169 +labels\r\n*0\r\n\
15170 +sourceKey\r\n$-1\r\n\
15171 +rules\r\n*0\r\n\
15172 +ignoreMaxTimeDiff\r\n:0\r\n\
15173 +ignoreMaxValDiff\r\n$1\r\n0\r\n"
15174 );
15175 assert_eq!(
15178 f.run(&[b"TS.CREATE", b"t"]),
15179 "-ERR TSDB: key already exists\r\n"
15180 );
15181 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
15182 assert_eq!(
15183 f.run(&[b"TS.CREATE", b"str"]),
15184 "-ERR TSDB: key already exists\r\n"
15185 );
15186 assert_eq!(
15189 f.run(&[b"TS.CREATE", b"t", b"RETENTION", b"abc"]),
15190 "-ERR TSDB: Couldn't parse RETENTION\r\n"
15191 );
15192 let wrong = "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
15198 assert_eq!(f.run(&[b"TS.INFO", b"str"]), wrong);
15199 assert_eq!(f.run(&[b"TS.GET", b"str"]), wrong);
15200 assert_eq!(f.run(&[b"TS.ALTER", b"str"]), wrong);
15201 assert_eq!(f.run(&[b"TS.DEL", b"str", b"0", b"1"]), wrong);
15202 assert_eq!(f.run(&[b"TS.INCRBY", b"str", b"1"]), wrong);
15203 assert_eq!(
15204 f.run(&[b"TS.ADD", b"str", b"1", b"1"]),
15205 "-ERR TSDB: the key is not a TSDB key\r\n"
15206 );
15207 assert_eq!(
15209 f.run(&[b"TS.INFO", b"nope"]),
15210 "-ERR TSDB: the key does not exist\r\n"
15211 );
15212 assert_eq!(
15213 f.run(&[b"TS.GET", b"nope"]),
15214 "-ERR TSDB: the key does not exist\r\n"
15215 );
15216 assert_eq!(
15217 f.run(&[b"TS.ALTER", b"nope"]),
15218 "-ERR TSDB: the key does not exist\r\n"
15219 );
15220 assert_eq!(
15221 f.run(&[b"TS.DEL", b"nope", b"1", b"2"]),
15222 "-ERR TSDB: the key does not exist\r\n"
15223 );
15224 }
15225
15226 #[test]
15229 fn the_options_are_a_keyword_scan_and_not_a_grammar() {
15230 let mut f = Fixture::new();
15231 assert_eq!(
15232 f.run(&[
15233 b"TS.CREATE",
15234 b"t",
15235 b"RETENTION",
15236 b"5000",
15237 b"ENCODING",
15238 b"UNCOMPRESSED",
15239 b"CHUNK_SIZE",
15240 b"128",
15241 b"DUPLICATE_POLICY",
15242 b"LAST",
15243 b"IGNORE",
15244 b"10",
15245 b"0.5",
15246 b"LABELS",
15247 b"room",
15248 b"kitchen"
15249 ]),
15250 "+OK\r\n"
15251 );
15252 let info = f.run(&[b"TS.INFO", b"t"]);
15253 assert!(info.contains("+retentionTime\r\n:5000\r\n"), "{info}");
15254 assert!(info.contains("+chunkSize\r\n:128\r\n"), "{info}");
15255 assert!(info.contains("+chunkType\r\n+uncompressed\r\n"), "{info}");
15256 assert!(info.contains("+duplicatePolicy\r\n+last\r\n"), "{info}");
15257 assert!(info.contains("+ignoreMaxTimeDiff\r\n:10\r\n"), "{info}");
15258 assert!(
15261 info.contains("+ignoreMaxValDiff\r\n$3\r\n0.5\r\n"),
15262 "{info}"
15263 );
15264 assert!(
15265 info.contains("+labels\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n"),
15266 "{info}"
15267 );
15268
15269 assert_eq!(f.run(&[b"TS.CREATE", b"junk", b"FOO"]), "+OK\r\n");
15271 assert_eq!(
15275 f.run(&[
15276 b"TS.CREATE",
15277 b"g",
15278 b"LABELS",
15279 b"a",
15280 b"b",
15281 b"RETENTION",
15282 b"5"
15283 ]),
15284 "+OK\r\n"
15285 );
15286 let greedy = f.run(&[b"TS.INFO", b"g"]);
15287 assert!(greedy.contains("+retentionTime\r\n:5\r\n"), "{greedy}");
15288 assert!(
15289 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"),
15290 "{greedy}"
15291 );
15292
15293 assert_eq!(
15295 f.run(&[b"TS.CREATE", b"e", b"LABELS", b"a", b"b(c"]),
15296 "-ERR TSDB: Couldn't parse LABELS\r\n"
15297 );
15298 assert_eq!(
15299 f.run(&[b"TS.CREATE", b"e", b"LABELS", b"", b"b"]),
15300 "-ERR TSDB: Couldn't parse LABELS\r\n"
15301 );
15302 assert_eq!(
15303 f.run(&[b"TS.CREATE", b"e", b"RETENTION"]),
15304 "-ERR TSDB: Couldn't parse RETENTION\r\n"
15305 );
15306 assert_eq!(
15309 f.run(&[b"TS.CREATE", b"e", b"RETENTION", b"-1"]),
15310 "-TSDB: Couldn't parse RETENTION\r\n"
15311 );
15312 assert_eq!(
15313 f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"abc"]),
15314 "-ERR TSDB: Couldn't parse CHUNK_SIZE\r\n"
15315 );
15316 assert_eq!(
15317 f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"100"]),
15318 "-ERR TSDB: CHUNK_SIZE value must be a multiple of 8 in the range [48 .. 1048576]\r\n"
15319 );
15320 assert_eq!(
15321 f.run(&[b"TS.CREATE", b"e", b"ENCODING", b"nope"]),
15322 "-ERR TSDB: unknown ENCODING parameter\r\n"
15323 );
15324 assert!(
15327 f.run(&[b"TS.CREATE", b"e", b"ENCODING"])
15328 .contains("wrong number of arguments for 'ts.create' command")
15329 );
15330 assert_eq!(
15331 f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY"]),
15332 "-ERR TSDB: Couldn't parse DUPLICATE_POLICY\r\n"
15333 );
15334 assert_eq!(
15335 f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY", b"nope"]),
15336 "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
15337 );
15338 assert_eq!(
15339 f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"10"]),
15340 "-ERR TSDB: Couldn't parse IGNORE\r\n"
15341 );
15342 assert_eq!(
15343 f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"-1", b"1"]),
15344 "-ERR TSDB: IGNORE arguments cannot be negative\r\n"
15345 );
15346 assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
15347
15348 assert_eq!(f.run(&[b"TS.ALTER", b"t", b"RETENTION", b"9"]), "+OK\r\n");
15351 let after = f.run(&[b"TS.INFO", b"t"]);
15352 assert!(after.contains("+retentionTime\r\n:9\r\n"), "{after}");
15353 assert!(after.contains("+chunkSize\r\n:128\r\n"), "{after}");
15354 assert!(after.contains("+duplicatePolicy\r\n+last\r\n"), "{after}");
15355 assert_eq!(
15356 f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"nope"]),
15357 "-ERR TSDB: unknown ENCODING parameter\r\n"
15358 );
15359 assert_eq!(
15361 f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"COMPRESSED"]),
15362 "+OK\r\n"
15363 );
15364 assert!(
15365 f.run(&[b"TS.INFO", b"t"])
15366 .contains("+chunkType\r\n+uncompressed\r\n")
15367 );
15368 }
15369
15370 #[test]
15373 fn samples_land_where_they_are_put_and_the_newest_comes_back() {
15374 let mut f = Fixture::new();
15375 assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1.5"]), ":100\r\n");
15376 assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
15378 assert_eq!(f.run(&[b"TS.ADD", b"t", b"200", b"2"]), ":200\r\n");
15379 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+2\r\n");
15382 assert_eq!(f.run(&[b"TS.ADD", b"t", b"300", b"1e300"]), ":300\r\n");
15383 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+1E300\r\n");
15384 assert_eq!(f.run(&[b"TS.CREATE", b"empty"]), "+OK\r\n");
15387 assert_eq!(f.run(&[b"TS.GET", b"empty"]), "*0\r\n");
15388
15389 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
15392 assert_eq!(
15393 f.run(&[b"TS.ADD", b"str", b"1", b".5"]),
15394 "-ERR TSDB: invalid value\r\n"
15395 );
15396 for bad in [
15400 &b".5"[..],
15401 b"1.",
15402 b"+1",
15403 b" 1",
15404 b"0x10",
15405 b"inf",
15406 b"1e400",
15407 b"--1",
15408 b"1e",
15409 ] {
15410 assert_eq!(
15411 f.run(&[b"TS.ADD", b"v", b"1", bad]),
15412 "-ERR TSDB: invalid value\r\n",
15413 "{}",
15414 String::from_utf8_lossy(bad)
15415 );
15416 }
15417 assert_eq!(f.run(&[b"TS.ADD", b"v", b"1", b"NaN"]), ":1\r\n");
15419
15420 assert_eq!(
15423 f.run(&[b"TS.ADD", b"t", b"abc", b"1"]),
15424 "-ERR TSDB: invalid timestamp\r\n"
15425 );
15426 assert_eq!(
15427 f.run(&[b"TS.ADD", b"t", b"-1", b"1"]),
15428 "-ERR TSDB: invalid timestamp, must be a nonnegative integer\r\n"
15429 );
15430
15431 assert_eq!(
15434 f.run(&[b"TS.ADD", b"t", b"300", b"7"]),
15435 "-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"
15436 );
15437 assert_eq!(
15438 f.run(&[b"TS.ADD", b"t", b"300", b"7", b"ON_DUPLICATE", b"LAST"]),
15439 ":300\r\n"
15440 );
15441 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+7\r\n");
15442 assert_eq!(
15445 f.run(&[b"TS.ADD", b"fresh", b"1", b"1", b"ON_DUPLICATE", b"nope"]),
15446 ":1\r\n"
15447 );
15448 assert_eq!(
15449 f.run(&[b"TS.ADD", b"fresh", b"2", b"1", b"ON_DUPLICATE", b"nope"]),
15450 "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
15451 );
15452
15453 assert_eq!(f.run(&[b"TS.CREATE", b"r", b"RETENTION", b"50"]), "+OK\r\n");
15456 assert_eq!(f.run(&[b"TS.ADD", b"r", b"1000", b"1"]), ":1000\r\n");
15457 assert_eq!(f.run(&[b"TS.ADD", b"r", b"960", b"1"]), ":960\r\n");
15458 assert_eq!(
15459 f.run(&[b"TS.ADD", b"r", b"940", b"1"]),
15460 "-ERR TSDB: Timestamp is older than retention\r\n"
15461 );
15462 assert_eq!(f.run(&[b"TS.ADD", b"r", b"1100", b"1"]), ":1100\r\n");
15464 assert!(
15465 f.run(&[b"TS.INFO", b"r"])
15466 .contains("+totalSamples\r\n:1\r\n")
15467 );
15468
15469 assert_eq!(
15472 f.run(&[
15473 b"TS.CREATE",
15474 b"i",
15475 b"DUPLICATE_POLICY",
15476 b"LAST",
15477 b"IGNORE",
15478 b"10",
15479 b"0.5"
15480 ]),
15481 "+OK\r\n"
15482 );
15483 assert_eq!(f.run(&[b"TS.ADD", b"i", b"1000", b"1"]), ":1000\r\n");
15484 assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"1.2"]), ":1000\r\n");
15485 assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"9"]), ":1005\r\n");
15486 }
15487
15488 #[test]
15491 fn a_madd_answers_each_triple_and_creates_nothing() {
15492 let mut f = Fixture::new();
15493 assert_eq!(f.run(&[b"TS.CREATE", b"a"]), "+OK\r\n");
15494 assert_eq!(f.run(&[b"TS.CREATE", b"b"]), "+OK\r\n");
15495 assert_eq!(
15496 f.run(&[
15497 b"TS.MADD", b"a", b"100", b"1", b"b", b"100", b"2", b"a", b"200", b"3"
15498 ]),
15499 "*3\r\n:100\r\n:100\r\n:200\r\n"
15500 );
15501 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
15504 assert_eq!(
15505 f.run(&[
15506 b"TS.MADD", b"gone", b"1", b"1", b"str", b"1", b"1", b"a", b"300", b"4"
15507 ]),
15508 "*3\r\n\
15509 -ERR TSDB: the key is not a TSDB key\r\n\
15510 -ERR TSDB: the key is not a TSDB key\r\n\
15511 :300\r\n"
15512 );
15513 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
15514 assert_eq!(
15516 f.run(&[b"TS.MADD", b"a", b"400", b"zzz", b"a", b"abc", b"1"]),
15517 "*2\r\n-ERR TSDB: invalid value\r\n-ERR TSDB: invalid timestamp\r\n"
15518 );
15519 assert!(
15521 f.run(&[b"TS.MADD", b"a", b"1", b"1", b"a"])
15522 .contains("wrong number of arguments for 'ts.madd' command")
15523 );
15524 }
15525
15526 #[test]
15528 fn an_increment_walks_the_newest_value_up_and_down() {
15529 let mut f = Fixture::new();
15530 assert_eq!(
15531 f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
15532 ":100\r\n"
15533 );
15534 assert_eq!(
15535 f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
15536 ":100\r\n"
15537 );
15538 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n+10\r\n");
15541 assert_eq!(
15542 f.run(&[b"TS.DECRBY", b"t", b"3", b"TIMESTAMP", b"200"]),
15543 ":200\r\n"
15544 );
15545 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+7\r\n");
15546 assert_eq!(
15549 f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP", b"150"]),
15550 "-TSDB: timestamp must be equal to or higher than the maximum existing timestamp\r\n"
15551 );
15552 assert_eq!(
15556 f.run(&[b"TS.INCRBY", b"p", b"+5", b"TIMESTAMP", b"1"]),
15557 ":1\r\n"
15558 );
15559 assert_eq!(
15560 f.run(&[b"TS.INCRBY", b"q", b".5", b"TIMESTAMP", b"1"]),
15561 ":1\r\n"
15562 );
15563 assert_eq!(
15564 f.run(&[b"TS.INCRBY", b"t", b"nan"]),
15565 "-ERR TSDB: invalid increase/decrease value\r\n"
15566 );
15567 assert_eq!(
15568 f.run(&[b"TS.INCRBY", b"t", b"zzz"]),
15569 "-ERR TSDB: invalid increase/decrease value\r\n"
15570 );
15571 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
15574 assert_eq!(
15575 f.run(&[b"TS.INCRBY", b"str", b"zzz"]),
15576 "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15577 );
15578 assert_eq!(
15583 f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP"]),
15584 "-ERR TSDB: invalid timestamp\r\n"
15585 );
15586 assert_eq!(
15589 f.run(&[b"TS.INCRBY", b"lab", b"1", b"LABELS", b"TIMESTAMP", b"5"]),
15590 format!(":{}\r\n", f.server.now_ms())
15591 );
15592 assert_eq!(f.run(&[b"TS.ADD", b"n", b"1", b"nan"]), ":1\r\n");
15594 assert_eq!(
15595 f.run(&[b"TS.INCRBY", b"n", b"1", b"TIMESTAMP", b"2"]),
15596 "-ERR TSDB: cannot increment/decrement NaN value\r\n"
15597 );
15598 }
15599
15600 #[test]
15602 fn deleting_takes_out_a_span_and_answers_how_many_went() {
15603 let mut f = Fixture::new();
15604 for at in [b"100".as_slice(), b"200", b"300", b"400"] {
15605 f.run(&[b"TS.ADD", b"t", at, b"1"]);
15606 }
15607 assert_eq!(f.run(&[b"TS.DEL", b"t", b"200", b"300"]), ":2\r\n");
15608 assert!(
15609 f.run(&[b"TS.INFO", b"t"])
15610 .contains("+totalSamples\r\n:2\r\n")
15611 );
15612 assert_eq!(f.run(&[b"TS.DEL", b"t", b"400", b"100"]), ":0\r\n");
15614 assert_eq!(f.run(&[b"TS.DEL", b"t", b"-", b"+"]), ":2\r\n");
15616 let empty = f.run(&[b"TS.INFO", b"t"]);
15619 assert!(empty.contains("+totalSamples\r\n:0\r\n"), "{empty}");
15620 assert!(empty.contains("+chunkCount\r\n:1\r\n"), "{empty}");
15621 assert!(empty.contains("+firstTimestamp\r\n:0\r\n"), "{empty}");
15622 assert!(empty.contains("+lastTimestamp\r\n:0\r\n"), "{empty}");
15623 assert_eq!(f.run(&[b"TS.DEL", b"t", b"0", b"1000"]), ":0\r\n");
15624 assert_eq!(
15626 f.run(&[b"TS.DEL", b"t", b"abc", b"5"]),
15627 "-ERR TSDB: wrong fromTimestamp\r\n"
15628 );
15629 assert_eq!(
15630 f.run(&[b"TS.DEL", b"t", b"5", b"abc"]),
15631 "-ERR TSDB: wrong toTimestamp\r\n"
15632 );
15633 assert_eq!(
15634 f.run(&[b"TS.DEL", b"t", b"-5", b"5"]),
15635 "-ERR TSDB: wrong fromTimestamp\r\n"
15636 );
15637 }
15638
15639 #[test]
15642 fn resp3_writes_a_sample_as_a_double_and_the_info_as_a_map() {
15643 let mut f = Fixture::new();
15644 f.out = Out::new(Proto::Resp3);
15645 assert_eq!(
15646 f.run(&[b"TS.CREATE", b"t", b"LABELS", b"room", b"kitchen"]),
15647 "+OK\r\n"
15648 );
15649 assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1e300"]), ":100\r\n");
15650 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n,1e+300\r\n");
15652 assert_eq!(
15653 without_memory(&f.run(&[b"TS.INFO", b"t"])),
15654 "%14\r\n\
15655 +totalSamples\r\n:1\r\n\
15656 +memoryUsage\r\n:\r\n\
15657 +firstTimestamp\r\n:100\r\n\
15658 +lastTimestamp\r\n:100\r\n\
15659 +retentionTime\r\n:0\r\n\
15660 +chunkCount\r\n:1\r\n\
15661 +chunkSize\r\n:4096\r\n\
15662 +chunkType\r\n+compressed\r\n\
15663 +duplicatePolicy\r\n+block\r\n\
15664 +labels\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
15665 +sourceKey\r\n_\r\n\
15666 +rules\r\n%0\r\n\
15667 +ignoreMaxTimeDiff\r\n:0\r\n\
15668 +ignoreMaxValDiff\r\n,0\r\n"
15669 );
15670 }
15671
15672 #[test]
15675 fn a_range_walks_a_span_and_a_revrange_walks_it_backwards() {
15676 let mut f = Fixture::new();
15677 for (at, v) in [
15678 (b"100".as_slice(), b"1".as_slice()),
15679 (b"200", b"2"),
15680 (b"300", b"3"),
15681 (b"400", b"4"),
15682 ] {
15683 f.run(&[b"TS.ADD", b"t", at, v]);
15684 }
15685 assert_eq!(
15686 f.run(&[b"TS.RANGE", b"t", b"-", b"+"]),
15687 "*4\r\n*2\r\n:100\r\n+1\r\n*2\r\n:200\r\n+2\r\n\
15688 *2\r\n:300\r\n+3\r\n*2\r\n:400\r\n+4\r\n"
15689 );
15690 assert_eq!(
15692 f.run(&[b"TS.RANGE", b"t", b"150", b"350"]),
15693 "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
15694 );
15695 assert_eq!(
15698 f.run(&[b"TS.REVRANGE", b"t", b"-", b"+", b"COUNT", b"2"]),
15699 "*2\r\n*2\r\n:400\r\n+4\r\n*2\r\n:300\r\n+3\r\n"
15700 );
15701 assert_eq!(f.run(&[b"TS.RANGE", b"t", b"400", b"100"]), "*0\r\n");
15703 assert_eq!(
15705 f.run(&[
15706 b"TS.RANGE",
15707 b"t",
15708 b"-",
15709 b"+",
15710 b"FILTER_BY_VALUE",
15711 b"2",
15712 b"3"
15713 ]),
15714 "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
15715 );
15716 assert_eq!(
15717 f.run(&[
15718 b"TS.RANGE",
15719 b"t",
15720 b"-",
15721 b"+",
15722 b"FILTER_BY_TS",
15723 b"100",
15724 b"400"
15725 ]),
15726 "*2\r\n*2\r\n:100\r\n+1\r\n*2\r\n:400\r\n+4\r\n"
15727 );
15728 assert_eq!(
15730 f.run(&[
15731 b"TS.RANGE",
15732 b"t",
15733 b"-",
15734 b"+",
15735 b"ZZZ",
15736 b"FILTER_BY_TS",
15737 b"400"
15738 ]),
15739 "*1\r\n*2\r\n:400\r\n+4\r\n"
15740 );
15741 assert_eq!(
15743 f.run(&[b"TS.RANGE", b"t", b"-", b"+", b"LATEST", b"COUNT", b"1"]),
15744 "*1\r\n*2\r\n:100\r\n+1\r\n"
15745 );
15746 }
15747
15748 #[test]
15750 fn aggregation_puts_one_column_a_reduction_in_a_flat_row() {
15751 let mut f = Fixture::new();
15752 for (at, v) in [
15753 (b"100".as_slice(), b"1".as_slice()),
15754 (b"200", b"2"),
15755 (b"300", b"3"),
15756 (b"400", b"4"),
15757 ] {
15758 f.run(&[b"TS.ADD", b"t", at, v]);
15759 }
15760 assert_eq!(
15761 f.run(&[
15762 b"TS.RANGE",
15763 b"t",
15764 b"-",
15765 b"+",
15766 b"AGGREGATION",
15767 b"avg",
15768 b"200"
15769 ]),
15770 "*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"
15771 );
15772 assert_eq!(
15775 f.run(&[
15776 b"TS.RANGE",
15777 b"t",
15778 b"-",
15779 b"+",
15780 b"AGGREGATION",
15781 b"min,max,count",
15782 b"200"
15783 ]),
15784 "*3\r\n\
15785 *4\r\n:0\r\n+1\r\n+1\r\n+1\r\n\
15786 *4\r\n:200\r\n+2\r\n+3\r\n+2\r\n\
15787 *4\r\n:400\r\n+4\r\n+4\r\n+1\r\n"
15788 );
15789 assert_eq!(
15791 f.run(&[
15792 b"TS.RANGE",
15793 b"t",
15794 b"-",
15795 b"+",
15796 b"AGGREGATION",
15797 b"avg",
15798 b"200",
15799 b"BUCKETTIMESTAMP",
15800 b"+"
15801 ]),
15802 "*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"
15803 );
15804 assert_eq!(
15806 f.run(&[
15807 b"TS.RANGE",
15808 b"t",
15809 b"100",
15810 b"400",
15811 b"ALIGN",
15812 b"100",
15813 b"AGGREGATION",
15814 b"sum",
15815 b"200"
15816 ]),
15817 "*2\r\n*2\r\n:100\r\n+3\r\n*2\r\n:300\r\n+7\r\n"
15818 );
15819 assert_eq!(
15822 f.run(&[
15823 b"TS.RANGE",
15824 b"t",
15825 b"-",
15826 b"+",
15827 b"AGGREGATION",
15828 b"count",
15829 b"200"
15830 ]),
15831 "*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"
15832 );
15833 assert_eq!(
15834 f.run(&[
15835 b"TS.RANGE",
15836 b"t",
15837 b"-",
15838 b"+",
15839 b"AGGREGATION",
15840 b"count",
15841 b"200",
15842 b"COUNT",
15843 b"1"
15844 ]),
15845 "*1\r\n*2\r\n:0\r\n+1\r\n"
15846 );
15847 }
15848
15849 #[test]
15852 fn empty_fills_a_gap_and_last_carries_the_reading_before_it() {
15853 let mut f = Fixture::new();
15854 for (at, v) in [
15855 (b"0".as_slice(), b"1".as_slice()),
15856 (b"100", b"2"),
15857 (b"500", b"nan"),
15858 (b"600", b"3"),
15859 ] {
15860 f.run(&[b"TS.ADD", b"g", at, v]);
15861 }
15862 assert_eq!(
15865 f.run(&[
15866 b"TS.RANGE",
15867 b"g",
15868 b"-",
15869 b"+",
15870 b"AGGREGATION",
15871 b"avg",
15872 b"100"
15873 ]),
15874 "*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"
15875 );
15876 assert_eq!(
15878 f.run(&[
15879 b"TS.RANGE",
15880 b"g",
15881 b"-",
15882 b"+",
15883 b"AGGREGATION",
15884 b"sum",
15885 b"100",
15886 b"EMPTY"
15887 ]),
15888 "*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\
15889 *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\
15890 *2\r\n:600\r\n+3\r\n"
15891 );
15892 assert_eq!(
15897 f.run(&[
15898 b"TS.RANGE",
15899 b"g",
15900 b"-",
15901 b"+",
15902 b"AGGREGATION",
15903 b"last",
15904 b"100",
15905 b"EMPTY"
15906 ]),
15907 "*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\
15908 *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\
15909 *2\r\n:600\r\n+3\r\n"
15910 );
15911 assert_eq!(
15912 f.run(&[
15913 b"TS.REVRANGE",
15914 b"g",
15915 b"-",
15916 b"+",
15917 b"AGGREGATION",
15918 b"last",
15919 b"100",
15920 b"EMPTY"
15921 ]),
15922 "*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\
15923 *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\
15924 *2\r\n:0\r\n+1\r\n"
15925 );
15926 assert_eq!(
15929 f.run(&[
15930 b"TS.RANGE",
15931 b"g",
15932 b"500",
15933 b"600",
15934 b"AGGREGATION",
15935 b"last",
15936 b"100",
15937 b"EMPTY"
15938 ]),
15939 "*2\r\n*2\r\n:500\r\n+NaN\r\n*2\r\n:600\r\n+3\r\n"
15940 );
15941 }
15942
15943 #[test]
15946 fn a_range_says_what_the_module_says_when_the_options_do_not_add_up() {
15947 let mut f = Fixture::new();
15948 f.run(&[b"TS.ADD", b"t", b"100", b"1"]);
15949 f.run(&[b"SET", b"str", b"x"]);
15950 let cases: &[(&[&[u8]], &str)] = &[
15951 (
15952 &[b"TS.RANGE", b"t"],
15953 "-ERR wrong number of arguments for 'ts.range' command\r\n",
15954 ),
15955 (
15957 &[b"TS.RANGE", b"gone", b"-", b"+", b"COUNT", b"x"],
15958 "-ERR TSDB: the key does not exist\r\n",
15959 ),
15960 (
15961 &[b"TS.RANGE", b"str", b"-", b"+"],
15962 "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n",
15963 ),
15964 (
15965 &[b"TS.RANGE", b"t", b"abc", b"+"],
15966 "-ERR TSDB: wrong fromTimestamp\r\n",
15967 ),
15968 (
15969 &[b"TS.RANGE", b"t", b"-", b"abc"],
15970 "-ERR TSDB: wrong toTimestamp\r\n",
15971 ),
15972 (
15973 &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT"],
15974 "-ERR TSDB: COUNT argument is missing\r\n",
15975 ),
15976 (
15977 &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"x"],
15978 "-ERR TSDB: Couldn't parse COUNT\r\n",
15979 ),
15980 (
15981 &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"0"],
15982 "-ERR TSDB: Invalid COUNT value\r\n",
15983 ),
15984 (
15985 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg"],
15986 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
15987 ),
15988 (
15989 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"x"],
15990 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
15991 ),
15992 (
15993 &[
15994 b"TS.RANGE",
15995 b"t",
15996 b"-",
15997 b"+",
15998 b"AGGREGATION",
15999 b"nope",
16000 b"100",
16001 ],
16002 "-ERR TSDB: Unknown aggregation type\r\n",
16003 ),
16004 (
16005 &[
16006 b"TS.RANGE",
16007 b"t",
16008 b"-",
16009 b"+",
16010 b"AGGREGATION",
16011 b"avg,,min",
16012 b"100",
16013 ],
16014 "-ERR TSDB: Empty aggregation type in list\r\n",
16015 ),
16016 (
16018 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"nope", b"0"],
16019 "-ERR TSDB: Unknown aggregation type\r\n",
16020 ),
16021 (
16022 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"0"],
16023 "-ERR TSDB: bucketDuration must be greater than zero\r\n",
16024 ),
16025 (
16026 &[
16027 b"TS.RANGE",
16028 b"t",
16029 b"-",
16030 b"+",
16031 b"AGGREGATION",
16032 b"avg",
16033 b"100",
16034 b"X",
16035 b"EMPTY",
16036 ],
16037 "-ERR TSDB: EMPTY flag should be the 3rd or 5th flag after AGGREGATION flag\r\n",
16038 ),
16039 (
16040 &[
16041 b"TS.RANGE",
16042 b"t",
16043 b"-",
16044 b"+",
16045 b"AGGREGATION",
16046 b"avg",
16047 b"100",
16048 b"BUCKETTIMESTAMP",
16049 b"z",
16050 ],
16051 "-ERR TSDB: unknown BUCKETTIMESTAMP parameter\r\n",
16052 ),
16053 (
16054 &[
16055 b"TS.RANGE",
16056 b"t",
16057 b"-",
16058 b"+",
16059 b"AGGREGATION",
16060 b"avg",
16061 b"100",
16062 b"X",
16063 b"Y",
16064 b"BUCKETTIMESTAMP",
16065 b"-",
16066 ],
16067 "-ERR TSDB: BUCKETTIMESTAMP flag should be the 3rd or 4th flag after \
16068 AGGREGATION flag\r\n",
16069 ),
16070 (
16071 &[
16072 b"TS.RANGE",
16073 b"t",
16074 b"-",
16075 b"+",
16076 b"ALIGN",
16077 b"z",
16078 b"AGGREGATION",
16079 b"avg",
16080 b"100",
16081 ],
16082 "-ERR TSDB: unknown ALIGN parameter\r\n",
16083 ),
16084 (
16085 &[b"TS.RANGE", b"t", b"-", b"+", b"ALIGN", b"5"],
16086 "-ERR TSDB: ALIGN parameter can only be used with AGGREGATION\r\n",
16087 ),
16088 (
16089 &[
16090 b"TS.RANGE",
16091 b"t",
16092 b"-",
16093 b"+",
16094 b"ALIGN",
16095 b"-",
16096 b"AGGREGATION",
16097 b"avg",
16098 b"100",
16099 ],
16100 "-ERR TSDB: start alignment can only be used with explicit start timestamp\r\n",
16101 ),
16102 (
16103 &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_VALUE", b"1"],
16104 "-ERR TSDB: FILTER_BY_VALUE one or more arguments are missing\r\n",
16105 ),
16106 (
16107 &[
16108 b"TS.RANGE",
16109 b"t",
16110 b"-",
16111 b"+",
16112 b"FILTER_BY_VALUE",
16113 b"x",
16114 b"2",
16115 ],
16116 "-ERR TSDB: Couldn't parse MIN\r\n",
16117 ),
16118 (
16119 &[
16120 b"TS.RANGE",
16121 b"t",
16122 b"-",
16123 b"+",
16124 b"FILTER_BY_VALUE",
16125 b"1",
16126 b"y",
16127 ],
16128 "-ERR TSDB: Couldn't parse MAX\r\n",
16129 ),
16130 (
16131 &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_TS"],
16132 "-ERR TSDB: FILTER_BY_TS one or more arguments are missing\r\n",
16133 ),
16134 ];
16135 for (argv, want) in cases {
16136 let got = f.run(argv);
16137 assert_eq!(&got, want, "{:?}", argv.last());
16138 }
16139 f.run(&[b"TS.ADD", b"wide", b"0", b"1"]);
16143 f.run(&[b"TS.ADD", b"wide", b"1000000000000", b"2"]);
16144 assert_eq!(
16145 f.run(&[
16146 b"TS.RANGE",
16147 b"wide",
16148 b"-",
16149 b"+",
16150 b"AGGREGATION",
16151 b"avg",
16152 b"1",
16153 b"EMPTY"
16154 ]),
16155 "-ERR TSDB: the requested range holds too many empty buckets\r\n"
16156 );
16157 }
16158
16159 #[test]
16161 fn resp3_writes_a_read_value_as_a_double() {
16162 let mut f = Fixture::new();
16163 f.out = Out::new(Proto::Resp3);
16164 for (at, v) in [
16165 (b"0".as_slice(), b"1".as_slice()),
16166 (b"100", b"2"),
16167 (b"500", b"nan"),
16168 (b"600", b"3"),
16169 ] {
16170 f.run(&[b"TS.ADD", b"g", at, v]);
16171 }
16172 assert_eq!(
16173 f.run(&[
16174 b"TS.RANGE",
16175 b"g",
16176 b"0",
16177 b"100",
16178 b"AGGREGATION",
16179 b"avg,min",
16180 b"200"
16181 ]),
16182 "*1\r\n*3\r\n:0\r\n,1.5\r\n,1\r\n"
16183 );
16184 assert_eq!(
16185 f.run(&[
16186 b"TS.RANGE",
16187 b"g",
16188 b"500",
16189 b"600",
16190 b"AGGREGATION",
16191 b"last",
16192 b"100",
16193 b"EMPTY"
16194 ]),
16195 "*2\r\n*2\r\n:500\r\n,nan\r\n*2\r\n:600\r\n,3\r\n"
16196 );
16197 }
16198
16199 fn joined() -> Fixture {
16202 let mut f = Fixture::new();
16203 f.run(&[b"TS.CREATE", b"z"]);
16204 for (at, v) in [
16205 (b"10".as_slice(), b"1".as_slice()),
16206 (b"20", b"2"),
16207 (b"40", b"4"),
16208 (b"50", b"5"),
16209 ] {
16210 f.run(&[b"TS.ADD", b"x", at, v]);
16211 }
16212 for (at, v) in [
16213 (b"20".as_slice(), b"20".as_slice()),
16214 (b"30", b"30"),
16215 (b"50", b"50"),
16216 (b"60", b"60"),
16217 ] {
16218 f.run(&[b"TS.ADD", b"y", at, v]);
16219 }
16220 f
16221 }
16222
16223 #[test]
16227 fn an_nrange_joins_its_keys_on_the_timestamp() {
16228 let mut f = joined();
16229 assert_eq!(
16231 f.run(&[b"TS.NRANGE", b"1", b"x", b"-", b"+"]),
16232 "*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\
16233 *2\r\n:40\r\n*1\r\n+4\r\n*2\r\n:50\r\n*1\r\n+5\r\n"
16234 );
16235 assert_eq!(
16237 f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+"]),
16238 "*6\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n\
16239 *2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
16240 *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
16241 *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
16242 *2\r\n:50\r\n*2\r\n+5\r\n+50\r\n\
16243 *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
16244 );
16245 assert_eq!(
16248 f.run(&[b"TS.NRANGE", b"2", b"x", b"z", b"20", b"40"]),
16249 "*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"
16250 );
16251 assert_eq!(
16252 f.run(&[b"TS.NRANGE", b"2", b"x", b"x", b"40", b"50"]),
16253 "*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"
16254 );
16255 assert_eq!(
16258 f.run(&[
16259 b"TS.NREVRANGE",
16260 b"2",
16261 b"x",
16262 b"y",
16263 b"-",
16264 b"+",
16265 b"COUNT",
16266 b"1"
16267 ]),
16268 "*1\r\n*2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
16269 );
16270 assert_eq!(
16271 f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+", b"COUNT", b"1"]),
16272 "*1\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n"
16273 );
16274 assert_eq!(
16276 f.run(&[
16277 b"TS.NRANGE",
16278 b"2",
16279 b"x",
16280 b"y",
16281 b"-",
16282 b"+",
16283 b"FILTER_BY_VALUE",
16284 b"2",
16285 b"30"
16286 ]),
16287 "*4\r\n*2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
16288 *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
16289 *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
16290 *2\r\n:50\r\n*2\r\n+5\r\n+NaN\r\n"
16291 );
16292 }
16293
16294 #[test]
16298 fn an_nrange_aggregation_names_one_reduction_a_key() {
16299 let mut f = joined();
16300 assert_eq!(
16301 f.run(&[
16302 b"TS.NRANGE",
16303 b"2",
16304 b"x",
16305 b"y",
16306 b"-",
16307 b"+",
16308 b"AGGREGATION",
16309 b"sum",
16310 b"sum",
16311 b"20"
16312 ]),
16313 "*4\r\n*2\r\n:0\r\n*2\r\n+1\r\n+NaN\r\n\
16314 *2\r\n:20\r\n*2\r\n+2\r\n+50\r\n\
16315 *2\r\n:40\r\n*2\r\n+9\r\n+50\r\n\
16316 *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
16317 );
16318 assert_eq!(
16320 f.run(&[
16321 b"TS.NRANGE",
16322 b"2",
16323 b"x",
16324 b"y",
16325 b"-",
16326 b"+",
16327 b"AGGREGATION",
16328 b"sum,count",
16329 b"avg",
16330 b"20"
16331 ]),
16332 "*4\r\n*2\r\n:0\r\n*3\r\n+1\r\n+1\r\n+NaN\r\n\
16333 *2\r\n:20\r\n*3\r\n+2\r\n+1\r\n+25\r\n\
16334 *2\r\n:40\r\n*3\r\n+9\r\n+2\r\n+50\r\n\
16335 *2\r\n:60\r\n*3\r\n+NaN\r\n+NaN\r\n+60\r\n"
16336 );
16337 assert_eq!(
16340 f.run(&[
16341 b"TS.NRANGE",
16342 b"2",
16343 b"x",
16344 b"y",
16345 b"-",
16346 b"+",
16347 b"AGGREGATION",
16348 b"avg",
16349 b"sum",
16350 b"100",
16351 b"EMPTY",
16352 b"BUCKETTIMESTAMP",
16353 b"end"
16354 ]),
16355 "*1\r\n*2\r\n:100\r\n*2\r\n+3\r\n+160\r\n"
16356 );
16357 assert_eq!(
16360 f.run(&[
16361 b"TS.NRANGE",
16362 b"2",
16363 b"x",
16364 b"y",
16365 b"-",
16366 b"+",
16367 b"AGGREGATION",
16368 b"avg",
16369 b"COUNT",
16370 b"100"
16371 ]),
16372 "*1\r\n*2\r\n:0\r\n*2\r\n+3\r\n+4\r\n"
16373 );
16374 }
16375
16376 #[test]
16379 fn an_nrange_says_what_the_module_says_when_it_does_not_add_up() {
16380 let mut f = joined();
16381 f.run(&[b"SET", b"str", b"hi"]);
16382 let bad_keys = "-ERR TSDB: numkeys must be a positive integer\r\n";
16383 let numkeys = "-ERR TSDB: the number of AGGREGATION arguments \
16384 must be equal to numkeys\r\n";
16385 let cases: &[(&[&[u8]], &str)] = &[
16386 (&[b"TS.NRANGE", b"0", b"x", b"-", b"+"], bad_keys),
16387 (&[b"TS.NRANGE", b"-1", b"x", b"-", b"+"], bad_keys),
16388 (&[b"TS.NRANGE", b"abc", b"x", b"-", b"+"], bad_keys),
16389 (
16392 &[b"TS.NRANGE", b"2", b"x", b"-", b"+"],
16393 "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
16394 ),
16395 (
16396 &[b"TS.NRANGE", b"99", b"x", b"-", b"+"],
16397 "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
16398 ),
16399 (
16402 &[
16403 b"TS.NRANGE",
16404 b"2",
16405 b"x",
16406 b"y",
16407 b"abc",
16408 b"+",
16409 b"AGGREGATION",
16410 b"nope",
16411 b"sum",
16412 b"100",
16413 ],
16414 "-ERR TSDB: Unknown aggregation type\r\n",
16415 ),
16416 (
16417 &[b"TS.NRANGE", b"2", b"x", b"y", b"abc", b"+"],
16418 "-ERR TSDB: wrong fromTimestamp\r\n",
16419 ),
16420 (
16421 &[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"abc"],
16422 "-ERR TSDB: wrong toTimestamp\r\n",
16423 ),
16424 (
16428 &[
16429 b"TS.NRANGE",
16430 b"2",
16431 b"x",
16432 b"y",
16433 b"-",
16434 b"+",
16435 b"AGGREGATION",
16436 b"avg",
16437 ],
16438 numkeys,
16439 ),
16440 (
16441 &[
16442 b"TS.NRANGE",
16443 b"2",
16444 b"x",
16445 b"y",
16446 b"-",
16447 b"+",
16448 b"AGGREGATION",
16449 b"100",
16450 b"sum",
16451 b"100",
16452 ],
16453 numkeys,
16454 ),
16455 (
16456 &[
16457 b"TS.NRANGE",
16458 b"2",
16459 b"x",
16460 b"y",
16461 b"-",
16462 b"+",
16463 b"AGGREGATION",
16464 b"avg",
16465 b"sum",
16466 b"sum",
16467 b"100",
16468 ],
16469 numkeys,
16470 ),
16471 (
16472 &[
16473 b"TS.NRANGE",
16474 b"2",
16475 b"x",
16476 b"y",
16477 b"-",
16478 b"+",
16479 b"AGGREGATION",
16480 b"avg",
16481 b"sum",
16482 b"abc",
16483 ],
16484 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
16485 ),
16486 (
16487 &[
16488 b"TS.NRANGE",
16489 b"2",
16490 b"x",
16491 b"y",
16492 b"-",
16493 b"+",
16494 b"AGGREGATION",
16495 b"avg",
16496 b"sum",
16497 b"0",
16498 ],
16499 "-ERR TSDB: bucketDuration must be greater than zero\r\n",
16500 ),
16501 (
16504 &[b"TS.NRANGE", b"1", b"x", b"-", b"+", b"AGGREGATION", b"100"],
16505 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
16506 ),
16507 (
16508 &[
16509 b"TS.NRANGE",
16510 b"1",
16511 b"x",
16512 b"-",
16513 b"+",
16514 b"AGGREGATION",
16515 b"100",
16516 b"200",
16517 ],
16518 "-ERR TSDB: Unknown aggregation type\r\n",
16519 ),
16520 (
16522 &[b"TS.NRANGE", b"2", b"x", b"nope", b"-", b"+"],
16523 "-ERR TSDB: the key does not exist\r\n",
16524 ),
16525 (
16526 &[b"TS.NRANGE", b"2", b"str", b"nope", b"-", b"+"],
16527 "-ERR WRONGTYPE Operation against a key \
16528 holding the wrong kind of value\r\n",
16529 ),
16530 ];
16531 for (argv, want) in cases {
16532 let got = f.run(argv);
16533 assert_eq!(&got, want, "{argv:?}");
16534 }
16535 }
16536
16537 #[test]
16539 fn a_read_walks_from_a_timestamp_to_the_end_of_the_series() {
16540 let mut f = joined();
16541 assert_eq!(
16542 f.run(&[b"TS.READ", b"x", b"-"]),
16543 "*4\r\n*2\r\n:10\r\n+1\r\n*2\r\n:20\r\n+2\r\n\
16544 *2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
16545 );
16546 assert_eq!(
16549 f.run(&[b"TS.READ", b"x", b"+"]),
16550 "*1\r\n*2\r\n:50\r\n+5\r\n"
16551 );
16552 assert_eq!(
16553 f.run(&[b"TS.READ", b"x", b"25"]),
16554 "*2\r\n*2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
16555 );
16556 assert_eq!(f.run(&[b"TS.READ", b"x", b"99"]), "*0\r\n");
16559 assert_eq!(f.run(&[b"TS.READ", b"z", b"-"]), "*0\r\n");
16560 assert_eq!(f.run(&[b"TS.READ", b"z", b"+"]), "*0\r\n");
16561 assert_eq!(f.run(&[b"TS.READ", b"nope", b"-"]), "*0\r\n");
16562 assert_eq!(
16566 f.run(&[b"TS.READ", b"x", b"abc"]),
16567 "-TSDB: invalid timestamp\r\n"
16568 );
16569 assert_eq!(
16570 f.run(&[b"TS.READ", b"x", b"-1"]),
16571 "-TSDB: invalid timestamp\r\n"
16572 );
16573 f.run(&[b"SET", b"str", b"hi"]);
16574 assert_eq!(
16575 f.run(&[b"TS.READ", b"str", b"-"]),
16576 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
16577 );
16578 assert_eq!(
16581 f.run(&[b"TS.READ", b"x"]),
16582 "-ERR wrong number of arguments for 'ts.read' command\r\n"
16583 );
16584 assert_eq!(
16585 f.run(&[b"TS.READ", b"x", b"-", b"COUNT", b"1"]),
16586 "-ERR wrong number of arguments for 'ts.read' command\r\n"
16587 );
16588 }
16589
16590 #[test]
16593 fn getkeys_reads_the_count_of_a_joined_read() {
16594 let mut f = Fixture::new();
16595 assert_eq!(
16596 f.run(&[
16597 b"COMMAND",
16598 b"GETKEYS",
16599 b"TS.NRANGE",
16600 b"2",
16601 b"a",
16602 b"b",
16603 b"-",
16604 b"+"
16605 ]),
16606 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
16607 );
16608 assert_eq!(
16609 f.run(&[
16610 b"COMMAND",
16611 b"GETKEYS",
16612 b"TS.NREVRANGE",
16613 b"1",
16614 b"a",
16615 b"-",
16616 b"+"
16617 ]),
16618 "*1\r\n$1\r\na\r\n"
16619 );
16620 for n in [b"0".as_slice(), b"9", b"abc"] {
16623 assert_eq!(
16624 f.run(&[b"COMMAND", b"GETKEYS", b"TS.NRANGE", n, b"a", b"-", b"+"]),
16625 "-ERR Invalid arguments specified for command\r\n"
16626 );
16627 }
16628 }
16629
16630 fn labelled() -> Fixture {
16632 let mut f = Fixture::new();
16633 f.run(&[
16634 b"TS.CREATE",
16635 b"a",
16636 b"LABELS",
16637 b"room",
16638 b"kitchen",
16639 b"x",
16640 b"1",
16641 ]);
16642 f.run(&[
16643 b"TS.CREATE",
16644 b"b",
16645 b"LABELS",
16646 b"room",
16647 b"bedroom",
16648 b"x",
16649 b"2",
16650 ]);
16651 f.run(&[b"TS.CREATE", b"c", b"LABELS", b"room", b"kitchen"]);
16652 f.run(&[b"TS.CREATE", b"d"]);
16653 f.run(&[b"TS.CREATE", b"e", b"LABELS", b"r", b"bb", b"r", b"b"]);
16654 f.run(&[b"TS.ADD", b"a", b"100", b"1.5"]);
16655 f.run(&[b"TS.ADD", b"b", b"200", b"2"]);
16656 f
16657 }
16658
16659 #[test]
16662 fn a_filter_is_taken_apart_the_way_the_module_takes_one_apart() {
16663 let mut f = labelled();
16664 let cases: &[(&[&[u8]], &str)] = &[
16665 (
16668 &[b"TS.QUERYINDEX", b"room=kitchen"],
16669 "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
16670 ),
16671 (
16672 &[b"TS.QUERYINDEX", b"room=kitchen", b"x=1"],
16673 "*1\r\n$1\r\na\r\n",
16674 ),
16675 (
16676 &[b"TS.QUERYINDEX", b"room=(kitchen,bedroom)"],
16677 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n",
16678 ),
16679 (&[b"TS.QUERYINDEX", b"room=()"], "*0\r\n"),
16682 (
16684 &[b"TS.QUERYINDEX", b"x=", b"room=kitchen"],
16685 "*1\r\n$1\r\nc\r\n",
16686 ),
16687 (
16688 &[b"TS.QUERYINDEX", b"room=kitchen", b"x!="],
16689 "*1\r\n$1\r\na\r\n",
16690 ),
16691 (
16692 &[b"TS.QUERYINDEX", b"room!=kitchen", b"x!="],
16693 "-ERR TSDB: please provide at least one matcher\r\n",
16694 ),
16695 (
16698 &[b"TS.QUERYINDEX", b"room==kitchen"],
16699 "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
16700 ),
16701 (
16702 &[b"TS.QUERYINDEX", b"room=kitchen=zz"],
16703 "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
16704 ),
16705 (&[b"TS.QUERYINDEX", b"room!!=kitchen", b"x=1"], "*0\r\n"),
16706 (&[b"TS.QUERYINDEX", b"()=1"], "*0\r\n"),
16709 (
16710 &[b"TS.QUERYINDEX", b"=(1)"],
16711 "-ERR TSDB: failed parsing labels\r\n",
16712 ),
16713 (
16714 &[b"TS.QUERYINDEX", b"room=(kitchen,)"],
16715 "-ERR TSDB: failed parsing labels\r\n",
16716 ),
16717 (
16718 &[b"TS.QUERYINDEX", b"room=(kitchen"],
16719 "-ERR TSDB: failed parsing labels\r\n",
16720 ),
16721 (&[b"TS.QUERYINDEX", b"room=x()"], "*0\r\n"),
16722 (
16723 &[b"TS.QUERYINDEX", b"nonsense"],
16724 "-ERR TSDB: failed parsing labels\r\n",
16725 ),
16726 (
16728 &[b"TS.QUERYINDEX", b"room!=kitchen"],
16729 "-ERR TSDB: please provide at least one matcher\r\n",
16730 ),
16731 (&[b"TS.QUERYINDEX", b"ROOM=kitchen"], "*0\r\n"),
16733 (&[b"TS.QUERYINDEX", b"room=KITCHEN"], "*0\r\n"),
16734 (
16735 &[b"TS.QUERYINDEX"],
16736 "-ERR wrong number of arguments for 'ts.queryindex' command\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]
16747 fn querylabels_says_which_names_are_worn_and_what_they_are_set_to() {
16748 let mut f = labelled();
16749 let cases: &[(&[&[u8]], &str)] = &[
16750 (
16751 &[b"TS.QUERYLABELS", b"LABELS"],
16752 "*3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
16753 ),
16754 (
16755 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=kitchen"],
16756 "*2\r\n$4\r\nroom\r\n$1\r\nx\r\n",
16757 ),
16758 (
16759 &[b"TS.QUERYLABELS", b"VALUES", b"room"],
16760 "*2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
16761 ),
16762 (&[b"TS.QUERYLABELS", b"VALUES", b"r"], "*1\r\n$1\r\nb\r\n"),
16765 (&[b"TS.QUERYLABELS", b"VALUES", b"nolabel"], "*0\r\n"),
16766 (
16767 &[b"TS.QUERYLABELS", b"VALUES"],
16768 "-ERR wrong number of arguments for 'ts.querylabels' command\r\n",
16769 ),
16770 (
16771 &[b"TS.QUERYLABELS", b"ZZZ"],
16772 "-ERR TSDB: unknown subtype, must be one of LABELS|VALUES\r\n",
16773 ),
16774 (
16775 &[b"TS.QUERYLABELS", b"LABELS", b"ZZZ"],
16776 "-ERR TSDB: unknown argument, expected FILTER\r\n",
16777 ),
16778 (
16779 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER"],
16780 "-ERR TSDB: FILTER given with no filter expressions\r\n",
16781 ),
16782 (
16786 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room!=kitchen"],
16787 "-ERR TSDB: please provide at least one matcher\r\n",
16788 ),
16789 (
16790 &[
16791 b"TS.QUERYLABELS",
16792 b"LABELS",
16793 b"FILTER",
16794 b"room=kitchen",
16795 b"x=",
16796 ],
16797 "*1\r\n$4\r\nroom\r\n",
16798 ),
16799 ];
16800 for (argv, want) in cases {
16801 let got = f.run(argv);
16802 assert_eq!(&got, want, "{:?}", argv.last());
16803 }
16804 }
16805
16806 #[test]
16809 fn mget_writes_the_newest_sample_and_the_labels_that_were_asked_for() {
16810 let mut f = labelled();
16811 let cases: &[(&[&[u8]], &str)] = &[
16812 (
16815 &[b"TS.MGET", b"FILTER", b"room=kitchen"],
16816 "*2\r\n*3\r\n$1\r\na\r\n*0\r\n*2\r\n:100\r\n+1.5\r\n\
16817 *3\r\n$1\r\nc\r\n*0\r\n*0\r\n",
16818 ),
16819 (
16820 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
16821 "*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\
16822 *2\r\n$1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n+1.5\r\n\
16823 *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",
16824 ),
16825 (
16827 &[
16828 b"TS.MGET",
16829 b"SELECTED_LABELS",
16830 b"x",
16831 b"FILTER",
16832 b"room=kitchen",
16833 ],
16834 "*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\
16835 *2\r\n:100\r\n+1.5\r\n\
16836 *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",
16837 ),
16838 (
16841 &[b"TS.MGET", b"SELECTED_LABELS", b"r", b"FILTER", b"r=b"],
16842 "*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",
16843 ),
16844 (
16845 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
16846 "*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\
16847 *2\r\n$1\r\nr\r\n$1\r\nb\r\n*0\r\n",
16848 ),
16849 (
16852 &[b"TS.MGET", b"ZZZ", b"FILTER", b"room=bedroom"],
16853 "*1\r\n*3\r\n$1\r\nb\r\n*0\r\n*2\r\n:200\r\n+2\r\n",
16854 ),
16855 (
16856 &[b"TS.MGET", b"a", b"b", b"c"],
16857 "-ERR wrong number of arguments for 'ts.mget' command\r\n",
16858 ),
16859 (
16860 &[b"TS.MGET", b"FILTER"],
16861 "-ERR wrong number of arguments for 'ts.mget' command\r\n",
16862 ),
16863 (
16866 &[
16867 b"TS.MGET",
16868 b"WITHLABELS",
16869 b"SELECTED_LABELS",
16870 b"x",
16871 b"FILTER",
16872 b"bad",
16873 ],
16874 "-ERR TSDB: cannot accept WITHLABELS and SELECT_LABELS together\r\n",
16875 ),
16876 (
16877 &[b"TS.MGET", b"SELECTED_LABELS", b"FILTER", b"bad"],
16878 "-ERR TSDB: SELECT_LABELS should have at least 1 parameter\r\n",
16879 ),
16880 ];
16881 for (argv, want) in cases {
16882 let got = f.run(argv);
16883 assert_eq!(&got, want, "{:?}", argv.last());
16884 }
16885 }
16886
16887 #[test]
16890 fn resp3_writes_the_label_surface_as_sets_and_maps() {
16891 let mut f = labelled();
16892 f.out = Out::new(Proto::Resp3);
16893 let cases: &[(&[&[u8]], &str)] = &[
16894 (
16895 &[b"TS.QUERYINDEX", b"room=kitchen"],
16896 "~2\r\n$1\r\na\r\n$1\r\nc\r\n",
16897 ),
16898 (
16899 &[b"TS.QUERYLABELS", b"LABELS"],
16900 "~3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
16901 ),
16902 (
16903 &[b"TS.QUERYLABELS", b"VALUES", b"room"],
16904 "~2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
16905 ),
16906 (
16909 &[b"TS.MGET", b"FILTER", b"room=kitchen"],
16910 "%2\r\n$1\r\na\r\n*2\r\n%0\r\n*2\r\n:100\r\n,1.5\r\n\
16911 $1\r\nc\r\n*2\r\n%0\r\n*0\r\n",
16912 ),
16913 (
16914 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
16915 "%2\r\n$1\r\na\r\n*2\r\n%2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
16916 $1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n,1.5\r\n\
16917 $1\r\nc\r\n*2\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n*0\r\n",
16918 ),
16919 (
16920 &[
16921 b"TS.MGET",
16922 b"SELECTED_LABELS",
16923 b"x",
16924 b"FILTER",
16925 b"room=kitchen",
16926 ],
16927 "%2\r\n$1\r\na\r\n*2\r\n%1\r\n$1\r\nx\r\n$1\r\n1\r\n\
16928 *2\r\n:100\r\n,1.5\r\n\
16929 $1\r\nc\r\n*2\r\n%1\r\n$1\r\nx\r\n_\r\n*0\r\n",
16930 ),
16931 (
16934 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
16935 "%1\r\n$1\r\ne\r\n*2\r\n%2\r\n$1\r\nr\r\n$2\r\nbb\r\n\
16936 $1\r\nr\r\n$1\r\nb\r\n*0\r\n",
16937 ),
16938 ];
16939 for (argv, want) in cases {
16940 let got = f.run(argv);
16941 assert_eq!(&got, want, "{:?}", argv.last());
16942 }
16943 }
16944
16945 fn spanned() -> Fixture {
16948 let mut f = labelled();
16949 f.run(&[b"TS.ADD", b"a", b"200", b"2.5"]);
16950 f.run(&[b"TS.ADD", b"c", b"100", b"10"]);
16951 f.run(&[b"TS.ADD", b"c", b"300", b"30"]);
16952 f
16953 }
16954
16955 #[test]
16958 fn mrange_reads_every_series_and_folds_the_groups_it_is_asked_for() {
16959 let mut f = spanned();
16960 let cases: &[(&[&[u8]], &str)] = &[
16961 (
16962 &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=kitchen"],
16963 "*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\
16964 *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",
16965 ),
16966 (
16969 &[
16970 b"TS.MREVRANGE",
16971 b"-",
16972 b"+",
16973 b"WITHLABELS",
16974 b"FILTER",
16975 b"room=kitchen",
16976 ],
16977 "*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\
16978 *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\
16979 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
16980 *2\r\n*2\r\n:300\r\n+30\r\n*2\r\n:100\r\n+10\r\n",
16981 ),
16982 (
16985 &[
16986 b"TS.MRANGE",
16987 b"-",
16988 b"+",
16989 b"SELECTED_LABELS",
16990 b"x",
16991 b"FILTER",
16992 b"room=kitchen",
16993 ],
16994 "*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\
16995 *2\r\n*2\r\n:100\r\n+1.5\r\n*2\r\n:200\r\n+2.5\r\n\
16996 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$1\r\nx\r\n$-1\r\n\
16997 *2\r\n*2\r\n:100\r\n+10\r\n*2\r\n:300\r\n+30\r\n",
16998 ),
16999 (
17002 &[
17003 b"TS.MRANGE",
17004 b"-",
17005 b"+",
17006 b"FILTER",
17007 b"room=kitchen",
17008 b"GROUPBY",
17009 b"room",
17010 b"REDUCE",
17011 b"sum",
17012 ],
17013 "*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\
17014 *2\r\n:200\r\n+2.5\r\n*2\r\n:300\r\n+30\r\n",
17015 ),
17016 (
17019 &[
17020 b"TS.MRANGE",
17021 b"-",
17022 b"+",
17023 b"WITHLABELS",
17024 b"FILTER",
17025 b"room=kitchen",
17026 b"GROUPBY",
17027 b"room",
17028 b"REDUCE",
17029 b"max",
17030 ],
17031 "*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\
17032 *2\r\n$11\r\n__reducer__\r\n$3\r\nmax\r\n\
17033 *2\r\n$10\r\n__source__\r\n$3\r\na,c\r\n\
17034 *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",
17035 ),
17036 (
17038 &[
17039 b"TS.MREVRANGE",
17040 b"-",
17041 b"+",
17042 b"COUNT",
17043 b"1",
17044 b"FILTER",
17045 b"room=kitchen",
17046 b"GROUPBY",
17047 b"room",
17048 b"REDUCE",
17049 b"count",
17050 ],
17051 "*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",
17052 ),
17053 (
17055 &[
17056 b"TS.MRANGE",
17057 b"-",
17058 b"+",
17059 b"FILTER",
17060 b"room=kitchen",
17061 b"GROUPBY",
17062 b"nope",
17063 b"REDUCE",
17064 b"sum",
17065 ],
17066 "*0\r\n",
17067 ),
17068 (
17069 &[
17070 b"TS.MRANGE",
17071 b"-",
17072 b"+",
17073 b"AGGREGATION",
17074 b"sum,avg",
17075 b"100",
17076 b"FILTER",
17077 b"room=bedroom",
17078 ],
17079 "*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",
17080 ),
17081 (
17083 &[b"TS.MRANGE", b"-", b"+", b"room=kitchen"],
17084 "-ERR TSDB: missing FILTER argument\r\n",
17085 ),
17086 (
17087 &[b"TS.MRANGE", b"-", b"+", b"FILTER"],
17088 "-ERR TSDB: missing labels for filter argument\r\n",
17089 ),
17090 (
17091 &[
17092 b"TS.MRANGE",
17093 b"-",
17094 b"+",
17095 b"GROUPBY",
17096 b"room",
17097 b"REDUCE",
17098 b"sum",
17099 b"FILTER",
17100 b"room=kitchen",
17101 ],
17102 "-ERR TSDB: GROUPBY should always come after filter\r\n",
17103 ),
17104 (
17107 &[
17108 b"TS.MRANGE",
17109 b"-",
17110 b"+",
17111 b"FILTER",
17112 b"room=kitchen",
17113 b"GROUPBY",
17114 b"room",
17115 b"REDUCE",
17116 b"sum",
17117 b"x",
17118 ],
17119 "-ERR wrong number of arguments for 'ts.mrange' command\r\n",
17120 ),
17121 (
17123 &[
17124 b"TS.MRANGE",
17125 b"-",
17126 b"+",
17127 b"FILTER",
17128 b"nope",
17129 b"GROUPBY",
17130 b"room",
17131 b"REDUCE",
17132 b"sum",
17133 b"x",
17134 ],
17135 "-ERR TSDB: failed parsing labels\r\n",
17136 ),
17137 (
17138 &[
17139 b"TS.MRANGE",
17140 b"-",
17141 b"+",
17142 b"FILTER",
17143 b"room=kitchen",
17144 b"GROUPBY",
17145 b"room",
17146 b"REDUCE",
17147 b"twa",
17148 ],
17149 "-ERR TSDB: Invalid reducer type\r\n",
17150 ),
17151 (
17152 &[
17153 b"TS.MRANGE",
17154 b"-",
17155 b"+",
17156 b"AGGREGATION",
17157 b"sum,avg",
17158 b"100",
17159 b"FILTER",
17160 b"room=kitchen",
17161 b"GROUPBY",
17162 b"room",
17163 b"REDUCE",
17164 b"sum",
17165 ],
17166 "-ERR TSDB: GROUPBY is not allowed when multiple aggregators are specified\r\n",
17167 ),
17168 (
17171 &[
17172 b"TS.MRANGE",
17173 b"-",
17174 b"+",
17175 b"SELECTED_LABELS",
17176 b"COUNT",
17177 b"FILTER",
17178 b"room=kitchen",
17179 ],
17180 "-ERR TSDB: Couldn't parse COUNT\r\n",
17181 ),
17182 ];
17183 for (argv, want) in cases {
17184 let got = f.run(argv);
17185 assert_eq!(&got, want, "{argv:?}");
17186 }
17187 }
17188
17189 #[test]
17192 fn resp3_writes_a_multi_key_read_as_a_map_of_four() {
17193 let mut f = spanned();
17194 f.out = Out::new(Proto::Resp3);
17195 let cases: &[(&[&[u8]], &str)] = &[
17196 (
17197 &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=bedroom"],
17198 "%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\
17199 *1\r\n*2\r\n:200\r\n,2\r\n",
17200 ),
17201 (
17204 &[
17205 b"TS.MRANGE",
17206 b"-",
17207 b"+",
17208 b"AGGREGATION",
17209 b"sum,avg",
17210 b"100",
17211 b"FILTER",
17212 b"room=bedroom",
17213 ],
17214 "%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\
17215 $3\r\navg\r\n*1\r\n*3\r\n:200\r\n,2\r\n,2\r\n",
17216 ),
17217 (
17218 &[
17219 b"TS.MRANGE",
17220 b"-",
17221 b"+",
17222 b"FILTER",
17223 b"room=kitchen",
17224 b"GROUPBY",
17225 b"room",
17226 b"REDUCE",
17227 b"sum",
17228 ],
17229 "%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\
17230 $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\
17231 *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",
17232 ),
17233 (
17236 &[
17237 b"TS.MRANGE",
17238 b"-",
17239 b"+",
17240 b"WITHLABELS",
17241 b"FILTER",
17242 b"room=kitchen",
17243 b"GROUPBY",
17244 b"room",
17245 b"REDUCE",
17246 b"max",
17247 ],
17248 "%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\
17249 %1\r\n$8\r\nreducers\r\n*1\r\n$3\r\nmax\r\n\
17250 %1\r\n$7\r\nsources\r\n*2\r\n$1\r\na\r\n$1\r\nc\r\n\
17251 *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",
17252 ),
17253 (
17254 &[
17255 b"TS.MRANGE",
17256 b"-",
17257 b"+",
17258 b"FILTER",
17259 b"room=kitchen",
17260 b"GROUPBY",
17261 b"nope",
17262 b"REDUCE",
17263 b"sum",
17264 ],
17265 "%0\r\n",
17266 ),
17267 ];
17268 for (argv, want) in cases {
17269 let got = f.run(argv);
17270 assert_eq!(&got, want, "{argv:?}");
17271 }
17272 }
17273
17274 #[test]
17276 fn createrule_checks_the_two_keys_last_and_the_two_links_after_that() {
17277 let mut f = Fixture::new();
17278 f.run(&[b"TS.CREATE", b"src"]);
17279 f.run(&[b"TS.CREATE", b"dst"]);
17280 f.run(&[b"SET", b"plain", b"v"]);
17281 let cases: &[(&[&[u8]], &str)] = &[
17282 (
17286 &[
17287 b"TS.CREATERULE",
17288 b"src",
17289 b"dst",
17290 b"AGGREGATION",
17291 b"nope",
17292 b"x",
17293 ],
17294 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
17295 ),
17296 (
17297 &[
17298 b"TS.CREATERULE",
17299 b"src",
17300 b"dst",
17301 b"AGGREGATION",
17302 b"nope",
17303 b"10",
17304 ],
17305 "-ERR TSDB: Unknown aggregation type\r\n",
17306 ),
17307 (
17308 &[
17309 b"TS.CREATERULE",
17310 b"src",
17311 b"dst",
17312 b"AGGREGATION",
17313 b"avg",
17314 b"0",
17315 ],
17316 "-ERR TSDB: bucketDuration must be greater than zero\r\n",
17317 ),
17318 (
17319 &[
17320 b"TS.CREATERULE",
17321 b"src",
17322 b"dst",
17323 b"AGGREGATION",
17324 b"avg",
17325 b"10",
17326 b"x",
17327 ],
17328 "-ERR TSDB: Couldn't parse alignTimestamp\r\n",
17329 ),
17330 (
17331 &[
17332 b"TS.CREATERULE",
17333 b"src",
17334 b"src",
17335 b"AGGREGATION",
17336 b"avg",
17337 b"10",
17338 ],
17339 "-ERR TSDB: the source key and destination key should be different\r\n",
17340 ),
17341 (
17345 &[
17346 b"TS.CREATERULE",
17347 b"nope",
17348 b"plain",
17349 b"AGGREGATION",
17350 b"avg",
17351 b"10",
17352 ],
17353 "-ERR TSDB: the key does not exist\r\n",
17354 ),
17355 (
17356 &[
17357 b"TS.CREATERULE",
17358 b"src",
17359 b"nope",
17360 b"AGGREGATION",
17361 b"avg",
17362 b"10",
17363 ],
17364 "-ERR TSDB: the key does not exist\r\n",
17365 ),
17366 (
17369 &[b"TS.CREATERULE", b"src", b"dst", b"NOPE", b"avg", b"10"],
17370 "-ERR wrong number of arguments for 'ts.createrule' command\r\n",
17371 ),
17372 (
17373 &[
17374 b"TS.CREATERULE",
17375 b"src",
17376 b"dst",
17377 b"AGGREGATION",
17378 b"avg",
17379 b"10",
17380 ],
17381 "+OK\r\n",
17382 ),
17383 (
17386 &[
17387 b"TS.CREATERULE",
17388 b"src",
17389 b"dst",
17390 b"AGGREGATION",
17391 b"avg",
17392 b"10",
17393 ],
17394 "-ERR TSDB: the destination key already has a src rule\r\n",
17395 ),
17396 (
17399 &[
17400 b"TS.CREATERULE",
17401 b"dst",
17402 b"src",
17403 b"AGGREGATION",
17404 b"avg",
17405 b"10",
17406 ],
17407 "-ERR TSDB: the source key already has a source rule\r\n",
17408 ),
17409 (&[b"TS.DELETERULE", b"src", b"dst"], "+OK\r\n"),
17410 (
17411 &[b"TS.DELETERULE", b"src", b"dst"],
17412 "-ERR TSDB: compaction rule does not exist\r\n",
17413 ),
17414 (
17418 &[b"TS.DELETERULE", b"src", b"nope"],
17419 "-ERR TSDB: compaction rule does not exist\r\n",
17420 ),
17421 (
17422 &[b"TS.DELETERULE", b"nope", b"dst"],
17423 "-ERR TSDB: the key does not exist\r\n",
17424 ),
17425 ];
17426 for (argv, want) in cases {
17427 let got = f.run(argv);
17428 assert_eq!(&got, want, "{argv:?}");
17429 }
17430 }
17431
17432 #[test]
17434 fn a_rule_writes_a_bucket_when_a_later_reading_closes_it() {
17435 let mut f = Fixture::new();
17436 f.run(&[b"TS.CREATE", b"src"]);
17437 f.run(&[b"TS.CREATE", b"dst"]);
17438 f.run(&[b"TS.ADD", b"src", b"10", b"1"]);
17441 f.run(&[
17442 b"TS.CREATERULE",
17443 b"src",
17444 b"dst",
17445 b"AGGREGATION",
17446 b"sum",
17447 b"100",
17448 ]);
17449 f.run(&[b"TS.ADD", b"src", b"20", b"2"]);
17450 assert_eq!(f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]), "*0\r\n");
17451 assert_eq!(f.run(&[b"TS.GET", b"dst", b"LATEST"]), "*2\r\n:0\r\n+2\r\n");
17454 f.run(&[b"TS.ADD", b"src", b"110", b"4"]);
17455 assert_eq!(
17456 f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
17457 "*1\r\n*2\r\n:0\r\n+2\r\n"
17458 );
17459 f.run(&[b"TS.ADD", b"src", b"30", b"8"]);
17462 assert_eq!(
17463 f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
17464 "*1\r\n*2\r\n:0\r\n+11\r\n"
17465 );
17466 assert_eq!(f.run(&[b"TS.DEL", b"src", b"0", b"25"]), ":2\r\n");
17469 assert_eq!(
17470 f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
17471 "*1\r\n*2\r\n:0\r\n+8\r\n"
17472 );
17473 assert_eq!(
17474 f.run(&[b"TS.GET", b"dst", b"LATEST"]),
17475 "*2\r\n:100\r\n+4\r\n"
17476 );
17477 assert!(f.run(&[b"TS.INFO", b"dst"]).contains("sourceKey"));
17479 f.run(&[b"DEL", b"dst"]);
17480 assert_eq!(
17481 f.run(&[b"TS.DELETERULE", b"src", b"dst"]),
17482 "-ERR TSDB: compaction rule does not exist\r\n"
17483 );
17484 }
17485
17486 #[test]
17489 fn xadd_ids_only_ever_go_up() {
17490 let mut f = Fixture::new();
17491 assert_eq!(f.run(&[b"XADD", b"s", b"5", b"a", b"1"]), "$3\r\n5-0\r\n");
17493 assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"2"]), "$3\r\n5-1\r\n");
17495 assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"3"]), "$3\r\n5-2\r\n");
17496 assert_eq!(f.run(&[b"XADD", b"s", b"6-9", b"a", b"4"]), "$3\r\n6-9\r\n");
17497 assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
17498
17499 assert!(
17500 f.run(&[b"XADD", b"s", b"6-9", b"a", b"5"])
17501 .contains("equal or smaller")
17502 );
17503 assert!(
17504 f.run(&[b"XADD", b"s", b"0-0", b"a", b"5"])
17505 .contains("must be greater than 0-0")
17506 );
17507 assert!(
17508 f.run(&[b"XADD", b"s", b"nonsense", b"a", b"5"])
17509 .contains("Invalid stream ID")
17510 );
17511 assert!(
17514 f.run(&[b"XADD", b"s", b"*", b"a"])
17515 .contains("wrong number of arguments")
17516 );
17517
17518 assert_eq!(
17521 f.run(&[b"XADD", b"gone", b"NOMKSTREAM", b"*", b"a", b"1"]),
17522 "$-1\r\n"
17523 );
17524 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
17525 assert_eq!(f.run(&[b"TYPE", b"s"]), "+stream\r\n");
17526 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$6\r\nstream\r\n");
17527 }
17528
17529 #[test]
17532 fn trimming_reads_its_options_the_way_redis_does() {
17533 let mut f = Fixture::new();
17534 for i in 1..=10u32 {
17535 f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
17536 }
17537 assert_eq!(f.run(&[b"XTRIM", b"s", b"MAXLEN", b"4"]), ":6\r\n");
17538 assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
17539 assert_eq!(f.run(&[b"XTRIM", b"s", b"MINID", b"9"]), ":2\r\n");
17540 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
17541
17542 assert!(
17546 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"~"])
17547 .contains("not an integer")
17548 );
17549 assert!(
17550 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"-1"])
17551 .contains("MAXLEN argument must be >= 0")
17552 );
17553 assert!(
17556 f.run(&[b"XTRIM", b"s", b"LIMIT", b"5"])
17557 .contains("without specifying a trimming strategy")
17558 );
17559 assert!(
17560 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"LIMIT", b"5"])
17561 .contains("without the special ~ option")
17562 );
17563 assert!(
17564 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"MINID", b"5"])
17565 .contains("at the same time are not compatible")
17566 );
17567 assert!(
17569 f.run(&[b"XTRIM", b"s", b"NOMKSTREAM", b"MAXLEN", b"5"])
17570 .contains("syntax error")
17571 );
17572 assert_eq!(f.run(&[b"XTRIM", b"missing", b"MAXLEN", b"5"]), ":0\r\n");
17573 }
17574
17575 #[test]
17577 fn xrange_looks_the_key_up_before_it_reads_the_count() {
17578 let mut f = Fixture::new();
17579 f.run(&[b"XADD", b"s", b"5-1", b"a", b"1"]);
17580 f.run(&[b"XADD", b"s", b"6-1", b"b", b"2"]);
17581
17582 assert_eq!(
17583 f.run(&[b"XRANGE", b"s", b"-", b"+"]),
17584 "*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\
17585 *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
17586 );
17587 assert_eq!(
17588 f.run(&[b"XREVRANGE", b"s", b"+", b"-", b"COUNT", b"1"]),
17589 "*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"
17590 );
17591 assert_eq!(
17595 f.run(&[b"XRANGE", b"s", b"-", b"(6"]),
17596 "*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\
17597 *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
17598 );
17599 assert_eq!(
17600 f.run(&[b"XRANGE", b"s", b"(5-1", b"+"]),
17601 "*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"
17602 );
17603 assert!(
17604 f.run(&[b"XRANGE", b"s", b"(-", b"+"])
17605 .contains("Invalid stream ID")
17606 );
17607
17608 assert_eq!(
17612 f.run(&[b"XRANGE", b"missing", b"-", b"+", b"COUNT", b"0"]),
17613 "*0\r\n"
17614 );
17615 assert_eq!(
17616 f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"]),
17617 "*-1\r\n"
17618 );
17619 f.run(&[b"SET", b"str", b"v"]);
17620 assert!(
17621 f.run(&[b"XRANGE", b"str", b"-", b"+", b"COUNT", b"0"])
17622 .starts_with("-WRONGTYPE")
17623 );
17624 assert_eq!(
17626 f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"2", b"COUNT", b"1"]),
17627 "*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"
17628 );
17629 }
17630
17631 #[test]
17633 fn a_bad_id_late_in_the_list_stops_the_whole_command() {
17634 let mut f = Fixture::new();
17635 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
17636 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
17637 assert!(
17638 f.run(&[b"XDEL", b"s", b"1-1", b"nonsense"])
17639 .contains("Invalid stream ID")
17640 );
17641 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
17642 assert_eq!(f.run(&[b"XDEL", b"s", b"1-1", b"9-9"]), ":1\r\n");
17643 assert_eq!(f.run(&[b"XLEN", b"s"]), ":1\r\n");
17644 assert_eq!(f.run(&[b"XDEL", b"missing", b"1-1"]), ":0\r\n");
17645 assert_eq!(f.run(&[b"XACK", b"missing", b"g", b"1-1"]), ":0\r\n");
17646 }
17647
17648 #[test]
17650 fn xgroup_has_an_arity_per_subcommand() {
17651 let mut f = Fixture::new();
17652 assert!(
17653 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
17654 .contains("requires the key")
17655 );
17656 assert_eq!(
17657 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$", b"MKSTREAM"]),
17658 "+OK\r\n"
17659 );
17660 assert!(
17663 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
17664 .starts_with("-BUSYGROUP")
17665 );
17666 assert_eq!(
17667 f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
17668 ":1\r\n"
17669 );
17670 assert_eq!(
17671 f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
17672 ":0\r\n"
17673 );
17674 assert_eq!(
17675 f.run(&[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c"]),
17676 ":0\r\n"
17677 );
17678
17679 let short = f.run(&[b"XGROUP", b"DESTROY", b"s"]);
17681 assert!(
17682 short.contains("wrong number of arguments for 'xgroup|destroy' command"),
17683 "{short}"
17684 );
17685 let odd = f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0", b"ENTRIESREAD"]);
17687 assert!(
17688 odd.contains("unknown subcommand or wrong number of arguments for 'SETID'"),
17689 "{odd}"
17690 );
17691 assert!(
17692 f.run(&[b"XGROUP", b"NOSUCH", b"s"])
17693 .contains("Try XGROUP HELP")
17694 );
17695
17696 assert_eq!(f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0"]), "+OK\r\n");
17697 assert!(
17698 f.run(&[b"XGROUP", b"SETID", b"s", b"nogroup", b"0"])
17699 .starts_with("-NOGROUP")
17700 );
17701 assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":1\r\n");
17702 assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":0\r\n");
17703 assert!(
17704 f.run(&[b"XGROUP", b"DESTROY", b"missing", b"g"])
17705 .contains("requires the key")
17706 );
17707 }
17708
17709 #[test]
17711 fn xreadgroup_hands_out_and_xack_takes_back() {
17712 let mut f = Fixture::new();
17713 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
17714 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
17715 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
17716
17717 let first = f.run(&[
17718 b"XREADGROUP",
17719 b"GROUP",
17720 b"g",
17721 b"c1",
17722 b"COUNT",
17723 b"1",
17724 b"STREAMS",
17725 b"s",
17726 b">",
17727 ]);
17728 assert_eq!(
17729 first,
17730 "*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"
17731 );
17732 assert_eq!(
17735 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b"0"]),
17736 "*1\r\n*2\r\n$1\r\ns\r\n*0\r\n"
17737 );
17738 assert_eq!(
17739 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
17740 "*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"
17741 );
17742
17743 assert_eq!(
17744 f.run(&[b"XPENDING", b"s", b"g"]),
17745 "*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"
17746 );
17747 assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":1\r\n");
17748 assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":0\r\n");
17749 assert_eq!(
17751 f.run(&[b"XPENDING", b"s", b"g"]),
17752 "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n"
17753 );
17754
17755 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
17758 f.run(&[b"XDEL", b"s", b"2-1"]);
17759 assert_eq!(
17760 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
17761 "*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"
17762 );
17763
17764 assert!(
17767 f.run(&[
17768 b"XREADGROUP",
17769 b"GROUP",
17770 b"nope",
17771 b"c",
17772 b"STREAMS",
17773 b"s",
17774 b"+"
17775 ])
17776 .starts_with("-NOGROUP")
17777 );
17778 assert!(
17779 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"])
17780 .contains("meaningless in the context of XREADGROUP")
17781 );
17782 assert!(
17783 f.run(&[b"XREAD", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"0"])
17784 .contains("only supported by XREADGROUP")
17785 );
17786 assert!(
17787 f.run(&[
17788 b"XREADGROUP",
17789 b"GROUP",
17790 b"g",
17791 b"c",
17792 b"STREAMS",
17793 b"s",
17794 b"a",
17795 b"b"
17796 ])
17797 .contains("Unbalanced 'xreadgroup' list of streams")
17798 );
17799 }
17800
17801 #[test]
17804 fn xread_with_no_block_writes_the_null_itself() {
17805 let mut f = Fixture::new();
17806 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
17807 assert_eq!(
17808 f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
17809 "*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"
17810 );
17811 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "*-1\r\n");
17814 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"missing", b"0"]), "*-1\r\n");
17815 f.run(&[b"XADD", b"other", b"1-1", b"b", b"2"]);
17816 assert_eq!(
17817 f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"1-1", b"0"]),
17818 "*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"
17819 );
17820 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"$"]), "*-1\r\n");
17822 assert_eq!(
17824 f.run(&[b"XREAD", b"COUNT", b"5", b"STREAMS", b"s", b"+"]),
17825 "*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"
17826 );
17827 assert_eq!(
17830 f.run(&[b"XREAD", b"COUNT", b"0", b"STREAMS", b"s", b"0"]),
17831 "*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"
17832 );
17833 assert!(
17835 f.run(&[b"XREAD", b"BLOCK", b"0.5", b"STREAMS", b"s", b"$"])
17836 .contains("not an integer")
17837 );
17838 assert!(
17839 f.run(&[b"XREAD", b"BLOCK", b"-1", b"STREAMS", b"s", b"$"])
17840 .contains("timeout is negative")
17841 );
17842 assert!(
17843 f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"0"])
17844 .contains("Unbalanced 'xread' list of streams")
17845 );
17846 }
17847
17848 #[test]
17850 fn a_blocked_xread_wakes_on_the_next_entry() {
17851 let mut f = Fixture::new();
17852 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
17853 let (flow, reply) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
17854 assert_eq!(flow, Flow::Block);
17855 assert!(reply.is_empty());
17856
17857 f.session = Session::new(8);
17862 let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
17863 assert_eq!(flow, Flow::Block);
17864 assert_eq!(f.server.parked(), 2);
17865
17866 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
17867 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";
17868 for client in [7, 8] {
17869 let mut out = Out::new(Proto::Resp2);
17870 assert!(f.server.serve_waiter(client, 0, &mut out));
17871 assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
17872 }
17873
17874 f.server.forget_waiters(7);
17877 f.server.forget_waiters(8);
17878 let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"50", b"STREAMS", b"s", b"$"]);
17879 assert_eq!(flow, Flow::Block);
17880 let mut out = Out::new(Proto::Resp2);
17881 assert!(!f.server.serve_waiter(8, 0, &mut out));
17882 assert!(out.as_slice().is_empty());
17883 assert!(f.server.serve_waiter(8, u64::MAX, &mut out));
17884 assert_eq!(
17885 core::str::from_utf8(out.as_slice()).expect("ascii"),
17886 "*-1\r\n"
17887 );
17888 }
17889
17890 #[test]
17892 fn losing_a_group_while_blocked_is_the_ordinary_sentence() {
17893 let mut f = Fixture::new();
17894 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
17895 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"]);
17896 let (flow, _) = f.flow(&[
17897 b"XREADGROUP",
17898 b"GROUP",
17899 b"g",
17900 b"c",
17901 b"BLOCK",
17902 b"0",
17903 b"STREAMS",
17904 b"s",
17905 b">",
17906 ]);
17907 assert_eq!(flow, Flow::Block);
17908
17909 f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]);
17910 let mut out = Out::new(Proto::Resp2);
17911 assert!(f.server.serve_waiter(7, 0, &mut out));
17912 assert_eq!(
17915 core::str::from_utf8(out.as_slice()).expect("ascii"),
17916 "-NOGROUP No such key 's' or consumer group 'g' in XREADGROUP with GROUP option\r\n"
17917 );
17918 }
17919
17920 #[test]
17922 fn xclaim_reads_ids_until_one_will_not_parse() {
17923 let mut f = Fixture::new();
17924 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
17925 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
17926 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
17927 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
17928
17929 assert!(
17932 f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"-"])
17933 .contains("Unrecognized XCLAIM option '-'")
17934 );
17935 assert_eq!(
17936 f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1", b"JUSTID"]),
17937 "*1\r\n$3\r\n1-1\r\n"
17938 );
17939 f.run(&[b"XDEL", b"s", b"2-1"]);
17942 assert_eq!(
17943 f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1"]),
17944 "*0\r\n"
17945 );
17946 assert!(
17947 f.run(&[b"XPENDING", b"s", b"g"])
17948 .starts_with("*4\r\n:1\r\n")
17949 );
17950 assert!(
17951 f.run(&[b"XCLAIM", b"s", b"nope", b"c", b"0", b"1-1"])
17952 .starts_with("-NOGROUP")
17953 );
17954 assert!(
17955 f.run(&[b"XCLAIM", b"s", b"g", b"c", b"nan", b"1-1"])
17956 .contains("Invalid min-idle-time argument for XCLAIM")
17957 );
17958 }
17959
17960 #[test]
17962 fn xautoclaim_reports_what_it_dropped() {
17963 let mut f = Fixture::new();
17964 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
17965 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
17966 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
17967 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
17968 f.run(&[b"XDEL", b"s", b"1-1"]);
17969
17970 assert_eq!(
17973 f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"JUSTID"]),
17974 "*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"
17975 );
17976 assert!(
17977 f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"COUNT", b"0"])
17978 .contains("COUNT must be > 0")
17979 );
17980 assert!(
17981 f.run(&[b"XAUTOCLAIM", b"s", b"nope", b"c", b"0", b"-"])
17982 .starts_with("-NOGROUP")
17983 );
17984 }
17985
17986 #[test]
17988 fn xdelex_answers_one_integer_an_id() {
17989 let mut f = Fixture::new();
17990 for i in 1..=4 {
17991 f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
17992 }
17993 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
17994 f.run(&[
17995 b"XREADGROUP",
17996 b"GROUP",
17997 b"g",
17998 b"c",
17999 b"COUNT",
18000 b"2",
18001 b"STREAMS",
18002 b"s",
18003 b">",
18004 ]);
18005
18006 assert_eq!(
18008 f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1", b"9-9"]),
18009 "*2\r\n:1\r\n:-1\r\n"
18010 );
18011 assert!(
18014 f.run(&[b"XPENDING", b"s", b"g"])
18015 .starts_with("*4\r\n:2\r\n")
18016 );
18017 assert_eq!(
18019 f.run(&[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"]),
18020 "*1\r\n:1\r\n"
18021 );
18022 assert_eq!(
18024 f.run(&[b"XPENDING", b"s", b"g"]),
18025 "*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"
18026 );
18027
18028 assert_eq!(
18032 f.run(&[b"XDELEX", b"s", b"ACKED", b"IDS", b"2", b"3-1", b"4-1"]),
18033 "*2\r\n:2\r\n:2\r\n"
18034 );
18035
18036 assert_eq!(
18038 f.run(&[b"XDELEX", b"nope", b"IDS", b"2", b"bad", b"worse"]),
18039 "*2\r\n:-1\r\n:-1\r\n"
18040 );
18041 assert!(
18043 f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"3-1", b"bad"])
18044 .starts_with("-ERR Invalid stream ID")
18045 );
18046 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
18047
18048 assert!(
18049 f.run(&[b"XDELEX", b"s", b"IDS", b"0", b"1-1"])
18050 .contains("Number of IDs must be a positive integer")
18051 );
18052 assert!(
18053 f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1"])
18054 .contains("The `numids` parameter must match the number of arguments")
18055 );
18056 assert!(
18059 f.run(&[b"XDELEX", b"s", b"KEEPREF", b"DELREF", b"IDS", b"1", b"1-1"])
18060 .starts_with("-ERR syntax error")
18061 );
18062 assert!(
18063 f.run(&[b"XDELEX", b"s", b"IDS", b"1", b"1-1", b"2-1"])
18064 .starts_with("-ERR syntax error")
18065 );
18066 f.run(&[b"SET", b"str", b"v"]);
18068 assert!(
18069 f.run(&[b"XDELEX", b"str", b"BOGUS", b"IDS", b"0", b"1-1"])
18070 .starts_with("-WRONGTYPE")
18071 );
18072 }
18073
18074 #[test]
18076 fn xackdel_reports_what_the_group_was_holding() {
18077 let mut f = Fixture::new();
18078 for i in 1..=3 {
18079 f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
18080 }
18081 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
18082 f.run(&[
18083 b"XREADGROUP",
18084 b"GROUP",
18085 b"g",
18086 b"c",
18087 b"COUNT",
18088 b"1",
18089 b"STREAMS",
18090 b"s",
18091 b">",
18092 ]);
18093
18094 assert_eq!(
18098 f.run(&[b"XACKDEL", b"s", b"g", b"IDS", b"2", b"1-1", b"2-1"]),
18099 "*2\r\n:1\r\n:-1\r\n"
18100 );
18101 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
18102
18103 assert_eq!(
18105 f.run(&[b"XACKDEL", b"s", b"nope", b"IDS", b"1", b"2-1"]),
18106 "*1\r\n:-1\r\n"
18107 );
18108 assert_eq!(
18109 f.run(&[b"XACKDEL", b"nope", b"g", b"IDS", b"1", b"2-1"]),
18110 "*1\r\n:-1\r\n"
18111 );
18112
18113 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b">"]);
18116 f.run(&[b"XGROUP", b"CREATE", b"s", b"g2", b"0"]);
18117 assert_eq!(
18118 f.run(&[b"XACKDEL", b"s", b"g", b"ACKED", b"IDS", b"1", b"2-1"]),
18119 "*1\r\n:2\r\n"
18120 );
18121 assert_eq!(
18122 f.run(&[b"XPENDING", b"s", b"g"]),
18123 "*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"
18124 );
18125 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
18126 }
18127
18128 #[test]
18130 fn xnack_releases_an_entry_for_the_next_claim() {
18131 let mut f = Fixture::new();
18132 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
18133 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
18134 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
18135 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
18136 f.run(&[b"XCLAIM", b"s", b"g", b"c1", b"0", b"1-1", b"2-1"]);
18139
18140 assert_eq!(
18141 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1"]),
18142 ":1\r\n"
18143 );
18144 assert_eq!(
18148 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]),
18149 "*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"
18150 );
18151 assert_eq!(
18153 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
18154 "*1\r\n*4\r\n$3\r\n2-1\r\n$2\r\nc1\r\n:0\r\n:2\r\n"
18155 );
18156 assert_eq!(
18158 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b">"]),
18159 "*-1\r\n"
18160 );
18161 assert_eq!(
18163 f.run(&[
18164 b"XAUTOCLAIM",
18165 b"s",
18166 b"g",
18167 b"c2",
18168 b"99999999",
18169 b"-",
18170 b"JUSTID"
18171 ]),
18172 "*3\r\n$3\r\n0-0\r\n*1\r\n$3\r\n1-1\r\n*0\r\n"
18173 );
18174
18175 f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
18179 assert!(
18180 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
18181 .contains(":-1\r\n:1\r\n")
18182 );
18183 f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
18185 f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
18186 assert!(
18187 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
18188 .contains(":-1\r\n:0\r\n")
18189 );
18190 f.run(&[b"XNACK", b"s", b"g", b"FATAL", b"IDS", b"1", b"1-1"]);
18192 assert!(
18193 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
18194 .contains(":9223372036854775807\r\n")
18195 );
18196 f.run(&[
18197 b"XNACK",
18198 b"s",
18199 b"g",
18200 b"FATAL",
18201 b"IDS",
18202 b"1",
18203 b"1-1",
18204 b"RETRYCOUNT",
18205 b"3",
18206 ]);
18207 assert!(
18208 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
18209 .contains(":-1\r\n:3\r\n")
18210 );
18211
18212 f.run(&[b"XACK", b"s", b"g", b"2-1"]);
18216 assert_eq!(
18217 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"]),
18218 ":0\r\n"
18219 );
18220 assert_eq!(
18221 f.run(&[
18222 b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1", b"FORCE"
18223 ]),
18224 ":1\r\n"
18225 );
18226 assert!(
18227 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
18228 .contains(":-1\r\n:0\r\n")
18229 );
18230 assert_eq!(
18232 f.run(&[
18233 b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"9-9", b"FORCE"
18234 ]),
18235 ":0\r\n"
18236 );
18237
18238 assert_eq!(
18241 f.run(&[b"XNACK", b"s", b"nope", b"BOGUS", b"IDS", b"1", b"1-1"]),
18242 "-NOGROUP No such key 's' or consumer group 'nope'\r\n"
18243 );
18244 assert!(
18245 f.run(&[b"XNACK", b"s", b"g", b"BOGUS", b"IDS", b"1", b"1-1"])
18246 .starts_with("-ERR")
18247 );
18248 assert!(
18250 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"0", b"1-1"])
18251 .contains("numids must be a positive integer")
18252 );
18253 assert!(
18254 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"2", b"1-1"])
18255 .contains("number of IDs doesn't match numids")
18256 );
18257 assert!(
18260 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1", b"2-1"])
18261 .contains("Unrecognized XNACK option '2-1'")
18262 );
18263 }
18264
18265 #[test]
18267 fn xinfo_reports_the_stream_the_groups_and_the_consumers() {
18268 let mut f = Fixture::new();
18269 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
18270 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
18271 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
18272 f.run(&[
18273 b"XREADGROUP",
18274 b"GROUP",
18275 b"g",
18276 b"c1",
18277 b"COUNT",
18278 b"1",
18279 b"STREAMS",
18280 b"s",
18281 b">",
18282 ]);
18283
18284 let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
18285 assert!(info.starts_with("*20\r\n"), "{info}");
18288 assert!(info.contains("$6\r\nlength\r\n:2\r\n"), "{info}");
18289 assert!(
18290 info.contains("$17\r\nlast-generated-id\r\n$3\r\n2-1\r\n"),
18291 "{info}"
18292 );
18293 assert!(info.contains("$13\r\nentries-added\r\n:2\r\n"), "{info}");
18294 assert!(info.contains("$6\r\ngroups\r\n:1\r\n"), "{info}");
18295
18296 let groups = f.run(&[b"XINFO", b"GROUPS", b"s"]);
18297 assert!(groups.starts_with("*1\r\n*12\r\n"), "{groups}");
18298 assert!(groups.contains("$9\r\nconsumers\r\n:1\r\n"), "{groups}");
18299 assert!(groups.contains("$7\r\npending\r\n:1\r\n"), "{groups}");
18300 assert!(groups.contains("$3\r\nlag\r\n:1\r\n"), "{groups}");
18301
18302 f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c2"]);
18306 let consumers = f.run(&[b"XINFO", b"CONSUMERS", b"s", b"g"]);
18307 assert!(consumers.starts_with("*2\r\n"), "{consumers}");
18308 assert!(
18309 consumers.contains("$8\r\ninactive\r\n:-1\r\n"),
18310 "{consumers}"
18311 );
18312 let c1 = consumers.find("c1").unwrap();
18314 let c2 = consumers.find("c2").unwrap();
18315 assert!(c1 < c2, "{consumers}");
18316
18317 let full = f.run(&[b"XINFO", b"STREAM", b"s", b"FULL"]);
18318 assert!(full.starts_with("*18\r\n"), "{full}");
18319 assert!(full.contains("$12\r\nnacked-count\r\n:0\r\n"), "{full}");
18320 assert!(full.contains("$11\r\nactive-time\r\n"), "{full}");
18321
18322 assert!(
18323 f.run(&[b"XINFO", b"STREAM", b"missing"])
18324 .contains("no such key")
18325 );
18326 assert!(
18327 f.run(&[b"XINFO", b"GROUPS", b"missing"])
18328 .contains("no such key")
18329 );
18330 assert!(
18331 f.run(&[b"XINFO", b"CONSUMERS", b"s", b"nope"])
18332 .starts_with("-NOGROUP")
18333 );
18334 assert!(
18335 f.run(&[b"XINFO", b"NOSUCH", b"s"])
18336 .contains("Try XINFO HELP")
18337 );
18338 assert!(f.run(&[b"XINFO", b"HELP"]).contains("XINFO <subcommand>"));
18339 assert!(f.run(&[b"XGROUP", b"HELP"]).contains("XGROUP <subcommand>"));
18340 }
18341
18342 #[test]
18344 fn xpending_takes_the_consumer_only_when_the_count_comes_out_right() {
18345 let mut f = Fixture::new();
18346 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
18347 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
18348 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
18349
18350 let list = f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]);
18351 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");
18352 assert_eq!(
18353 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
18354 "*1\r\n*4\r\n$3\r\n1-1\r\n$2\r\nc1\r\n:0\r\n:1\r\n"
18355 );
18356 assert_eq!(
18358 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"nope"]),
18359 "*0\r\n"
18360 );
18361 assert_eq!(
18362 f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0", b"-", b"+", b"10"]),
18363 list
18364 );
18365 assert!(
18367 f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0"])
18368 .contains("syntax error")
18369 );
18370 assert!(
18371 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+"])
18372 .contains("syntax error")
18373 );
18374 assert_eq!(
18375 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"-1"]),
18376 "*0\r\n"
18377 );
18378 assert!(
18379 f.run(&[b"XPENDING", b"missing", b"g"])
18380 .starts_with("-NOGROUP")
18381 );
18382 }
18383
18384 #[test]
18386 fn xsetid_will_not_go_below_what_is_there() {
18387 let mut f = Fixture::new();
18388 f.run(&[b"XADD", b"s", b"5-5", b"a", b"1"]);
18389 assert_eq!(f.run(&[b"XSETID", b"s", b"9-9"]), "+OK\r\n");
18390 assert_eq!(
18391 f.run(&[
18392 b"XSETID",
18393 b"s",
18394 b"10-1",
18395 b"ENTRIESADDED",
18396 b"7",
18397 b"MAXDELETEDID",
18398 b"9-1"
18399 ]),
18400 "+OK\r\n"
18401 );
18402 let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
18403 assert!(info.contains("$13\r\nentries-added\r\n:7\r\n"), "{info}");
18404 assert!(
18405 info.contains("$20\r\nmax-deleted-entry-id\r\n$3\r\n9-1\r\n"),
18406 "{info}"
18407 );
18408
18409 assert!(
18410 f.run(&[b"XSETID", b"s", b"1-1"])
18411 .contains("smaller than the target stream top item")
18412 );
18413 assert!(
18414 f.run(&[b"XSETID", b"s", b"10-1", b"ENTRIESADDED", b"-1"])
18415 .contains("entries_added must be positive")
18416 );
18417 assert!(
18418 f.run(&[b"XSETID", b"missing", b"1-1"])
18419 .contains("no such key")
18420 );
18421 }
18422
18423 #[test]
18425 fn xread_answers_a_map_on_resp3_and_the_fields_stay_flat() {
18426 let mut f = Fixture::new();
18427 f.run(&[b"HELLO", b"3"]);
18428 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
18429 assert_eq!(
18432 f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
18433 "%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"
18434 );
18435 assert_eq!(
18438 f.run(&[b"XRANGE", b"s", b"-", b"+"]),
18439 "*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"
18440 );
18441 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "_\r\n");
18442 }
18443
18444 struct Mem {
18450 blobs: Vec<Vec<u8>>,
18451 }
18452
18453 impl yo_kv::cold::Blocks for Mem {
18454 fn put(&mut self, bytes: &[u8]) -> yo_common::Result<yo_common::Addr> {
18455 self.blobs.push(bytes.to_vec());
18456 Ok(yo_common::Addr::new(
18457 yo_common::Space::Log,
18458 (self.blobs.len() - 1) as u64,
18459 ))
18460 }
18461
18462 fn get(&self, at: yo_common::Addr) -> yo_common::Result<&[u8]> {
18463 self.blobs
18464 .get(at.offset() as usize)
18465 .map(Vec::as_slice)
18466 .ok_or_else(|| {
18467 yo_common::Error::new(yo_common::Code::Corrupt, "no chunk at that address")
18468 })
18469 }
18470
18471 fn bytes(&self) -> u64 {
18472 self.blobs.iter().map(|b| b.len() as u64).sum()
18473 }
18474 }
18475
18476 fn filled(attach: bool) -> (Fixture, usize) {
18489 let mut f = Fixture::new();
18490 if attach {
18491 f.server
18492 .striped(0)
18493 .hold_stripe(0)
18494 .attach(Box::new(Mem { blobs: Vec::new() }));
18495 }
18496 let val = vec![b'v'; 256];
18497 for i in 0..24000u32 {
18498 let k = format!("key:{i:08}");
18499 f.run(&[b"SET", k.as_bytes(), &val]);
18500 }
18501 let full = f.server.memory_bytes();
18502 assert!(full > 3 * 1024 * 1024, "the arena is several segments");
18503 (f, full)
18504 }
18505
18506 fn press(f: &mut Fixture, limit: usize) {
18512 let val = vec![b'v'; 256];
18513 for i in 0..3000u32 {
18514 let k = format!("new:{i:08}");
18515 assert_eq!(
18516 f.run(&[b"SET", k.as_bytes(), &val]),
18517 "+OK\r\n",
18518 "write {i} was refused"
18519 );
18520 f.server.refresh_memory();
18521 if f.server.memory_bytes() <= limit {
18522 return;
18523 }
18524 }
18525 panic!(
18526 "it never got under: {} against {limit}",
18527 f.server.memory_bytes()
18528 );
18529 }
18530
18531 #[test]
18532 fn the_storage_limit_reads_back_and_minus_one_is_no_limit() {
18533 let mut f = Fixture::new();
18534 assert_eq!(
18535 f.run(&[b"CONFIG", b"GET", b"maxstore"]),
18536 "*2\r\n$8\r\nmaxstore\r\n$2\r\n-1\r\n",
18537 "no limit is the default"
18538 );
18539 for (typed, bytes) in [
18542 (&b"0"[..], "0"),
18543 (b"1024", "1024"),
18544 (b"1k", "1000"),
18545 (b"1gb", "1073741824"),
18546 (b"-1", "-1"),
18547 ] {
18548 assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxstore", typed]), "+OK\r\n");
18549 assert_eq!(
18550 f.run(&[b"CONFIG", b"GET", b"maxstore"]),
18551 format!("*2\r\n$8\r\nmaxstore\r\n${}\r\n{bytes}\r\n", bytes.len()),
18552 "set {}",
18553 String::from_utf8_lossy(typed)
18554 );
18555 }
18556 for bad in [&b"1tb"[..], b"-2", b"", b"lots"] {
18557 assert_eq!(
18558 f.run(&[b"CONFIG", b"SET", b"maxstore", bad]),
18559 "-ERR CONFIG SET failed (possibly related to argument 'maxstore') - argument must be a memory value or -1\r\n",
18560 "refused {}",
18561 String::from_utf8_lossy(bad)
18562 );
18563 }
18564 let info = f.run(&[b"INFO", b"memory"]);
18566 assert!(info.contains("maxstore:-1"), "{info}");
18567 assert!(info.contains("yo_memory_regime:evict"), "{info}");
18568 assert!(info.contains("yo_store_bytes:0"), "{info}");
18569 }
18570
18571 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
18572 #[test]
18573 fn a_memory_limit_moves_values_to_the_file_instead_of_dropping_keys() {
18574 let (mut f, full) = filled(true);
18578 let keys = f.run(&[b"DBSIZE"]);
18579 assert!(
18580 f.run(&[b"INFO", b"memory"])
18581 .contains("yo_memory_regime:migrate"),
18582 "a database with somewhere to put values migrates"
18583 );
18584
18585 let limit = full - 2 * 1024 * 1024;
18586 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
18587 f.run(&[
18588 b"CONFIG",
18589 b"SET",
18590 b"maxmemory",
18591 limit.to_string().as_bytes(),
18592 ]);
18593 press(&mut f, limit);
18594
18595 assert!(
18596 f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
18597 "nothing was thrown away"
18598 );
18599 let after: usize = f.run(&[b"DBSIZE"])[1..]
18600 .trim_end()
18601 .parse()
18602 .expect("a count");
18603 let before: usize = keys[1..].trim_end().parse().expect("a count");
18604 assert!(after > before, "the keys that came in are all still here");
18605 assert!(
18606 f.server.store_bytes() > 0,
18607 "and what came out of memory went to the file"
18608 );
18609 let val = format!("$256\r\n{}\r\n", "v".repeat(256));
18612 assert_eq!(f.run(&[b"GET", b"key:00000000"]), val);
18613 assert_eq!(f.run(&[b"GET", b"key:00023999"]), val);
18614 }
18615
18616 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
18617 #[test]
18618 fn a_storage_limit_of_zero_restores_redis_behaviour_exactly() {
18619 let (mut f, full) = filled(true);
18623 f.run(&[b"CONFIG", b"SET", b"maxstore", b"0"]);
18624 assert!(
18625 f.run(&[b"INFO", b"memory"])
18626 .contains("yo_memory_regime:evict"),
18627 "nothing may go to the file"
18628 );
18629
18630 let limit = full - 2 * 1024 * 1024;
18631 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
18632 f.run(&[
18633 b"CONFIG",
18634 b"SET",
18635 b"maxmemory",
18636 limit.to_string().as_bytes(),
18637 ]);
18638 press(&mut f, limit);
18639
18640 assert!(
18641 !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
18642 "keys were thrown away, which is what was asked for"
18643 );
18644 assert_eq!(f.server.store_bytes(), 0, "and the file was never written");
18645 }
18646
18647 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
18648 #[test]
18649 fn a_full_file_goes_back_to_evicting() {
18650 let (mut f, full) = filled(true);
18654 f.run(&[b"CONFIG", b"SET", b"maxstore", b"64kb"]);
18655 let limit = full - 2 * 1024 * 1024;
18656 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
18657 f.run(&[
18658 b"CONFIG",
18659 b"SET",
18660 b"maxmemory",
18661 limit.to_string().as_bytes(),
18662 ]);
18663 press(&mut f, limit);
18664
18665 assert!(f.server.store_bytes() >= 64 * 1024, "the file filled up");
18666 assert!(
18667 !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
18668 "and then it started evicting"
18669 );
18670 assert!(
18671 f.run(&[b"INFO", b"memory"])
18672 .contains("yo_memory_regime:evict"),
18673 "and it says so"
18674 );
18675 }
18676 #[test]
18692 fn the_string_group_answers_the_same_however_many_stripes_there_are() {
18693 let script: &[&[&[u8]]] = &[
18694 &[b"SET", b"k1", b"v1"],
18697 &[b"SET", b"k2", b"v2"],
18698 &[b"GET", b"k1"],
18699 &[b"GET", b"nothing"],
18700 &[b"GETSET", b"k1", b"v1b"],
18701 &[b"SETNX", b"k1", b"no"],
18702 &[b"SETNX", b"k3", b"yes"],
18703 &[b"APPEND", b"k3", b"!"],
18704 &[b"STRLEN", b"k3"],
18705 &[b"SETRANGE", b"k3", b"1", b"XY"],
18706 &[b"GETRANGE", b"k3", b"0", b"-1"],
18707 &[b"INCR", b"n1"],
18708 &[b"INCRBY", b"n1", b"41"],
18709 &[b"DECRBY", b"n1", b"2"],
18710 &[b"INCRBYFLOAT", b"f1", b"1.5"],
18711 &[b"SETEX", b"e1", b"100", b"v"],
18712 &[b"PSETEX", b"e2", b"100000", b"v"],
18713 &[b"GETEX", b"e1", b"PERSIST"],
18714 &[b"GETDEL", b"k2"],
18715 &[b"GET", b"k2"],
18716 &[b"DIGEST", b"k1"],
18717 &[b"DELEX", b"k3"],
18718 &[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"],
18721 &[b"MGET", b"a", b"b", b"c", b"missing"],
18722 &[b"MSETNX", b"d", b"4", b"e", b"5"],
18723 &[b"MSETNX", b"e", b"6", b"f", b"7"],
18724 &[b"MGET", b"d", b"e", b"f"],
18725 &[b"MSETEX", b"2", b"g", b"7", b"h", b"8", b"NX"],
18726 &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"NX"],
18727 &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"XX"],
18728 &[b"MGET", b"g", b"h"],
18729 &[b"SET", b"s1", b"ohmytext"],
18730 &[b"SET", b"s2", b"mynewtext"],
18731 &[b"LCS", b"s1", b"s2"],
18732 &[b"LCS", b"s1", b"s2", b"LEN"],
18733 &[b"LCS", b"s1", b"s2", b"IDX", b"MINMATCHLEN", b"4"],
18734 &[b"LCS", b"s1", b"s2", b"IDX", b"WITHMATCHLEN"],
18735 &[b"LCS", b"s1", b"gone"],
18736 &[b"MSET", b"odd"],
18738 &[b"LCS", b"s1", b"s2", b"LEN", b"IDX"],
18739 &[b"MGET"],
18740 ];
18741
18742 let mut one = Fixture::new();
18743 let mut many = Fixture::striped(8);
18744 for parts in script {
18745 let a = one.run(parts);
18746 let b = many.run(parts);
18747 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
18748 }
18749 }
18750
18751 #[test]
18756 fn a_striped_database_spreads_the_keys_it_is_given() {
18757 let mut f = Fixture::striped(8);
18758 for i in 0..256 {
18759 let key = format!("key:{i}");
18760 f.run(&[b"SET", key.as_bytes(), b"v"]);
18761 }
18762 assert_eq!(f.run(&[b"DBSIZE"]), ":256\r\n");
18763 }
18764
18765 #[test]
18768 fn a_wrong_type_in_the_middle_of_an_mget_is_still_one_nil() {
18769 let mut one = Fixture::new();
18770 let mut many = Fixture::striped(8);
18771 for f in [&mut one, &mut many] {
18772 f.run(&[b"SET", b"str", b"v"]);
18773 f.server
18779 .striped(0)
18780 .hold(b"list")
18781 .push(b"list", yo_kv::End::Right, core::iter::once(&b"v"[..]))
18782 .expect("a new list");
18783 }
18784 assert_eq!(
18785 one.run(&[b"MGET", b"str", b"list", b"gone"]),
18786 many.run(&[b"MGET", b"str", b"list", b"gone"])
18787 );
18788 }
18789
18790 #[test]
18798 fn the_keyspace_group_answers_the_same_however_many_stripes_there_are() {
18799 let script: &[&[&[u8]]] = &[
18800 &[b"SET", b"k1", b"v1"],
18801 &[b"SET", b"k2", b"v2"],
18802 &[b"EXISTS", b"k1", b"k2", b"k1", b"gone"],
18803 &[b"TYPE", b"k1"],
18804 &[b"TYPE", b"gone"],
18805 &[b"TOUCH", b"k1", b"k2", b"k1", b"gone"],
18806 &[b"EXPIRE", b"k1", b"100"],
18807 &[b"TTL", b"k1"],
18808 &[b"EXPIRE", b"k1", b"200", b"NX"],
18809 &[b"PERSIST", b"k1"],
18810 &[b"TTL", b"k1"],
18811 &[b"PEXPIREAT", b"k2", b"1900000000000"],
18812 &[b"EXPIRETIME", b"k2"],
18813 &[b"PEXPIRETIME", b"k2"],
18814 &[b"PERSIST", b"k2"],
18815 &[b"OBJECT", b"ENCODING", b"k1"],
18816 &[b"OBJECT", b"REFCOUNT", b"k1"],
18817 &[b"OBJECT", b"IDLETIME", b"k1"],
18818 &[b"OBJECT", b"FREQ", b"k1"],
18819 &[b"OBJECT", b"ENCODING", b"gone"],
18820 &[b"OBJECT", b"HELP"],
18821 &[b"RENAME", b"k1", b"k9"],
18822 &[b"GET", b"k9"],
18823 &[b"RENAME", b"gone", b"x"],
18824 &[b"RENAMENX", b"k9", b"k2"],
18825 &[b"RENAMENX", b"k9", b"k8"],
18826 &[b"GET", b"k8"],
18827 &[b"COPY", b"k8", b"c1"],
18828 &[b"COPY", b"k8", b"c1"],
18829 &[b"COPY", b"k8", b"c1", b"REPLACE"],
18830 &[b"COPY", b"k8", b"k8"],
18831 &[b"COPY", b"gone", b"c2"],
18832 &[b"COPY", b"k8", b"k8", b"DB", b"1"],
18833 &[b"COPY", b"k8", b"c9", b"DB", b"9"],
18834 &[b"MOVE", b"c1", b"1"],
18835 &[b"MOVE", b"c1", b"1"],
18836 &[b"MOVE", b"k8", b"0"],
18837 &[b"DEL", b"k2", b"gone"],
18838 &[b"UNLINK", b"k8", b"k8"],
18839 &[b"DBSIZE"],
18840 ];
18841
18842 let mut one = Fixture::new();
18843 let mut many = Fixture::striped(8);
18844 for parts in script {
18845 let a = one.run(parts);
18846 let b = many.run(parts);
18847 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
18848 }
18849
18850 for f in [&mut one, &mut many] {
18856 f.run(&[b"SET", b"d1", b"payload"]);
18857 let payload = f
18858 .server
18859 .striped(0)
18860 .hold(b"d1")
18861 .dump(b"d1")
18862 .expect("a key that is there");
18863 assert!(
18864 f.run(&[b"DUMP", b"d1"])
18865 .starts_with(&format!("${}", payload.len())),
18866 "a payload of the length the store gave"
18867 );
18868 assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
18869 assert_eq!(f.run(&[b"RESTORE", b"d2", b"0", &payload]), "+OK\r\n");
18870 assert_eq!(f.run(&[b"GET", b"d2"]), "$7\r\npayload\r\n");
18871 assert_eq!(
18872 f.run(&[b"RESTORE", b"d2", b"0", &payload]),
18873 "-BUSYKEY Target key name already exists.\r\n"
18874 );
18875 assert_eq!(
18876 f.run(&[b"RESTORE", b"d3", b"0", b"rubbish"]),
18877 "-ERR DUMP payload version or checksum are wrong\r\n"
18878 );
18879 }
18880 }
18881
18882 #[test]
18889 fn a_scan_of_a_striped_database_walks_all_of_it() {
18890 let n = if cfg!(miri) { 80 } else { 500 };
18894 let mut f = Fixture::striped(8);
18895 for i in 0..n {
18896 let key = format!("key:{i}");
18897 f.run(&[b"SET", key.as_bytes(), b"v"]);
18898 }
18899
18900 let mut seen = Vec::new();
18901 let mut cursor = "0".to_owned();
18902 let mut calls = 0;
18903 loop {
18904 let reply = f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"10"]);
18905 let (next, keys) = scan_reply(&reply);
18906 seen.extend(keys);
18907 cursor = next;
18908 calls += 1;
18909 assert!(calls < 5_000, "a scan that will not finish");
18910 if cursor == "0" {
18911 break;
18912 }
18913 }
18914 seen.sort();
18915 assert_eq!(seen.len(), n, "a quiet scan answered a key twice");
18916 assert_eq!(seen, sorted(&f.run(&[b"KEYS", b"*"])));
18917
18918 let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"MATCH", b"key:4?"]);
18922 let (_, keys) = scan_reply(&reply);
18923 assert_eq!(keys.len(), 10, "key:40 through key:49");
18924 let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"TYPE", b"list"]);
18925 let (_, keys) = scan_reply(&reply);
18926 assert!(keys.is_empty(), "nothing here is a list");
18927 }
18928
18929 #[test]
18935 fn a_random_key_can_come_from_any_stripe() {
18936 let mut f = Fixture::striped(8);
18937 assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
18938 for i in 0..200 {
18939 let key = format!("key:{i}");
18940 f.run(&[b"SET", key.as_bytes(), b"v"]);
18941 }
18942 let mut homes = std::collections::HashSet::new();
18943 for _ in 0..200 {
18944 let got = f.run(&[b"RANDOMKEY"]);
18945 let key = got.split("\r\n").nth(1).expect("a key").to_owned();
18946 assert_eq!(f.run(&[b"EXISTS", key.as_bytes()]), ":1\r\n");
18947 homes.insert(f.server.striped(0).stripe_of(key.as_bytes()));
18948 }
18949 assert_eq!(homes.len(), 8, "some stripe was never drawn from");
18950 }
18951
18952 fn apart(f: &mut Fixture, src: &str) -> String {
18956 let home = f.server.striped(0).stripe_of(src.as_bytes());
18957 for i in 0..1_000 {
18958 let dst = format!("dst:{i}");
18959 if f.server.striped(0).stripe_of(dst.as_bytes()) != home {
18960 return dst;
18961 }
18962 }
18963 panic!("eight stripes and a thousand keys all landed in one place");
18964 }
18965
18966 #[test]
18969 fn a_rename_across_stripes_takes_everything_with_it() {
18970 let mut f = Fixture::striped(8);
18971 let dst = apart(&mut f, "src");
18972 let (src, dst) = (b"src".as_slice(), dst.as_bytes());
18973
18974 f.run(&[b"SET", src, b"v"]);
18975 f.run(&[b"EXPIRE", src, b"100"]);
18976 assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
18977 assert_eq!(f.run(&[b"EXISTS", src, dst]), ":1\r\n");
18978 assert_eq!(f.run(&[b"GET", dst]), "$1\r\nv\r\n");
18979 assert_eq!(f.run(&[b"TTL", dst]), ":100\r\n", "the deadline came too");
18980
18981 f.server
18986 .striped(0)
18987 .hold(src)
18988 .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
18989 .expect("a new list");
18990 assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
18991 assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n");
18992 assert_eq!(
18993 f.server.striped(0).hold(dst).llen(dst).expect("a list"),
18994 2,
18995 "the members are on the stripe the key moved to"
18996 );
18997
18998 f.run(&[b"SET", src, b"v"]);
19001 assert_eq!(f.run(&[b"RENAMENX", src, dst]), ":0\r\n");
19002 assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n", "and left it alone");
19003 assert_eq!(f.run(&[b"GET", src]), "$1\r\nv\r\n", "and left the source");
19004 }
19005
19006 #[test]
19008 fn a_copy_across_stripes_leaves_the_source_where_it_was() {
19009 let mut f = Fixture::striped(8);
19010 let dst = apart(&mut f, "src");
19011 let (src, dst) = (b"src".as_slice(), dst.as_bytes());
19012
19013 f.run(&[b"SET", src, b"v"]);
19014 assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
19015 assert_eq!(f.run(&[b"EXISTS", src, dst]), ":2\r\n");
19016 assert_eq!(
19017 f.run(&[b"COPY", src, dst]),
19018 ":0\r\n",
19019 "the destination is taken"
19020 );
19021 f.run(&[b"SET", src, b"w"]);
19022 assert_eq!(f.run(&[b"COPY", src, dst, b"REPLACE"]), ":1\r\n");
19023 assert_eq!(f.run(&[b"GET", dst]), "$1\r\nw\r\n");
19024
19025 f.run(&[b"DEL", src, dst]);
19029 f.server
19030 .striped(0)
19031 .hold(src)
19032 .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
19033 .expect("a new list");
19034 assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
19035 f.server
19036 .striped(0)
19037 .hold(src)
19038 .push(src, yo_kv::End::Right, core::iter::once(&b"c"[..]))
19039 .expect("a list that is there");
19040 assert_eq!(f.server.striped(0).hold(src).llen(src).expect("a list"), 3);
19041 assert_eq!(f.server.striped(0).hold(dst).llen(dst).expect("a list"), 2);
19042 }
19043
19044 #[test]
19052 fn the_bitmap_group_answers_the_same_however_many_stripes_there_are() {
19053 let script: &[&[&[u8]]] = &[
19054 &[b"SET", b"k1", b"foobar"],
19055 &[b"SETBIT", b"b1", b"7", b"1"],
19056 &[b"SETBIT", b"b1", b"7", b"0"],
19057 &[b"GETBIT", b"k1", b"6"],
19058 &[b"GETBIT", b"k1", b"100"],
19059 &[b"BITCOUNT", b"k1"],
19060 &[b"BITCOUNT", b"k1", b"0", b"0"],
19061 &[b"BITCOUNT", b"k1", b"5", b"30", b"BIT"],
19062 &[b"BITPOS", b"k1", b"1"],
19063 &[b"BITPOS", b"k1", b"0", b"2"],
19064 &[b"BITPOS", b"k1", b"1", b"2", b"-1", b"BIT"],
19065 &[
19066 b"BITFIELD",
19067 b"bf",
19068 b"SET",
19069 b"u8",
19070 b"0",
19071 b"255",
19072 b"GET",
19073 b"u8",
19074 b"0",
19075 ],
19076 &[
19077 b"BITFIELD",
19078 b"bf",
19079 b"OVERFLOW",
19080 b"SAT",
19081 b"INCRBY",
19082 b"u8",
19083 b"0",
19084 b"10",
19085 ],
19086 &[b"BITFIELD_RO", b"bf", b"GET", b"u8", b"0"],
19087 &[b"SET", b"s1", b"abc"],
19090 &[b"SET", b"s2", b"abd"],
19091 &[b"SET", b"s3", b"a"],
19092 &[b"BITOP", b"AND", b"d1", b"s1", b"s2"],
19093 &[b"GET", b"d1"],
19094 &[b"BITOP", b"OR", b"d2", b"s1", b"s2", b"s3"],
19095 &[b"GET", b"d2"],
19096 &[b"BITOP", b"XOR", b"d3", b"s1", b"s2"],
19097 &[b"STRLEN", b"d3"],
19098 &[b"BITOP", b"NOT", b"d4", b"s1"],
19099 &[b"STRLEN", b"d4"],
19100 &[b"BITOP", b"DIFF", b"d5", b"s1", b"s2"],
19101 &[b"BITOP", b"DIFF1", b"d6", b"s1", b"s2"],
19102 &[b"BITOP", b"ANDOR", b"d7", b"s1", b"s2"],
19103 &[b"BITOP", b"ONE", b"d8", b"s1", b"s2"],
19104 &[b"BITOP", b"AND", b"d1", b"gone", b"also-gone"],
19107 &[b"EXISTS", b"d1"],
19108 &[b"BITOP", b"OR", b"d9", b"s1", b"gone"],
19109 &[b"GET", b"d9"],
19110 &[b"BITOP", b"AND", b"d1", b"s1", b"list"],
19114 &[b"BITOP", b"AND", b"list", b"s1", b"s2"],
19115 &[b"BITOP", b"NOT", b"d1", b"s1", b"s2"],
19116 &[b"BITOP", b"DIFF", b"d1", b"s1"],
19117 &[b"BITOP", b"NOPE", b"d1", b"s1"],
19118 &[b"BITCOUNT", b"list"],
19119 &[b"BITFIELD_RO", b"bf", b"SET", b"u8", b"0", b"1"],
19120 ];
19121
19122 let mut one = Fixture::new();
19123 let mut many = Fixture::striped(8);
19124 for f in [&mut one, &mut many] {
19125 plant_list(f, b"list");
19126 }
19127 for parts in script {
19128 let a = one.run(parts);
19129 let b = many.run(parts);
19130 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
19131 }
19132 }
19133
19134 fn plant_list(f: &mut Fixture, key: &[u8]) {
19140 f.server
19141 .striped(0)
19142 .hold(key)
19143 .push(key, yo_kv::End::Right, core::iter::once(&b"x"[..]))
19144 .expect("a new list");
19145 }
19146
19147 #[test]
19154 fn a_bitop_across_stripes_reads_every_source() {
19155 let mut f = Fixture::striped(8);
19156 let other = apart(&mut f, "src");
19157 let (src, far) = (b"src".as_slice(), other.as_bytes());
19158 assert_ne!(
19159 f.server.striped(0).stripe_of(src),
19160 f.server.striped(0).stripe_of(far),
19161 "the two keys are the point of the test"
19162 );
19163
19164 f.run(&[b"SET", src, b"abc"]);
19165 f.run(&[b"SET", far, b"abd"]);
19166 assert_eq!(f.run(&[b"BITOP", b"AND", far, src, far]), ":3\r\n");
19167 assert_eq!(
19168 f.run(&[b"GET", far]),
19169 "$3\r\nab`\r\n",
19170 "a destination that is also a source"
19171 );
19172 f.run(&[b"SET", far, b"abd"]);
19173 assert_eq!(f.run(&[b"BITOP", b"XOR", src, src, far]), ":3\r\n");
19174 assert_eq!(
19175 f.run(&[b"GET", src]),
19176 "$3\r\n\0\0\x07\r\n",
19177 "and the other way round"
19178 );
19179
19180 f.run(&[b"SET", src, b"abc"]);
19184 f.run(&[b"DEL", far]);
19185 assert_eq!(f.run(&[b"BITOP", b"AND", src, far, b"gone"]), ":0\r\n");
19186 assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n");
19187 f.run(&[b"SET", src, b"abc"]);
19188 f.run(&[b"DEL", far]);
19189 plant_list(&mut f, far);
19190 assert_eq!(
19191 f.run(&[b"BITOP", b"OR", b"out", src, far]),
19192 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19193 );
19194 assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
19195 }
19196
19197 #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
19203 #[test]
19204 fn the_hyperloglog_group_answers_the_same_however_many_stripes_there_are() {
19205 let script: &[&[&[u8]]] = &[
19206 &[b"PFADD", b"h1", b"a", b"b", b"c"],
19207 &[b"PFADD", b"h1", b"a"],
19208 &[b"PFADD", b"h2"],
19209 &[b"PFADD", b"h2", b"c", b"d", b"e"],
19210 &[b"PFCOUNT", b"h1"],
19211 &[b"PFCOUNT", b"h2"],
19212 &[b"PFCOUNT", b"missing"],
19213 &[b"PFCOUNT", b"h1", b"h2"],
19215 &[b"PFCOUNT", b"h1", b"missing"],
19216 &[b"PFMERGE", b"m", b"h1", b"h2"],
19217 &[b"PFCOUNT", b"m"],
19218 &[b"STRLEN", b"m"],
19219 &[b"PFMERGE", b"m"],
19220 &[b"PFCOUNT", b"m"],
19221 &[b"PFMERGE", b"m2", b"missing"],
19222 &[b"PFCOUNT", b"m2"],
19223 &[b"PFDEBUG", b"ENCODING", b"h1"],
19226 &[b"PFDEBUG", b"DECODE", b"h1"],
19227 &[b"PFDEBUG", b"TODENSE", b"h1"],
19228 &[b"PFDEBUG", b"ENCODING", b"h1"],
19229 &[b"PFDEBUG", b"TODENSE", b"h1"],
19230 &[b"PFCOUNT", b"h1", b"h2"],
19231 &[b"PFSELFTEST"],
19232 &[b"SET", b"plain", b"not a sketch at all"],
19234 &[b"PFADD", b"plain", b"a"],
19235 &[b"PFCOUNT", b"plain"],
19236 &[b"PFCOUNT", b"h1", b"plain"],
19237 &[b"PFMERGE", b"plain", b"h1"],
19238 &[b"PFMERGE", b"m", b"plain"],
19239 &[b"PFDEBUG", b"ENCODING", b"gone"],
19240 &[b"PFDEBUG", b"NOPE", b"h1"],
19241 ];
19242
19243 let mut one = Fixture::new();
19244 let mut many = Fixture::striped(8);
19245 for parts in script {
19246 let a = one.run(parts);
19247 let b = many.run(parts);
19248 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
19249 }
19250 }
19251
19252 #[test]
19261 fn the_set_group_answers_the_same_however_many_stripes_there_are() {
19262 const UNORDERED: [&str; 4] = ["SMEMBERS", "SINTER", "SUNION", "SDIFF"];
19263 let script: &[&[&[u8]]] = &[
19264 &[b"SADD", b"s1", b"a", b"b", b"c"],
19265 &[b"SADD", b"s1", b"a"],
19266 &[b"SADD", b"s2", b"b", b"c", b"d"],
19267 &[b"SADD", b"ints", b"1", b"2", b"3"],
19268 &[b"SCARD", b"s1"],
19269 &[b"SISMEMBER", b"s1", b"a"],
19270 &[b"SISMEMBER", b"s1", b"z"],
19271 &[b"SMISMEMBER", b"s1", b"a", b"z", b"c"],
19272 &[b"SMEMBERS", b"s1"],
19273 &[b"SREM", b"s1", b"c"],
19274 &[b"SADD", b"s1", b"c"],
19275 &[b"SSCAN", b"s1", b"0"],
19276 &[b"SSCAN", b"s1", b"0", b"COUNT", b"100", b"MATCH", b"a*"],
19277 &[b"SADD", b"one", b"m"],
19280 &[b"SRANDMEMBER", b"one"],
19281 &[b"SRANDMEMBER", b"one", b"-3"],
19282 &[b"SRANDMEMBER", b"gone"],
19283 &[b"SPOP", b"one"],
19284 &[b"SPOP", b"one"],
19285 &[b"SPOP", b"gone", b"2"],
19286 &[b"SMOVE", b"s1", b"s2", b"a"],
19288 &[b"SMOVE", b"s1", b"s2", b"zzz"],
19289 &[b"SMOVE", b"gone", b"s2", b"a"],
19290 &[b"SMEMBERS", b"s1"],
19291 &[b"SMEMBERS", b"s2"],
19292 &[b"SINTER", b"s1", b"s2"],
19294 &[b"SUNION", b"s1", b"s2"],
19295 &[b"SDIFF", b"s2", b"s1"],
19296 &[b"SINTER", b"s1", b"gone"],
19297 &[b"SUNION", b"s1", b"gone"],
19298 &[b"SDIFF", b"gone", b"s1"],
19299 &[b"SINTER", b"ints", b"s1"],
19300 &[b"SINTERCARD", b"2", b"s1", b"s2"],
19301 &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"1"],
19302 &[b"SUNIONCARD", b"2", b"s1", b"s2"],
19303 &[b"SDIFFCARD", b"2", b"s2", b"s1"],
19304 &[b"SINTERSTORE", b"d1", b"s1", b"s2"],
19305 &[b"SMEMBERS", b"d1"],
19306 &[b"SUNIONSTORE", b"d2", b"s1", b"s2"],
19307 &[b"SCARD", b"d2"],
19308 &[b"SDIFFSTORE", b"d3", b"s2", b"s1"],
19309 &[b"SCARD", b"d3"],
19310 &[b"SINTERSTORE", b"d4", b"s1", b"gone"],
19313 &[b"EXISTS", b"d4"],
19314 &[b"SUNIONSTORE", b"s2", b"s1", b"s2"],
19316 &[b"SCARD", b"s2"],
19317 &[b"SET", b"str", b"v"],
19319 &[b"SADD", b"str", b"a"],
19320 &[b"SINTER", b"s1", b"str"],
19321 &[b"SINTERSTORE", b"d5", b"s1", b"str"],
19322 &[b"EXISTS", b"d5"],
19323 &[b"SMOVE", b"str", b"s2", b"a"],
19324 &[b"SMOVE", b"s1", b"str", b"b"],
19325 &[b"SMOVE", b"gone", b"str", b"b"],
19326 &[b"SINTERCARD", b"0", b"s1"],
19327 &[b"SINTERCARD", b"3", b"s1", b"s2"],
19328 &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"-1"],
19329 &[b"SPOP", b"s1", b"-1"],
19330 ];
19331
19332 let mut one = Fixture::new();
19333 let mut many = Fixture::striped(8);
19334 for parts in script {
19335 let a = one.run(parts);
19336 let b = many.run(parts);
19337 let name = String::from_utf8_lossy(parts[0]).to_uppercase();
19338 if UNORDERED.contains(&name.as_str()) && a.starts_with(['*', '~']) {
19339 assert_eq!(sorted(&a), sorted(&b), "{name}");
19340 } else {
19341 assert_eq!(a, b, "{name}");
19342 }
19343 }
19344 }
19345
19346 #[test]
19348 fn a_set_operation_across_stripes_reads_every_set() {
19349 let mut f = Fixture::striped(8);
19350 let second = apart(&mut f, "s1");
19351 let third = apart(&mut f, &second);
19352 let (s1, s2, s3) = (b"s1".as_slice(), second.as_bytes(), third.as_bytes());
19353
19354 f.run(&[b"SADD", s1, b"a", b"b", b"c"]);
19355 f.run(&[b"SADD", s2, b"b", b"c", b"d"]);
19356 assert_eq!(sorted(&f.run(&[b"SINTER", s1, s2])), ["b", "c"]);
19357 assert_eq!(
19358 sorted(&f.run(&[b"SUNION", s1, s2])),
19359 ["a", "b", "c", "d"],
19360 "a union of two stripes is both of them"
19361 );
19362 assert_eq!(sorted(&f.run(&[b"SDIFF", s1, s2])), ["a"]);
19363 assert_eq!(f.run(&[b"SINTERCARD", b"2", s1, s2]), ":2\r\n");
19364 assert_eq!(f.run(&[b"SUNIONCARD", b"2", s1, s2]), ":4\r\n");
19365 assert_eq!(f.run(&[b"SDIFFCARD", b"2", s1, s2]), ":1\r\n");
19366
19367 assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, s2]), ":2\r\n");
19369 assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["b", "c"]);
19370 assert_eq!(f.run(&[b"SUNIONSTORE", s2, s1, s2]), ":4\r\n");
19371 assert_eq!(sorted(&f.run(&[b"SMEMBERS", s2])), ["a", "b", "c", "d"]);
19372 assert_eq!(f.run(&[b"SDIFFSTORE", s3, s2, s1]), ":1\r\n");
19373 assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["d"]);
19374
19375 assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, b"gone"]), ":0\r\n");
19378 assert_eq!(f.run(&[b"EXISTS", s3]), ":0\r\n");
19379 f.run(&[b"SET", s3, b"v"]);
19380 assert_eq!(
19381 f.run(&[b"SINTER", s1, s3]),
19382 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19383 );
19384 assert_eq!(f.run(&[b"GET", s3]), "$1\r\nv\r\n", "and left it alone");
19385 }
19386
19387 #[test]
19389 fn a_move_across_stripes_takes_the_member_with_it() {
19390 let mut f = Fixture::striped(8);
19391 let other = apart(&mut f, "src");
19392 let (src, dst) = (b"src".as_slice(), other.as_bytes());
19393
19394 f.run(&[b"SADD", src, b"a", b"b"]);
19395 f.run(&[b"SADD", dst, b"c"]);
19396 assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":1\r\n");
19397 assert_eq!(sorted(&f.run(&[b"SMEMBERS", src])), ["b"]);
19398 assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["a", "c"]);
19399 assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":0\r\n", "it has gone");
19400
19401 f.run(&[b"DEL", dst]);
19404 assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":1\r\n");
19405 assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n", "the source is empty");
19406 assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["b"]);
19407
19408 f.run(&[b"SET", dst, b"v"]);
19412 assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":0\r\n");
19413 f.run(&[b"SADD", src, b"b"]);
19414 assert_eq!(
19415 f.run(&[b"SMOVE", src, dst, b"b"]),
19416 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19417 );
19418 }
19419
19420 #[test]
19422 fn a_pfcount_and_a_pfmerge_reach_across_stripes() {
19423 let mut f = Fixture::striped(8);
19424 let other = apart(&mut f, "src");
19425 let (src, far) = (b"src".as_slice(), other.as_bytes());
19426
19427 for i in 0..150 {
19428 let ele = format!("e:{i}");
19429 f.run(&[b"PFADD", src, ele.as_bytes()]);
19430 }
19431 for i in 150..200 {
19432 let ele = format!("e:{i}");
19433 f.run(&[b"PFADD", far, ele.as_bytes()]);
19434 }
19435 assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n");
19438 assert_eq!(f.run(&[b"PFCOUNT", far]), ":49\r\n");
19439 assert_eq!(f.run(&[b"PFCOUNT", src, far]), ":199\r\n");
19440
19441 let dest = apart(&mut f, &other);
19444 assert_eq!(f.run(&[b"PFMERGE", dest.as_bytes(), src, far]), "+OK\r\n");
19445 assert_eq!(f.run(&[b"PFCOUNT", dest.as_bytes()]), ":199\r\n");
19446 assert_eq!(f.run(&[b"PFMERGE", far, src]), "+OK\r\n");
19447 assert_eq!(f.run(&[b"PFCOUNT", far]), ":199\r\n", "and kept its own");
19448 assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n", "and left the source");
19449 }
19450
19451 #[test]
19458 fn the_sorted_set_group_answers_the_same_however_many_stripes_there_are() {
19459 let script: &[&[&[u8]]] = &[
19460 &[b"ZADD", b"z1", b"1", b"a", b"2", b"b", b"3", b"c"],
19461 &[b"ZADD", b"z1", b"NX", b"9", b"a"],
19462 &[b"ZADD", b"z1", b"XX", b"CH", b"5", b"a"],
19463 &[b"ZADD", b"z1", b"GT", b"CH", b"1", b"a"],
19464 &[b"ZADD", b"z1", b"INCR", b"2", b"a"],
19465 &[b"ZINCRBY", b"z1", b"1.5", b"b"],
19466 &[b"ZADD", b"z2", b"1", b"b", b"2", b"c", b"3", b"d"],
19467 &[b"ZADD", b"lex", b"0", b"a", b"0", b"b", b"0", b"c"],
19468 &[b"ZADD", b"one", b"1", b"m"],
19469 &[b"ZCARD", b"z1"],
19470 &[b"ZCARD", b"gone"],
19471 &[b"ZSCORE", b"z1", b"a"],
19472 &[b"ZSCORE", b"z1", b"zz"],
19473 &[b"ZMSCORE", b"z1", b"a", b"zz", b"c"],
19474 &[b"ZRANK", b"z1", b"c"],
19475 &[b"ZRANK", b"z1", b"c", b"WITHSCORE"],
19476 &[b"ZREVRANK", b"z1", b"c"],
19477 &[b"ZRANK", b"z1", b"gone"],
19478 &[b"ZCOUNT", b"z1", b"-inf", b"+inf"],
19479 &[b"ZCOUNT", b"z1", b"(1", b"3"],
19480 &[b"ZLEXCOUNT", b"lex", b"-", b"+"],
19481 &[b"ZRANGE", b"z1", b"0", b"-1"],
19483 &[b"ZRANGE", b"z1", b"0", b"-1", b"WITHSCORES"],
19484 &[b"ZRANGE", b"z1", b"1", b"9", b"BYSCORE"],
19485 &[b"ZRANGE", b"z1", b"9", b"1", b"BYSCORE", b"REV"],
19486 &[b"ZRANGE", b"lex", b"[a", b"(c", b"BYLEX"],
19487 &[b"ZREVRANGE", b"z1", b"0", b"-1"],
19488 &[
19489 b"ZRANGEBYSCORE",
19490 b"z1",
19491 b"-inf",
19492 b"+inf",
19493 b"LIMIT",
19494 b"1",
19495 b"1",
19496 ],
19497 &[b"ZREVRANGEBYLEX", b"lex", b"+", b"-"],
19498 &[b"ZSCAN", b"z1", b"0"],
19499 &[b"ZSCAN", b"z1", b"0", b"MATCH", b"a*", b"COUNT", b"100"],
19500 &[b"ZRANDMEMBER", b"one"],
19503 &[b"ZRANDMEMBER", b"one", b"-3", b"WITHSCORES"],
19504 &[b"ZRANDMEMBER", b"gone"],
19505 &[b"ZRANGESTORE", b"d0", b"z1", b"0", b"1"],
19507 &[b"ZRANGE", b"d0", b"0", b"-1", b"WITHSCORES"],
19508 &[b"ZRANGESTORE", b"d0", b"z1", b"5", b"1"],
19509 &[b"EXISTS", b"d0"],
19510 &[b"ZUNION", b"2", b"z1", b"z2"],
19512 &[b"ZUNION", b"2", b"z1", b"z2", b"WITHSCORES"],
19513 &[
19514 b"ZUNION",
19515 b"2",
19516 b"z1",
19517 b"z2",
19518 b"WEIGHTS",
19519 b"2",
19520 b"3",
19521 b"AGGREGATE",
19522 b"MAX",
19523 b"WITHSCORES",
19524 ],
19525 &[b"ZINTER", b"2", b"z1", b"z2", b"WITHSCORES"],
19526 &[b"ZDIFF", b"2", b"z1", b"z2", b"WITHSCORES"],
19527 &[b"ZDIFF", b"2", b"gone", b"z1"],
19528 &[b"ZINTERCARD", b"2", b"z1", b"z2"],
19529 &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"1"],
19530 &[b"ZUNIONSTORE", b"d1", b"2", b"z1", b"z2"],
19531 &[b"ZRANGE", b"d1", b"0", b"-1", b"WITHSCORES"],
19532 &[
19533 b"ZINTERSTORE",
19534 b"d2",
19535 b"2",
19536 b"z1",
19537 b"z2",
19538 b"AGGREGATE",
19539 b"MIN",
19540 ],
19541 &[b"ZRANGE", b"d2", b"0", b"-1", b"WITHSCORES"],
19542 &[b"ZDIFFSTORE", b"d3", b"2", b"z1", b"z2"],
19543 &[b"ZCARD", b"d3"],
19544 &[b"ZINTERSTORE", b"d4", b"2", b"z1", b"gone"],
19547 &[b"EXISTS", b"d4"],
19548 &[b"SADD", b"plain", b"a", b"x"],
19551 &[b"ZUNIONSTORE", b"d5", b"2", b"z1", b"plain"],
19552 &[b"ZRANGE", b"d5", b"0", b"-1", b"WITHSCORES"],
19553 &[b"ZUNIONSTORE", b"z2", b"2", b"z1", b"z2"],
19555 &[b"ZRANGE", b"z2", b"0", b"-1", b"WITHSCORES"],
19556 &[b"ZREM", b"d5", b"x", b"nothere"],
19558 &[b"ZREMRANGEBYRANK", b"d5", b"0", b"0"],
19559 &[b"ZREMRANGEBYSCORE", b"d1", b"-inf", b"1"],
19560 &[b"ZREMRANGEBYLEX", b"lex", b"[a", b"[a"],
19561 &[b"ZPOPMIN", b"z1"],
19562 &[b"ZPOPMAX", b"z1", b"2"],
19563 &[b"ZPOPMIN", b"gone"],
19564 &[b"ZMPOP", b"2", b"gone", b"z2", b"MIN"],
19565 &[b"ZMPOP", b"2", b"gone", b"nothere", b"MAX", b"COUNT", b"2"],
19566 &[b"SET", b"str", b"v"],
19568 &[b"ZADD", b"str", b"1", b"a"],
19569 &[b"ZSCORE", b"str", b"a"],
19570 &[b"ZADD", b"z1", b"nan", b"a"],
19571 &[b"ZUNION", b"2", b"z1", b"str"],
19572 &[b"ZUNIONSTORE", b"d6", b"2", b"z1", b"str"],
19573 &[b"EXISTS", b"d6"],
19574 &[b"ZINTERCARD", b"0", b"z1"],
19575 &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"-1"],
19576 &[b"ZRANGESTORE", b"d7", b"str", b"0", b"-1"],
19577 &[b"ZMPOP", b"1", b"str", b"MIN"],
19578 &[b"ZPOPMIN", b"z1", b"-1"],
19579 ];
19580
19581 let mut one = Fixture::new();
19582 let mut many = Fixture::striped(8);
19583 for parts in script {
19584 let a = one.run(parts);
19585 let b = many.run(parts);
19586 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
19587 }
19588 }
19589
19590 #[test]
19592 fn a_sorted_set_operation_across_stripes_reads_every_input() {
19593 let mut f = Fixture::striped(8);
19594 let second = apart(&mut f, "z1");
19595 let third = apart(&mut f, &second);
19596 let (z1, z2, z3) = (b"z1".as_slice(), second.as_bytes(), third.as_bytes());
19597
19598 f.run(&[b"ZADD", z1, b"1", b"a", b"2", b"b"]);
19599 f.run(&[b"ZADD", z2, b"3", b"b", b"4", b"c"]);
19600 assert_eq!(
19603 f.run(&[b"ZUNION", b"2", z1, z2]),
19604 "*3\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n"
19605 );
19606 assert_eq!(f.run(&[b"ZINTER", b"2", z1, z2]), "*1\r\n$1\r\nb\r\n");
19607 assert_eq!(f.run(&[b"ZDIFF", b"2", z1, z2]), "*1\r\n$1\r\na\r\n");
19608 assert_eq!(f.run(&[b"ZINTERCARD", b"2", z1, z2]), ":1\r\n");
19609 assert_eq!(
19610 f.run(&[b"ZINTERCARD", b"2", z1, z2, b"LIMIT", b"1"]),
19611 ":1\r\n"
19612 );
19613
19614 assert_eq!(f.run(&[b"ZUNIONSTORE", z3, b"2", z1, z2]), ":3\r\n");
19617 assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n5\r\n");
19618 assert_eq!(
19619 f.run(&[
19620 b"ZUNIONSTORE",
19621 z3,
19622 b"2",
19623 z1,
19624 z2,
19625 b"WEIGHTS",
19626 b"2",
19627 b"3",
19628 b"AGGREGATE",
19629 b"MAX"
19630 ]),
19631 ":3\r\n"
19632 );
19633 assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n9\r\n");
19634 assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, z2]), ":1\r\n");
19635 assert_eq!(f.run(&[b"ZCARD", z3]), ":1\r\n");
19636 assert_eq!(f.run(&[b"ZDIFFSTORE", z3, b"2", z2, z1]), ":1\r\n");
19637 assert_eq!(f.run(&[b"ZSCORE", z3, b"c"]), "$1\r\n4\r\n");
19638
19639 let popped = format!(
19642 "*2\r\n${}\r\n{second}\r\n*1\r\n*2\r\n$1\r\nb\r\n$1\r\n3\r\n",
19643 second.len()
19644 );
19645 assert_eq!(f.run(&[b"ZMPOP", b"3", b"gone", z2, z1, b"MIN"]), popped);
19646 f.run(&[b"ZADD", z2, b"3", b"b"]);
19647
19648 assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, b"gone"]), ":0\r\n");
19651 assert_eq!(f.run(&[b"EXISTS", z3]), ":0\r\n");
19652 f.run(&[b"SET", z3, b"v"]);
19653 assert_eq!(
19654 f.run(&[b"ZUNION", b"2", z1, z3]),
19655 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19656 );
19657 assert_eq!(f.run(&[b"GET", z3]), "$1\r\nv\r\n", "and left it alone");
19658
19659 assert_eq!(f.run(&[b"ZUNIONSTORE", z2, b"2", z1, z2]), ":3\r\n");
19663 assert_eq!(f.run(&[b"ZSCORE", z2, b"b"]), "$1\r\n5\r\n");
19664 assert_eq!(f.run(&[b"ZCARD", z2]), ":3\r\n");
19665 }
19666
19667 #[test]
19669 fn a_range_store_across_stripes_copies_the_window() {
19670 let mut f = Fixture::striped(8);
19671 let other = apart(&mut f, "src");
19672 let third = apart(&mut f, &other);
19673 let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
19674
19675 f.run(&[b"ZADD", src, b"1", b"a", b"2", b"b", b"3", b"c"]);
19676 assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"0", b"1"]), ":2\r\n");
19677 assert_eq!(
19678 f.run(&[b"ZRANGE", dst, b"0", b"-1", b"WITHSCORES"]),
19679 "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
19680 );
19681 assert_eq!(f.run(&[b"ZCARD", src]), ":3\r\n", "the source kept its own");
19682
19683 assert_eq!(
19686 f.run(&[
19687 b"ZRANGESTORE",
19688 dst,
19689 src,
19690 b"+inf",
19691 b"-inf",
19692 b"BYSCORE",
19693 b"REV",
19694 b"LIMIT",
19695 b"0",
19696 b"2"
19697 ]),
19698 ":2\r\n"
19699 );
19700 assert_eq!(
19701 f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
19702 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
19703 );
19704
19705 assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"5", b"1"]), ":0\r\n");
19708 assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
19709 f.run(&[b"ZRANGESTORE", dst, src, b"0", b"-1"]);
19710 f.run(&[b"SET", plain, b"v"]);
19711 assert_eq!(
19712 f.run(&[b"ZRANGESTORE", dst, plain, b"0", b"-1"]),
19713 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19714 );
19715 assert_eq!(
19716 f.run(&[b"ZCARD", dst]),
19717 ":3\r\n",
19718 "and left the destination"
19719 );
19720 }
19721
19722 #[test]
19729 fn the_list_group_answers_the_same_however_many_stripes_there_are() {
19730 let script: &[&[&[u8]]] = &[
19731 &[b"RPUSH", b"l1", b"a", b"b", b"c"],
19732 &[b"LPUSH", b"l1", b"z"],
19733 &[b"RPUSHX", b"l1", b"d"],
19734 &[b"LPUSHX", b"gone", b"x"],
19735 &[b"RPUSHX", b"gone", b"x"],
19736 &[b"LLEN", b"l1"],
19737 &[b"LLEN", b"gone"],
19738 &[b"LRANGE", b"l1", b"0", b"-1"],
19739 &[b"LRANGE", b"l1", b"1", b"2"],
19740 &[b"LRANGE", b"l1", b"5", b"9"],
19741 &[b"LINDEX", b"l1", b"0"],
19742 &[b"LINDEX", b"l1", b"-1"],
19743 &[b"LINDEX", b"l1", b"99"],
19744 &[b"LSET", b"l1", b"0", b"y"],
19745 &[b"LINSERT", b"l1", b"BEFORE", b"b", b"aa"],
19746 &[b"LINSERT", b"l1", b"AFTER", b"nothere", b"x"],
19747 &[b"LPOS", b"l1", b"b"],
19748 &[b"LPOS", b"l1", b"b", b"COUNT", b"0"],
19749 &[b"LPOS", b"l1", b"nothere"],
19750 &[b"LPOS", b"l1", b"b", b"RANK", b"-1", b"MAXLEN", b"2"],
19751 &[b"LREM", b"l1", b"1", b"aa"],
19752 &[b"LTRIM", b"l1", b"0", b"3"],
19753 &[b"LRANGE", b"l1", b"0", b"-1"],
19754 &[b"LPOP", b"l1"],
19755 &[b"RPOP", b"l1"],
19756 &[b"LPOP", b"l1", b"2"],
19757 &[b"LPOP", b"gone"],
19758 &[b"LPOP", b"gone", b"2"],
19759 &[b"EXISTS", b"l1"],
19760 &[b"RPUSH", b"src", b"a", b"b", b"c", b"d"],
19763 &[b"LMOVE", b"src", b"dst", b"LEFT", b"RIGHT"],
19764 &[b"RPOPLPUSH", b"src", b"dst"],
19765 &[b"LRANGE", b"dst", b"0", b"-1"],
19766 &[b"LMOVE", b"gone", b"dst", b"LEFT", b"RIGHT"],
19767 &[b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT"],
19768 &[
19769 b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK",
19770 ],
19771 &[
19772 b"LMOVEM", b"dst", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"OBO",
19773 ],
19774 &[b"LRANGE", b"dst", b"0", b"-1"],
19775 &[
19776 b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"EXACTLY", b"9", b"BULK",
19777 ],
19778 &[b"LMPOP", b"2", b"gone", b"dst", b"LEFT"],
19779 &[b"LMPOP", b"2", b"gone", b"dst", b"RIGHT", b"COUNT", b"2"],
19780 &[b"LMPOP", b"1", b"gone", b"LEFT"],
19781 &[b"RPUSH", b"q", b"a", b"b", b"c"],
19784 &[b"BLPOP", b"gone", b"q", b"0"],
19785 &[b"BRPOP", b"q", b"0"],
19786 &[b"BLMPOP", b"0", b"2", b"gone", b"q", b"LEFT"],
19787 &[b"RPUSH", b"q", b"x", b"y", b"z"],
19788 &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
19789 &[b"BRPOPLPUSH", b"q", b"dst", b"0"],
19790 &[b"BLMOVEM", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
19791 &[b"BLPOP", b"q", b"0"],
19792 &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
19793 &[b"SET", b"plain", b"v"],
19795 &[b"LPUSH", b"plain", b"a"],
19796 &[b"LLEN", b"plain"],
19797 &[b"LMOVE", b"dst", b"plain", b"LEFT", b"RIGHT"],
19798 &[b"LRANGE", b"dst", b"0", b"-1"],
19799 &[b"LMOVEM", b"dst", b"plain", b"LEFT", b"RIGHT"],
19800 &[b"LSET", b"gone", b"0", b"v"],
19801 &[b"LSET", b"dst", b"99", b"v"],
19802 &[b"LPOP", b"dst", b"-1"],
19803 &[b"LMPOP", b"0", b"dst", b"LEFT"],
19804 &[b"LPOS", b"dst", b"a", b"RANK", b"0"],
19805 ];
19806
19807 let mut one = Fixture::new();
19808 let mut many = Fixture::striped(8);
19809 for parts in script {
19810 let a = one.run(parts);
19811 let b = many.run(parts);
19812 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
19813 }
19814 }
19815
19816 #[test]
19818 fn a_list_move_across_stripes_takes_the_elements_with_it() {
19819 let mut f = Fixture::striped(8);
19820 let other = apart(&mut f, "src");
19821 let third = apart(&mut f, &other);
19822 let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
19823
19824 f.run(&[b"RPUSH", src, b"a", b"b", b"c", b"d"]);
19825 assert_eq!(
19826 f.run(&[b"LMOVE", src, dst, b"LEFT", b"RIGHT"]),
19827 "$1\r\na\r\n"
19828 );
19829 assert_eq!(f.run(&[b"RPOPLPUSH", src, dst]), "$1\r\nd\r\n");
19830 assert_eq!(
19831 f.run(&[b"LRANGE", dst, b"0", b"-1"]),
19832 "*2\r\n$1\r\nd\r\n$1\r\na\r\n",
19833 "one went on each end of the destination"
19834 );
19835 assert_eq!(
19836 f.run(&[b"LRANGE", src, b"0", b"-1"]),
19837 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
19838 );
19839
19840 assert_eq!(
19842 f.run(&[
19843 b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
19844 ]),
19845 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
19846 );
19847 assert_eq!(
19848 f.run(&[b"LRANGE", dst, b"0", b"-1"]),
19849 "*4\r\n$1\r\nd\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
19850 );
19851 assert_eq!(
19852 f.run(&[b"EXISTS", src]),
19853 ":0\r\n",
19854 "and the source is gone with its last element"
19855 );
19856
19857 f.run(&[b"RPUSH", src, b"e", b"f"]);
19860 assert_eq!(
19861 f.run(&[
19862 b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"EXACTLY", b"3", b"BULK"
19863 ]),
19864 "*-1\r\n"
19865 );
19866 assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n", "and took none of them");
19867 assert_eq!(
19868 f.run(&[b"LMOVE", b"gone", dst, b"LEFT", b"RIGHT"]),
19869 "$-1\r\n"
19870 );
19871 assert_eq!(
19872 f.run(&[b"LMOVEM", b"gone", dst, b"LEFT", b"RIGHT"]),
19873 "*-1\r\n"
19874 );
19875
19876 f.run(&[b"SET", plain, b"v"]);
19880 assert_eq!(
19881 f.run(&[b"LMOVE", src, plain, b"LEFT", b"RIGHT"]),
19882 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19883 );
19884 assert_eq!(
19885 f.run(&[b"LLEN", src]),
19886 ":2\r\n",
19887 "and left the source alone"
19888 );
19889 assert_eq!(
19890 f.run(&[b"LMOVEM", src, plain, b"LEFT", b"RIGHT"]),
19891 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19892 );
19893 assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n");
19894 }
19895
19896 #[test]
19902 fn a_parked_client_is_served_from_the_stripe_its_key_is_on() {
19903 let mut f = Fixture::striped(8);
19904 let other = apart(&mut f, "q");
19905 let (q, far) = (b"q".as_slice(), other.as_bytes());
19906
19907 assert_eq!(f.flow(&[b"BLPOP", q, far, b"0"]).0, Flow::Block);
19908 assert_eq!(f.server.parked(), 1);
19909 f.run(&[b"RPUSH", far, b"v"]);
19910 let mut out = Out::new(Proto::Resp2);
19911 assert!(f.server.serve_waiter(7, 0, &mut out));
19912 let want = format!("*2\r\n${}\r\n{other}\r\n$1\r\nv\r\n", other.len());
19913 assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
19914 assert_eq!(
19915 f.run(&[b"EXISTS", far]),
19916 ":0\r\n",
19917 "and it took the element with it"
19918 );
19919
19920 f.server.forget_waiters(7);
19923 assert_eq!(
19924 f.flow(&[b"BLMOVE", q, far, b"LEFT", b"RIGHT", b"0"]).0,
19925 Flow::Block
19926 );
19927 f.run(&[b"RPUSH", q, b"w"]);
19928 let mut out = Out::new(Proto::Resp2);
19929 assert!(f.server.serve_waiter(7, 0, &mut out));
19930 assert_eq!(
19931 core::str::from_utf8(out.as_slice()).expect("ascii"),
19932 "$1\r\nw\r\n"
19933 );
19934 assert_eq!(f.run(&[b"LRANGE", far, b"0", b"-1"]), "*1\r\n$1\r\nw\r\n");
19935 }
19936
19937 #[test]
19943 fn the_stream_group_answers_the_same_however_many_stripes_there_are() {
19944 let script: &[&[&[u8]]] = &[
19945 &[b"XADD", b"s", b"1-1", b"a", b"1"],
19946 &[b"XADD", b"s", b"2-1", b"b", b"2", b"c", b"3"],
19947 &[b"XADD", b"s", b"3-1", b"d", b"4"],
19948 &[b"XADD", b"s", b"1-1", b"e", b"5"],
19949 &[b"XADD", b"nomk", b"NOMKSTREAM", b"1-1", b"a", b"1"],
19950 &[b"XLEN", b"s"],
19951 &[b"XLEN", b"gone"],
19952 &[b"XRANGE", b"s", b"-", b"+"],
19953 &[b"XRANGE", b"s", b"2", b"+", b"COUNT", b"1"],
19954 &[b"XRANGE", b"gone", b"-", b"+", b"COUNT", b"0"],
19955 &[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"],
19956 &[b"XREVRANGE", b"s", b"+", b"-"],
19957 &[b"XREAD", b"COUNT", b"2", b"STREAMS", b"s", b"0"],
19958 &[b"XREAD", b"STREAMS", b"s", b"gone", b"0", b"0"],
19959 &[b"XREAD", b"STREAMS", b"s", b"$"],
19960 &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
19962 &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
19963 &[b"XGROUP", b"CREATE", b"gone", b"g", b"0"],
19964 &[b"XGROUP", b"CREATE", b"made", b"g", b"$", b"MKSTREAM"],
19965 &[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"idle"],
19966 &[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"],
19967 &[
19968 b"XREADGROUP",
19969 b"GROUP",
19970 b"g",
19971 b"c1",
19972 b"COUNT",
19973 b"1",
19974 b"STREAMS",
19975 b"s",
19976 b"0",
19977 ],
19978 &[
19979 b"XREADGROUP",
19980 b"GROUP",
19981 b"nope",
19982 b"c1",
19983 b"STREAMS",
19984 b"s",
19985 b">",
19986 ],
19987 &[b"XPENDING", b"s", b"g"],
19988 &[b"XPENDING", b"s", b"g", b"-", b"+", b"10"],
19989 &[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"],
19990 &[b"XPENDING", b"s", b"nope"],
19991 &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1"],
19992 &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1", b"JUSTID"],
19993 &[b"XAUTOCLAIM", b"s", b"g", b"c3", b"0", b"0"],
19994 &[b"XACK", b"s", b"g", b"1-1"],
19995 &[b"XACK", b"s", b"g", b"1-1"],
19996 &[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"],
19997 &[b"XPENDING", b"s", b"g"],
19998 &[b"XINFO", b"STREAM", b"s"],
19999 &[b"XINFO", b"GROUPS", b"s"],
20000 &[b"XINFO", b"CONSUMERS", b"s", b"g"],
20001 &[b"XINFO", b"STREAM", b"gone"],
20002 &[b"XDEL", b"s", b"3-1"],
20004 &[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"],
20005 &[b"XACKDEL", b"s", b"g", b"KEEPREF", b"IDS", b"1", b"1-1"],
20006 &[b"XADD", b"s", b"9-1", b"z", b"9"],
20007 &[b"XTRIM", b"s", b"MAXLEN", b"1"],
20008 &[b"XTRIM", b"s", b"MINID", b"9"],
20009 &[b"XSETID", b"s", b"99-1"],
20010 &[b"XSETID", b"s", b"1-1"],
20011 &[b"XLEN", b"s"],
20012 &[b"XGROUP", b"SETID", b"s", b"g", b"0"],
20013 &[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c1"],
20014 &[b"XGROUP", b"DESTROY", b"s", b"g"],
20015 &[b"XGROUP", b"DESTROY", b"s", b"g"],
20016 &[b"SET", b"plain", b"v"],
20018 &[b"XADD", b"plain", b"1-1", b"a", b"1"],
20019 &[b"XLEN", b"plain"],
20020 &[b"XREAD", b"STREAMS", b"plain", b"0"],
20021 &[b"XRANGE", b"s", b"bogus", b"+"],
20022 &[b"XADD", b"s", b"1-1", b"a"],
20023 &[b"XREAD", b"STREAMS", b"s", b"gone", b"0"],
20024 &[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"],
20025 ];
20026
20027 let mut one = Fixture::new();
20028 let mut many = Fixture::striped(8);
20029 for parts in script {
20030 let a = one.run(parts);
20031 let b = many.run(parts);
20032 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20033 }
20034 }
20035
20036 #[test]
20042 fn a_stream_read_across_stripes_reads_every_key() {
20043 let mut f = Fixture::striped(8);
20044 let other = apart(&mut f, "s1");
20045 let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
20046
20047 f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
20048 f.run(&[b"XADD", s2, b"2-1", b"b", b"2"]);
20049 let got = f.run(&[b"XREAD", b"STREAMS", s1, s2, b"0", b"0"]);
20050 assert!(got.starts_with("*2\r\n"), "both streams answered: {got}");
20051 assert!(got.contains("1-1"), "the first one is in there: {got}");
20052 assert!(got.contains("2-1"), "and so is the second: {got}");
20053
20054 f.run(&[b"XGROUP", b"CREATE", s1, b"g", b"0"]);
20057 let got = f.run(&[
20058 b"XREADGROUP",
20059 b"GROUP",
20060 b"g",
20061 b"c",
20062 b"STREAMS",
20063 s1,
20064 s2,
20065 b">",
20066 b">",
20067 ]);
20068 assert!(got.starts_with("-NOGROUP"), "{got}");
20069 assert_eq!(
20070 f.run(&[b"XPENDING", s1, b"g"]),
20071 "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n",
20072 "and read nothing from the key that did have the group"
20073 );
20074
20075 f.run(&[b"XGROUP", b"CREATE", s2, b"g", b"0"]);
20076 let got = f.run(&[
20077 b"XREADGROUP",
20078 b"GROUP",
20079 b"g",
20080 b"c",
20081 b"STREAMS",
20082 s1,
20083 s2,
20084 b">",
20085 b">",
20086 ]);
20087 assert!(got.starts_with("*2\r\n"), "now both are read: {got}");
20088 }
20089
20090 #[test]
20092 fn a_parked_stream_reader_is_served_from_the_stripe_its_key_is_on() {
20093 let mut f = Fixture::striped(8);
20094 let other = apart(&mut f, "s1");
20095 let (s1, far) = (b"s1".as_slice(), other.as_bytes());
20096 f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
20097 f.run(&[b"XADD", far, b"1-1", b"a", b"1"]);
20098
20099 assert_eq!(
20100 f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", s1, far, b"$", b"$"])
20101 .0,
20102 Flow::Block
20103 );
20104 f.run(&[b"XADD", far, b"2-1", b"b", b"2"]);
20105 let mut out = Out::new(Proto::Resp2);
20106 assert!(f.server.serve_waiter(7, 0, &mut out));
20107 let want = format!(
20108 "*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",
20109 other.len()
20110 );
20111 assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
20112 }
20113
20114 #[test]
20116 fn the_json_group_answers_the_same_however_many_stripes_there_are() {
20117 let script: &[&[&[u8]]] = &[
20118 &[
20119 b"JSON.SET",
20120 b"d",
20121 b"$",
20122 br#"{"a":1,"b":[1,2,3],"s":"hi","t":true}"#,
20123 ],
20124 &[b"JSON.SET", b"d", b"$.a", b"2"],
20125 &[b"JSON.SET", b"d", b"$.new", b"9", b"NX"],
20126 &[b"JSON.SET", b"d", b"$.new", b"8", b"NX"],
20127 &[b"JSON.SET", b"d", b"$.nope", b"7", b"XX"],
20128 &[b"JSON.GET", b"d"],
20129 &[b"JSON.GET", b"d", b"$.b"],
20130 &[b"JSON.GET", b"gone", b"$"],
20131 &[b"JSON.TYPE", b"d", b"$.b"],
20132 &[b"JSON.TYPE", b"d", b"$.s"],
20133 &[b"JSON.TOGGLE", b"d", b"$.t"],
20134 &[b"JSON.ARRLEN", b"d", b"$.b"],
20135 &[b"JSON.OBJLEN", b"d", b"$"],
20136 &[b"JSON.OBJKEYS", b"d", b"$"],
20137 &[b"JSON.STRLEN", b"d", b"$.s"],
20138 &[b"JSON.STRAPPEND", b"d", b"$.s", br#""there""#],
20139 &[b"JSON.ARRAPPEND", b"d", b"$.b", b"4"],
20140 &[b"JSON.ARRINSERT", b"d", b"$.b", b"0", b"0"],
20141 &[b"JSON.ARRINDEX", b"d", b"$.b", b"3"],
20142 &[b"JSON.ARRTRIM", b"d", b"$.b", b"1", b"3"],
20143 &[b"JSON.ARRPOP", b"d", b"$.b"],
20144 &[b"JSON.NUMINCRBY", b"d", b"$.a", b"5"],
20145 &[b"JSON.NUMMULTBY", b"d", b"$.a", b"2"],
20146 &[b"JSON.NUMPOWBY", b"d", b"$.a", b"2"],
20147 &[b"JSON.MERGE", b"d", b"$", br#"{"a":null,"m":1}"#],
20148 &[b"JSON.RESP", b"d", b"$.b"],
20149 &[b"JSON.DEBUG", b"MEMORY", b"d"],
20150 &[b"JSON.CLEAR", b"d", b"$.b"],
20151 &[b"JSON.DEL", b"d", b"$.m"],
20152 &[b"JSON.FORGET", b"d", b"$.nothere"],
20153 &[
20155 b"JSON.MSET",
20156 b"m1",
20157 b"$",
20158 b"1",
20159 b"m2",
20160 b"$",
20161 b"2",
20162 b"m3",
20163 b"$",
20164 b"3",
20165 ],
20166 &[b"JSON.MGET", b"m1", b"m2", b"m3", b"gone", b"$"],
20167 &[b"JSON.MSET", b"m1", b"$", b"9", b"m2", b"$.deep", b"9"],
20168 &[b"JSON.GET", b"m1", b"$"],
20169 &[b"JSON.MSET", b"m1", b"$", b"nonsense", b"m2", b"$", b"5"],
20170 &[b"JSON.GET", b"m2", b"$"],
20171 &[b"SET", b"plain", b"v"],
20173 &[b"JSON.GET", b"plain", b"$"],
20174 &[b"JSON.SET", b"plain", b"$", b"1"],
20175 &[b"JSON.MGET", b"m1", b"plain", b"$"],
20176 &[b"JSON.SET", b"d", b"$.b", b"["],
20177 &[b"JSON.DEL", b"plain"],
20178 ];
20179
20180 let mut one = Fixture::new();
20181 let mut many = Fixture::striped(8);
20182 for parts in script {
20183 let a = one.run(parts);
20184 let b = many.run(parts);
20185 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20186 }
20187 }
20188
20189 #[test]
20196 fn a_json_multi_write_across_stripes_reaches_every_key() {
20197 let mut f = Fixture::striped(8);
20198 let second = apart(&mut f, "m1");
20199 let third = apart(&mut f, &second);
20200 let (m1, m2, m3) = (b"m1".as_slice(), second.as_bytes(), third.as_bytes());
20201
20202 assert_eq!(
20203 f.run(&[b"JSON.MSET", m1, b"$", b"1", m2, b"$", b"2", m3, b"$", b"3"]),
20204 "+OK\r\n"
20205 );
20206 assert_eq!(
20207 f.run(&[b"JSON.MGET", m1, m2, m3, b"gone", b"$"]),
20208 "*4\r\n$3\r\n[1]\r\n$3\r\n[2]\r\n$3\r\n[3]\r\n$-1\r\n"
20209 );
20210
20211 assert_eq!(
20214 f.run(&[b"JSON.MSET", m1, b"$", b"9", m2, b"$", b"nonsense"]),
20215 "-this is not the start of a value, at byte 0 of the JSON text\r\n"
20216 );
20217 assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[1]\r\n");
20218
20219 assert_eq!(
20223 f.run(&[
20224 b"JSON.MSET",
20225 m1,
20226 b"$",
20227 b"9",
20228 m2,
20229 b"$.deep",
20230 b"9",
20231 m3,
20232 b"$",
20233 b"7"
20234 ]),
20235 "$-1\r\n"
20236 );
20237 assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[9]\r\n");
20238 assert_eq!(f.run(&[b"JSON.GET", m2, b"$"]), "$3\r\n[2]\r\n");
20239 assert_eq!(f.run(&[b"JSON.GET", m3, b"$"]), "$3\r\n[7]\r\n");
20240 }
20241
20242 #[test]
20244 fn the_geo_group_answers_the_same_however_many_stripes_there_are() {
20245 let script: &[&[&[u8]]] = &[
20246 &[
20247 b"GEOADD",
20248 b"g",
20249 b"13.361389",
20250 b"38.115556",
20251 b"palermo",
20252 b"15.087269",
20253 b"37.502669",
20254 b"catania",
20255 ],
20256 &[
20257 b"GEOADD",
20258 b"g",
20259 b"NX",
20260 b"13.361389",
20261 b"38.115556",
20262 b"palermo",
20263 ],
20264 &[b"GEOADD", b"g", b"XX", b"CH", b"13.4", b"38.1", b"palermo"],
20265 &[b"GEOPOS", b"g", b"palermo", b"nothere"],
20266 &[b"GEOHASH", b"g", b"palermo", b"catania"],
20267 &[b"GEODIST", b"g", b"palermo", b"catania"],
20268 &[b"GEODIST", b"g", b"palermo", b"catania", b"KM"],
20269 &[b"GEODIST", b"g", b"palermo", b"nothere"],
20270 &[
20271 b"GEOSEARCH",
20272 b"g",
20273 b"FROMLONLAT",
20274 b"15",
20275 b"37",
20276 b"BYRADIUS",
20277 b"200",
20278 b"KM",
20279 b"ASC",
20280 b"WITHCOORD",
20281 b"WITHDIST",
20282 b"WITHHASH",
20283 ],
20284 &[
20285 b"GEOSEARCH",
20286 b"g",
20287 b"FROMMEMBER",
20288 b"palermo",
20289 b"BYBOX",
20290 b"400",
20291 b"400",
20292 b"KM",
20293 b"DESC",
20294 ],
20295 &[
20296 b"GEORADIUS",
20297 b"g",
20298 b"15",
20299 b"37",
20300 b"200",
20301 b"KM",
20302 b"COUNT",
20303 b"1",
20304 ],
20305 &[b"GEORADIUSBYMEMBER", b"g", b"palermo", b"200", b"KM"],
20306 &[b"GEORADIUSBYMEMBER_RO", b"g", b"nothere", b"200", b"KM"],
20307 &[
20308 b"GEOSEARCHSTORE",
20309 b"dst",
20310 b"g",
20311 b"FROMLONLAT",
20312 b"15",
20313 b"37",
20314 b"BYRADIUS",
20315 b"200",
20316 b"KM",
20317 ],
20318 &[b"ZRANGE", b"dst", b"0", b"-1"],
20319 &[
20320 b"GEOSEARCHSTORE",
20321 b"dst",
20322 b"g",
20323 b"FROMLONLAT",
20324 b"15",
20325 b"37",
20326 b"BYRADIUS",
20327 b"1",
20328 b"M",
20329 b"STOREDIST",
20330 ],
20331 &[b"EXISTS", b"dst"],
20332 &[
20333 b"GEORADIUS",
20334 b"g",
20335 b"15",
20336 b"37",
20337 b"200",
20338 b"KM",
20339 b"STORE",
20340 b"dst",
20341 ],
20342 &[b"ZCARD", b"dst"],
20343 &[b"GEOADD", b"g", b"181", b"38", b"nowhere"],
20345 &[b"SET", b"plain", b"v"],
20346 &[b"GEOPOS", b"plain", b"a"],
20347 &[b"GEOSEARCH", b"g", b"FROMLONLAT", b"15", b"37"],
20348 &[
20349 b"GEOSEARCHSTORE",
20350 b"dst",
20351 b"g",
20352 b"FROMLONLAT",
20353 b"15",
20354 b"37",
20355 b"BYRADIUS",
20356 b"200",
20357 b"KM",
20358 b"WITHCOORD",
20359 ],
20360 ];
20361
20362 let mut one = Fixture::new();
20363 let mut many = Fixture::striped(8);
20364 for parts in script {
20365 let a = one.run(parts);
20366 let b = many.run(parts);
20367 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20368 }
20369 }
20370
20371 #[test]
20373 fn a_geo_search_store_across_stripes_writes_what_it_found() {
20374 let mut f = Fixture::striped(8);
20375 let other = apart(&mut f, "g");
20376 let third = apart(&mut f, &other);
20377 let (g, dst, plain) = (b"g".as_slice(), other.as_bytes(), third.as_bytes());
20378
20379 f.run(&[
20380 b"GEOADD",
20381 g,
20382 b"13.361389",
20383 b"38.115556",
20384 b"palermo",
20385 b"15.087269",
20386 b"37.502669",
20387 b"catania",
20388 ]);
20389 assert_eq!(
20390 f.run(&[
20391 b"GEOSEARCHSTORE",
20392 dst,
20393 g,
20394 b"FROMLONLAT",
20395 b"15",
20396 b"37",
20397 b"BYRADIUS",
20398 b"200",
20399 b"KM",
20400 b"ASC",
20401 ]),
20402 ":2\r\n"
20403 );
20404 assert_eq!(
20405 f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
20406 "*2\r\n$7\r\npalermo\r\n$7\r\ncatania\r\n",
20407 "the geohash is the score, so the order is not the search order"
20408 );
20409 assert_eq!(f.run(&[b"ZCARD", g]), ":2\r\n", "the source is untouched");
20410
20411 assert_eq!(
20414 f.run(&[
20415 b"GEOSEARCHSTORE",
20416 dst,
20417 g,
20418 b"FROMMEMBER",
20419 b"palermo",
20420 b"BYRADIUS",
20421 b"200",
20422 b"KM",
20423 b"STOREDIST",
20424 ]),
20425 ":2\r\n"
20426 );
20427 assert_eq!(
20428 f.run(&[b"ZSCORE", dst, b"palermo"]),
20429 "$1\r\n0\r\n",
20430 "the centre is nought away from itself"
20431 );
20432
20433 assert_eq!(
20437 f.run(&[
20438 b"GEOSEARCHSTORE",
20439 dst,
20440 g,
20441 b"FROMLONLAT",
20442 b"0",
20443 b"0",
20444 b"BYRADIUS",
20445 b"1",
20446 b"M",
20447 ]),
20448 ":0\r\n"
20449 );
20450 assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
20451 f.run(&[
20452 b"GEOSEARCHSTORE",
20453 dst,
20454 g,
20455 b"FROMLONLAT",
20456 b"15",
20457 b"37",
20458 b"BYRADIUS",
20459 b"200",
20460 b"KM",
20461 ]);
20462 f.run(&[b"SET", plain, b"v"]);
20463 assert_eq!(
20464 f.run(&[
20465 b"GEOSEARCHSTORE",
20466 dst,
20467 plain,
20468 b"FROMLONLAT",
20469 b"15",
20470 b"37",
20471 b"BYRADIUS",
20472 b"200",
20473 b"KM",
20474 ]),
20475 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
20476 );
20477 assert_eq!(
20478 f.run(&[b"ZCARD", dst]),
20479 ":2\r\n",
20480 "and left the destination"
20481 );
20482 }
20483
20484 #[test]
20490 fn the_time_series_group_answers_the_same_however_many_stripes_there_are() {
20491 let script: &[&[&[u8]]] = &[
20492 &[
20493 b"TS.CREATE",
20494 b"ts:a",
20495 b"LABELS",
20496 b"sensor",
20497 b"a",
20498 b"room",
20499 b"1",
20500 ],
20501 &[b"TS.CREATE", b"ts:a"],
20502 &[b"TS.ALTER", b"ts:a", b"RETENTION", b"0"],
20503 &[b"TS.ADD", b"ts:a", b"1000", b"1.5"],
20504 &[
20505 b"TS.ADD", b"ts:b", b"1000", b"2", b"LABELS", b"sensor", b"b", b"room", b"1",
20506 ],
20507 &[
20508 b"TS.MADD", b"ts:a", b"2000", b"2.5", b"ts:b", b"2000", b"3", b"gone", b"1", b"1",
20509 ],
20510 &[b"TS.INCRBY", b"ts:a", b"1", b"TIMESTAMP", b"3000"],
20511 &[b"TS.DECRBY", b"ts:a", b"0.5", b"TIMESTAMP", b"4000"],
20512 &[b"TS.GET", b"ts:a"],
20513 &[b"TS.GET", b"gone"],
20514 &[b"TS.RANGE", b"ts:a", b"-", b"+"],
20515 &[b"TS.RANGE", b"ts:a", b"1000", b"3000", b"COUNT", b"2"],
20516 &[
20517 b"TS.RANGE",
20518 b"ts:a",
20519 b"-",
20520 b"+",
20521 b"AGGREGATION",
20522 b"avg",
20523 b"2000",
20524 ],
20525 &[b"TS.REVRANGE", b"ts:a", b"-", b"+"],
20526 &[b"TS.NRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
20527 &[b"TS.NREVRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
20528 &[b"TS.NRANGE", b"2", b"ts:a", b"gone", b"-", b"+"],
20529 &[b"TS.READ", b"ts:a", b"0"],
20530 &[b"TS.READ", b"ts:a", b"+"],
20531 &[b"TS.QUERYINDEX", b"sensor=a"],
20533 &[b"TS.QUERYINDEX", b"room=1"],
20534 &[b"TS.QUERYINDEX", b"room=9"],
20535 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"],
20536 &[
20537 b"TS.QUERYLABELS",
20538 b"VALUES",
20539 b"sensor",
20540 b"FILTER",
20541 b"room=1",
20542 ],
20543 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=1"],
20544 &[
20545 b"TS.MGET",
20546 b"SELECTED_LABELS",
20547 b"sensor",
20548 b"FILTER",
20549 b"sensor=a",
20550 ],
20551 &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"],
20552 &[
20553 b"TS.MREVRANGE",
20554 b"-",
20555 b"+",
20556 b"WITHLABELS",
20557 b"FILTER",
20558 b"sensor=a",
20559 ],
20560 &[
20561 b"TS.MRANGE",
20562 b"-",
20563 b"+",
20564 b"FILTER",
20565 b"room=1",
20566 b"GROUPBY",
20567 b"room",
20568 b"REDUCE",
20569 b"max",
20570 ],
20571 &[b"TS.INFO", b"ts:a"],
20572 &[
20574 b"TS.CREATERULE",
20575 b"ts:a",
20576 b"ts:down",
20577 b"AGGREGATION",
20578 b"avg",
20579 b"1000",
20580 ],
20581 &[b"TS.CREATE", b"ts:down"],
20582 &[
20583 b"TS.CREATERULE",
20584 b"ts:a",
20585 b"ts:down",
20586 b"AGGREGATION",
20587 b"avg",
20588 b"1000",
20589 ],
20590 &[b"TS.ADD", b"ts:a", b"5000", b"4"],
20591 &[b"TS.ADD", b"ts:a", b"6000", b"5"],
20592 &[b"TS.RANGE", b"ts:down", b"-", b"+"],
20593 &[b"TS.GET", b"ts:down", b"LATEST"],
20594 &[b"TS.INFO", b"ts:down"],
20595 &[b"TS.DEL", b"ts:a", b"5000", b"6000"],
20596 &[b"TS.RANGE", b"ts:down", b"-", b"+"],
20597 &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
20598 &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
20599 &[b"TS.DEL", b"ts:a", b"0", b"1000"],
20600 &[b"SET", b"plain", b"v"],
20602 &[b"TS.ADD", b"plain", b"1", b"1"],
20603 &[b"TS.GET", b"plain"],
20604 &[b"TS.READ", b"plain", b"0"],
20605 &[b"TS.ALTER", b"gone", b"RETENTION", b"0"],
20606 &[b"TS.RANGE", b"gone", b"-", b"+"],
20607 &[b"TS.INFO", b"gone"],
20608 ];
20609
20610 let mut one = Fixture::new();
20611 let mut many = Fixture::striped(8);
20612 for parts in script {
20613 let a = one.run(parts);
20614 let b = many.run(parts);
20615 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20616 }
20617 }
20618
20619 #[test]
20629 fn a_compaction_rule_across_stripes_reaches_both_ends() {
20630 let mut many = Fixture::striped(8);
20631 let other = apart(&mut many, "src");
20632 let (src, dst) = (b"src".as_slice(), other.as_bytes());
20633 let mut one = Fixture::new();
20634 let mut both = |parts: &[&[u8]]| {
20635 let a = one.run(parts);
20636 let b = many.run(parts);
20637 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20638 a
20639 };
20640
20641 both(&[b"TS.CREATE", src]);
20642 both(&[b"TS.CREATE", dst]);
20643 assert_eq!(
20644 both(&[b"TS.CREATERULE", src, dst, b"AGGREGATION", b"avg", b"1000"]),
20645 "+OK\r\n"
20646 );
20647 both(&[b"TS.ADD", src, b"1000", b"1"]);
20648 both(&[b"TS.ADD", src, b"1500", b"3"]);
20649 assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
20652 let open = both(&[b"TS.GET", dst, b"LATEST"]);
20653 assert!(open.contains(":1000"), "the open bucket is folded: {open}");
20654
20655 both(&[b"TS.ADD", src, b"2000", b"5"]);
20658 let got = both(&[b"TS.RANGE", dst, b"-", b"+"]);
20659 assert!(got.starts_with("*1\r\n"), "the bucket was written: {got}");
20660 assert!(got.contains(":1000"), "{got}");
20661
20662 both(&[b"TS.DEL", src, b"1000", b"1999"]);
20664 assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
20665
20666 assert!(
20669 both(&[b"TS.INFO", dst]).contains("src"),
20670 "the source is named"
20671 );
20672 assert_eq!(both(&[b"TS.DELETERULE", src, dst]), "+OK\r\n");
20673 assert_eq!(
20674 both(&[b"TS.DELETERULE", src, dst]),
20675 "-ERR TSDB: compaction rule does not exist\r\n"
20676 );
20677 }
20678
20679 #[test]
20681 fn a_label_query_across_stripes_finds_every_series() {
20682 let names: [&[u8]; 6] = [b"q:1", b"q:2", b"q:3", b"q:4", b"q:5", b"q:6"];
20683 let mut many = Fixture::striped(8);
20684 let mut homes: Vec<usize> = names
20685 .iter()
20686 .map(|name| many.server.striped(0).stripe_of(name))
20687 .collect();
20688 homes.sort_unstable();
20689 homes.dedup();
20690 assert!(homes.len() > 1, "the six keys are not all on one stripe");
20691
20692 let mut one = Fixture::new();
20693 let mut both = |parts: &[&[u8]]| {
20694 let a = one.run(parts);
20695 let b = many.run(parts);
20696 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20697 a
20698 };
20699 for name in &names {
20700 both(&[b"TS.CREATE", name, b"LABELS", b"room", b"1"]);
20701 both(&[b"TS.ADD", name, b"1000", b"1"]);
20702 }
20703
20704 let got = both(&[b"TS.QUERYINDEX", b"room=1"]);
20705 assert!(got.starts_with("*6\r\n"), "every series answered: {got}");
20706 assert!(both(&[b"TS.MGET", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
20707 assert!(both(&[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
20708 assert_eq!(
20709 both(&[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"]),
20710 "*1\r\n$4\r\nroom\r\n"
20711 );
20712 }
20713
20714 #[test]
20721 fn the_hash_group_answers_the_same_however_many_stripes_there_are() {
20722 let script: &[&[&[u8]]] = &[
20723 &[b"HSET", b"h", b"a", b"1", b"b", b"2"],
20724 &[b"HMSET", b"h", b"c", b"3"],
20725 &[b"HSETNX", b"h", b"a", b"9"],
20726 &[b"HSETNX", b"h", b"d", b"4"],
20727 &[b"HGET", b"h", b"a"],
20728 &[b"HGET", b"h", b"nope"],
20729 &[b"HMGET", b"h", b"a", b"nope"],
20730 &[b"HLEN", b"h"],
20731 &[b"HEXISTS", b"h", b"a"],
20732 &[b"HSTRLEN", b"h", b"a"],
20733 &[b"HGETALL", b"h"],
20734 &[b"HKEYS", b"h"],
20735 &[b"HVALS", b"h"],
20736 &[b"HINCRBY", b"h", b"a", b"5"],
20737 &[b"HINCRBYFLOAT", b"h", b"a", b"1.5"],
20738 &[b"HSCAN", b"h", b"0"],
20739 &[b"HSCAN", b"h", b"0", b"MATCH", b"a", b"COUNT", b"10"],
20740 &[b"HSCAN", b"h", b"0", b"NOVALUES"],
20741 &[b"HDEL", b"h", b"d"],
20742 &[b"HSET", b"one", b"f", b"v"],
20743 &[b"HRANDFIELD", b"one"],
20744 &[b"HRANDFIELD", b"one", b"1", b"WITHVALUES"],
20745 &[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"],
20747 &[b"HTTL", b"h", b"FIELDS", b"1", b"a"],
20748 &[b"HPTTL", b"h", b"FIELDS", b"1", b"a"],
20749 &[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
20750 &[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
20751 &[b"HPERSIST", b"h", b"FIELDS", b"1", b"a"],
20752 &[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"],
20753 &[b"HGET", b"h", b"b"],
20754 &[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"e", b"5"],
20756 &[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"e"],
20757 &[b"HGETDEL", b"h", b"FIELDS", b"1", b"e"],
20758 &[b"HGET", b"h", b"e"],
20759 &[b"HIMPORT", b"PREPARE", b"fs", b"x", b"y"],
20761 &[b"HIMPORT", b"SET", b"imp", b"fs", b"1", b"2"],
20762 &[b"HGETALL", b"imp"],
20763 &[b"HIMPORT", b"SET", b"imp", b"nofs", b"1", b"2"],
20764 &[b"HIMPORT", b"DISCARD", b"fs"],
20765 &[b"SET", b"plain", b"v"],
20767 &[b"HSET", b"plain", b"a", b"1"],
20768 &[b"HGETALL", b"plain"],
20769 &[b"HGET", b"gone", b"a"],
20770 &[b"HINCRBY", b"h", b"a", b"nan"],
20771 ];
20772
20773 let mut one = Fixture::new();
20774 let mut many = Fixture::striped(8);
20775 one.server.set_clock_ms(1_700_000_000_000);
20779 many.server.set_clock_ms(1_700_000_000_000);
20780 for parts in script {
20781 let a = one.run(parts);
20782 let b = many.run(parts);
20783 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20784 }
20785 }
20786
20787 #[test]
20789 fn the_array_group_answers_the_same_however_many_stripes_there_are() {
20790 let script: &[&[&[u8]]] = &[
20791 &[b"ARSET", b"a", b"0", b"x", b"y", b"z"],
20792 &[b"ARMSET", b"a", b"5", b"p", b"7", b"q"],
20793 &[b"ARGET", b"a", b"1"],
20794 &[b"ARGET", b"a", b"99"],
20795 &[b"ARMGET", b"a", b"0", b"5", b"99"],
20796 &[b"ARGETRANGE", b"a", b"0", b"7"],
20797 &[b"ARLEN", b"a"],
20798 &[b"ARCOUNT", b"a"],
20799 &[b"ARINSERT", b"a", b"m", b"n"],
20800 &[b"ARSCAN", b"a", b"0", b"20"],
20801 &[b"ARSCAN", b"a", b"0", b"20", b"LIMIT", b"2"],
20802 &[b"ARGREP", b"a", b"0", b"20", b"EXACT", b"x"],
20803 &[b"ARGREP", b"a", b"0", b"20", b"GLOB", b"*", b"WITHVALUES"],
20804 &[b"ARLASTITEMS", b"a", b"2"],
20805 &[b"ARLASTITEMS", b"a", b"2", b"REV"],
20806 &[b"ARNEXT", b"a"],
20807 &[b"ARSEEK", b"a", b"3"],
20808 &[b"AROP", b"a", b"0", b"20", b"USED"],
20809 &[b"AROP", b"a", b"0", b"20", b"MATCH", b"x"],
20810 &[b"ARINFO", b"a"],
20811 &[b"ARINFO", b"a", b"FULL"],
20812 &[b"ARDEL", b"a", b"0"],
20813 &[b"ARDELRANGE", b"a", b"1", b"2"],
20814 &[b"ARCOUNT", b"a"],
20815 &[b"ARRING", b"r", b"3", b"1", b"2", b"3", b"4"],
20816 &[b"ARGETRANGE", b"r", b"0", b"9"],
20817 &[b"SET", b"plain", b"v"],
20819 &[b"ARGET", b"plain", b"0"],
20820 &[b"ARSET", b"plain", b"0", b"v"],
20821 &[b"ARGET", b"gone", b"0"],
20822 &[b"ARSET", b"a", b"bad", b"v"],
20823 ];
20824
20825 let mut one = Fixture::new();
20826 let mut many = Fixture::striped(8);
20827 for parts in script {
20828 let a = one.run(parts);
20829 let b = many.run(parts);
20830 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20831 }
20832 }
20833
20834 #[test]
20840 fn the_graph_and_vector_groups_answer_the_same_however_many_stripes_there_are() {
20841 let script: &[&[&[u8]]] = &[
20842 &[b"G.NADD", b"g", b"n1", b"name", b"one"],
20843 &[b"G.NADD", b"g", b"n2", b"name", b"two"],
20844 &[b"G.NADD", b"g", b"n3"],
20845 &[b"G.NGET", b"g", b"n1"],
20846 &[b"G.NGET", b"g", b"gone"],
20847 &[b"G.EADD", b"g", b"n1", b"n2", b"knows"],
20848 &[b"G.EADD", b"g", b"n2", b"n3", b"knows"],
20849 &[b"G.OUT", b"g", b"n1", b"knows"],
20850 &[b"G.IN", b"g", b"n2", b"knows"],
20851 &[b"G.DEG", b"g", b"n1", b"knows"],
20852 &[b"G.DEG", b"g", b"n2", b"knows", b"BOTH"],
20853 &[b"G.NEIGH", b"g", b"n1", b"knows", b"DEPTH", b"2"],
20854 &[b"G.PATH", b"g", b"n1", b"n3"],
20855 &[b"G.EDEL", b"g", b"n1", b"n2", b"knows"],
20856 &[b"G.NDEL", b"g", b"n3"],
20857 &[b"G.NGET", b"g", b"n3"],
20858 &[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"e1"],
20860 &[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"e2"],
20861 &[b"VCARD", b"v"],
20862 &[b"VDIM", b"v"],
20863 &[b"VEMB", b"v", b"e1"],
20864 &[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0"],
20865 &[b"VSIM", b"v", b"ELE", b"e1"],
20866 &[b"VISMEMBER", b"v", b"e1"],
20867 &[b"VISMEMBER", b"v", b"gone"],
20868 &[b"VSETATTR", b"v", b"e1", b"{\"k\":1}"],
20869 &[b"VGETATTR", b"v", b"e1"],
20870 &[b"VRANGE", b"v", b"-", b"+"],
20871 &[b"VLINKS", b"v", b"e1"],
20872 &[b"VINFO", b"v"],
20873 &[b"VREM", b"v", b"e2"],
20874 &[b"VCARD", b"v"],
20875 &[b"SET", b"plain", b"v"],
20877 &[b"G.NGET", b"plain", b"n1"],
20878 &[b"VCARD", b"plain"],
20879 &[b"G.NADD", b"gone2", b"n"],
20880 &[b"VEMB", b"gone3", b"e"],
20881 ];
20882
20883 let mut one = Fixture::new();
20884 let mut many = Fixture::striped(8);
20885 for parts in script {
20886 let a = one.run(parts);
20887 let b = many.run(parts);
20888 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20889 }
20890 }
20891
20892 #[test]
20895 fn the_probabilistic_groups_answer_the_same_however_many_stripes_there_are() {
20896 let script: &[&[&[u8]]] = &[
20897 &[b"BF.RESERVE", b"bf", b"0.01", b"100"],
20899 &[b"BF.ADD", b"bf", b"a"],
20900 &[b"BF.ADD", b"bf", b"a"],
20901 &[b"BF.MADD", b"bf", b"b", b"c"],
20902 &[b"BF.EXISTS", b"bf", b"a"],
20903 &[b"BF.MEXISTS", b"bf", b"a", b"zz"],
20904 &[b"BF.CARD", b"bf"],
20905 &[b"BF.INFO", b"bf"],
20906 &[b"BF.INFO", b"bf", b"CAPACITY"],
20907 &[b"BF.DEBUG", b"bf"],
20908 &[b"BF.INSERT", b"made", b"CAPACITY", b"50", b"ITEMS", b"x"],
20909 &[b"BF.EXISTS", b"made", b"x"],
20910 &[b"BF.SCANDUMP", b"bf", b"0"],
20911 &[b"CF.RESERVE", b"cf", b"100"],
20913 &[b"CF.ADD", b"cf", b"a"],
20914 &[b"CF.ADDNX", b"cf", b"a"],
20915 &[b"CF.COUNT", b"cf", b"a"],
20916 &[b"CF.EXISTS", b"cf", b"a"],
20917 &[b"CF.MEXISTS", b"cf", b"a", b"zz"],
20918 &[b"CF.INSERT", b"cf", b"ITEMS", b"b", b"c"],
20919 &[b"CF.DEL", b"cf", b"a"],
20920 &[b"CF.COMPACT", b"cf"],
20921 &[b"CF.INFO", b"cf"],
20922 &[b"CF.DEBUG", b"cf"],
20923 &[b"CF.SCANDUMP", b"cf", b"0"],
20924 &[b"CMS.INITBYDIM", b"cms", b"100", b"5"],
20926 &[b"CMS.INITBYPROB", b"cms2", b"0.01", b"0.01"],
20927 &[b"CMS.INCRBY", b"cms", b"a", b"5", b"b", b"3"],
20928 &[b"CMS.QUERY", b"cms", b"a", b"b", b"gone"],
20929 &[b"CMS.INFO", b"cms"],
20930 &[b"TOPK.RESERVE", b"tk", b"3"],
20932 &[b"TOPK.ADD", b"tk", b"a", b"b", b"a"],
20933 &[b"TOPK.INCRBY", b"tk", b"c", b"4"],
20934 &[b"TOPK.QUERY", b"tk", b"a", b"zz"],
20935 &[b"TOPK.COUNT", b"tk", b"a", b"c"],
20936 &[b"TOPK.LIST", b"tk"],
20937 &[b"TOPK.LIST", b"tk", b"WITHCOUNT"],
20938 &[b"TOPK.INFO", b"tk"],
20939 &[b"TDIGEST.CREATE", b"td"],
20941 &[b"TDIGEST.ADD", b"td", b"1", b"2", b"3", b"4", b"5"],
20942 &[b"TDIGEST.MIN", b"td"],
20943 &[b"TDIGEST.MAX", b"td"],
20944 &[b"TDIGEST.QUANTILE", b"td", b"0.5"],
20945 &[b"TDIGEST.CDF", b"td", b"3"],
20946 &[b"TDIGEST.RANK", b"td", b"3"],
20947 &[b"TDIGEST.REVRANK", b"td", b"3"],
20948 &[b"TDIGEST.BYRANK", b"td", b"0"],
20949 &[b"TDIGEST.BYREVRANK", b"td", b"0"],
20950 &[b"TDIGEST.TRIMMED_MEAN", b"td", b"0.1", b"0.9"],
20951 &[b"TDIGEST.INFO", b"td"],
20952 &[b"TDIGEST.RESET", b"td"],
20953 &[b"TDIGEST.MIN", b"td"],
20954 &[b"SET", b"plain", b"v"],
20956 &[b"BF.ADD", b"plain", b"a"],
20957 &[b"CF.ADD", b"plain", b"a"],
20958 &[b"CMS.QUERY", b"plain", b"a"],
20959 &[b"TOPK.ADD", b"plain", b"a"],
20960 &[b"TDIGEST.ADD", b"plain", b"1"],
20961 &[b"CMS.INFO", b"gone"],
20962 &[b"TOPK.INFO", b"gone"],
20963 &[b"TDIGEST.INFO", b"gone"],
20964 ];
20965
20966 let mut one = Fixture::new();
20967 let mut many = Fixture::striped(8);
20968 for parts in script {
20969 let a = one.run(parts);
20970 let b = many.run(parts);
20971 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20972 }
20973 }
20974
20975 #[test]
20982 fn a_sketch_merge_across_stripes_reads_every_source() {
20983 let mut many = Fixture::striped(8);
20984 let other = apart(&mut many, "s1");
20985 let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
20986 let mut one = Fixture::new();
20987 let mut both = |parts: &[&[u8]]| {
20988 let a = one.run(parts);
20989 let b = many.run(parts);
20990 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20991 a
20992 };
20993
20994 for key in [b"cd".as_slice(), s1, s2] {
20998 both(&[b"CMS.INITBYDIM", key, b"100", b"5"]);
20999 }
21000 both(&[b"CMS.INCRBY", s1, b"x", b"5"]);
21001 both(&[b"CMS.INCRBY", s2, b"x", b"3"]);
21002 assert_eq!(
21003 both(&[b"CMS.MERGE", b"cd", b"2", s1, s2]),
21004 "+OK\r\n",
21005 "the merge took both sources"
21006 );
21007 assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:8\r\n");
21008 both(&[b"CMS.MERGE", b"cd", b"2", s1, s2, b"WEIGHTS", b"2", b"1"]);
21010 assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
21011 both(&[b"SET", b"plain", b"v"]);
21013 assert!(both(&[b"CMS.MERGE", b"cd", b"2", s1, b"plain"]).starts_with('-'));
21014 assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
21015
21016 both(&[b"FLUSHALL"]);
21019 both(&[b"TDIGEST.CREATE", b"td"]);
21020 both(&[b"TDIGEST.CREATE", s1]);
21021 both(&[b"TDIGEST.CREATE", s2]);
21022 both(&[b"TDIGEST.ADD", s1, b"1", b"2"]);
21023 both(&[b"TDIGEST.ADD", s2, b"9", b"10"]);
21024 assert_eq!(both(&[b"TDIGEST.MERGE", b"td", b"2", s1, s2]), "+OK\r\n");
21025 assert_eq!(both(&[b"TDIGEST.MIN", b"td"]), "$1\r\n1\r\n");
21026 assert_eq!(both(&[b"TDIGEST.MAX", b"td"]), "$2\r\n10\r\n");
21027 }
21028
21029 #[test]
21036 fn the_sort_command_answers_the_same_however_many_stripes_there_are() {
21037 let script: &[&[&[u8]]] = &[
21038 &[b"RPUSH", b"l", b"3", b"1", b"2", b"10"],
21039 &[b"SORT", b"l"],
21040 &[b"SORT", b"l", b"DESC"],
21041 &[b"SORT", b"l", b"ALPHA"],
21042 &[b"SORT", b"l", b"LIMIT", b"1", b"2"],
21043 &[b"SORT_RO", b"l"],
21044 &[
21047 b"MSET", b"w_1", b"4", b"w_2", b"3", b"w_3", b"2", b"w_10", b"1",
21048 ],
21049 &[b"SORT", b"l", b"BY", b"w_*"],
21050 &[b"SORT", b"l", b"BY", b"w_*", b"DESC"],
21051 &[b"DEL", b"w_2"],
21052 &[b"SORT", b"l", b"BY", b"w_*"],
21053 &[b"MSET", b"d_1", b"one", b"d_3", b"three"],
21056 &[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"],
21057 &[b"HSET", b"h_1", b"f", b"9"],
21059 &[b"HSET", b"h_2", b"f", b"8"],
21060 &[b"HSET", b"h_3", b"f", b"7"],
21061 &[b"HSET", b"h_10", b"f", b"6"],
21062 &[b"SORT", b"l", b"BY", b"h_*->f"],
21063 &[b"SORT", b"l", b"BY", b"nosort", b"GET", b"h_*->f"],
21064 &[b"SORT", b"l", b"BY", b"w_*", b"STORE", b"out"],
21066 &[b"LRANGE", b"out", b"0", b"-1"],
21067 &[b"SORT", b"l", b"STORE", b"l"],
21068 &[b"LRANGE", b"l", b"0", b"-1"],
21069 &[b"SORT", b"missing", b"STORE", b"out"],
21072 &[b"EXISTS", b"out"],
21073 &[b"SADD", b"s", b"c", b"a", b"b"],
21076 &[b"SORT", b"s", b"ALPHA"],
21077 &[b"SORT", b"s", b"BY", b"nosort", b"STORE", b"out"],
21078 &[b"LRANGE", b"out", b"0", b"-1"],
21079 &[b"ZADD", b"z", b"3", b"c", b"1", b"a", b"2", b"b"],
21080 &[b"SORT", b"z", b"BY", b"nosort"],
21081 &[b"SORT", b"z", b"ALPHA", b"DESC"],
21082 &[b"SET", b"str", b"v"],
21085 &[b"SORT", b"str"],
21086 &[b"RPUSH", b"words", b"one", b"two"],
21087 &[b"SORT", b"words"],
21088 &[b"SORT_RO", b"l", b"STORE", b"out"],
21089 ];
21090
21091 let mut one = Fixture::new();
21092 let mut many = Fixture::striped(8);
21093 for parts in script {
21094 let a = one.run(parts);
21095 let b = many.run(parts);
21096 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
21097 }
21098 }
21099
21100 #[test]
21107 fn a_sort_across_stripes_reads_every_pattern_key() {
21108 let mut f = Fixture::striped(8);
21109 let out = apart(&mut f, "l");
21110 let (list, dest) = (b"l".as_slice(), out.as_bytes());
21111
21112 f.run(&[b"RPUSH", list, b"a", b"b", b"c", b"d"]);
21113 f.run(&[
21114 b"MSET", b"w_a", b"4", b"w_b", b"3", b"w_c", b"2", b"w_d", b"1",
21115 ]);
21116 f.run(&[
21117 b"MSET", b"d_a", b"A", b"d_b", b"B", b"d_c", b"C", b"d_d", b"D",
21118 ]);
21119
21120 let db = f.server.striped(0);
21123 let weights: Vec<usize> = [b"w_a", b"w_b", b"w_c", b"w_d"]
21124 .iter()
21125 .map(|k| db.stripe_of(k.as_slice()))
21126 .collect();
21127 assert!(
21128 weights.iter().any(|s| *s != weights[0]),
21129 "the four weight keys all landed on one stripe, so this proves nothing"
21130 );
21131
21132 assert_eq!(
21133 f.run(&[b"SORT", list, b"BY", b"w_*", b"GET", b"d_*"]),
21134 "*4\r\n$1\r\nD\r\n$1\r\nC\r\n$1\r\nB\r\n$1\r\nA\r\n",
21135 "the order came off the weights and the answer off the data keys"
21136 );
21137 assert_eq!(
21138 f.run(&[b"SORT", list, b"BY", b"w_*", b"STORE", dest]),
21139 ":4\r\n"
21140 );
21141 assert_eq!(
21142 f.run(&[b"LRANGE", dest, b"0", b"-1"]),
21143 "*4\r\n$1\r\nd\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n",
21144 "the destination is on a stripe of its own and got the whole answer"
21145 );
21146 }
21147
21148 #[test]
21156 fn a_setting_reaches_every_stripe_and_reads_back_from_any_of_them() {
21157 let mut f = Fixture::striped(8);
21158 let other = apart(&mut f, "h");
21159 let (first, second) = (b"h".as_slice(), other.as_bytes());
21160
21161 assert_eq!(
21162 f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"2"]),
21163 "+OK\r\n"
21164 );
21165 assert_eq!(
21166 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
21167 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n",
21168 "the read comes off one stripe and has to answer for all of them"
21169 );
21170 for key in [first, second] {
21171 f.run(&[b"HSET", key, b"a", b"1", b"b", b"2"]);
21172 assert_eq!(
21173 f.run(&[b"OBJECT", b"ENCODING", key]),
21174 "$8\r\nlistpack\r\n",
21175 "two fields is still under the ladder"
21176 );
21177 f.run(&[b"HSET", key, b"c", b"3"]);
21178 assert_eq!(
21179 f.run(&[b"OBJECT", b"ENCODING", key]),
21180 "$9\r\nhashtable\r\n",
21181 "three fields is over it, on whichever stripe the key is on"
21182 );
21183 }
21184
21185 assert_eq!(
21188 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]),
21189 "+OK\r\n"
21190 );
21191 let db = f.server.striped(0);
21192 assert!(
21193 (0..db.width()).all(|i| db.hold_stripe(i).policy().name() == "allkeys-lru"),
21194 "a stripe kept the old policy"
21195 );
21196 }
21197
21198 fn held(f: &Fixture, name: &[u8]) -> (usize, u32) {
21204 let search = f.server.search.lock();
21205 let index = search.named(name).expect("the index is there");
21206 (index.held.docs.len(), index.held.docs.last())
21207 }
21208
21209 #[test]
21212 fn a_hash_that_is_written_reaches_the_index_that_follows_it() {
21213 let mut f = Fixture::new();
21214 f.run(&[
21215 b"FT.CREATE",
21216 b"ix",
21217 b"PREFIX",
21218 b"1",
21219 b"p:",
21220 b"SCHEMA",
21221 b"t",
21222 b"TEXT",
21223 ]);
21224 f.run(&[b"HSET", b"p:1", b"t", b"running dogs"]);
21225 assert_eq!(held(&f, b"ix"), (1, 1));
21226 f.run(&[b"HSET", b"other:1", b"t", b"running dogs"]);
21227 assert_eq!(held(&f, b"ix"), (1, 1));
21228
21229 f.run(&[b"HSET", b"p:1", b"u", b"beta"]);
21232 f.run(&[b"HDEL", b"p:1", b"u"]);
21233 assert_eq!(held(&f, b"ix"), (1, 3));
21234 let search = f.server.search.lock();
21235 let index = search.named(b"ix").expect("there");
21236 assert_eq!(index.held.docs.id(b"p:1"), Some(3));
21237 }
21238
21239 #[test]
21242 fn a_fresh_index_reads_the_keys_that_were_already_there() {
21243 let mut f = Fixture::new();
21244 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
21245 f.run(&[b"SET", b"p:str", b"not a hash"]);
21246 f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
21247 f.run(&[
21248 b"FT.CREATE",
21249 b"ix",
21250 b"PREFIX",
21251 b"1",
21252 b"p:",
21253 b"SCHEMA",
21254 b"t",
21255 b"TEXT",
21256 ]);
21257
21258 assert_eq!(held(&f, b"ix"), (1, 1));
21259 let search = f.server.search.lock();
21260 let index = search.named(b"ix").expect("there");
21261 assert_eq!(index.trouble.whole().failures(), 0);
21262 }
21263
21264 #[test]
21267 fn an_index_that_skipped_the_scan_fills_up_on_the_next_write() {
21268 let mut f = Fixture::new();
21269 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
21270 f.run(&[
21271 b"FT.CREATE",
21272 b"ix",
21273 b"PREFIX",
21274 b"1",
21275 b"p:",
21276 b"SKIPINITIALSCAN",
21277 b"SCHEMA",
21278 b"t",
21279 b"TEXT",
21280 ]);
21281 assert_eq!(held(&f, b"ix"), (0, 0));
21282 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
21283 assert_eq!(held(&f, b"ix"), (1, 1));
21284 }
21285
21286 #[test]
21294 fn only_a_real_change_gives_the_document_a_new_number() {
21295 let mut f = Fixture::new();
21296 f.run(&[
21297 b"FT.CREATE",
21298 b"ix",
21299 b"PREFIX",
21300 b"1",
21301 b"p:",
21302 b"SCHEMA",
21303 b"t",
21304 b"TEXT",
21305 ]);
21306 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
21307 assert_eq!(held(&f, b"ix"), (1, 1));
21308
21309 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
21310 assert_eq!(held(&f, b"ix"), (1, 2), "the same value still rewrites");
21311
21312 for quiet in [
21313 vec![b"HSETNX".as_slice(), b"p:1", b"t", b"other"],
21314 vec![b"HDEL".as_slice(), b"p:1", b"nosuch"],
21315 vec![b"HGET".as_slice(), b"p:1", b"t"],
21316 vec![b"HGETALL".as_slice(), b"p:1"],
21317 vec![b"HEXPIRE".as_slice(), b"p:1", b"100", b"FIELDS", b"1", b"t"],
21318 vec![b"HPERSIST".as_slice(), b"p:1", b"FIELDS", b"1", b"t"],
21319 vec![
21320 b"HGETEX".as_slice(),
21321 b"p:1",
21322 b"EX",
21323 b"100",
21324 b"FIELDS",
21325 b"1",
21326 b"t",
21327 ],
21328 vec![b"HGETDEL".as_slice(), b"p:1", b"FIELDS", b"1", b"nosuch"],
21329 ] {
21330 f.run(&quiet);
21331 assert_eq!(held(&f, b"ix"), (1, 2), "{:?} moved the document", quiet[0]);
21332 }
21333
21334 f.run(&[b"HSET", b"p:2", b"n", b"1"]);
21336 f.run(&[b"HINCRBY", b"p:2", b"n", b"1"]);
21337 assert_eq!(held(&f, b"ix"), (2, 4));
21338 f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"n"]);
21344 assert_eq!(held(&f, b"ix"), (1, 5));
21345 }
21346
21347 #[test]
21353 fn a_key_emptied_by_a_deadline_spends_a_number_and_one_emptied_by_hdel_does_not() {
21354 fn refused(f: &Fixture, name: &[u8]) -> u64 {
21356 let search = f.server.search.lock();
21357 let index = search.named(name).expect("the index is there");
21358 index.trouble.whole().failures()
21359 }
21360
21361 let mut f = Fixture::new();
21362 f.run(&[
21363 b"FT.CREATE",
21364 b"ix",
21365 b"PREFIX",
21366 b"1",
21367 b"p:",
21368 b"SCHEMA",
21369 b"t",
21370 b"TEXT",
21371 ]);
21372 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
21373 assert_eq!(held(&f, b"ix"), (1, 1));
21374 f.run(&[b"HDEL", b"p:1", b"t"]);
21375 assert_eq!(
21376 held(&f, b"ix"),
21377 (0, 1),
21378 "HDEL of the last field spends none"
21379 );
21380 assert_eq!(refused(&f, b"ix"), 1, "and is counted as a refusal");
21381
21382 f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
21383 assert_eq!(held(&f, b"ix"), (1, 2));
21384 f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"t"]);
21385 assert_eq!(held(&f, b"ix"), (0, 3), "a deadline spends one");
21386 assert_eq!(refused(&f, b"ix"), 1, "and is counted as nothing");
21387
21388 f.run(&[b"HSET", b"p:3", b"t", b"alpha"]);
21389 assert_eq!(held(&f, b"ix"), (1, 4));
21390 f.run(&[b"HGETDEL", b"p:3", b"FIELDS", b"1", b"t"]);
21391 assert_eq!(held(&f, b"ix"), (0, 5), "and so does HGETDEL");
21392
21393 f.run(&[b"HSET", b"p:4", b"t", b"alpha", b"u", b"beta"]);
21396 assert_eq!(held(&f, b"ix"), (1, 6));
21397 f.run(&[b"HEXPIRE", b"p:4", b"0", b"FIELDS", b"2", b"t", b"u"]);
21398 assert_eq!(held(&f, b"ix"), (0, 7));
21399 assert_eq!(refused(&f, b"ix"), 1);
21400 }
21401
21402 #[test]
21406 fn a_field_written_already_past_its_deadline_moves_the_number_twice() {
21407 let mut f = Fixture::new();
21408 f.run(&[
21409 b"FT.CREATE",
21410 b"ix",
21411 b"PREFIX",
21412 b"1",
21413 b"p:",
21414 b"SCHEMA",
21415 b"t",
21416 b"TEXT",
21417 b"u",
21418 b"TEXT",
21419 ]);
21420 f.run(&[b"HSET", b"p:1", b"u", b"keepme"]);
21421 assert_eq!(held(&f, b"ix"), (1, 1));
21422 f.run(&[
21423 b"HSETEX", b"p:1", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
21424 ]);
21425 assert_eq!(
21426 held(&f, b"ix"),
21427 (1, 3),
21428 "the key lived and the field did not"
21429 );
21430
21431 f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
21433 assert_eq!(held(&f, b"ix"), (2, 4));
21434 f.run(&[
21435 b"HSETEX", b"p:2", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
21436 ]);
21437 assert_eq!(held(&f, b"ix"), (1, 6));
21438 }
21439
21440 fn number(f: &Fixture, name: &[u8], key: &[u8]) -> Option<u32> {
21443 let search = f.server.search.lock();
21444 let index = search.named(name).expect("the index is there");
21445 index.held.docs.id(key)
21446 }
21447
21448 fn indexed() -> Fixture {
21451 let mut f = Fixture::new();
21452 f.run(&[
21453 b"FT.CREATE",
21454 b"ix",
21455 b"PREFIX",
21456 b"1",
21457 b"p:",
21458 b"SCHEMA",
21459 b"t",
21460 b"TEXT",
21461 ]);
21462 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
21463 f
21464 }
21465
21466 #[test]
21469 fn a_key_a_keyspace_command_takes_away_loses_its_document() {
21470 for take in [
21471 vec![b"DEL".as_slice(), b"p:1"],
21472 vec![b"UNLINK".as_slice(), b"p:1"],
21473 vec![b"PEXPIREAT".as_slice(), b"p:1", b"1"],
21474 vec![b"EXPIRE".as_slice(), b"p:1", b"-1"],
21475 ] {
21476 let mut f = indexed();
21477 assert_eq!(held(&f, b"ix"), (1, 1));
21478 f.run(&take);
21479 assert_eq!(held(&f, b"ix"), (0, 1), "{:?} left something", take[0]);
21480 let search = f.server.search.lock();
21481 let index = search.named(b"ix").expect("the index is there");
21482 assert_eq!(index.trouble.whole().failures(), 0, "{:?}", take[0]);
21483 }
21484
21485 let mut f = indexed();
21487 f.run(&[b"EXPIRE", b"p:1", b"1000"]);
21488 assert_eq!(held(&f, b"ix"), (1, 1));
21489 f.run(&[b"PERSIST", b"p:1"]);
21490 assert_eq!(held(&f, b"ix"), (1, 1));
21491 }
21492
21493 #[test]
21497 fn a_rename_inside_the_prefix_keeps_the_number_the_document_had() {
21498 let mut f = indexed();
21499 f.run(&[b"RENAME", b"p:1", b"p:2"]);
21500 assert_eq!(held(&f, b"ix"), (1, 1), "nothing was read again");
21501 assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
21502 assert_eq!(number(&f, b"ix", b"p:1"), None);
21503
21504 f.run(&[b"RENAME", b"p:2", b"q:1"]);
21505 assert_eq!(held(&f, b"ix"), (0, 1), "out of the prefix is an erase");
21506
21507 f.run(&[b"RENAME", b"q:1", b"p:3"]);
21508 assert_eq!(held(&f, b"ix"), (1, 2), "and into it is a reading");
21509 assert_eq!(number(&f, b"ix", b"p:3"), Some(2));
21510
21511 f.run(&[b"HSET", b"p:4", b"t", b"beta"]);
21514 assert_eq!(f.run(&[b"RENAMENX", b"p:3", b"p:4"]), ":0\r\n");
21515 assert_eq!(held(&f, b"ix"), (2, 3));
21516 f.run(&[b"RENAMENX", b"p:3", b"p:5"]);
21517 assert_eq!(number(&f, b"ix", b"p:5"), Some(2));
21518 }
21519
21520 #[test]
21523 fn a_rename_over_a_document_leaves_one_of_them() {
21524 let mut f = indexed();
21525 f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
21526 assert_eq!(held(&f, b"ix"), (2, 2));
21527 f.run(&[b"RENAME", b"p:1", b"p:2"]);
21528 assert_eq!(held(&f, b"ix"), (1, 2));
21529 assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
21530 }
21531
21532 #[test]
21536 fn a_key_that_arrives_under_the_prefix_is_read_and_one_overwritten_is_erased() {
21537 let mut f = indexed();
21538 f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
21539 f.run(&[b"COPY", b"q:1", b"p:2"]);
21540 assert_eq!(held(&f, b"ix"), (2, 2));
21541 assert_eq!(number(&f, b"ix", b"p:2"), Some(2));
21542
21543 f.run(&[b"COPY", b"p:1", b"q:2"]);
21545 assert_eq!(held(&f, b"ix"), (2, 2));
21546
21547 f.run(&[b"COPY", b"q:1", b"p:1", b"REPLACE"]);
21549 assert_eq!(held(&f, b"ix"), (2, 3));
21550 assert_eq!(number(&f, b"ix", b"p:1"), Some(3));
21551
21552 f.run(&[b"SET", b"s:1", b"plain"]);
21555 f.run(&[b"COPY", b"s:1", b"p:1", b"REPLACE"]);
21556 assert_eq!(held(&f, b"ix"), (1, 3));
21557 let dump = f.run(&[b"DUMP", b"q:1"]);
21558 assert!(dump.starts_with('$'), "{dump}");
21559 }
21560
21561 #[test]
21566 fn the_keyspace_group_reads_database_zero_whatever_database_it_ran_on() {
21567 let mut f = indexed();
21568 f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
21569 assert_eq!(held(&f, b"ix"), (2, 2));
21570 f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
21573 assert_eq!(held(&f, b"ix"), (2, 3));
21574 f.run(&[b"DEL", b"p:2"]);
21577 assert_eq!(held(&f, b"ix"), (1, 3));
21578 f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
21579 assert_eq!(held(&f, b"ix"), (1, 3), "the copy landed out of sight");
21580
21581 let dump = f.run(&[b"DUMP", b"p:1"]);
21583 assert!(dump.starts_with('$'), "{dump}");
21584 f.run(&[b"SELECT", b"1"]);
21585 f.run(&[b"HSET", b"q:1", b"t", b"gamma"]);
21586 f.run(&[b"RENAME", b"q:1", b"p:3"]);
21587 assert_eq!(held(&f, b"ix"), (1, 3), "and so is a rename");
21588 }
21589
21590 #[test]
21593 fn a_move_leaves_the_document_where_it_is() {
21594 let mut f = indexed();
21595 f.run(&[b"MOVE", b"p:1", b"1"]);
21596 assert_eq!(held(&f, b"ix"), (1, 1), "the key moved and nothing else");
21597 assert_eq!(number(&f, b"ix", b"p:1"), Some(1));
21598
21599 f.run(&[b"SELECT", b"1"]);
21600 f.run(&[b"HSET", b"p:1", b"t", b"beta"]);
21601 assert_eq!(held(&f, b"ix"), (1, 2), "and a write there still lands");
21602 f.run(&[b"DEL", b"p:1"]);
21603 assert_eq!(held(&f, b"ix"), (0, 2));
21604 }
21605
21606 #[test]
21608 fn a_flush_drops_the_indexes() {
21609 for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
21610 let mut f = indexed();
21611 f.run(&[flush]);
21612 assert!(f.server.search.lock().is_empty(), "{flush:?} kept an index");
21613 assert_eq!(f.run(&[b"FT._LIST"]), "*0\r\n");
21614 }
21615
21616 let mut f = indexed();
21619 f.run(&[b"SELECT", b"9"]);
21620 f.run(&[b"FLUSHDB"]);
21621 assert!(f.server.search.lock().is_empty());
21622 }
21623
21624 fn tagged() -> Fixture {
21627 let mut f = Fixture::new();
21628 f.run(&[
21629 b"FT.CREATE",
21630 b"tv",
21631 b"PREFIX",
21632 b"1",
21633 b"tv:",
21634 b"SCHEMA",
21635 b"g",
21636 b"AS",
21637 b"gg",
21638 b"TAG",
21639 b"h",
21640 b"TAG",
21641 b"SEPARATOR",
21642 b"|",
21643 b"CASESENSITIVE",
21644 b"n",
21645 b"NUMERIC",
21646 ]);
21647 f.run(&[
21648 b"HSET",
21649 b"tv:1",
21650 b"g",
21651 b"Red, BLUE ",
21652 b"h",
21653 b"Aa|bB",
21654 b"n",
21655 b"1",
21656 ]);
21657 f.run(&[b"HSET", b"tv:2", b"g", b"red", b"h", b"aa", b"n", b"2"]);
21658 f
21659 }
21660
21661 #[test]
21665 fn tag_values_come_back_as_they_are_stored_and_sorted_by_their_bytes() {
21666 let mut f = tagged();
21667 assert_eq!(
21668 f.run(&[b"FT.TAGVALS", b"tv", b"gg"]),
21669 "*2\r\n$4\r\nblue\r\n$3\r\nred\r\n"
21670 );
21671 assert_eq!(
21672 f.run(&[b"FT.TAGVALS", b"tv", b"h"]),
21673 "*3\r\n$2\r\nAa\r\n$2\r\naa\r\n$2\r\nbB\r\n"
21674 );
21675 }
21676
21677 #[test]
21680 fn tag_values_are_asked_for_by_the_attribute_and_not_the_identifier() {
21681 let mut f = tagged();
21682 for (name, want) in [
21683 (b"g".as_slice(), "-SEARCH_ATTR_BAD No such field\r\n"),
21684 (b"zz", "-SEARCH_ATTR_BAD No such field\r\n"),
21685 (b"n", "-SEARCH_ATTR_BAD Not a tag field\r\n"),
21686 ] {
21687 assert_eq!(f.run(&[b"FT.TAGVALS", b"tv", name]), want);
21688 }
21689 assert_eq!(
21690 f.run(&[b"FT.TAGVALS", b"nope", b"g"]),
21691 "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
21692 );
21693 }
21694
21695 #[test]
21698 fn asking_for_tag_values_counts_a_use_of_the_index() {
21699 let mut f = tagged();
21700 let uses = |f: &mut Fixture| {
21701 let reply = f.run(&[b"FT.INFO", b"tv"]);
21702 let at = reply.find("number_of_uses").expect("the field is reported");
21703 let value = reply[at..].split("\r\n").nth(1).unwrap();
21704 value.trim_start_matches(':').parse::<i64>().unwrap()
21705 };
21706 let before = uses(&mut f);
21707 f.run(&[b"FT.TAGVALS", b"tv", b"gg"]);
21708 f.run(&[b"FT.TAGVALS", b"tv", b"zz"]);
21709 assert_eq!(uses(&mut f), before + 3);
21711 }
21712
21713 #[test]
21716 fn a_tag_field_with_nothing_in_it_answers_empty() {
21717 let mut f = Fixture::new();
21718 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"g", b"TAG"]);
21719 assert_eq!(f.run(&[b"FT.TAGVALS", b"e", b"g"]), "*0\r\n");
21720 }
21721
21722 #[test]
21725 fn a_dictionary_is_not_a_key() {
21726 let mut f = Fixture::new();
21727 assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"a", b"b"]), ":2\r\n");
21728 assert_eq!(f.run(&[b"TYPE", b"d"]), "+none\r\n");
21729 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
21730 assert_eq!(f.run(&[b"KEYS", b"d"]), "*0\r\n");
21731 }
21732
21733 #[test]
21736 fn a_dictionary_counts_the_terms_it_had_not_seen() {
21737 let mut f = Fixture::new();
21738 assert_eq!(
21739 f.run(&[b"FT.DICTADD", b"d", b"zeta", b"alpha", b"Beta", b"alpha"]),
21740 ":3\r\n"
21741 );
21742 assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"alpha"]), ":0\r\n");
21743 assert_eq!(f.run(&[b"FT.DICTADD", b"d", b""]), ":0\r\n");
21744 assert_eq!(
21745 f.run(&[b"FT.DICTDUMP", b"d"]),
21746 "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nzeta\r\n"
21747 );
21748 assert_eq!(f.run(&[b"FT.DICTDEL", b"d", b"alpha", b"nope"]), ":1\r\n");
21749 }
21750
21751 #[test]
21755 fn a_dictionary_nobody_made_dumps_empty_rather_than_failing() {
21756 let mut f = Fixture::new();
21757 assert_eq!(f.run(&[b"FT.DICTDUMP", b"nope"]), "*0\r\n");
21758 assert_eq!(f.run(&[b"FT.DICTDEL", b"nope", b"a"]), ":0\r\n");
21759 }
21760
21761 #[test]
21763 fn a_flush_drops_the_dictionaries() {
21764 for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
21765 let mut f = Fixture::new();
21766 f.run(&[b"FT.DICTADD", b"d", b"a"]);
21767 f.run(&[flush]);
21768 assert_eq!(f.run(&[b"FT.DICTDUMP", b"d"]), "*0\r\n", "{flush:?}");
21769 }
21770 }
21771
21772 fn profiling() -> Fixture {
21777 let mut f = Fixture::new();
21778 f.run(&[
21779 b"FT.CREATE",
21780 b"ix",
21781 b"PREFIX",
21782 b"1",
21783 b"p:",
21784 b"SCHEMA",
21785 b"t",
21786 b"TEXT",
21787 b"n",
21788 b"NUMERIC",
21789 ]);
21790 f.run(&[b"HSET", b"p:1", b"t", b"alpha", b"n", b"1"]);
21791 f.run(&[b"HSET", b"p:2", b"t", b"alpha beta", b"n", b"2"]);
21792 f.run(&[b"HSET", b"p:3", b"t", b"beta", b"n", b"3"]);
21793 f
21794 }
21795
21796 fn timeless(reply: &str) -> String {
21799 const KEYS: &[&str] = &[
21800 "+Total profile time",
21801 "+Parsing time",
21802 "+Workers queue time",
21803 "+Pipeline creation time",
21804 "+Time",
21805 ];
21806 let mut out = String::new();
21807 let mut parts = reply.split("\r\n").peekable();
21808 while let Some(part) = parts.next() {
21809 out.push_str(part);
21810 out.push_str("\r\n");
21811 if !KEYS.contains(&part) {
21812 continue;
21813 }
21814 match parts.next() {
21817 Some(head) if head.starts_with('$') => {
21818 parts.next();
21819 }
21820 _ => {}
21821 }
21822 out.push_str("<t>\r\n");
21823 }
21824 out.truncate(out.len() - 2);
21826 out
21827 }
21828
21829 #[test]
21832 fn a_profile_wraps_the_reply_it_would_have_answered_anyway() {
21833 let mut f = profiling();
21834 assert_eq!(
21835 timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"])),
21836 "*2\r\n\
21837 *5\r\n:2\r\n$3\r\np:1\r\n*4\r\n$1\r\nt\r\n$5\r\nalpha\r\n$1\r\nn\r\n$1\r\n1\r\n\
21838 $3\r\np:2\r\n*4\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n$1\r\nn\r\n$1\r\n2\r\n\
21839 *4\r\n+Shards\r\n*1\r\n*14\r\n\
21840 +Total profile time\r\n<t>\r\n+Parsing time\r\n<t>\r\n\
21841 +Workers queue time\r\n<t>\r\n+Pipeline creation time\r\n<t>\r\n\
21842 +Warning\r\n*1\r\n+None\r\n\
21843 +Iterators profile\r\n*10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
21844 +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
21845 +Estimated number of matches\r\n:2\r\n\
21846 +Result processors profile\r\n*4\r\n\
21847 *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
21848 *6\r\n+Type\r\n+Scorer\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
21849 *6\r\n+Type\r\n+Sorter\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
21850 *6\r\n+Type\r\n+Loader\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
21851 +Coordinator\r\n*0\r\n"
21852 );
21853 let mut g = profiling();
21854 g.run(&[b"HELLO", b"3"]);
21855 let three = timeless(&g.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"]));
21856 assert!(three.starts_with("%2\r\n+Results\r\n"), "{three}");
21857 assert!(
21858 three.contains("+Profile\r\n%2\r\n+Shards\r\n*1\r\n%7\r\n"),
21859 "{three}"
21860 );
21861 assert!(three.ends_with("+Coordinator\r\n%0\r\n"), "{three}");
21862 assert!(
21863 three.contains(
21864 "+Iterators profile\r\n%5\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
21865 +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
21866 +Estimated number of matches\r\n:2\r\n"
21867 ),
21868 "{three}"
21869 );
21870 }
21871
21872 #[test]
21875 fn each_kind_of_step_writes_the_keys_that_belong_to_it() {
21876 let mut f = profiling();
21877 let tree = |f: &mut Fixture, query: &[u8]| {
21878 let reply = timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", query]));
21879 let at = reply.find("+Iterators profile").expect("a tree");
21880 let end = reply.find("+Result processors").expect("a list of steps");
21881 reply[at..end].to_string()
21882 };
21883 assert_eq!(
21884 tree(&mut f, b"alpha beta"),
21885 "+Iterators profile\r\n*8\r\n+Type\r\n+INTERSECT\r\n+Time\r\n<t>\r\n\
21886 +Number of reading operations\r\n:1\r\n+Child iterators\r\n*2\r\n\
21887 *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n+Time\r\n<t>\r\n\
21888 +Number of reading operations\r\n:2\r\n+Estimated number of matches\r\n:2\r\n\
21889 *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$4\r\nbeta\r\n+Time\r\n<t>\r\n\
21890 +Number of reading operations\r\n:1\r\n+Estimated number of matches\r\n:2\r\n"
21891 );
21892 assert!(tree(&mut f, b"alpha|beta").starts_with(
21893 "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n+Query type\r\n+UNION\r\n\
21894 +Time\r\n<t>\r\n+Number of reading operations\r\n:3\r\n+Child iterators\r\n*2\r\n"
21895 ));
21896 assert!(tree(&mut f, b"-alpha").starts_with(
21899 "+Iterators profile\r\n*8\r\n+Type\r\n+NOT\r\n+Time\r\n<t>\r\n\
21900 +Number of reading operations\r\n:1\r\n+Child iterator\r\n*10\r\n"
21901 ));
21902 assert!(tree(&mut f, b"~alpha").starts_with(
21903 "+Iterators profile\r\n*8\r\n+Type\r\n+OPTIONAL\r\n+Time\r\n<t>\r\n\
21904 +Number of reading operations\r\n:3\r\n+Child iterator\r\n*10\r\n"
21905 ));
21906 assert_eq!(
21908 tree(&mut f, b"*"),
21909 "+Iterators profile\r\n*6\r\n+Type\r\n+WILDCARD\r\n+Time\r\n<t>\r\n\
21910 +Number of reading operations\r\n:3\r\n"
21911 );
21912 assert!(tree(&mut f, b"@n:[1 2]").starts_with(
21913 "+Iterators profile\r\n*10\r\n+Type\r\n+NUMERIC\r\n+Term\r\n\
21914 $19\r\n1.000000 - 2.000000\r\n"
21915 ));
21916 }
21917
21918 #[test]
21921 fn limited_folds_the_branches_an_expansion_made_and_leaves_a_bar_alone() {
21922 let mut f = profiling();
21923 f.run(&[b"HSET", b"p:4", b"t", b"alps"]);
21924 let tree = |f: &mut Fixture, words: &[&[u8]]| {
21925 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH"];
21926 argv.extend_from_slice(words);
21927 let reply = timeless(&f.run(&argv));
21928 let at = reply.find("+Iterators profile").expect("a tree");
21929 let end = reply.find("+Result processors").expect("a list of steps");
21930 reply[at..end].to_string()
21931 };
21932 assert_eq!(
21933 tree(&mut f, &[b"LIMITED", b"QUERY", b"al*"]),
21934 "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n\
21935 +Query type\r\n$11\r\nPREFIX - al\r\n+Time\r\n<t>\r\n\
21936 +Number of reading operations\r\n:3\r\n+Child iterators\r\n\
21937 +The number of iterators in the union is 2\r\n"
21938 );
21939 assert!(tree(&mut f, &[b"QUERY", b"al*"]).contains("+Child iterators\r\n*2\r\n"));
21940 assert!(
21941 tree(&mut f, &[b"LIMITED", b"QUERY", b"alpha|beta"])
21942 .contains("+Child iterators\r\n*2\r\n")
21943 );
21944 assert!(tree(&mut f, &[b"QUERY", b"alpha|beta"]).contains("+Query type\r\n+UNION\r\n"));
21948 assert!(
21949 tree(&mut f, &[b"QUERY", b"al*"]).contains("+Query type\r\n$11\r\nPREFIX - al\r\n")
21950 );
21951 }
21952
21953 #[test]
21956 fn the_steps_a_search_runs_depend_on_what_was_asked_for() {
21957 let mut f = profiling();
21958 let steps = |f: &mut Fixture, words: &[&[u8]]| {
21959 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"];
21960 argv.extend_from_slice(words);
21961 let reply = timeless(&f.run(&argv));
21962 let at = reply.find("+Result processors").expect("a list of steps");
21963 let end = reply.find("+Coordinator").expect("an end");
21964 let mut out = Vec::new();
21965 let mut parts = reply[at..end].split("\r\n").peekable();
21966 while let Some(part) = parts.next() {
21967 if part == "+Type" {
21968 out.push(parts.next().unwrap_or_default().to_string());
21969 }
21970 }
21971 out
21972 };
21973 assert_eq!(
21974 steps(&mut f, &[]),
21975 ["+Index", "+Scorer", "+Sorter", "+Loader"]
21976 );
21977 assert_eq!(
21978 steps(&mut f, &[b"NOCONTENT"]),
21979 ["+Index", "+Scorer", "+Sorter"]
21980 );
21981 assert_eq!(
21984 steps(&mut f, &[b"LIMIT", b"0", b"0"]),
21985 ["+Index", "+Counter"]
21986 );
21987 assert_eq!(
21990 steps(&mut f, &[b"SORTBY", b"n"]),
21991 ["+Index", "+Sorter", "+Loader"]
21992 );
21993 assert_eq!(
21994 steps(&mut f, &[b"SORTBY", b"n", b"WITHSCORES"]),
21995 ["+Index", "+Scorer", "+Sorter", "+Loader"]
21996 );
21997 assert_eq!(
21998 steps(&mut f, &[b"HIGHLIGHT"]),
21999 ["+Index", "+Scorer", "+Sorter", "+Loader", "+Highlighter"]
22000 );
22001 assert_eq!(
22002 steps(&mut f, &[b"SUMMARIZE", b"NOCONTENT"]),
22003 ["+Index", "+Scorer", "+Sorter"]
22004 );
22005 }
22006
22007 #[test]
22010 fn a_pipeline_names_every_step_after_what_it_runs() {
22011 let mut f = profiling();
22012 let steps = |f: &mut Fixture, words: &[&[u8]]| {
22013 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"AGGREGATE", b"QUERY", b"*"];
22014 argv.extend_from_slice(words);
22015 let reply = timeless(&f.run(&argv));
22016 let at = reply.find("+Result processors").expect("a list of steps");
22017 let end = reply.find("+Coordinator").expect("an end");
22018 let mut out = Vec::new();
22019 let mut parts = reply[at..end].split("\r\n").peekable();
22020 while let Some(part) = parts.next() {
22021 if part == "+Type" {
22022 out.push(parts.next().unwrap_or_default().to_string());
22023 }
22024 }
22025 out
22026 };
22027 assert_eq!(steps(&mut f, &[]), ["+Index"]);
22028 assert_eq!(
22029 steps(&mut f, &[b"APPLY", b"1", b"AS", b"one"]),
22030 ["+Index", "+Projector - Literal 1"]
22031 );
22032 assert_eq!(
22033 steps(
22034 &mut f,
22035 &[b"LOAD", b"1", b"@n", b"APPLY", b"@n * 2", b"AS", b"d"]
22036 ),
22037 ["+Index", "+Loader", "+Projector - Operator *"]
22038 );
22039 assert_eq!(
22040 steps(&mut f, &[b"LOAD", b"1", b"@n", b"FILTER", b"@n > 1"]),
22041 ["+Index", "+Loader", "+Filter - Predicate >"]
22042 );
22043 assert_eq!(
22044 steps(
22045 &mut f,
22046 &[b"GROUPBY", b"1", b"@n", b"REDUCE", b"COUNT", b"0"]
22047 ),
22048 ["+Index", "+Loader", "+Grouper"]
22049 );
22050 assert_eq!(
22051 steps(&mut f, &[b"SORTBY", b"1", b"@n"]),
22052 ["+Index", "+Loader", "+Sorter"]
22053 );
22054 assert_eq!(
22055 steps(&mut f, &[b"LIMIT", b"0", b"2"]),
22056 ["+Index", "+Pager/Limiter"]
22057 );
22058 assert_eq!(
22061 steps(
22062 &mut f,
22063 &[
22064 b"ADDSCORES",
22065 b"LOAD",
22066 b"1",
22067 b"@n",
22068 b"APPLY",
22069 b"@__score",
22070 b"AS",
22071 b"s"
22072 ]
22073 ),
22074 [
22075 "+Index",
22076 "+Scorer",
22077 "+Loader",
22078 "+Projector - Property __score"
22079 ]
22080 );
22081 }
22082
22083 #[test]
22091 fn a_sortable_field_is_read_without_the_key_being_opened() {
22092 let mut f = Fixture::new();
22093 f.run(&[
22094 b"FT.CREATE",
22095 b"sx",
22096 b"PREFIX",
22097 b"1",
22098 b"s:",
22099 b"SCHEMA",
22100 b"n",
22101 b"NUMERIC",
22102 b"SORTABLE",
22103 b"g",
22104 b"TAG",
22105 ]);
22106 f.run(&[b"HSET", b"s:1", b"n", b"1", b"g", b"one"]);
22107 f.run(&[b"HSET", b"s:2", b"n", b"2", b"g", b"two"]);
22108 let loads = |f: &mut Fixture, words: &[&[u8]]| {
22109 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"sx", b"AGGREGATE", b"QUERY", b"*"];
22110 argv.extend_from_slice(words);
22111 f.run(&argv).contains("+Loader")
22112 };
22113 assert!(!loads(&mut f, &[b"LOAD", b"1", b"@n"]));
22114 assert!(!loads(&mut f, &[b"SORTBY", b"1", b"@n"]));
22115 assert!(!loads(&mut f, &[b"APPLY", b"@n * 2", b"AS", b"d"]));
22116 assert!(loads(&mut f, &[b"LOAD", b"1", b"@g"]));
22117 assert!(loads(&mut f, &[b"LOAD", b"2", b"@n", b"@g"]));
22118 assert!(loads(
22119 &mut f,
22120 &[b"GROUPBY", b"1", b"@g", b"REDUCE", b"COUNT", b"0"]
22121 ));
22122 assert!(loads(&mut f, &[b"LOAD", b"*"]));
22123 }
22124
22125 #[test]
22128 fn a_profile_checks_its_own_words_before_it_runs_anything() {
22129 let mut f = profiling();
22130 assert_eq!(
22131 f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY"]),
22132 "-ERR wrong number of arguments for 'FT.PROFILE' command\r\n"
22133 );
22134 assert_eq!(
22135 f.run(&[b"FT.PROFILE", b"ix", b"BOGUS", b"QUERY", b"alpha"]),
22136 "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
22137 );
22138 assert_eq!(
22141 f.run(&[
22142 b"FT.PROFILE",
22143 b"ix",
22144 b"LIMITED",
22145 b"SEARCH",
22146 b"QUERY",
22147 b"alpha"
22148 ]),
22149 "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
22150 );
22151 assert_eq!(
22152 f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"BOGUS", b"alpha"]),
22153 "-The QUERY keyword is expected\r\n"
22154 );
22155 assert_eq!(
22156 f.run(&[
22157 b"FT.PROFILE",
22158 b"ix",
22159 b"AGGREGATE",
22160 b"QUERY",
22161 b"alpha",
22162 b"WITHCURSOR"
22163 ]),
22164 "-FT.PROFILE does not support cursor\r\n"
22165 );
22166 assert_eq!(
22169 f.run(&[b"FT.PROFILE", b"nope", b"SEARCH", b"QUERY", b"alpha"]),
22170 "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
22171 );
22172 assert_eq!(
22173 f.run(&[
22174 b"FT.PROFILE",
22175 b"ix",
22176 b"SEARCH",
22177 b"QUERY",
22178 b"alpha",
22179 b"extra"
22180 ]),
22181 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `extra` at position 1 for <main>\r\n"
22182 );
22183 }
22184
22185 #[test]
22187 fn the_words_of_a_profile_are_read_the_way_every_other_word_is() {
22188 let mut f = profiling();
22189 let one = f.run(&[
22190 b"FT.PROFILE",
22191 b"ix",
22192 b"search",
22193 b"limited",
22194 b"query",
22195 b"alpha",
22196 ]);
22197 let two = f.run(&[
22198 b"FT.PROFILE",
22199 b"ix",
22200 b"SEARCH",
22201 b"LIMITED",
22202 b"QUERY",
22203 b"alpha",
22204 ]);
22205 assert_eq!(timeless(&one), timeless(&two));
22206 }
22207
22208 #[test]
22213 fn a_setting_reads_back_as_a_pair_on_one_protocol_and_a_map_on_the_other() {
22214 let mut f = Fixture::new();
22215 assert_eq!(
22216 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
22217 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
22218 );
22219 assert_eq!(
22220 f.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
22221 "*1\r\n*2\r\n+EXTLOAD\r\n$-1\r\n"
22222 );
22223 let mut g = Fixture::new();
22224 g.run(&[b"HELLO", b"3"]);
22225 assert_eq!(
22226 g.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
22227 "%1\r\n+TIMEOUT\r\n$3\r\n500\r\n"
22228 );
22229 assert_eq!(
22230 g.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
22231 "%1\r\n+EXTLOAD\r\n_\r\n"
22232 );
22233 }
22234
22235 #[test]
22238 fn a_help_row_carries_the_description_and_the_value_together() {
22239 let mut f = Fixture::new();
22240 assert_eq!(
22241 f.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
22242 "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
22243 +Value\r\n$3\r\n500\r\n"
22244 );
22245 let mut g = Fixture::new();
22246 g.run(&[b"HELLO", b"3"]);
22247 assert_eq!(
22248 g.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
22249 "%1\r\n+TIMEOUT\r\n%2\r\n+Description\r\n+Query (search) timeout\r\n\
22250 +Value\r\n$3\r\n500\r\n"
22251 );
22252 }
22253
22254 #[test]
22257 fn only_a_bare_star_asks_for_every_setting_and_nothing_else_globs() {
22258 let mut f = Fixture::new();
22259 assert_eq!(
22260 f.run(&[b"FT.CONFIG", b"GET", b"timeout"]),
22261 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
22262 );
22263 for name in [
22264 b"TIMEOUT*".as_slice(),
22265 b"?IMEOUT",
22266 b"*TIMEOUT*",
22267 b"TIME",
22268 b"NOSUCH",
22269 b"",
22270 ] {
22271 assert_eq!(f.run(&[b"FT.CONFIG", b"GET", name]), "*0\r\n", "{name:?}");
22272 }
22273 assert!(f.run(&[b"FT.CONFIG", b"GET", b"*"]).starts_with("*69\r\n"));
22274 assert!(f.run(&[b"FT.CONFIG", b"HELP", b"*"]).starts_with("*69\r\n"));
22275 }
22276
22277 #[test]
22280 fn a_read_ignores_whatever_follows_the_name() {
22281 let mut f = Fixture::new();
22282 assert_eq!(
22283 f.run(&[b"FT.CONFIG", b"GET", b"timeout", b"extra", b"more"]),
22284 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
22285 );
22286 assert_eq!(
22287 f.run(&[b"FT.CONFIG", b"HELP", b"timeout", b"extra"]),
22288 "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
22289 +Value\r\n$3\r\n500\r\n"
22290 );
22291 }
22292
22293 #[test]
22296 fn a_missing_subcommand_and_a_missing_name_are_told_apart() {
22297 let mut f = Fixture::new();
22298 assert_eq!(
22299 f.run(&[b"FT.CONFIG"]),
22300 "-ERR wrong number of arguments for 'FT.CONFIG' command\r\n"
22301 );
22302 for sub in [b"GET".as_slice(), b"SET", b"HELP"] {
22303 let want = format!(
22304 "-ERR wrong number of arguments for 'FT.CONFIG|{}' command\r\n",
22305 String::from_utf8_lossy(sub)
22306 );
22307 assert_eq!(f.run(&[b"FT.CONFIG", sub]), want);
22308 }
22309 assert_eq!(
22310 f.run(&[b"ft.config", b"get"]),
22311 "-ERR wrong number of arguments for 'FT.CONFIG|GET' command\r\n"
22312 );
22313 assert_eq!(
22314 f.run(&[b"FT.CONFIG", b"bogus"]),
22315 "-ERR unknown subcommand 'bogus'. Try FT.CONFIG HELP.\r\n"
22316 );
22317 }
22318
22319 #[test]
22322 fn a_write_checks_the_name_then_the_setting_then_the_value() {
22323 let mut f = Fixture::new();
22324 for tail in [vec![b"1".as_slice()], vec![], vec![b"1", b"2", b"3"]] {
22325 let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"NOSUCH"];
22326 cmd.extend(tail);
22327 assert_eq!(f.run(&cmd), "-SEARCH_OPTION_INVALID Invalid option\r\n");
22328 }
22329 for tail in [vec![b"1000".as_slice()], vec![], vec![b"x", b"y"]] {
22330 let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"MAXDOCTABLESIZE"];
22331 cmd.extend(tail);
22332 assert_eq!(
22333 f.run(&cmd),
22334 "-SEARCH_OPTION_BAD Not modifiable at runtime\r\n"
22335 );
22336 }
22337 assert_eq!(
22338 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"x", b"y", b"z"]),
22339 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n"
22340 );
22341 }
22342
22343 #[test]
22346 fn an_excess_of_words_is_noticed_after_the_value_is_kept() {
22347 let mut f = Fixture::new();
22348 assert_eq!(
22349 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"500"]),
22350 "+OK\r\n"
22351 );
22352 assert_eq!(
22353 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"600", b"junk"]),
22354 "+EXCESSARGS\r\n"
22355 );
22356 assert_eq!(
22357 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
22358 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n600\r\n"
22359 );
22360 }
22361
22362 #[test]
22365 fn a_number_is_read_the_strict_way_and_then_the_loose_one() {
22366 let mut f = Fixture::new();
22367 for (given, want) in [
22368 (b"0x10".as_slice(), "16"),
22369 (b"0X1f", "31"),
22370 (b"+0x10", "16"),
22371 (b"+5", "5"),
22372 (b"010", "10"),
22373 (b"08", "8"),
22374 (b"0777", "777"),
22375 (b"1e3", "1000"),
22376 (b"0.0", "0"),
22377 (b"-0.0", "0"),
22378 ] {
22379 assert_eq!(
22380 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
22381 "+OK\r\n",
22382 "{given:?}"
22383 );
22384 let want = format!("*1\r\n*2\r\n+TIMEOUT\r\n${}\r\n{want}\r\n", want.len());
22385 assert_eq!(
22386 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
22387 want,
22388 "{given:?}"
22389 );
22390 }
22391 for given in [
22392 b" 5".as_slice(),
22393 b"5 ",
22394 b"1.5",
22395 b"1e-3",
22396 b"x",
22397 b"",
22398 b"0b11",
22399 b"0xg",
22400 b"nan",
22401 b"inf",
22402 b"1e100",
22403 b"99999999999999999999",
22404 ] {
22405 assert_eq!(
22406 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
22407 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
22408 "{given:?}"
22409 );
22410 }
22411 }
22412
22413 #[test]
22416 fn a_negative_is_answered_by_whichever_reader_found_it() {
22417 let mut f = Fixture::new();
22418 for given in [b"-1".as_slice(), b"-16"] {
22419 assert_eq!(
22420 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
22421 "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n",
22422 "{given:?}"
22423 );
22424 }
22425 for given in [b"-0x10".as_slice(), b"-1e3", b"-010", b"-2.0"] {
22426 assert_eq!(
22427 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
22428 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
22429 "{given:?}"
22430 );
22431 }
22432 let unlimited = "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n$9\r\nunlimited\r\n";
22433 for given in [b"-1".as_slice(), b"-0x10", b"-1e3", b"-010"] {
22434 assert_eq!(
22435 f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
22436 "+OK\r\n",
22437 "{given:?}"
22438 );
22439 assert_eq!(
22440 f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
22441 unlimited,
22442 "{given:?}"
22443 );
22444 }
22445 }
22446
22447 #[test]
22450 fn a_wide_setting_wraps_into_its_slot_before_it_is_read_back() {
22451 let mut f = Fixture::new();
22452 for (given, want) in [
22453 (b"2147483647".as_slice(), "2147483647"),
22454 (b"2147483648", "unlimited"),
22455 (b"4294967295", "unlimited"),
22456 (b"9223372036854775806", "unlimited"),
22457 (b"0", "0"),
22458 ] {
22459 assert_eq!(
22460 f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
22461 "+OK\r\n",
22462 "{given:?}"
22463 );
22464 let want = format!(
22465 "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n${}\r\n{want}\r\n",
22466 want.len()
22467 );
22468 assert_eq!(
22469 f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
22470 want,
22471 "{given:?}"
22472 );
22473 }
22474 }
22475
22476 #[test]
22479 fn a_number_out_of_range_names_the_limit_it_crossed() {
22480 let mut f = Fixture::new();
22481 let bounds = "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n";
22482 for (name, given) in [
22483 (b"MINPREFIX".as_slice(), b"0".as_slice()),
22484 (b"MAX_AGGREGATE_GROUPS", b"0"),
22485 (b"BM25STD_TANH_FACTOR", b"0"),
22486 (b"DEFAULT_DIALECT", b"0"),
22487 (b"MINSTEMLEN", b"4294967296"),
22488 (b"_BG_INDEX_OOM_PAUSE_TIME", b"4294967296"),
22489 (b"INDEXER_YIELD_EVERY_OPS", b"4294967296"),
22490 (b"CONNECT_TIMEOUT", b"2147483648"),
22491 ] {
22492 assert_eq!(
22493 f.run(&[b"FT.CONFIG", b"SET", name, given]),
22494 bounds,
22495 "{name:?}"
22496 );
22497 }
22498 for (name, given, want) in [
22499 (
22500 b"MINSTEMLEN".as_slice(),
22501 b"1".as_slice(),
22502 "-SEARCH_SYNTAX Minimum stem length cannot be lower than 2\r\n",
22503 ),
22504 (
22505 b"MAX_AGGREGATE_GROUPS",
22506 b"67108865",
22507 "-SEARCH_LIMIT_OVER Value exceeds maximum possible aggregate groups\r\n",
22508 ),
22509 (
22510 b"WORKERS",
22511 b"17",
22512 "-SEARCH_LIMIT_OVER Number of worker threads cannot exceed 16\r\n",
22513 ),
22514 (
22515 b"_NUMERIC_RANGES_PARENTS",
22516 b"3",
22517 "-SEARCH_PARSE_ARGS Max depth for range cannot be higher than max \
22518 depth for balance\r\n",
22519 ),
22520 (
22521 b"DEFAULT_DIALECT",
22522 b"5",
22523 "-SEARCH_VALUE_BAD Default dialect version cannot be higher than 4\r\n",
22524 ),
22525 (
22526 b"_BG_INDEX_MEM_PCT_THR",
22527 b"101",
22528 "-SEARCH_LIMIT_OVER Memory limit for indexing cannot be greater then \
22529 100%\r\n",
22530 ),
22531 (
22532 b"BM25STD_TANH_FACTOR",
22533 b"10001",
22534 "-SEARCH_LIMIT_OVER BM25STD_TANH_FACTOR must be between 1 and 10000 \
22535 inclusive\r\n",
22536 ),
22537 (
22538 b"BG_INDEX_SLEEP_DURATION_US",
22539 b"1000000",
22540 "-SEARCH_LIMIT_OVER BG_INDEX_SLEEP_DURATION_US must be between 1 and \
22541 999999 (usleep POSIX limit)\r\n",
22542 ),
22543 ] {
22544 assert_eq!(
22545 f.run(&[b"FT.CONFIG", b"SET", name, given]),
22546 want,
22547 "{name:?}"
22548 );
22549 }
22550 }
22551
22552 #[test]
22555 fn the_trimming_delays_are_checked_against_one_another() {
22556 let mut f = Fixture::new();
22557 assert_eq!(
22558 f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"5000"]),
22559 "-SEARCH_PARSE_ARGS _MIN_TRIM_DELAY_MS (5000) must be less than \
22560 _MAX_TRIM_DELAY_MS (5000)\r\n"
22561 );
22562 assert_eq!(
22563 f.run(&[b"FT.CONFIG", b"SET", b"_MAX_TRIM_DELAY_MS", b"1999"]),
22564 "-SEARCH_PARSE_ARGS _MAX_TRIM_DELAY_MS (1999) must be greater than \
22565 _MIN_TRIM_DELAY_MS (2000)\r\n"
22566 );
22567 assert_eq!(
22568 f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"4999"]),
22569 "+OK\r\n"
22570 );
22571 }
22572
22573 #[test]
22576 fn a_word_setting_folds_where_a_real_server_folds_and_not_otherwise() {
22577 let mut f = Fixture::new();
22578 assert_eq!(
22579 f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"RETURN"]),
22580 "+OK\r\n"
22581 );
22582 assert_eq!(
22583 f.run(&[b"FT.CONFIG", b"GET", b"ON_TIMEOUT"]),
22584 "*1\r\n*2\r\n+ON_TIMEOUT\r\n$6\r\nreturn\r\n"
22585 );
22586 assert_eq!(
22587 f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"nope"]),
22588 "-SEARCH_VALUE_BAD Invalid ON_TIMEOUT value\r\n"
22589 );
22590 assert_eq!(
22591 f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"IGNORE"]),
22592 "+OK\r\n"
22593 );
22594 assert_eq!(
22595 f.run(&[b"FT.CONFIG", b"GET", b"ON_OOM"]),
22596 "*1\r\n*2\r\n+ON_OOM\r\n$6\r\nignore\r\n"
22597 );
22598 assert_eq!(
22599 f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"nope"]),
22600 "-SEARCH_VALUE_BAD Invalid ON_OOM value\r\n"
22601 );
22602 let bad = "-SEARCH_VALUE_BAD Invalid default scorer value\r\n";
22603 for given in [b"bm25std".as_slice(), b"Bm25", b"TFIDF.docnorm", b""] {
22604 assert_eq!(
22605 f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", given]),
22606 bad,
22607 "{given:?}"
22608 );
22609 }
22610 assert_eq!(
22611 f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", b"TFIDF.DOCNORM"]),
22612 "+OK\r\n"
22613 );
22614 }
22615
22616 #[test]
22619 fn a_yes_or_no_setting_takes_those_two_words_only() {
22620 let mut f = Fixture::new();
22621 assert_eq!(
22622 f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", b"TRUE"]),
22623 "+OK\r\n"
22624 );
22625 assert_eq!(
22626 f.run(&[b"FT.CONFIG", b"GET", b"_NUMERIC_COMPRESS"]),
22627 "*1\r\n*2\r\n+_NUMERIC_COMPRESS\r\n$4\r\ntrue\r\n"
22628 );
22629 for given in [b"yes".as_slice(), b"no", b"1", b"0", b"enabled", b""] {
22630 assert_eq!(
22631 f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", given]),
22632 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
22633 "{given:?}"
22634 );
22635 }
22636 }
22637
22638 #[test]
22641 fn two_names_for_one_setting_move_together() {
22642 let mut f = Fixture::new();
22643 f.run(&[b"FT.CONFIG", b"SET", b"MAXEXPANSIONS", b"300"]);
22644 assert_eq!(
22645 f.run(&[b"FT.CONFIG", b"GET", b"MAXPREFIXEXPANSIONS"]),
22646 "*1\r\n*2\r\n+MAXPREFIXEXPANSIONS\r\n$3\r\n300\r\n"
22647 );
22648 f.run(&[b"FT.CONFIG", b"SET", b"MAXPREFIXEXPANSIONS", b"200"]);
22649 assert_eq!(
22650 f.run(&[b"FT.CONFIG", b"GET", b"MAXEXPANSIONS"]),
22651 "*1\r\n*2\r\n+MAXEXPANSIONS\r\n$3\r\n200\r\n"
22652 );
22653 let long = b"_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
22654 let short = b"FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
22655 f.run(&[b"FT.CONFIG", b"SET", long, b"false"]);
22656 assert_eq!(
22657 f.run(&[b"FT.CONFIG", b"GET", short]),
22658 "*1\r\n*2\r\n+FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$5\r\nfalse\r\n"
22659 );
22660 assert_eq!(f.run(&[b"FT.CONFIG", b"SET", short]), "+OK\r\n");
22661 assert_eq!(
22662 f.run(&[b"FT.CONFIG", b"GET", long]),
22663 "*1\r\n*2\r\n+_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$4\r\ntrue\r\n"
22664 );
22665 }
22666
22667 #[test]
22669 fn a_password_reads_back_as_stars_whatever_was_written() {
22670 let mut f = Fixture::new();
22671 assert_eq!(
22672 f.run(&[b"FT.CONFIG", b"SET", b"OSS_GLOBAL_PASSWORD", b"hunter2"]),
22673 "+OK\r\n"
22674 );
22675 assert_eq!(
22676 f.run(&[b"FT.CONFIG", b"GET", b"OSS_GLOBAL_PASSWORD"]),
22677 "*1\r\n*2\r\n+OSS_GLOBAL_PASSWORD\r\n$17\r\nPassword: *******\r\n"
22678 );
22679 }
22680
22681 #[test]
22684 fn a_flush_leaves_the_settings_alone() {
22685 for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
22686 let mut f = Fixture::new();
22687 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"777"]);
22688 f.run(&[flush]);
22689 assert_eq!(
22690 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
22691 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n777\r\n",
22692 "{flush:?}"
22693 );
22694 }
22695 }
22696
22697 #[test]
22702 fn a_synonym_dump_folds_the_terms_and_keeps_the_ids_as_given() {
22703 let mut f = Fixture::new();
22704 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
22705 assert_eq!(
22706 f.run(&[b"FT.SYNUPDATE", b"e", b"G1", b"BOY", b"kid"]),
22707 "+OK\r\n"
22708 );
22709 assert_eq!(f.run(&[b"FT.SYNUPDATE", b"e", b"g2", b"boy"]), "+OK\r\n");
22710 assert_eq!(
22711 f.run(&[b"FT.SYNDUMP", b"e"]),
22712 "*4\r\n$3\r\nboy\r\n*2\r\n$2\r\nG1\r\n$2\r\ng2\r\n\
22713 $3\r\nkid\r\n*1\r\n$2\r\nG1\r\n"
22714 );
22715 }
22716
22717 #[test]
22721 fn a_word_in_a_group_reads_as_a_union_with_the_group_term() {
22722 let mut f = Fixture::new();
22723 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
22724 f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"jogging"]);
22725 assert_eq!(
22726 f.run(&[b"FT.EXPLAIN", b"e", b"jogging"]),
22727 "$69\r\nUNION {\n jogging\n ~gr(expanded)\n +jog(expanded)\n jog(expanded)\n}\n\r\n"
22728 );
22729 }
22730
22731 #[test]
22740 fn a_group_matches_the_word_it_holds_and_not_a_stem_of_it() {
22741 let mut f = Fixture::new();
22742 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
22743 f.run(&[b"HSET", b"d1", b"t", b"boy"]);
22744 f.run(&[b"HSET", b"d2", b"t", b"runs"]);
22745 f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"boy", b"child", b"running"]);
22746 assert_eq!(
22747 f.run(&[b"FT.SEARCH", b"e", b"child", b"NOCONTENT"]),
22748 "*2\r\n:1\r\n$2\r\nd1\r\n"
22749 );
22750 assert_eq!(
22751 f.run(&[b"FT.SEARCH", b"e", b"running", b"NOCONTENT"]),
22752 "*3\r\n:2\r\n$2\r\nd1\r\n$2\r\nd2\r\n"
22753 );
22754 }
22755
22756 #[test]
22759 fn a_synonym_command_on_a_name_that_is_not_there_fails() {
22760 let mut f = Fixture::new();
22761 let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
22762 assert_eq!(f.run(&[b"FT.SYNDUMP", b"nope"]), missing);
22763 assert_eq!(f.run(&[b"FT.SYNUPDATE", b"nope", b"g", b"a"]), missing);
22764 }
22765
22766 #[test]
22770 fn params_counts_the_words_before_it_pairs_them_up() {
22771 let mut f = Fixture::new();
22772 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
22773 let none = "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: \
22774 Expected an argument, but none provided\r\n";
22775 let odd = "-SEARCH_ADD_ARGS Parameters must be specified in PARAM VALUE pairs\r\n";
22776 assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1"]), none);
22777 assert_eq!(
22778 f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"3", b"a", b"b"]),
22779 none
22780 );
22781 assert_eq!(
22782 f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1", b"a"]),
22783 odd
22784 );
22785 assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"0"]), odd);
22786 assert_eq!(
22787 f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"-1"]),
22788 "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: Value is outside acceptable bounds\r\n"
22789 );
22790 }
22791
22792 fn vectored(f: &mut Fixture) {
22802 f.run(&[
22803 b"FT.CREATE",
22804 b"h",
22805 b"SCHEMA",
22806 b"t",
22807 b"TEXT",
22808 b"v",
22809 b"VECTOR",
22810 b"FLAT",
22811 b"6",
22812 b"TYPE",
22813 b"FLOAT32",
22814 b"DIM",
22815 b"2",
22816 b"DISTANCE_METRIC",
22817 b"L2",
22818 ]);
22819 let at: [&[u8]; 5] = [
22820 b"\x00\x00\x80\x40\x00\x00\x00\x00",
22821 b"\x00\x00\x40\x40\x00\x00\x00\x00",
22822 b"\x00\x00\x00\x40\x00\x00\x00\x00",
22823 b"\x00\x00\x80\x3f\x00\x00\x00\x00",
22824 b"\x00\x00\x00\x00\x00\x00\x00\x00",
22825 ];
22826 for (n, point) in at.iter().enumerate() {
22827 let key = format!("d{}", n + 1);
22828 let word: &[u8] = match n {
22829 0 | 1 | 3 => b"alpha",
22830 _ => b"beta",
22831 };
22832 f.run(&[b"HSET", key.as_bytes(), b"t", word, b"v", point]);
22833 }
22834 }
22835
22836 const ORIGIN: &[u8] = b"\x00\x00\x00\x00\x00\x00\x00\x00";
22838
22839 #[test]
22843 fn a_knn_picks_the_nearest_and_answers_them_in_document_order() {
22844 let mut f = Fixture::new();
22845 vectored(&mut f);
22846 assert_eq!(
22847 f.run(&[
22848 b"FT.SEARCH",
22849 b"h",
22850 b"*=>[KNN 5 @v $vec]",
22851 b"PARAMS",
22852 b"2",
22853 b"vec",
22854 ORIGIN,
22855 b"DIALECT",
22856 b"2",
22857 b"NOCONTENT",
22858 ]),
22859 "*6\r\n:5\r\n$2\r\nd1\r\n$2\r\nd2\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
22860 );
22861 assert_eq!(
22862 f.run(&[
22863 b"FT.SEARCH",
22864 b"h",
22865 b"*=>[KNN 3 @v $vec]",
22866 b"PARAMS",
22867 b"2",
22868 b"vec",
22869 ORIGIN,
22870 b"DIALECT",
22871 b"2",
22872 b"NOCONTENT",
22873 ]),
22874 "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
22875 );
22876 }
22877
22878 #[test]
22881 fn a_range_takes_what_is_inside_it_and_the_distance_is_squared() {
22882 let mut f = Fixture::new();
22883 vectored(&mut f);
22884 for (radius, want) in [
22885 ("0", "*2\r\n:1\r\n$2\r\nd5\r\n"),
22886 ("2", "*3\r\n:2\r\n$2\r\nd4\r\n$2\r\nd5\r\n"),
22887 (
22888 "9",
22889 "*5\r\n:4\r\n$2\r\nd2\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n",
22890 ),
22891 ] {
22892 let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
22893 assert_eq!(
22894 f.run(&[
22895 b"FT.SEARCH",
22896 b"h",
22897 query.as_bytes(),
22898 b"PARAMS",
22899 b"2",
22900 b"vec",
22901 ORIGIN,
22902 b"DIALECT",
22903 b"2",
22904 b"NOCONTENT",
22905 ]),
22906 want,
22907 "radius {radius}"
22908 );
22909 }
22910 }
22911
22912 #[test]
22916 fn a_knn_measures_what_the_query_in_front_of_it_matched() {
22917 let mut f = Fixture::new();
22918 vectored(&mut f);
22919 assert_eq!(
22920 f.run(&[
22921 b"FT.SEARCH",
22922 b"h",
22923 b"alpha=>[KNN 2 @v $vec]",
22924 b"PARAMS",
22925 b"2",
22926 b"vec",
22927 ORIGIN,
22928 b"DIALECT",
22929 b"2",
22930 b"NOCONTENT",
22931 ]),
22932 "*3\r\n:2\r\n$2\r\nd2\r\n$2\r\nd4\r\n"
22933 );
22934 }
22935
22936 #[test]
22945 fn a_count_and_a_radius_are_refused_in_their_own_words() {
22946 let mut f = Fixture::new();
22947 vectored(&mut f);
22948 let ask = |f: &mut Fixture, query: &str| {
22949 f.run(&[
22950 b"FT.SEARCH",
22951 b"h",
22952 query.as_bytes(),
22953 b"PARAMS",
22954 b"2",
22955 b"vec",
22956 ORIGIN,
22957 b"DIALECT",
22958 b"2",
22959 b"NOCONTENT",
22960 ])
22961 };
22962 for (query, at, near) in [
22963 ("*=>[KNN -1 @v $vec]", 8, "-1"),
22964 ("*=>[KNN 1.5 @v $vec]", 8, "1.5"),
22965 ("*=>[KNN +3 @v $vec]", 8, "+3"),
22966 ("*=>[KNN 0x10 @v $vec]", 8, "0x10"),
22967 ("*=>[KNN abc @v $vec]", 8, "abc"),
22968 ("*=>[KNN 3 $vec]", 10, "vec"),
22969 ("*=>[KNN 3 @v vec]", 13, "vec"),
22970 ("@v:[VECTOR_RANGE 2 -1]", 19, "-1"),
22971 ] {
22972 assert_eq!(
22973 ask(&mut f, query),
22974 format!("-SEARCH_SYNTAX Syntax error at offset {at} near {near}\r\n"),
22975 "{query}"
22976 );
22977 }
22978
22979 let large = "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
22982 query KNN K parameter is too large, must not exceed 288230376151711744\r\n";
22983 assert_eq!(
22984 ask(&mut f, "*=>[KNN 288230376151711776 @v $vec]"),
22985 "*6\r\n:5\r\n$2\r\nd1\r\n$2\r\nd2\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
22986 );
22987 assert_eq!(ask(&mut f, "*=>[KNN 288230376151711777 @v $vec]"), large);
22988 assert_eq!(ask(&mut f, "*=>[KNN 99999999999999999999 @v $vec]"), large);
22989
22990 for (radius, printed) in [("-1", "-1"), ("-0.5", "-0.5"), ("-1e2", "-100")] {
22991 let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
22992 assert_eq!(
22993 ask(&mut f, &query),
22994 format!(
22995 "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
22996 negative radius ({printed}) given in a range query\r\n"
22997 ),
22998 "{query}"
22999 );
23000 }
23001 assert_eq!(
23003 ask(&mut f, "@v:[VECTOR_RANGE -0 $vec]"),
23004 "*2\r\n:1\r\n$2\r\nd5\r\n"
23005 );
23006 }
23007
23008 #[test]
23011 fn a_count_that_came_from_params_is_read_by_its_own_rules() {
23012 let mut f = Fixture::new();
23013 vectored(&mut f);
23014 let ask = |f: &mut Fixture, count: &[u8]| {
23015 f.run(&[
23016 b"FT.SEARCH",
23017 b"h",
23018 b"*=>[KNN $k @v $vec]",
23019 b"PARAMS",
23020 b"4",
23021 b"vec",
23022 ORIGIN,
23023 b"k",
23024 count,
23025 b"DIALECT",
23026 b"2",
23027 b"NOCONTENT",
23028 ])
23029 };
23030 let three = "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n";
23031 assert_eq!(ask(&mut f, b"3"), three);
23032 assert_eq!(ask(&mut f, b" 3"), three);
23033 assert_eq!(ask(&mut f, b"+3"), three);
23034 for bad in [
23035 &b"3.0"[..],
23036 b"0x3",
23037 b"-1",
23038 b"abc",
23039 b"",
23040 b"99999999999999999999",
23041 ] {
23042 let value = String::from_utf8_lossy(bad).into_owned();
23043 assert_eq!(
23044 ask(&mut f, bad),
23045 format!(
23046 "-SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value ({value}) \
23047 for parameter `k`\r\n"
23048 ),
23049 "{value}"
23050 );
23051 }
23052 assert_eq!(
23053 ask(&mut f, b"288230376151711777"),
23054 "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
23055 query KNN K parameter is too large, must not exceed 288230376151711744\r\n"
23056 );
23057 }
23058
23059 #[test]
23062 fn a_vector_the_wrong_size_is_refused_by_the_field_it_reached() {
23063 let mut f = Fixture::new();
23064 vectored(&mut f);
23065 assert_eq!(
23066 f.run(&[
23067 b"FT.SEARCH",
23068 b"h",
23069 b"*=>[KNN 5 @v $vec]",
23070 b"PARAMS",
23071 b"2",
23072 b"vec",
23073 b"abc",
23074 b"DIALECT",
23075 b"2",
23076 b"NOCONTENT",
23077 ]),
23078 "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
23079 query vector blob size (3) does not match index's expected size (8).\r\n"
23080 );
23081 }
23082
23083 #[test]
23089 fn a_spellcheck_scores_a_suggestion_by_how_common_it_is() {
23090 let mut f = Fixture::new();
23091 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
23092 f.run(&[b"HSET", b"d1", b"t", b"hello"]);
23093 f.run(&[b"HSET", b"d2", b"t", b"hallo hello"]);
23094 assert_eq!(
23095 f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"2"]),
23096 "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
23097 *2\r\n*2\r\n$1\r\n1\r\n$5\r\nhello\r\n*2\r\n$3\r\n0.5\r\n$5\r\nhallo\r\n"
23098 );
23099 }
23100
23101 #[test]
23105 fn a_spellcheck_answers_a_map_of_maps_on_resp3() {
23106 let mut f = Fixture::new();
23107 f.run(&[b"HELLO", b"3"]);
23108 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
23109 f.run(&[b"HSET", b"d1", b"t", b"hello"]);
23110 assert_eq!(
23111 f.run(&[b"FT.SPELLCHECK", b"e", b"hellp"]),
23112 "%1\r\n$7\r\nresults\r\n%1\r\n$5\r\nhellp\r\n\
23113 *1\r\n%1\r\n$5\r\nhello\r\n,1\r\n"
23114 );
23115 }
23116
23117 #[test]
23121 fn a_word_the_index_holds_is_never_asked_about_whatever_field_it_names() {
23122 let mut f = Fixture::new();
23123 f.run(&[
23124 b"FT.CREATE",
23125 b"e",
23126 b"SCHEMA",
23127 b"a",
23128 b"TEXT",
23129 b"NOSTEM",
23130 b"b",
23131 b"TEXT",
23132 b"NOSTEM",
23133 ]);
23134 f.run(&[b"HSET", b"d1", b"b", b"world"]);
23135 assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"@a:world"]), "*0\r\n");
23136 assert_eq!(
23137 f.run(&[b"FT.SPELLCHECK", b"e", b"@a:worlt"]),
23138 "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nworlt\r\n*0\r\n"
23139 );
23140 }
23141
23142 #[test]
23146 fn a_spellcheck_reads_the_dictionaries_it_is_pointed_at() {
23147 let mut f = Fixture::new();
23148 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
23149 f.run(&[b"FT.DICTADD", b"d", b"Hellp", b"hellq"]);
23150 assert_eq!(
23151 f.run(&[b"FT.SPELLCHECK", b"e", b"hellz", b"TERMS", b"INCLUDE", b"d"]),
23152 "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellz\r\n\
23153 *2\r\n*2\r\n$1\r\n0\r\n$5\r\nHellp\r\n*2\r\n$1\r\n0\r\n$5\r\nhellq\r\n"
23154 );
23155 assert_eq!(
23156 f.run(&[b"FT.SPELLCHECK", b"e", b"hellq", b"TERMS", b"EXCLUDE", b"d"]),
23157 "*0\r\n"
23158 );
23159 assert_eq!(
23160 f.run(&[b"FT.SPELLCHECK", b"e", b"x", b"TERMS", b"INCLUDE", b"nope"]),
23161 "-Dict does not exist: nope\r\n"
23162 );
23163 }
23164
23165 #[test]
23169 fn a_spellcheck_reads_its_arguments_leniently() {
23170 let mut f = Fixture::new();
23171 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
23172 f.run(&[b"HSET", b"d1", b"t", b"hello"]);
23173 let one = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
23174 *1\r\n*2\r\n$1\r\n1\r\n$5\r\nhello\r\n";
23175 assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"BOGUS"]), one);
23176 let none = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhelqp\r\n*0\r\n";
23177 let args: &[&[u8]] = &[
23178 b"FT.SPELLCHECK",
23179 b"e",
23180 b"helqp",
23181 b"DISTANCE",
23182 b"1",
23183 b"DISTANCE",
23184 b"4",
23185 ];
23186 assert_eq!(f.run(args), none);
23187 assert_eq!(
23188 f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"5"]),
23189 "-bad distance given, distance must be a natural number between 1 to 4\r\n"
23190 );
23191 assert_eq!(
23192 f.run(&[b"FT.SPELLCHECK", b"nope", b"hellp"]),
23193 "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
23194 );
23195 }
23196
23197 #[test]
23202 fn an_add_answers_how_many_suggestions_are_in_there_now() {
23203 let mut f = Fixture::new();
23204 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]), ":1\r\n");
23205 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"9"]), ":1\r\n");
23206 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]), ":2\r\n");
23207 assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":2\r\n");
23208 assert_eq!(f.run(&[b"FT.SUGLEN", b"nokey"]), ":0\r\n");
23209 }
23210
23211 #[test]
23214 fn a_suggestion_dictionary_is_a_key_with_a_type_of_its_own() {
23215 let mut f = Fixture::new();
23216 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
23217 assert_eq!(f.run(&[b"TYPE", b"s"]), "+trietype0\r\n");
23218 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$3\r\nraw\r\n");
23219 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
23220 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\ns\r\n");
23221 assert_eq!(f.run(&[b"EXPIRE", b"s", b"100"]), ":1\r\n");
23222 assert_eq!(f.run(&[b"TTL", b"s"]), ":100\r\n");
23223 assert_eq!(f.run(&[b"DEL", b"s"]), ":1\r\n");
23224 assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":0\r\n");
23225 }
23226
23227 #[test]
23230 fn deleting_the_last_suggestion_deletes_the_key() {
23231 let mut f = Fixture::new();
23232 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
23233 assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"nope"]), ":0\r\n");
23234 assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"one"]), ":1\r\n");
23235 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
23236 assert_eq!(f.run(&[b"FT.SUGDEL", b"nokey", b"a"]), ":0\r\n");
23237 }
23238
23239 #[test]
23242 fn a_suggestion_command_on_another_kind_of_key_is_wrongtype() {
23243 let mut f = Fixture::new();
23244 f.run(&[b"SET", b"s", b"x"]);
23245 for cmd in [
23246 vec![&b"FT.SUGADD"[..], b"s", b"t", b"1"],
23247 vec![&b"FT.SUGGET"[..], b"s", b"t"],
23248 vec![&b"FT.SUGDEL"[..], b"s", b"t"],
23249 vec![&b"FT.SUGLEN"[..], b"s"],
23250 ] {
23251 assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{cmd:?}");
23252 }
23253 assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nx\r\n");
23254 }
23255
23256 #[test]
23259 fn a_lookup_answers_a_score_it_works_out_rather_than_the_one_stored() {
23260 let mut f = Fixture::new();
23261 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
23262 f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
23263 f.run(&[b"FT.SUGADD", b"s", b"ontario", b"3"]);
23264 assert_eq!(
23265 f.run(&[b"FT.SUGGET", b"s", b"on", b"WITHSCORES"]),
23266 "*6\r\n$7\r\nontario\r\n$18\r\n1.2247449159622192\r\n\
23267 $4\r\nonly\r\n$17\r\n1.154700517654419\r\n\
23268 $3\r\none\r\n$18\r\n0.7071067690849304\r\n"
23269 );
23270 assert_eq!(
23271 f.run(&[b"FT.SUGGET", b"s", b"one", b"WITHSCORES"]),
23272 "*2\r\n$3\r\none\r\n$10\r\n2147483648\r\n"
23273 );
23274 assert_eq!(f.run(&[b"FT.SUGGET", b"nokey", b"a"]), "*0\r\n");
23275 }
23276
23277 #[test]
23279 fn fuzzy_allows_one_edit_and_nothing_allows_two() {
23280 let mut f = Fixture::new();
23281 f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
23282 assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"one"]), "*0\r\n");
23283 assert_eq!(
23284 f.run(&[b"FT.SUGGET", b"s", b"one", b"FUZZY", b"WITHSCORES"]),
23285 "*2\r\n$4\r\nonly\r\n$19\r\n0.19139298796653748\r\n"
23286 );
23287 assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"xyz", b"FUZZY"]), "*0\r\n");
23288 }
23289
23290 #[test]
23292 fn a_lookup_answers_five_unless_it_is_told_otherwise() {
23293 let mut f = Fixture::new();
23294 for (term, score) in [
23295 (&b"a1"[..], &b"1"[..]),
23296 (b"a2", b"2"),
23297 (b"a3", b"3"),
23298 (b"a4", b"4"),
23299 (b"a5", b"5"),
23300 (b"a6", b"6"),
23301 ] {
23302 f.run(&[b"FT.SUGADD", b"s", term, score]);
23303 }
23304 assert_eq!(
23305 f.run(&[b"FT.SUGGET", b"s", b"a"]),
23306 "*5\r\n$2\r\na6\r\n$2\r\na5\r\n$2\r\na4\r\n$2\r\na3\r\n$2\r\na2\r\n"
23307 );
23308 assert_eq!(
23309 f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"2"]),
23310 "*2\r\n$2\r\na6\r\n$2\r\na5\r\n"
23311 );
23312 assert!(
23314 f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"100"])
23315 .starts_with("*6\r\n")
23316 );
23317 }
23318
23319 #[test]
23322 fn a_payload_comes_back_beside_the_term_or_a_null_does() {
23323 let mut f = Fixture::new();
23324 f.run(&[b"FT.SUGADD", b"s", b"one", b"1", b"PAYLOAD", b"p"]);
23325 assert_eq!(
23326 f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
23327 "*2\r\n$3\r\none\r\n$1\r\np\r\n"
23328 );
23329 f.run(&[b"FT.SUGADD", b"s", b"one", b"2"]);
23330 assert_eq!(
23331 f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
23332 "*2\r\n$3\r\none\r\n$1\r\np\r\n"
23333 );
23334 f.run(&[b"FT.SUGADD", b"s", b"one", b"2", b"PAYLOAD", b""]);
23337 assert_eq!(
23338 f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
23339 "*2\r\n$3\r\none\r\n$1\r\np\r\n"
23340 );
23341 f.run(&[b"FT.SUGADD", b"s", b"other", b"1", b"PAYLOAD", b""]);
23343 assert_eq!(
23344 f.run(&[b"FT.SUGGET", b"s", b"ot", b"WITHPAYLOADS"]),
23345 "*2\r\n$5\r\nother\r\n$-1\r\n"
23346 );
23347 }
23348
23349 #[test]
23353 fn incr_adds_to_the_score_that_is_already_there() {
23354 let mut f = Fixture::new();
23355 for _ in 0..3 {
23356 f.run(&[b"FT.SUGADD", b"s", b"xxx", b"0.1", b"INCR"]);
23357 }
23358 assert_eq!(
23359 f.run(&[b"FT.SUGGET", b"s", b"xx", b"WITHSCORES"]),
23360 "*2\r\n$3\r\nxxx\r\n$18\r\n0.2121320366859436\r\n"
23361 );
23362 }
23363
23364 #[test]
23366 fn the_suggestion_errors_are_the_lines_the_module_sends() {
23367 let mut f = Fixture::new();
23368 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
23369 assert_eq!(
23370 f.run(&[b"FT.SUGADD", b"s", b"t", b"abc"]),
23371 "-ERR invalid score\r\n"
23372 );
23373 assert_eq!(
23375 f.run(&[b"FT.SUGADD", b"s", b"t", b"abc", b"NOPE"]),
23376 "-Unknown argument `NOPE`\r\n"
23377 );
23378 assert_eq!(
23379 f.run(&[b"FT.SUGADD", b"s", b"t", b"1", b"PAYLOAD"]),
23380 "-Invalid payload: Expected an argument, but none provided\r\n"
23381 );
23382 assert!(
23384 f.run(&[
23385 b"FT.SUGADD",
23386 b"s",
23387 b"t",
23388 b"1",
23389 b"PAYLOAD",
23390 b"a",
23391 b"PAYLOAD",
23392 b"b"
23393 ])
23394 .contains("wrong number of arguments")
23395 );
23396 assert_eq!(
23397 f.run(&[b"FT.SUGGET", b"s", b"o", b"NOPE"]),
23398 "-SEARCH_PARSE_ARGS Unrecognized argument: NOPE\r\n"
23399 );
23400 for max in [&b"0"[..], b"-1", b"4294967296", b"1e10", b"inf"] {
23404 assert_eq!(
23405 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
23406 "-SEARCH_PARSE_ARGS MAX: Value is outside acceptable bounds\r\n",
23407 "{}",
23408 String::from_utf8_lossy(max)
23409 );
23410 }
23411 for max in [
23412 &b"abc"[..],
23413 b"0.0",
23414 b"00",
23415 b"-0",
23416 b"+0",
23417 b"0.5",
23418 b"-1.5",
23419 b"1e400",
23420 ] {
23421 assert_eq!(
23422 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
23423 "-SEARCH_PARSE_ARGS MAX: Could not convert argument to expected type\r\n",
23424 "{}",
23425 String::from_utf8_lossy(max)
23426 );
23427 }
23428 for max in [&b"01"[..], b"+1", b"1.5", b"0x10", b"1e2"] {
23429 assert_eq!(
23430 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
23431 "*1\r\n$3\r\none\r\n",
23432 "{}",
23433 String::from_utf8_lossy(max)
23434 );
23435 }
23436 assert_eq!(
23437 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX"]),
23438 "-SEARCH_PARSE_ARGS MAX: Expected an argument, but none provided\r\n"
23439 );
23440 assert_eq!(
23443 f.run(&[b"FT.SUGADD", b"s", b"t", b"1e400"]),
23444 "-ERR invalid score\r\n"
23445 );
23446 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"t", b"inf"]), ":2\r\n");
23447 }
23448
23449 #[test]
23453 fn an_empty_suggestion_is_taken_and_dropped_but_still_makes_the_key() {
23454 let mut f = Fixture::new();
23455 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
23456 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"", b"1"]), ":1\r\n");
23457 assert_eq!(f.run(&[b"FT.SUGGET", b"s", b""]), "*1\r\n$3\r\none\r\n");
23458 assert_eq!(f.run(&[b"FT.SUGADD", b"e", b"", b"1"]), ":0\r\n");
23459 assert_eq!(f.run(&[b"EXISTS", b"e"]), ":1\r\n");
23460 assert_eq!(f.run(&[b"TYPE", b"e"]), "+trietype0\r\n");
23461 assert_eq!(f.run(&[b"FT.SUGDEL", b"e", b"nothing"]), ":0\r\n");
23462 assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
23463 }
23464
23465 #[test]
23468 fn a_hash_that_will_not_read_is_counted_where_ft_info_reports_it() {
23469 let mut f = Fixture::new();
23470 f.run(&[
23471 b"FT.CREATE",
23472 b"ix",
23473 b"PREFIX",
23474 b"1",
23475 b"p:",
23476 b"SCHEMA",
23477 b"n",
23478 b"NUMERIC",
23479 ]);
23480 f.run(&[b"HSET", b"p:1", b"n", b"notanumber"]);
23481 assert_eq!(held(&f, b"ix"), (0, 0));
23482
23483 let reply = f.run(&[b"FT.INFO", b"ix"]);
23484 assert!(
23485 reply.contains("SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value: 'notanumber'"),
23486 "{reply}"
23487 );
23488 assert!(reply.contains("hash_indexing_failures"), "{reply}");
23489 }
23490
23491 #[test]
23494 fn an_index_can_only_be_made_on_database_zero() {
23495 let mut f = Fixture::new();
23496 f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]);
23497 f.run(&[b"SELECT", b"1"]);
23498 let refused = "-Cannot create index on db != 0\r\n";
23499 assert_eq!(
23500 f.run(&[b"FT.CREATE", b"jx", b"SCHEMA", b"t", b"TEXT"]),
23501 refused
23502 );
23503 assert_eq!(
23505 f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]),
23506 refused
23507 );
23508 assert_eq!(
23510 f.run(&[b"FT.CREATE", b"zz", b"BOGUS", b"SCHEMA", b"t", b"TEXT"]),
23511 refused
23512 );
23513 assert_eq!(
23515 f.run(&[b"FT._CREATEIFNX", b"ix", b"SCHEMA", b"t", b"TEXT"]),
23516 "+OK\r\n"
23517 );
23518 assert_eq!(f.server.search.lock().len(), 1);
23519 }
23520
23521 #[test]
23527 fn the_scan_is_one_database_and_the_following_is_all_of_them() {
23528 let mut f = Fixture::new();
23529 f.run(&[b"SELECT", b"1"]);
23530 f.run(&[b"HSET", b"p:9", b"t", b"on one"]);
23531 f.run(&[b"SELECT", b"0"]);
23532 f.run(&[b"HSET", b"p:0", b"t", b"on zero"]);
23533 f.run(&[
23534 b"FT.CREATE",
23535 b"ix",
23536 b"PREFIX",
23537 b"1",
23538 b"p:",
23539 b"SCHEMA",
23540 b"t",
23541 b"TEXT",
23542 ]);
23543 assert_eq!(held(&f, b"ix"), (1, 1), "the scan read database zero only");
23544
23545 f.run(&[b"SELECT", b"1"]);
23546 f.run(&[b"HSET", b"p:8", b"t", b"later"]);
23547 assert_eq!(
23548 held(&f, b"ix"),
23549 (2, 2),
23550 "and then it follows every database"
23551 );
23552 }
23553
23554 fn corpus(f: &mut Fixture) {
23557 f.run(&[
23558 b"FT.CREATE",
23559 b"sx",
23560 b"PREFIX",
23561 b"1",
23562 b"d:",
23563 b"SCHEMA",
23564 b"t",
23565 b"TEXT",
23566 b"g",
23567 b"TAG",
23568 b"n",
23569 b"NUMERIC",
23570 ]);
23571 for (key, text, tag, number) in [
23572 (b"d:1".as_slice(), "alpha beta", "aa,bb", "1"),
23573 (b"d:2", "alpha gamma", "bb", "2"),
23574 (b"d:3", "delta", "cc", "3"),
23575 (b"d:4", "alpha beta gamma", "aa,cc", "4"),
23576 ] {
23577 f.run(&[
23578 b"HSET",
23579 key,
23580 b"t",
23581 text.as_bytes(),
23582 b"g",
23583 tag.as_bytes(),
23584 b"n",
23585 number.as_bytes(),
23586 ]);
23587 }
23588 }
23589
23590 fn sortable(f: &mut Fixture) {
23594 f.run(&[
23595 b"FT.CREATE",
23596 b"sy",
23597 b"PREFIX",
23598 b"1",
23599 b"s:",
23600 b"SCHEMA",
23601 b"t",
23602 b"TEXT",
23603 b"SORTABLE",
23604 b"n",
23605 b"NUMERIC",
23606 b"SORTABLE",
23607 b"body",
23608 b"AS",
23609 b"b",
23610 b"TEXT",
23611 b"SORTABLE",
23612 b"p",
23613 b"TEXT",
23614 ]);
23615 for (key, text, number) in [
23616 (b"s:1".as_slice(), "Banana Split", "2"),
23617 (b"s:2", "apple", "10"),
23618 ] {
23619 f.run(&[
23620 b"HSET",
23621 key,
23622 b"t",
23623 text.as_bytes(),
23624 b"n",
23625 number.as_bytes(),
23626 b"body",
23627 text.as_bytes(),
23628 b"p",
23629 b"alpha",
23630 ]);
23631 }
23632 f.run(&[b"HSET", b"s:3", b"p", b"alpha"]);
23635 }
23636
23637 #[test]
23640 fn a_search_sorts_by_a_field_the_index_keeps_a_copy_of() {
23641 let mut f = Fixture::new();
23642 sortable(&mut f);
23643 assert_eq!(
23644 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"NOCONTENT"]),
23645 "*4\r\n:3\r\n$3\r\ns:1\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
23646 );
23647 assert_eq!(
23648 f.run(&[
23649 b"FT.SEARCH",
23650 b"sy",
23651 b"alpha",
23652 b"SORTBY",
23653 b"n",
23654 b"DESC",
23655 b"NOCONTENT"
23656 ]),
23657 "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
23658 );
23659 assert_eq!(
23662 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"t", b"NOCONTENT"]),
23663 "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
23664 );
23665 }
23666
23667 #[test]
23670 fn a_search_sorts_by_a_field_it_has_to_read_the_key_for() {
23671 let mut f = Fixture::new();
23672 sortable(&mut f);
23673 f.run(&[b"HSET", b"s:1", b"p", b"alpha zulu"]);
23674 assert_eq!(
23675 f.run(&[
23676 b"FT.SEARCH",
23677 b"sy",
23678 b"alpha",
23679 b"SORTBY",
23680 b"p",
23681 b"NOCONTENT",
23682 b"LIMIT",
23683 b"0",
23684 b"2"
23685 ]),
23686 "*3\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
23687 );
23688 assert_eq!(
23691 f.run(&[
23692 b"FT.SEARCH",
23693 b"sy",
23694 b"alpha",
23695 b"SORTBY",
23696 b"p",
23697 b"WITHSORTKEYS",
23698 b"NOCONTENT",
23699 b"LIMIT",
23700 b"2",
23701 b"1"
23702 ]),
23703 "*3\r\n:3\r\n$3\r\ns:1\r\n$11\r\n$alpha zulu\r\n"
23704 );
23705 }
23706
23707 #[test]
23710 fn a_search_can_send_the_value_it_sorted_by_back() {
23711 let mut f = Fixture::new();
23712 sortable(&mut f);
23713 assert_eq!(
23714 f.run(&[
23715 b"FT.SEARCH",
23716 b"sy",
23717 b"alpha",
23718 b"SORTBY",
23719 b"n",
23720 b"WITHSORTKEYS",
23721 b"NOCONTENT"
23722 ]),
23723 concat!(
23724 "*7\r\n:3\r\n",
23725 "$3\r\ns:1\r\n$2\r\n#2\r\n",
23726 "$3\r\ns:2\r\n$3\r\n#10\r\n",
23727 "$3\r\ns:3\r\n$-1\r\n"
23728 )
23729 );
23730 assert_eq!(
23731 f.run(&[
23732 b"FT.SEARCH",
23733 b"sy",
23734 b"alpha",
23735 b"SORTBY",
23736 b"t",
23737 b"WITHSORTKEYS",
23738 b"NOCONTENT"
23739 ]),
23740 concat!(
23741 "*7\r\n:3\r\n",
23742 "$3\r\ns:2\r\n$6\r\n$apple\r\n",
23743 "$3\r\ns:1\r\n$13\r\n$banana split\r\n",
23744 "$3\r\ns:3\r\n$-1\r\n"
23745 )
23746 );
23747 assert_eq!(
23750 f.run(&[
23751 b"FT.SEARCH",
23752 b"sy",
23753 b"banana",
23754 b"WITHSORTKEYS",
23755 b"NOCONTENT"
23756 ]),
23757 "*3\r\n:1\r\n$3\r\ns:1\r\n$-1\r\n"
23758 );
23759 }
23760
23761 #[test]
23764 fn a_sort_puts_the_field_it_sorted_by_in_front_of_the_row() {
23765 let mut f = Fixture::new();
23766 sortable(&mut f);
23767 assert_eq!(
23771 f.run(&[
23772 b"FT.SEARCH",
23773 b"sy",
23774 b"alpha",
23775 b"SORTBY",
23776 b"b",
23777 b"LIMIT",
23778 b"0",
23779 b"1"
23780 ]),
23781 concat!(
23782 "*3\r\n:3\r\n$3\r\ns:2\r\n*10\r\n",
23783 "$1\r\nb\r\n$5\r\napple\r\n",
23784 "$1\r\nt\r\n$5\r\napple\r\n",
23785 "$1\r\nn\r\n$2\r\n10\r\n",
23786 "$4\r\nbody\r\n$5\r\napple\r\n",
23787 "$1\r\np\r\n$5\r\nalpha\r\n"
23788 )
23789 );
23790 assert_eq!(
23793 f.run(&[
23794 b"FT.SEARCH",
23795 b"sy",
23796 b"alpha",
23797 b"SORTBY",
23798 b"b",
23799 b"RETURN",
23800 b"2",
23801 b"p",
23802 b"b",
23803 b"LIMIT",
23804 b"0",
23805 b"1"
23806 ]),
23807 concat!(
23808 "*3\r\n:3\r\n$3\r\ns:2\r\n*4\r\n",
23809 "$1\r\nb\r\n$5\r\napple\r\n",
23810 "$1\r\np\r\n$5\r\nalpha\r\n"
23811 )
23812 );
23813 }
23814
23815 #[test]
23817 fn a_search_refuses_the_sorts_it_cannot_run() {
23818 let mut f = Fixture::new();
23819 sortable(&mut f);
23820 assert_eq!(
23821 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY"]),
23822 "-SEARCH_PARSE_ARGS Bad SORTBY arguments\r\n"
23823 );
23824 assert_eq!(
23825 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"SORTBY"]),
23826 "-SEARCH_PARSE_ARGS Multiple SORTBY steps are not allowed\r\n"
23827 );
23828 assert_eq!(
23829 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"MAX", b"2"]),
23830 "-SEARCH_PARSE_ARGS SORTBY MAX is not supported by FT.SEARCH\r\n"
23831 );
23832 assert_eq!(
23833 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz"]),
23834 "-SEARCH_PROP_NOT_FOUND Property `zz` not loaded nor in schema\r\n"
23835 );
23836 assert_eq!(
23839 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz", b"NOPE"]),
23840 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `NOPE` at position 3 for <main>\r\n"
23841 );
23842 }
23843
23844 fn marking(f: &mut Fixture) {
23848 f.run(&[
23849 b"FT.CREATE",
23850 b"mk",
23851 b"ON",
23852 b"HASH",
23853 b"PREFIX",
23854 b"1",
23855 b"m:",
23856 b"SCHEMA",
23857 b"a",
23858 b"TEXT",
23859 b"b",
23860 b"TEXT",
23861 b"n",
23862 b"NUMERIC",
23863 b"g",
23864 b"TAG",
23865 ]);
23866 f.run(&[
23867 b"HSET",
23868 b"m:1",
23869 b"a",
23870 b"c1 c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2 e3",
23871 b"b",
23872 b"t1 t2 t3 t4 t5 t6 t7 t8",
23873 b"n",
23874 b"1",
23875 b"g",
23876 b"red",
23877 ]);
23878 }
23879
23880 #[test]
23883 fn a_summarize_cuts_a_field_down_to_what_matched() {
23884 let mut f = Fixture::new();
23885 marking(&mut f);
23886 let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"SUMMARIZE", b"LEN", b"2"]);
23887 assert!(got.contains("c3 fox d1 d2... d9 fox e1 e2... "), "{got}");
23888 assert!(got.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{got}");
23890 assert!(got.contains("$1\r\nr\r\n"), "{got}");
23892 }
23893
23894 #[test]
23898 fn a_dropped_fragment_stops_bounding_the_one_that_was_kept() {
23899 let mut f = Fixture::new();
23900 marking(&mut f);
23901 let got = f.run(&[
23902 b"FT.SEARCH",
23903 b"mk",
23904 b"fox",
23905 b"SUMMARIZE",
23906 b"FRAGS",
23907 b"1",
23908 b"LEN",
23909 b"20",
23910 ]);
23911 assert!(
23912 got.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2... "),
23913 "{got}"
23914 );
23915 let two = f.run(&[
23918 b"FT.SEARCH",
23919 b"mk",
23920 b"fox",
23921 b"SUMMARIZE",
23922 b"FRAGS",
23923 b"2",
23924 b"LEN",
23925 b"20",
23926 ]);
23927 assert!(
23928 two.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9... d1"),
23929 "{two}"
23930 );
23931 }
23932
23933 #[test]
23936 fn a_highlight_marks_the_matches_and_leaves_the_rest_of_the_field_alone() {
23937 let mut f = Fixture::new();
23938 marking(&mut f);
23939 let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"HIGHLIGHT"]);
23940 assert!(got.contains("<b>fox</b> d1 d2"), "{got}");
23941 let both = f.run(&[
23942 b"FT.SEARCH",
23943 b"mk",
23944 b"fox",
23945 b"SUMMARIZE",
23946 b"LEN",
23947 b"2",
23948 b"HIGHLIGHT",
23949 ]);
23950 assert!(both.contains("c3 <b>fox</b> d1 d2... "), "{both}");
23951 assert!(both.contains("t1 t2 t3 t4 t5 t6 t7 t8\r\n"), "{both}");
23953 assert!(both.contains("$3\r\nred\r\n"), "{both}");
23954 let split = f.run(&[
23956 b"FT.SEARCH",
23957 b"mk",
23958 b"fox",
23959 b"SUMMARIZE",
23960 b"FIELDS",
23961 b"1",
23962 b"b",
23963 b"LEN",
23964 b"2",
23965 b"HIGHLIGHT",
23966 b"FIELDS",
23967 b"1",
23968 b"a",
23969 ]);
23970 assert!(split.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{split}");
23971 }
23972
23973 #[test]
23975 fn a_highlight_does_not_mark_a_tag() {
23976 let mut f = Fixture::new();
23977 marking(&mut f);
23978 f.run(&[b"HSET", b"m:1", b"b", b"red and blue"]);
23979 let got = f.run(&[b"FT.SEARCH", b"mk", b"@g:{red}", b"HIGHLIGHT"]);
23980 assert!(!got.contains("<b>"), "{got}");
23981 assert!(got.contains("red and blue"), "{got}");
23982 }
23983
23984 #[test]
23987 fn a_search_answers_a_total_and_then_the_rows() {
23988 let mut f = Fixture::new();
23989 corpus(&mut f);
23990 assert_eq!(
23991 f.run(&[b"FT.SEARCH", b"sx", b"delta"]),
23992 "*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"
23993 );
23994 f.run(&[b"HSET", b"d:3", b"extra", b"more"]);
23997 assert!(f.run(&[b"FT.SEARCH", b"sx", b"delta"]).contains("extra"));
23998 assert_eq!(
24001 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
24002 "*2\r\n:1\r\n$3\r\nd:3\r\n"
24003 );
24004 assert_eq!(
24005 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
24006 "*1\r\n:3\r\n"
24007 );
24008 }
24009
24010 #[test]
24013 fn the_window_is_ten_rows_and_a_million_wide_at_most() {
24014 let mut f = Fixture::new();
24015 corpus(&mut f);
24016 assert_eq!(
24017 f.run(&[
24018 b"FT.SEARCH",
24019 b"sx",
24020 b"alpha",
24021 b"NOCONTENT",
24022 b"LIMIT",
24023 b"1",
24024 b"1"
24025 ]),
24026 "*2\r\n:3\r\n$3\r\nd:2\r\n"
24027 );
24028 assert_eq!(
24029 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0"]),
24030 "-SEARCH_PARSE_ARGS LIMIT requires two arguments\r\n"
24031 );
24032 assert_eq!(
24033 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"-1"]),
24034 "-SEARCH_PARSE_ARGS LIMIT needs two numeric arguments\r\n"
24035 );
24036 assert_eq!(
24037 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"1000001"]),
24038 "-SEARCH_LIMIT_OVER LIMIT exceeds maximum of 1000000\r\n"
24039 );
24040 assert_eq!(
24041 f.run(&[
24042 b"FT.SEARCH",
24043 b"sx",
24044 b"alpha",
24045 b"NOCONTENT",
24046 b"LIMIT",
24047 b"999999",
24048 b"1000000"
24049 ]),
24050 "*1\r\n:3\r\n"
24051 );
24052 }
24053
24054 #[test]
24058 fn a_return_of_nothing_is_not_the_same_as_nocontent() {
24059 let mut f = Fixture::new();
24060 corpus(&mut f);
24061 let bare = "*2\r\n:1\r\n$3\r\nd:3\r\n";
24062 assert_eq!(
24063 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"0"]),
24064 bare
24065 );
24066 assert_eq!(
24067 f.run(&[
24068 b"FT.SEARCH",
24069 b"sx",
24070 b"delta",
24071 b"NOCONTENT",
24072 b"RETURN",
24073 b"1",
24074 b"t"
24075 ]),
24076 bare
24077 );
24078 assert_eq!(
24079 f.run(&[
24080 b"FT.SEARCH",
24081 b"sx",
24082 b"delta",
24083 b"RETURN",
24084 b"0",
24085 b"RETURN",
24086 b"1",
24087 b"t"
24088 ]),
24089 "*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"
24090 );
24091 }
24092
24093 #[test]
24096 fn the_count_after_return_counts_words() {
24097 let mut f = Fixture::new();
24098 corpus(&mut f);
24099 assert_eq!(
24102 f.run(&[
24103 b"FT.SEARCH",
24104 b"sx",
24105 b"delta",
24106 b"RETURN",
24107 b"3",
24108 b"t",
24109 b"AS",
24110 b"x"
24111 ]),
24112 "*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"
24113 );
24114 assert_eq!(
24117 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"2", b"t", b"AS"]),
24118 "-SEARCH_PARSE_ARGS RETURN path AS name - must be accompanied with NAME\r\n"
24119 );
24120 assert_eq!(
24121 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"3", b"t", b"AS"]),
24122 "-SEARCH_PARSE_ARGS Bad arguments for RETURN: Expected an argument, but none provided\r\n"
24123 );
24124 assert_eq!(
24128 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"AS"]),
24129 "*3\r\n:1\r\n$3\r\nd:3\r\n*0\r\n"
24130 );
24131 }
24132
24133 #[test]
24136 fn a_filter_is_a_range_written_outside_the_query() {
24137 let mut f = Fixture::new();
24138 corpus(&mut f);
24139 assert_eq!(
24140 f.run(&[
24141 b"FT.SEARCH",
24142 b"sx",
24143 b"alpha",
24144 b"NOCONTENT",
24145 b"FILTER",
24146 b"n",
24147 b"2",
24148 b"4"
24149 ]),
24150 "*3\r\n:2\r\n$3\r\nd:2\r\n$3\r\nd:4\r\n"
24151 );
24152 assert_eq!(
24153 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2"]),
24154 "-SEARCH_PARSE_ARGS FILTER requires 3 arguments\r\n"
24155 );
24156 assert_eq!(
24157 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"x", b"1"]),
24158 "-SEARCH_PARSE_ARGS Bad lower range: x\r\n"
24159 );
24160 assert_eq!(
24161 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2", b"1"]),
24162 "-SEARCH_SYNTAX Invalid numeric range (min > max): @n:[2.000000 1.000000]\r\n"
24163 );
24164 for field in [b"g".as_slice(), b"nope"] {
24167 assert_eq!(
24168 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", field, b"2", b"1"]),
24169 "*1\r\n:0\r\n"
24170 );
24171 }
24172 }
24173
24174 #[test]
24177 fn the_index_is_found_before_the_arguments_are_read() {
24178 let mut f = Fixture::new();
24179 corpus(&mut f);
24180 let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
24181 assert_eq!(f.run(&[b"FT.SEARCH", b"nope", b"alpha", b"BOGUS"]), missing);
24182 assert_eq!(
24183 f.run(&[b"FT.EXPLAIN", b"nope", b"alpha", b"BOGUS"]),
24184 missing
24185 );
24186 assert_eq!(
24189 f.run(&[b"FT.SEARCH", b"sx", b"@@@", b"BOGUS"]),
24190 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `BOGUS` at position 1 for <main>\r\n"
24191 );
24192 }
24193
24194 #[test]
24197 fn inkeys_comes_off_the_total() {
24198 let mut f = Fixture::new();
24199 corpus(&mut f);
24200 assert_eq!(
24201 f.run(&[
24202 b"FT.SEARCH",
24203 b"sx",
24204 b"alpha",
24205 b"NOCONTENT",
24206 b"INKEYS",
24207 b"1",
24208 b"d:1"
24209 ]),
24210 "*2\r\n:1\r\n$3\r\nd:1\r\n"
24211 );
24212 assert_eq!(
24213 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"NOCONTENT", b"INKEYS", b"0"]),
24214 "*1\r\n:0\r\n"
24215 );
24216 }
24217
24218 #[test]
24225 fn the_fields_are_read_from_the_session_database() {
24226 let mut f = Fixture::new();
24227 corpus(&mut f);
24228 f.run(&[b"SELECT", b"1"]);
24229 f.run(&[b"HSET", b"d:9", b"t", b"delta", b"n", b"9"]);
24230 assert_eq!(
24233 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
24234 "*3\r\n:2\r\n$3\r\nd:3\r\n$3\r\nd:9\r\n"
24235 );
24236 assert_eq!(
24237 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
24238 "*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"
24239 );
24240 }
24241
24242 #[test]
24245 fn the_third_protocol_answers_a_map_of_five() {
24246 let mut f = Fixture::new();
24247 corpus(&mut f);
24248 f.out = Out::new(Proto::Resp3);
24249 assert_eq!(
24250 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
24251 concat!(
24252 "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
24253 "%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",
24254 "+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
24255 )
24256 );
24257 }
24258
24259 #[test]
24263 fn a_window_of_nothing_has_to_start_at_the_top() {
24264 let mut f = Fixture::new();
24265 corpus(&mut f);
24266 let refused = "-SEARCH_LIMIT_OVER The `offset` of the LIMIT must be 0 when `num` is 0\r\n";
24267 assert_eq!(
24268 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
24269 refused
24270 );
24271 assert_eq!(
24272 f.run(&[b"FT.EXPLAIN", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
24273 refused
24274 );
24275 assert_eq!(
24276 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
24277 refused
24278 );
24279 assert_eq!(
24280 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
24281 "*1\r\n:3\r\n"
24282 );
24283 }
24284
24285 #[test]
24288 fn an_aggregation_answers_a_count_and_then_the_properties() {
24289 let mut f = Fixture::new();
24290 corpus(&mut f);
24291 assert_eq!(
24292 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha"]),
24293 "*4\r\n:1\r\n*0\r\n*0\r\n*0\r\n"
24294 );
24295 assert_eq!(
24299 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"1", b"@t"]),
24300 concat!(
24301 "*4\r\n:1\r\n*2\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
24302 "*2\r\n$1\r\nt\r\n$11\r\nalpha gamma\r\n",
24303 "*2\r\n$1\r\nt\r\n$16\r\nalpha beta gamma\r\n"
24304 )
24305 );
24306 assert_eq!(
24310 f.run(&[
24311 b"FT.AGGREGATE",
24312 b"sx",
24313 b"alpha",
24314 b"LOAD",
24315 b"1",
24316 b"@n",
24317 b"LIMIT",
24318 b"1",
24319 b"2"
24320 ]),
24321 "*3\r\n:2\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"
24322 );
24323 assert_eq!(
24325 f.run(&[b"FT.AGGREGATE", b"sx", b"nope", b"LOAD", b"1", b"@t"]),
24326 "*1\r\n:0\r\n"
24327 );
24328 }
24329
24330 #[test]
24334 fn a_load_counts_words_and_can_rename_what_it_reads() {
24335 let mut f = Fixture::new();
24336 corpus(&mut f);
24337 assert_eq!(
24339 f.run(&[
24340 b"FT.AGGREGATE",
24341 b"sx",
24342 b"alpha",
24343 b"LOAD",
24344 b"3",
24345 b"@t",
24346 b"AS",
24347 b"text"
24348 ]),
24349 concat!(
24350 "*4\r\n:1\r\n*2\r\n$4\r\ntext\r\n$10\r\nalpha beta\r\n",
24351 "*2\r\n$4\r\ntext\r\n$11\r\nalpha gamma\r\n",
24352 "*2\r\n$4\r\ntext\r\n$16\r\nalpha beta gamma\r\n"
24353 )
24354 );
24355 assert_eq!(
24356 f.run(&[
24357 b"FT.AGGREGATE",
24358 b"sx",
24359 b"alpha",
24360 b"LOAD",
24361 b"*",
24362 b"LIMIT",
24363 b"0",
24364 b"1"
24365 ]),
24366 concat!(
24367 "*2\r\n:1\r\n*6\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
24368 "$1\r\ng\r\n$5\r\naa,bb\r\n$1\r\nn\r\n$1\r\n1\r\n"
24369 )
24370 );
24371 assert_eq!(
24373 f.run(&[
24374 b"FT.AGGREGATE",
24375 b"sx",
24376 b"alpha",
24377 b"LOAD",
24378 b"2",
24379 b"@n",
24380 b"@nope",
24381 b"LIMIT",
24382 b"0",
24383 b"2"
24384 ]),
24385 "*3\r\n:1\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"
24386 );
24387 }
24388
24389 #[test]
24392 fn a_load_refuses_a_count_it_cannot_use() {
24393 let mut f = Fixture::new();
24394 corpus(&mut f);
24395 let head = "-SEARCH_PARSE_ARGS Bad arguments for LOAD: ";
24396 assert_eq!(
24397 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"x"]),
24398 format!("{head}Expected number of fields or `*`\r\n")
24399 );
24400 assert_eq!(
24401 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"-1", b"@t"]),
24402 format!("{head}Value is outside acceptable bounds\r\n")
24403 );
24404 assert_eq!(
24405 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"5", b"@t"]),
24406 format!("{head}Expected an argument, but none provided\r\n")
24407 );
24408 assert_eq!(
24409 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD"]),
24410 format!("{head}Expected an argument, but none provided\r\n")
24411 );
24412 assert_eq!(
24416 f.run(&[
24417 b"FT.AGGREGATE",
24418 b"sx",
24419 b"alpha",
24420 b"LOAD",
24421 b"2",
24422 b"@t",
24423 b"AS"
24424 ]),
24425 "-SEARCH_PARSE_ARGS LOAD path AS name - must be accompanied with NAME\r\n"
24426 );
24427 assert_eq!(
24430 f.run(&[
24431 b"FT.AGGREGATE",
24432 b"sx",
24433 b"alpha",
24434 b"LOAD",
24435 b"2",
24436 b"@t",
24437 b"AS",
24438 b"VERBATIM"
24439 ]),
24440 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 5 for <main>\r\n"
24441 );
24442 assert_eq!(
24446 f.run(&[
24447 b"FT.AGGREGATE",
24448 b"sx",
24449 b"alpha",
24450 b"LOAD",
24451 b"0",
24452 b"LIMIT",
24453 b"0",
24454 b"1"
24455 ]),
24456 "*2\r\n:1\r\n*0\r\n"
24457 );
24458 }
24459
24460 #[test]
24463 fn a_pipeline_step_closes_the_door_on_the_search_words() {
24464 let mut f = Fixture::new();
24465 corpus(&mut f);
24466 assert_eq!(
24467 f.run(&[
24468 b"FT.AGGREGATE",
24469 b"sx",
24470 b"alpha",
24471 b"LOAD",
24472 b"1",
24473 b"@t",
24474 b"VERBATIM"
24475 ]),
24476 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 4 for <main>\r\n"
24477 );
24478 assert_eq!(
24479 f.run(&[
24480 b"FT.AGGREGATE",
24481 b"sx",
24482 b"alpha",
24483 b"LIMIT",
24484 b"0",
24485 b"1",
24486 b"VERBATIM"
24487 ]),
24488 "*2\r\n:1\r\n*0\r\n"
24489 );
24490 for word in [b"RETURN".as_slice(), b"SUMMARIZE", b"HIGHLIGHT"] {
24493 let name = core::str::from_utf8(word).expect("the three words are text");
24494 assert_eq!(
24495 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", word]),
24496 format!("-SEARCH_PARSE_ARGS {name} is not supported on FT.AGGREGATE\r\n")
24497 );
24498 }
24499 }
24500
24501 #[test]
24504 fn addscores_writes_a_shorter_score_than_withscores() {
24505 let mut f = Fixture::new();
24506 corpus(&mut f);
24507 assert_eq!(
24508 f.run(&[
24509 b"FT.AGGREGATE",
24510 b"sx",
24511 b"alpha",
24512 b"ADDSCORES",
24513 b"LOAD",
24514 b"1",
24515 b"@n",
24516 b"LIMIT",
24517 b"0",
24518 b"2"
24519 ]),
24520 concat!(
24521 "*3\r\n:1\r\n",
24522 "*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",
24523 "*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"
24524 )
24525 );
24526 assert_eq!(
24530 f.run(&[
24531 b"FT.AGGREGATE",
24532 b"sx",
24533 b"alpha",
24534 b"NOCONTENT",
24535 b"WITHSCORES",
24536 b"LIMIT",
24537 b"0",
24538 b"2"
24539 ]),
24540 "*3\r\n:1\r\n$18\r\n0.3566749439387324\r\n$18\r\n0.3566749439387324\r\n"
24541 );
24542 assert_eq!(
24543 f.run(&[
24544 b"FT.AGGREGATE",
24545 b"sx",
24546 b"alpha",
24547 b"WITHSORTKEYS",
24548 b"LOAD",
24549 b"1",
24550 b"@n",
24551 b"LIMIT",
24552 b"0",
24553 b"1"
24554 ]),
24555 "*3\r\n:1\r\n$-1\r\n*2\r\n$1\r\nn\r\n$1\r\n1\r\n"
24556 );
24557 }
24558
24559 #[test]
24562 fn a_normalising_scorer_answers_the_rows_backwards() {
24563 let mut f = Fixture::new();
24564 corpus(&mut f);
24565 assert_eq!(
24566 f.run(&[
24567 b"FT.AGGREGATE",
24568 b"sx",
24569 b"alpha",
24570 b"SCORER",
24571 b"BM25STD.NORM",
24572 b"ADDSCORES",
24573 b"LOAD",
24574 b"1",
24575 b"@n",
24576 b"LIMIT",
24577 b"1",
24578 b"2"
24579 ]),
24580 concat!(
24581 "*3\r\n:3\r\n",
24582 "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n2\r\n",
24583 "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n1\r\n"
24584 )
24585 );
24586 assert_eq!(
24589 f.run(&[
24590 b"FT.AGGREGATE",
24591 b"sx",
24592 b"alpha",
24593 b"SCORER",
24594 b"BM25STD.NORM",
24595 b"LOAD",
24596 b"1",
24597 b"@n",
24598 b"LIMIT",
24599 b"1",
24600 b"2"
24601 ]),
24602 "*3\r\n:2\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"
24603 );
24604 }
24605
24606 #[test]
24609 fn an_aggregation_answers_a_map_of_five_as_well() {
24610 let mut f = Fixture::new();
24611 corpus(&mut f);
24612 f.out = Out::new(Proto::Resp3);
24613 assert_eq!(
24614 f.run(&[
24615 b"FT.AGGREGATE",
24616 b"sx",
24617 b"alpha",
24618 b"ADDSCORES",
24619 b"WITHSCORES",
24620 b"WITHSORTKEYS",
24621 b"LOAD",
24622 b"1",
24623 b"@n",
24624 b"LIMIT",
24625 b"0",
24626 b"1"
24627 ]),
24628 concat!(
24629 "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
24630 "%4\r\n+score\r\n,0.3566749439387324\r\n+sortkey\r\n_\r\n",
24631 "+extra_attributes\r\n%2\r\n$7\r\n__score\r\n$14\r\n0.356674943939\r\n",
24632 "$1\r\nn\r\n$1\r\n1\r\n+values\r\n*0\r\n",
24633 "+total_results\r\n:1\r\n+warning\r\n*0\r\n"
24634 )
24635 );
24636 assert_eq!(
24639 f.run(&[
24640 b"FT.AGGREGATE",
24641 b"sx",
24642 b"alpha",
24643 b"NOCONTENT",
24644 b"LIMIT",
24645 b"0",
24646 b"1"
24647 ]),
24648 concat!(
24649 "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
24650 "%1\r\n+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
24651 )
24652 );
24653 }
24654}