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 lua;
73mod migrate;
74mod scan;
75mod scripting;
76mod search;
77mod server;
78mod sets;
79mod streams;
80mod strings;
81mod suggest;
82pub mod table;
83mod tdigest;
84mod topk;
85mod ts;
86mod vectors;
87mod vfilter;
88mod zsets;
89
90pub use args::Args;
91pub use blocking::{Parked, Waiters};
92pub use server::parse_memory;
93pub use table::{COMMANDS, Spec, arity_ok, lookup};
94
95use crate::reply::Out;
96use std::cell::Cell;
97use std::path::{Path, PathBuf};
98use std::sync::atomic::Ordering::{Acquire, Relaxed, Release};
99use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize};
100use yo_common::lock::{Held, Lock};
101use yo_common::{Code, Error};
102use yo_kv::cold::Store;
103use yo_kv::{Clock, Db, Keyspace};
104use yo_search::Registry;
105
106use search::cursor::Cursors;
107
108pub const DATABASES: usize = 16;
115
116const ALL_DATABASES: u64 = if DATABASES == 64 {
123 u64::MAX
124} else {
125 (1u64 << DATABASES) - 1
126};
127const _: () = assert!(DATABASES <= 64);
128
129const EVICT_BUDGET: usize = 64;
141
142const NO_MAXSTORE: u64 = u64::MAX;
150
151const OOM: &[u8] = b"command not allowed when used memory > 'maxmemory'.";
156
157#[derive(Debug, Clone, Copy, PartialEq, Eq)]
159pub enum Flow {
160 Continue,
162 Close,
164 Block,
171}
172
173#[derive(Debug, Default)]
183pub struct Counter(AtomicU64);
184
185impl Counter {
186 fn bump(&self) {
188 self.0.store(self.get().wrapping_add(1), Relaxed);
189 }
190
191 fn drop_one(&self) {
198 self.0.store(self.get().saturating_sub(1), Relaxed);
199 }
200
201 fn get(&self) -> u64 {
203 self.0.load(Relaxed)
204 }
205
206 fn zero(&self) {
208 self.0.store(0, Relaxed);
209 }
210}
211
212#[derive(Debug, Default)]
218pub struct Stats {
219 clients: Counter,
221 connections: Counter,
223 commands: Counter,
225}
226
227impl Stats {
228 pub fn opened(&self) {
230 self.clients.bump();
231 self.connections.bump();
232 }
233
234 pub fn closed(&self) {
236 self.clients.drop_one();
237 }
238}
239
240#[derive(Debug, Clone, Copy, Default)]
242pub struct Totals {
243 pub clients: u64,
245 pub connections: u64,
247 pub commands: u64,
249}
250
251thread_local! {
252 static SLOT: Cell<usize> = const { Cell::new(usize::MAX) };
260}
261
262#[derive(Debug)]
273#[repr(align(64))]
274struct Local {
275 stats: Stats,
277 cmdstats: CommandStats,
279 dirty: AtomicU64,
288 turn: AtomicU64,
301 parked: AtomicUsize,
313}
314
315impl Default for Local {
316 fn default() -> Local {
317 Local {
318 stats: Stats::default(),
319 cmdstats: CommandStats::default(),
320 dirty: AtomicU64::new(0),
321 turn: AtomicU64::new(ALL_DATABASES),
322 parked: AtomicUsize::new(0),
323 }
324 }
325}
326
327impl Local {
328 fn mark(&self, dbs: u64) {
330 self.dirty.store(self.dirty.load(Relaxed) | dbs, Relaxed);
331 }
332
333 fn note(&self, dbs: u64) {
335 self.turn.store(self.turn.load(Relaxed) | dbs, Relaxed);
336 }
337
338 fn done(&self, at: usize) {
340 self.turn
341 .store(self.turn.load(Relaxed) & !(1u64 << at), Relaxed);
342 }
343
344 fn wanted(&self, at: usize) -> bool {
346 self.turn.load(Relaxed) & (1u64 << at) != 0
347 }
348
349 fn blocked(&self, n: usize) {
351 self.parked
352 .store(self.parked.load(Relaxed).saturating_add(n), Relaxed);
353 }
354
355 fn woke(&self, n: usize) {
357 self.parked
358 .store(self.parked.load(Relaxed).saturating_sub(n), Relaxed);
359 }
360}
361
362fn one_thread() -> Box<[Local]> {
364 slots(1)
365}
366
367fn slots(threads: usize) -> Box<[Local]> {
369 (0..threads.max(1)).map(|_| Local::default()).collect()
370}
371
372fn working_dir() -> PathBuf {
379 std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
380}
381
382#[derive(Debug, Clone, Copy, Default)]
391pub struct CommandStat {
392 pub calls: u64,
394 pub rejected: u64,
397 pub failed: u64,
399}
400
401impl CommandStat {
402 const fn seen(&self) -> bool {
408 self.calls != 0 || self.rejected != 0 || self.failed != 0
409 }
410}
411
412#[derive(Debug, Default)]
417struct Row {
418 calls: Counter,
420 rejected: Counter,
422 failed: Counter,
424}
425
426#[derive(Debug)]
433struct CommandStats(Box<[Row]>);
434
435impl Default for CommandStats {
436 fn default() -> CommandStats {
437 CommandStats((0..table::count()).map(|_| Row::default()).collect())
438 }
439}
440
441impl CommandStats {
442 fn at(&self, spec: &'static Spec) -> &Row {
444 &self.0[table::index_of(spec)]
445 }
446}
447
448pub type StoreSource = dyn FnMut(usize) -> Option<Store> + Send;
454
455const _: () = {
460 const fn shareable<T: Send + Sync>() {}
461 shareable::<Server>();
462};
463
464pub struct Server {
472 dbs: Vec<Db>,
473 width: usize,
479 clock: Clock,
480 started_ms: u64,
481 next_db: AtomicUsize,
490 conn_bytes: AtomicUsize,
507 maxmemory: AtomicU64,
515 store: Lock<Option<Box<StoreSource>>>,
532 maxstore: AtomicU64,
554 used: AtomicUsize,
571 evict_db: AtomicUsize,
581 expire_db: AtomicUsize,
588 expire_ms: AtomicU64,
598 waiters: Lock<Waiters>,
606 parked: AtomicUsize,
619 peers: Lock<migrate::Peers>,
631 locals: Box<[Local]>,
637 claimed: AtomicUsize,
639 next_client: AtomicU64,
647 dir: PathBuf,
655 backup: Lock<backup::State>,
665 sealed: AtomicBool,
675 search: Lock<Registry>,
690 cursors: Lock<Cursors>,
699 scripts: Lock<lua::Scripts>,
711 libraries: Lock<lua::library::Libraries>,
718 stopping: AtomicBool,
727}
728
729impl Server {
730 #[must_use]
732 pub fn new() -> Server {
733 let clock = Clock::system();
734 Server {
735 dbs: (0..DATABASES)
736 .map(|_| Db::with_clock(clock.clone(), 1))
737 .collect(),
738 width: 1,
739 started_ms: clock.now_ms(),
740 clock,
741 next_db: AtomicUsize::new(0),
742 conn_bytes: AtomicUsize::new(0),
743 maxmemory: AtomicU64::new(0),
744 store: Lock::new(None),
745 maxstore: AtomicU64::new(NO_MAXSTORE),
746 used: AtomicUsize::new(0),
747 evict_db: AtomicUsize::new(0),
748 expire_db: AtomicUsize::new(0),
749 expire_ms: AtomicU64::new(0),
750 waiters: Lock::default(),
751 parked: AtomicUsize::new(0),
752 peers: Lock::default(),
753 locals: one_thread(),
754 claimed: AtomicUsize::new(0),
755 next_client: AtomicU64::new(1),
756 dir: working_dir(),
757 backup: Lock::default(),
758 sealed: AtomicBool::new(false),
759 search: Lock::new(Registry::new()),
760 cursors: Lock::default(),
761 scripts: Lock::default(),
762 libraries: Lock::default(),
763 stopping: AtomicBool::new(false),
764 }
765 }
766
767 #[must_use]
778 pub fn with_width(width: usize) -> Server {
779 let mut server = Server::new();
780 let clock = server.clock.clone();
784 server.dbs = (0..DATABASES)
785 .map(|_| Db::with_clock(clock.clone(), width))
786 .collect();
787 server.width = server.dbs[0].width();
788 server
789 }
790
791 #[must_use]
793 pub fn with_clock(clock: Clock) -> Server {
794 Server {
795 dbs: (0..DATABASES)
796 .map(|_| Db::with_clock(clock.clone(), 1))
797 .collect(),
798 width: 1,
799 started_ms: clock.now_ms(),
800 clock,
801 next_db: AtomicUsize::new(0),
802 conn_bytes: AtomicUsize::new(0),
803 maxmemory: AtomicU64::new(0),
804 store: Lock::new(None),
805 maxstore: AtomicU64::new(NO_MAXSTORE),
806 used: AtomicUsize::new(0),
807 evict_db: AtomicUsize::new(0),
808 expire_db: AtomicUsize::new(0),
809 expire_ms: AtomicU64::new(0),
810 waiters: Lock::default(),
811 parked: AtomicUsize::new(0),
812 peers: Lock::default(),
813 locals: one_thread(),
814 claimed: AtomicUsize::new(0),
815 next_client: AtomicU64::new(1),
816 dir: working_dir(),
817 backup: Lock::default(),
818 sealed: AtomicBool::new(false),
819 search: Lock::new(Registry::new()),
820 cursors: Lock::default(),
821 scripts: Lock::default(),
822 libraries: Lock::default(),
823 stopping: AtomicBool::new(false),
824 }
825 }
826
827 pub fn striped(&self, i: usize) -> &Db {
851 self.mine().mark(1u64 << i);
852 &self.dbs[i]
853 }
854
855 fn keyspaces(&self) -> impl Iterator<Item = Held<'_, Keyspace>> {
862 self.dbs
863 .iter()
864 .flat_map(|db| (0..db.width()).map(|i| db.hold_stripe(i)))
865 }
866
867 const fn slots(&self) -> usize {
873 DATABASES * self.width
874 }
875
876 const fn slot_db(&self, i: usize) -> usize {
878 i / self.width
879 }
880
881 fn slot(&self, i: usize) -> Held<'_, Keyspace> {
883 let (db, stripe) = (i / self.width, i % self.width);
884 self.dbs[db].hold_stripe(stripe)
885 }
886
887 #[must_use]
889 pub fn dir(&self) -> &Path {
890 &self.dir
891 }
892
893 pub fn set_dir(&mut self, dir: PathBuf) {
900 self.dir = dir;
901 }
902
903 pub fn backup_expire(&self) {
909 backup::expire(self);
910 }
911
912 pub fn stop(&self) {
918 self.stopping.store(true, Release);
919 }
920
921 #[must_use]
927 pub fn stopping(&self) -> bool {
928 self.stopping.load(Acquire)
929 }
930
931 #[must_use]
938 pub fn striped_ref(&self, i: usize) -> &Db {
939 &self.dbs[i]
940 }
941
942 fn settings(&self) -> Held<'_, Keyspace> {
950 self.dbs[0].hold_stripe(0)
951 }
952
953 pub fn refresh_clock(&self) {
964 self.clock.refresh();
965 }
966
967 pub fn advance_clock_ms(&self, ms: u64) {
973 let now = self.clock.now_ms() + ms;
974 self.set_clock_ms(now);
975 }
976
977 pub fn set_clock_ms(&self, ms: u64) {
985 self.clock.set(ms);
986 }
987
988 #[must_use]
990 pub fn uptime_secs(&self) -> u64 {
991 self.clock.now_ms().saturating_sub(self.started_ms) / 1000
992 }
993
994 #[must_use]
1002 pub fn memory_bytes(&self) -> usize {
1003 self.keyspaces().map(|db| db.memory_bytes()).sum::<usize>() + self.conn_bytes()
1004 }
1005
1006 #[must_use]
1012 pub fn dataset_bytes(&self) -> usize {
1013 self.keyspaces()
1014 .map(|db| db.map().arena().live_bytes() as usize)
1015 .sum()
1016 }
1017
1018 #[must_use]
1020 pub fn arena_bytes(&self) -> usize {
1021 self.keyspaces()
1022 .map(|db| db.map().arena().reserved_bytes() as usize)
1023 .sum()
1024 }
1025
1026 #[must_use]
1028 pub fn index_bytes(&self) -> usize {
1029 self.keyspaces()
1030 .map(|db| db.map().index().memory_bytes())
1031 .sum()
1032 }
1033
1034 #[must_use]
1041 pub fn compaction(&self) -> yo_kv::Compaction {
1042 self.keyspaces().map(|db| db.map().compaction()).fold(
1043 yo_kv::Compaction::default(),
1044 |a, b| yo_kv::Compaction {
1045 walked: a.walked + b.walked,
1046 moved: a.moved + b.moved,
1047 bytes: a.bytes + b.bytes,
1048 },
1049 )
1050 }
1051
1052 #[must_use]
1054 pub fn segment_count(&self) -> usize {
1055 self.keyspaces()
1056 .map(|db| db.map().arena().resident_segments())
1057 .sum()
1058 }
1059
1060 #[must_use]
1062 pub fn conn_bytes(&self) -> usize {
1063 self.conn_bytes.load(Relaxed)
1064 }
1065
1066 pub fn note_conn_bytes(&self, delta: isize) {
1074 self.conn_bytes
1080 .store(self.conn_bytes().saturating_add_signed(delta), Relaxed);
1081 }
1082
1083 #[must_use]
1085 pub fn expired_keys(&self) -> u64 {
1086 self.keyspaces().map(|db| db.expired_keys()).sum()
1087 }
1088
1089 #[must_use]
1091 pub fn evicted_keys(&self) -> u64 {
1092 self.keyspaces().map(|db| db.evicted_keys()).sum()
1093 }
1094
1095 pub fn command_stats(&self) -> impl Iterator<Item = (&'static str, CommandStat)> {
1101 (0..table::count())
1102 .map(|at| (table::name_at(at), self.command_stat(at)))
1103 .filter(|(_, row)| row.seen())
1104 }
1105
1106 fn command_stat(&self, at: usize) -> CommandStat {
1108 let mut sum = CommandStat::default();
1109 for thread in &self.locals {
1110 let row = &thread.cmdstats.0[at];
1111 sum.calls += row.calls.get();
1112 sum.rejected += row.rejected.get();
1113 sum.failed += row.failed.get();
1114 }
1115 sum
1116 }
1117
1118 pub fn counted(&self) -> &Stats {
1127 &self.mine().stats
1128 }
1129
1130 pub fn next_client(&self) -> u64 {
1135 self.next_client.fetch_add(1, Relaxed)
1136 }
1137
1138 pub fn my_slot(&self) -> usize {
1145 self.mine_at()
1146 }
1147
1148 fn mine(&self) -> &Local {
1150 &self.locals[self.mine_at()]
1151 }
1152
1153 fn mine_at(&self) -> usize {
1160 let mut slot = SLOT.get();
1161 if slot == usize::MAX {
1162 slot = self.claimed.fetch_add(1, Relaxed);
1163 SLOT.set(slot);
1164 }
1165 slot % self.locals.len()
1166 }
1167
1168 #[must_use]
1170 pub fn totals(&self) -> Totals {
1171 let mut sum = Totals::default();
1172 for thread in &self.locals {
1173 sum.clients += thread.stats.clients.get();
1174 sum.connections += thread.stats.connections.get();
1175 sum.commands += thread.stats.commands.get();
1176 }
1177 sum
1178 }
1179
1180 pub fn reset_stats(&self) {
1187 for thread in &self.locals {
1188 thread.stats.connections.zero();
1189 thread.stats.commands.zero();
1190 }
1191 }
1192
1193 pub fn set_threads(&mut self, threads: usize) {
1200 self.locals = slots(threads);
1201 self.claimed = AtomicUsize::new(0);
1202 }
1203
1204 #[must_use]
1206 pub fn maxmemory(&self) -> u64 {
1207 self.maxmemory.load(Relaxed)
1208 }
1209
1210 pub fn set_maxmemory(&self, bytes: u64) {
1224 self.maxmemory.store(bytes, Relaxed);
1225 for db in &self.dbs {
1226 db.track_memory(bytes != 0);
1227 }
1228 self.used.store(self.settled_memory(), Relaxed);
1229 }
1230
1231 pub fn set_store_source(
1241 &mut self,
1242 source: impl FnMut(usize) -> Option<Store> + Send + 'static,
1243 ) {
1244 *self.store.lock() = Some(Box::new(source));
1245 }
1246
1247 #[must_use]
1249 pub fn has_store_source(&self) -> bool {
1250 self.store.lock().is_some()
1251 }
1252
1253 fn attach_store(&self, at: usize) {
1260 if self.slot(at).store_bytes().is_some() {
1261 return;
1262 }
1263 let mut source = self.store.lock();
1267 let Some(source) = source.as_mut() else {
1268 return;
1269 };
1270 if let Some(blocks) = source(at) {
1271 self.slot(at).attach(blocks);
1272 }
1273 }
1274
1275 #[must_use]
1277 pub fn maxstore(&self) -> Option<u64> {
1278 match self.maxstore.load(Relaxed) {
1279 NO_MAXSTORE => None,
1280 bytes => Some(bytes),
1281 }
1282 }
1283
1284 pub fn set_maxstore(&self, bytes: Option<u64>) {
1290 self.maxstore.store(bytes.unwrap_or(NO_MAXSTORE), Relaxed);
1291 }
1292
1293 #[must_use]
1299 pub fn store_bytes(&self) -> u64 {
1300 self.keyspaces().filter_map(|db| db.store_bytes()).sum()
1301 }
1302
1303 #[must_use]
1318 pub fn cold_stats(&self) -> yo_kv::tier::Stats {
1319 let mut total = yo_kv::tier::Stats::default();
1320 for db in self.keyspaces() {
1321 let Some(tier) = db.tier() else { continue };
1322 let s = tier.stats();
1323 total.demoted += s.demoted;
1324 total.promoted += s.promoted;
1325 total.faults += s.faults;
1326 total.served += s.served;
1327 total.bytes_out += s.bytes_out;
1328 total.bytes_in += s.bytes_in;
1329 }
1330 total
1331 }
1332
1333 #[must_use]
1340 pub fn regime(&self) -> &'static str {
1341 if (0..self.slots()).any(|at| self.migrates(at)) {
1342 "migrate"
1343 } else {
1344 "evict"
1345 }
1346 }
1347
1348 fn migrates(&self, at: usize) -> bool {
1360 let cap = self.maxstore();
1361 if cap == Some(0) {
1362 return false;
1363 }
1364 let bytes = self.slot(at).store_bytes();
1368 match bytes {
1369 Some(held) => cap.is_none_or(|cap| held < cap),
1370 None => self.store.lock().is_some(),
1374 }
1375 }
1376
1377 pub fn refresh_memory(&self) {
1382 if self.maxmemory() != 0 {
1383 self.used.store(self.settled_memory(), Relaxed);
1384 }
1385 }
1386
1387 fn settled_memory(&self) -> usize {
1394 self.keyspaces()
1395 .map(|mut db| db.settled_memory_bytes())
1396 .sum::<usize>()
1397 + self.conn_bytes()
1398 }
1399
1400 pub fn make_room(&self) -> bool {
1434 let limit = self.maxmemory();
1435 if limit == 0 || self.used.load(Relaxed) as u64 <= limit {
1436 return true;
1437 }
1438 let mut used = self.settled_memory();
1443 self.used.store(used, Relaxed);
1444 let mut budget = EVICT_BUDGET;
1445 while used as u64 > limit {
1446 let over = used - limit as usize;
1447 if !self.relieve_step(over) {
1448 return false;
1449 }
1450 self.compact_hard_step();
1451 used = self.settled_memory();
1452 self.used.store(used, Relaxed);
1453 budget -= 1;
1454 if budget == 0 {
1455 break;
1456 }
1457 }
1458 true
1459 }
1460
1461 fn relieve_step(&self, over: usize) -> bool {
1478 let from = self.evict_db.load(Relaxed);
1479 for turn in 0..self.slots() {
1480 let i = (from + turn) % self.slots();
1481 let used = !self.slot(i).is_empty();
1484 let gave = if used && self.migrates(i) {
1485 self.attach_store(i);
1486 self.slot(i)
1491 .relieve(over)
1492 .is_ok_and(yo_kv::tier::Relief::made_room)
1493 } else {
1494 self.slot(i).evict_one()
1495 };
1496 if gave {
1497 self.evict_db.store((i + 1) % self.slots(), Relaxed);
1498 self.mine().mark(1u64 << self.slot_db(i));
1499 return true;
1500 }
1501 }
1502 false
1503 }
1504
1505 pub fn expire_slice(&self, budget: usize) -> usize {
1520 let now = self.clock.now_ms();
1521 if now == self.expire_ms.load(Relaxed) {
1522 return 0;
1523 }
1524 self.expire_ms.store(now, Relaxed);
1525 self.expire_step(budget)
1526 }
1527
1528 pub fn expire_step(&self, budget: usize) -> usize {
1544 let mut spent = 0;
1545 let from = self.expire_db.load(Relaxed);
1546 for turn in 0..self.slots() {
1547 if spent >= budget {
1548 break;
1549 }
1550 let i = (from + turn) % self.slots();
1551 let c = self.slot(i).expire_cycle(budget - spent);
1552 spent += c.examined;
1553 if c.expired > 0 {
1554 self.expire_db.store((i + 1) % self.slots(), Relaxed);
1555 self.mine().note(1u64 << self.slot_db(i));
1556 }
1557 }
1558 spent
1559 }
1560
1561 fn compact_hard_step(&self) -> Option<usize> {
1567 let from = self.next_db.load(Relaxed);
1568 for turn in 0..self.slots() {
1569 let i = (from + turn) % self.slots();
1570 if let Some(moved) = self.slot(i).compact_hard() {
1571 self.next_db.store((i + 1) % self.slots(), Relaxed);
1572 return Some(moved);
1573 }
1574 }
1575 None
1576 }
1577
1578 fn collect_marks(&self) {
1587 let mut marked = 0;
1588 for thread in &self.locals {
1589 marked |= thread.dirty.swap(0, Relaxed);
1590 }
1591 self.mine().note(marked);
1592 }
1593
1594 pub fn compact_step(&self) -> Option<usize> {
1607 self.collect_marks();
1608 let mine = self.mine();
1609 let from = self.next_db.load(Relaxed);
1610 for turn in 0..self.slots() {
1611 let i = (from + turn) % self.slots();
1612 let at = self.slot_db(i);
1616 if !mine.wanted(at) {
1617 continue;
1618 }
1619 if let Some(moved) = self.slot(i).compact_step() {
1620 self.next_db.store((i + 1) % self.slots(), Relaxed);
1621 return Some(moved);
1622 }
1623 if i % self.width == self.width - 1 {
1627 mine.done(at);
1628 }
1629 }
1630 None
1631 }
1632}
1633
1634impl Default for Server {
1635 fn default() -> Server {
1636 Server::new()
1637 }
1638}
1639
1640pub struct Session {
1642 db: usize,
1643 id: u64,
1644 name: Vec<u8>,
1645 sets: himport::Fieldsets,
1651 scripted: bool,
1661}
1662
1663impl Session {
1664 #[must_use]
1666 pub fn new(id: u64) -> Session {
1667 Session {
1668 db: 0,
1669 id,
1670 name: Vec::new(),
1671 sets: himport::Fieldsets::default(),
1672 scripted: false,
1673 }
1674 }
1675
1676 pub(crate) const fn scripted(&self) -> bool {
1678 self.scripted
1679 }
1680
1681 #[must_use]
1683 pub const fn id(&self) -> u64 {
1684 self.id
1685 }
1686
1687 #[must_use]
1689 pub const fn db(&self) -> usize {
1690 self.db
1691 }
1692
1693 #[must_use]
1695 pub fn name(&self) -> &[u8] {
1696 &self.name
1697 }
1698
1699 pub fn reset(&mut self) {
1704 self.db = 0;
1705 self.name.clear();
1706 self.sets.clear();
1710 }
1711
1712 fn set_name(&mut self, name: &[u8]) {
1714 yo_alloc::allow(|| {
1715 self.name.clear();
1716 self.name.extend_from_slice(name);
1717 });
1718 }
1719}
1720
1721pub fn execute(server: &Server, session: &mut Session, args: Args<'_>, out: &mut Out) -> Flow {
1726 if args.is_empty() {
1729 return Flow::Continue;
1730 }
1731 resolved(server, session, lookup(args.name()), args, out)
1732}
1733
1734pub fn resolved(
1746 server: &Server,
1747 session: &mut Session,
1748 spec: Option<&'static Spec>,
1749 args: Args<'_>,
1750 out: &mut Out,
1751) -> Flow {
1752 if args.is_empty() {
1753 return Flow::Continue;
1754 }
1755 server.mine().stats.commands.bump();
1756
1757 let Some(spec) = spec else {
1758 write_error(out, &args::unknown_command(args));
1759 return Flow::Continue;
1760 };
1761 if !arity_ok(spec, args.len()) {
1762 server.mine().cmdstats.at(spec).rejected.bump();
1763 write_error(out, &args::wrong_arity(spec.name));
1764 return Flow::Continue;
1765 }
1766
1767 if server.maxmemory() != 0 && !server.make_room() && spec.flags.contains(&"denyoom") {
1777 server.mine().cmdstats.at(spec).rejected.bump();
1778 out.error_line(b"OOM ", OOM);
1779 return Flow::Continue;
1780 }
1781
1782 server.mine().mark(match spec.group {
1789 "string" | "bitmap" | "hyperloglog" | "geo" | "set" | "hash" | "list" | "zset"
1790 | "array" | "stream" | "bloom" | "cuckoo" | "cms" | "topk" | "tdigest" | "ts" => {
1791 1u64 << session.db
1792 }
1793 _ => ALL_DATABASES,
1794 });
1795
1796 let mark = out.len();
1797 let done = if spec.flags.contains(&"blocking") {
1804 blocking::execute(server, session, spec, args, out)
1805 } else {
1806 match spec.group {
1807 "string" => {
1808 let db = session.db;
1809 strings::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1810 }
1811 "bitmap" => {
1815 let db = session.db;
1816 bits::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1817 }
1818 "hyperloglog" => {
1821 let db = session.db;
1822 hll::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1823 }
1824 "set" => {
1825 let db = session.db;
1826 sets::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1827 }
1828 "hash" if spec.name == "himport" => {
1833 let db = session.db;
1834 himport::execute(&server.dbs[db], &mut session.sets, args, out)
1835 .map(|()| Flow::Continue)
1836 }
1837 "hash" => {
1842 let db = session.db;
1843 let changed = hashes::execute(&server.dbs[db], spec, args, out);
1844 changed.map(|changed| {
1845 indexing::changed(server, db, args.get(1), changed);
1846 Flow::Continue
1847 })
1848 }
1849 "list" => {
1850 let db = session.db;
1851 lists::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1852 }
1853 "zset" => {
1854 let db = session.db;
1855 zsets::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1856 }
1857 "geo" => {
1861 let db = session.db;
1862 geo::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1863 }
1864 "array" => {
1865 let db = session.db;
1866 arrays::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1867 }
1868 "graph" => {
1869 let db = session.db;
1870 graph::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1871 }
1872 "json" => {
1876 let db = session.db;
1877 json::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1878 }
1879 "vector" => {
1880 let db = session.db;
1881 vectors::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1882 }
1883 "bloom" => {
1884 let db = session.db;
1885 bloom::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1886 }
1887 "cuckoo" => {
1888 let db = session.db;
1889 cuckoo::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1890 }
1891 "cms" => {
1892 let db = session.db;
1893 cms::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1894 }
1895 "topk" => {
1896 let db = session.db;
1897 topk::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1898 }
1899 "tdigest" => {
1900 let db = session.db;
1901 tdigest::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1902 }
1903 "ts" => {
1904 let db = session.db;
1905 ts::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1906 }
1907 "stream" => {
1912 let db = session.db;
1913 let now = server.now_ms();
1914 streams::execute(&server.dbs[db], spec, args, now, out).map(|()| Flow::Continue)
1915 }
1916 "keyspace" if spec.name == "migrate" => {
1920 migrate::execute(server, session.db, args, out).map(|()| Flow::Continue)
1921 }
1922 "keyspace" => {
1928 let mut touched = indexing::Touched::new(server);
1929 let done =
1930 keyspace::execute(&server.dbs, session.db, spec, args, out, &mut touched);
1931 done.map(|()| {
1932 indexing::touched(server, &touched);
1933 Flow::Continue
1934 })
1935 }
1936 "search" if spec.name == "FT.SEARCH" => {
1943 search::find(server, session.db, args, out).map(|()| Flow::Continue)
1948 }
1949 "search" if spec.name == "FT.AGGREGATE" => {
1950 search::roll(server, session.db, args, out).map(|()| Flow::Continue)
1951 }
1952 "search" if spec.name == "FT.HYBRID" => {
1953 search::hybrid(server, session.db, args, out).map(|()| Flow::Continue)
1954 }
1955 "search" if spec.name == "FT.PROFILE" => {
1956 search::profiled(server, session.db, args, out).map(|()| Flow::Continue)
1959 }
1960 "search" if spec.name.starts_with("FT.SUG") => {
1964 let db = session.db;
1965 suggest::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
1966 }
1967 "search"
1971 if matches!(
1972 spec.name,
1973 "FT.ADD" | "FT.SAFEADD" | "FT.GET" | "FT.MGET" | "FT.DEL"
1974 ) =>
1975 {
1976 let db = session.db;
1977 search::docs::execute(server, db, spec, args, out).map(|()| Flow::Continue)
1978 }
1979 "search" if spec.name == "FT.CURSOR" => {
1980 search::cursor::execute(server, args, out).map(|()| Flow::Continue)
1984 }
1985 "search" => {
1986 let db = session.db;
1987 let made = search::execute(server, &mut server.search.lock(), db, spec, args, out);
1988 made.map(|made| {
1989 match made {
1990 Some(search::After::Scan(fill)) => indexing::scan(server, db, &fill),
1991 Some(search::After::Sweep(keys)) => indexing::sweep(server, db, &keys),
1992 None => {}
1993 }
1994 Flow::Continue
1995 })
1996 }
1997 "scripting" => {
1998 scripting::execute(server, session, spec, args, out).map(|()| Flow::Continue)
1999 }
2000 _ => server::execute(server, session, spec, args, out),
2001 }
2002 };
2003 let flow = match done {
2004 Ok(flow) => flow,
2005 Err(e) => {
2006 out.truncate(mark);
2007 write_error(out, &e);
2008 Flow::Continue
2009 }
2010 };
2011
2012 let row = server.mine().cmdstats.at(spec);
2023 row.calls.bump();
2024 if matches!(out.as_slice().get(mark), Some(b'-' | b'!')) {
2025 row.failed.bump();
2026 }
2027 flow
2028}
2029
2030fn write_error(out: &mut Out, e: &Error) {
2040 let prefix: &[u8] = match e.code() {
2041 Code::WrongType => b"WRONGTYPE ",
2042 Code::Corrupt => b"INVALIDOBJ ",
2046 _ => b"ERR ",
2047 };
2048 out.error_line(prefix, e.message().as_bytes());
2049}
2050
2051#[cfg(test)]
2052mod tests {
2053 use super::*;
2054 use crate::proto::{Limits, Proto};
2055 use crate::request::Argv;
2056
2057 pub(crate) fn encode(parts: &[&[u8]]) -> Vec<u8> {
2062 let mut wire = format!("*{}\r\n", parts.len()).into_bytes();
2063 for p in parts {
2064 wire.extend_from_slice(format!("${}\r\n", p.len()).as_bytes());
2065 wire.extend_from_slice(p);
2066 wire.extend_from_slice(b"\r\n");
2067 }
2068 wire
2069 }
2070
2071 struct Fixture {
2073 server: Server,
2074 session: Session,
2075 argv: Argv,
2076 out: Out,
2077 }
2078
2079 impl Fixture {
2080 fn new() -> Fixture {
2081 Fixture::on(Server::new())
2082 }
2083
2084 fn striped(width: usize) -> Fixture {
2086 Fixture::on(Server::with_width(width))
2087 }
2088
2089 fn on(server: Server) -> Fixture {
2090 Fixture {
2091 server,
2092 session: Session::new(7),
2093 argv: Argv::new(),
2094 out: Out::new(Proto::Resp2),
2095 }
2096 }
2097
2098 fn run(&mut self, parts: &[&[u8]]) -> String {
2100 self.flow(parts).1
2101 }
2102
2103 fn raw(&mut self, parts: &[&[u8]]) -> Vec<u8> {
2109 let wire = encode(parts);
2110 self.argv.decode(&wire, &Limits::default()).unwrap();
2111 self.out.clear();
2112 execute(
2113 &self.server,
2114 &mut self.session,
2115 Args::new(&self.argv, &wire),
2116 &mut self.out,
2117 );
2118 self.out.as_slice().to_vec()
2119 }
2120
2121 fn advance(&mut self, ms: u64) {
2123 self.server.advance_clock_ms(ms);
2124 }
2125
2126 fn flow(&mut self, parts: &[&[u8]]) -> (Flow, String) {
2128 let wire = encode(parts);
2129 self.argv.decode(&wire, &Limits::default()).unwrap();
2130 self.out.clear();
2131 let flow = execute(
2132 &self.server,
2133 &mut self.session,
2134 Args::new(&self.argv, &wire),
2135 &mut self.out,
2136 );
2137 (
2138 flow,
2139 String::from_utf8_lossy(self.out.as_slice()).into_owned(),
2140 )
2141 }
2142 }
2143
2144 #[cfg_attr(miri, ignore = "megabytes through the arena")]
2157 #[test]
2158 fn rewriting_the_same_keys_does_not_grow_the_server() {
2159 let mut f = Fixture::new();
2160 let val = vec![b'v'; 1024];
2161 let keys: Vec<Vec<u8>> = (0..64).map(|i| format!("key:{i}").into_bytes()).collect();
2162
2163 for k in &keys {
2164 f.run(&[b"SET", k, &val]);
2165 }
2166 f.server.compact_step();
2167 let after_first = f.server.memory_bytes();
2168
2169 for _ in 0..500 {
2174 for k in &keys {
2175 f.run(&[b"SET", k, &val]);
2176 }
2177 f.server.compact_step();
2178 }
2179
2180 assert!(
2181 f.server.memory_bytes() <= after_first * 2,
2182 "held {} after five hundred passes against {after_first} after one",
2183 f.server.memory_bytes()
2184 );
2185 assert_eq!(f.run(&[b"DBSIZE"]), format!(":{}\r\n", keys.len()));
2186 assert_eq!(f.run(&[b"STRLEN", b"key:7"]), ":1024\r\n");
2187 }
2188
2189 #[cfg_attr(miri, ignore = "megabytes through the arena")]
2206 #[test]
2207 fn a_database_nobody_started_on_is_still_collected() {
2208 let mut f = Fixture::new();
2209 assert_eq!(f.run(&[b"SELECT", b"9"]), "+OK\r\n");
2210 let val = vec![b'v'; 1024];
2211 let keys: Vec<Vec<u8>> = (0..64).map(|i| format!("key:{i}").into_bytes()).collect();
2212
2213 for k in &keys {
2214 f.run(&[b"SET", k, &val]);
2215 }
2216 while f.server.compact_step().is_some() {}
2217 assert!(
2218 !f.server.mine().wanted(9),
2219 "database nine was drained and should not be asked again until it is written to"
2220 );
2221 let after_first = f.server.memory_bytes();
2222
2223 for _ in 0..500 {
2224 for k in &keys {
2225 f.run(&[b"SET", k, &val]);
2226 }
2227 f.server.compact_step();
2228 }
2229
2230 assert!(
2231 f.server.memory_bytes() <= after_first * 2,
2232 "held {} after five hundred passes against {after_first} after one",
2233 f.server.memory_bytes()
2234 );
2235 assert_eq!(f.run(&[b"DBSIZE"]), format!(":{}\r\n", keys.len()));
2236 assert_eq!(f.run(&[b"STRLEN", b"key:7"]), ":1024\r\n");
2237 f.run(&[b"SELECT", b"0"]);
2239 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
2240 }
2241
2242 #[test]
2243 fn a_command_goes_from_bytes_to_bytes() {
2244 let mut f = Fixture::new();
2245 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
2246 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
2247 assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
2248 assert_eq!(f.run(&[b"STRLEN", b"k"]), ":1\r\n");
2249 assert_eq!(f.run(&[b"set", b"k", b"v2", b"xx"]), "+OK\r\n");
2251 assert_eq!(f.run(&[b"GET", b"k"]), "$2\r\nv2\r\n");
2252 }
2253
2254 #[test]
2255 fn deleting_counts_keys_removed_and_existing_counts_arguments_matched() {
2256 let mut f = Fixture::new();
2257 f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
2258 assert_eq!(f.run(&[b"EXISTS", b"a", b"a", b"nosuch"]), ":2\r\n");
2261 assert_eq!(f.run(&[b"DEL", b"a", b"a", b"nosuch"]), ":1\r\n");
2262 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
2263 assert_eq!(f.run(&[b"UNLINK", b"b", b"c"]), ":2\r\n");
2265 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
2266 }
2267
2268 #[test]
2269 fn type_is_a_simple_string_and_says_none_for_a_key_that_is_not_there() {
2270 let mut f = Fixture::new();
2271 f.run(&[b"SET", b"k", b"v"]);
2272 assert_eq!(f.run(&[b"TYPE", b"k"]), "+string\r\n");
2275 assert_eq!(f.run(&[b"TYPE", b"nosuch"]), "+none\r\n");
2276 }
2277
2278 #[test]
2279 fn touch_counts_the_way_exists_counts() {
2280 let mut f = Fixture::new();
2281 f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
2282 assert_eq!(f.run(&[b"TOUCH", b"a", b"b"]), ":2\r\n");
2283 assert_eq!(
2284 f.run(&[b"TOUCH", b"a", b"a"]),
2285 ":2\r\n",
2286 "twice counts twice"
2287 );
2288 assert_eq!(f.run(&[b"TOUCH", b"a", b"nosuch"]), ":1\r\n");
2289 assert_eq!(f.run(&[b"TOUCH", b"nosuch"]), ":0\r\n");
2290 }
2291
2292 #[test]
2293 fn a_rename_moves_the_deadline_with_the_value_and_drops_the_one_it_lands_on() {
2294 let mut f = Fixture::new();
2295 f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
2296 f.run(&[b"SET", b"b", b"v2", b"EX", b"500"]);
2297
2298 assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "+OK\r\n");
2299 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
2300 assert_eq!(
2301 f.run(&[b"TTL", b"b"]),
2302 ":100\r\n",
2303 "the source's and not b's"
2304 );
2305 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
2306 }
2307
2308 #[test]
2309 fn a_rename_with_no_source_is_an_error_and_not_a_zero() {
2310 let mut f = Fixture::new();
2311 assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "-ERR no such key\r\n");
2312 assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), "-ERR no such key\r\n");
2315 }
2316
2317 #[test]
2318 fn renamenx_refuses_a_taken_name_including_the_one_it_already_has() {
2319 let mut f = Fixture::new();
2320 f.run(&[b"MSET", b"a", b"v1", b"b", b"v2"]);
2321
2322 assert_eq!(f.run(&[b"RENAMENX", b"a", b"b"]), ":0\r\n");
2323 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
2324 assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), ":0\r\n");
2327 assert_eq!(f.run(&[b"RENAME", b"a", b"a"]), "+OK\r\n");
2328 assert_eq!(f.run(&[b"RENAMENX", b"a", b"c"]), ":1\r\n");
2329 assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\nv1\r\n");
2330 }
2331
2332 #[test]
2333 fn renaming_a_set_does_not_touch_a_member() {
2334 let mut f = Fixture::new();
2335 for i in 0..300 {
2336 f.run(&[b"SADD", b"s", format!("m{i}").as_bytes()]);
2337 }
2338 let before = f.server.memory_bytes();
2339
2340 assert_eq!(f.run(&[b"RENAME", b"s", b"t"]), "+OK\r\n");
2341 assert_eq!(f.run(&[b"SCARD", b"t"]), ":300\r\n");
2342 assert_eq!(f.run(&[b"TYPE", b"t"]), "+set\r\n");
2343 assert!(
2344 f.server.memory_bytes().abs_diff(before) < 256,
2345 "the members were copied: {} against {before}",
2346 f.server.memory_bytes()
2347 );
2348 }
2349
2350 #[test]
2351 fn a_copy_is_a_second_value_and_not_a_second_name() {
2352 let mut f = Fixture::new();
2353 f.run(&[b"SADD", b"s", b"m1", b"m2"]);
2354
2355 assert_eq!(f.run(&[b"COPY", b"s", b"t"]), ":1\r\n");
2356 f.run(&[b"SADD", b"t", b"m3"]);
2357 assert_eq!(f.run(&[b"SCARD", b"s"]), ":2\r\n", "the original is intact");
2358 assert_eq!(f.run(&[b"SCARD", b"t"]), ":3\r\n");
2359 }
2360
2361 #[test]
2371 fn every_type_can_be_copied() {
2372 let mut f = Fixture::new();
2373 f.run(&[b"SET", b"str", b"v1"]);
2374 f.run(&[b"SADD", b"set", b"m1"]);
2375 f.run(&[b"HSET", b"hash", b"f", b"v"]);
2376 f.run(&[b"RPUSH", b"list", b"a", b"b"]);
2377 f.run(&[b"ZADD", b"zset", b"1", b"m1"]);
2378
2379 for name in [
2380 &b"str"[..],
2381 &b"set"[..],
2382 &b"hash"[..],
2383 &b"list"[..],
2384 &b"zset"[..],
2385 ] {
2386 let dst = [name, b":copy"].concat();
2387 assert_eq!(
2388 f.run(&[b"COPY", name, &dst]),
2389 ":1\r\n",
2390 "copying {}",
2391 String::from_utf8_lossy(name)
2392 );
2393 assert_eq!(f.run(&[b"TYPE", name]), f.run(&[b"TYPE", &dst]));
2394 }
2395
2396 assert_eq!(f.run(&[b"LRANGE", b"list:copy", b"0", b"-1"]), {
2397 let mut want = String::from("*2\r\n");
2398 want.push_str("$1\r\na\r\n$1\r\nb\r\n");
2399 want
2400 });
2401 assert_eq!(f.run(&[b"ZSCORE", b"zset:copy", b"m1"]), "$1\r\n1\r\n");
2402
2403 f.run(&[b"RPUSH", b"list:copy", b"c"]);
2405 assert_eq!(f.run(&[b"LLEN", b"list"]), ":2\r\n");
2406 assert_eq!(f.run(&[b"LLEN", b"list:copy"]), ":3\r\n");
2407 }
2408
2409 #[test]
2410 fn a_copy_refuses_a_taken_destination_until_it_is_told_it_can_have_it() {
2411 let mut f = Fixture::new();
2412 f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
2413 f.run(&[b"SET", b"b", b"v2"]);
2414
2415 assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":0\r\n");
2416 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
2417 assert_eq!(f.run(&[b"COPY", b"a", b"b", b"REPLACE"]), ":1\r\n");
2418 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
2419 assert_eq!(f.run(&[b"TTL", b"b"]), ":100\r\n", "the deadline came too");
2420 assert_eq!(f.run(&[b"COPY", b"nosuch", b"z"]), ":0\r\n");
2421 }
2422
2423 #[test]
2424 fn a_copy_into_another_database_is_a_copy_and_onto_itself_there_is_too() {
2425 let mut f = Fixture::new();
2426 f.run(&[b"SET", b"a", b"v1"]);
2427
2428 assert_eq!(f.run(&[b"COPY", b"a", b"a", b"DB", b"1"]), ":1\r\n");
2431 f.run(&[b"SELECT", b"1"]);
2432 assert_eq!(f.run(&[b"GET", b"a"]), "$2\r\nv1\r\n");
2433 assert_eq!(
2434 f.run(&[b"COPY", b"a", b"a", b"DB", b"0"]),
2435 ":0\r\n",
2436 "taken"
2437 );
2438 assert_eq!(
2439 f.run(&[b"COPY", b"a", b"a", b"DB", b"0", b"REPLACE"]),
2440 ":1\r\n"
2441 );
2442 }
2443
2444 #[test]
2445 fn sort_takes_its_options_in_any_order_and_the_last_one_wins() {
2446 let mut f = Fixture::new();
2447 f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
2448 assert_eq!(
2449 f.run(&[b"SORT", b"l"]),
2450 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
2451 );
2452 assert_eq!(
2454 f.run(&[b"SORT", b"l", b"DESC", b"asc"]),
2455 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
2456 );
2457 assert_eq!(
2458 f.run(&[b"sort", b"l", b"LIMIT", b"1", b"1", b"DESC"]),
2459 "*1\r\n$1\r\n2\r\n"
2460 );
2461 }
2462
2463 #[test]
2464 fn sort_reads_a_key_per_element_for_by_and_for_get() {
2465 let mut f = Fixture::new();
2466 f.run(&[b"RPUSH", b"l", b"a", b"b"]);
2467 f.run(&[b"MSET", b"w_a", b"2", b"w_b", b"1", b"d_b", b"bee"]);
2468 assert_eq!(
2471 f.run(&[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"]),
2472 "*4\r\n$1\r\nb\r\n$3\r\nbee\r\n$1\r\na\r\n$-1\r\n"
2473 );
2474 }
2475
2476 #[test]
2477 fn sort_store_writes_a_list_and_answers_its_length() {
2478 let mut f = Fixture::new();
2479 f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
2480 assert_eq!(f.run(&[b"SORT", b"l", b"STORE", b"out"]), ":3\r\n");
2481 assert_eq!(f.run(&[b"TYPE", b"out"]), "+list\r\n");
2482 assert_eq!(
2483 f.run(&[b"LRANGE", b"out", b"0", b"-1"]),
2484 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
2485 );
2486 assert_eq!(f.run(&[b"SORT", b"missing", b"STORE", b"out"]), ":0\r\n");
2489 assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
2490 }
2491
2492 #[test]
2493 fn sort_ro_does_not_know_the_word_store() {
2494 let mut f = Fixture::new();
2495 f.run(&[b"RPUSH", b"l", b"2", b"1"]);
2496 assert_eq!(f.run(&[b"SORT_RO", b"l"]), "*2\r\n$1\r\n1\r\n$1\r\n2\r\n");
2497 assert_eq!(
2498 f.run(&[b"SORT_RO", b"l", b"STORE", b"d"]),
2499 "-ERR syntax error\r\n"
2500 );
2501 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
2502 }
2503
2504 #[test]
2505 fn sort_refuses_what_it_cannot_sort() {
2506 let mut f = Fixture::new();
2507 assert_eq!(f.run(&[b"SORT", b"nosuchkey"]), "*0\r\n");
2508 f.run(&[b"SET", b"s", b"x"]);
2509 assert_eq!(
2510 f.run(&[b"SORT", b"s"]),
2511 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
2512 );
2513 f.run(&[b"RPUSH", b"words", b"one", b"two"]);
2514 assert_eq!(
2515 f.run(&[b"SORT", b"words"]),
2516 "-ERR One or more scores can't be converted into double\r\n"
2517 );
2518 assert_eq!(
2519 f.run(&[b"SORT", b"words", b"ALPHA"]),
2520 "*2\r\n$3\r\none\r\n$3\r\ntwo\r\n"
2521 );
2522 assert_eq!(f.run(&[b"SORT", b"words", b"BY"]), "-ERR syntax error\r\n");
2523 }
2524
2525 #[test]
2526 fn move_takes_the_key_out_of_one_database_and_puts_it_in_another() {
2527 let mut f = Fixture::new();
2528 assert_eq!(f.run(&[b"RPUSH", b"l", b"a", b"b"]), ":2\r\n");
2529 assert_eq!(f.run(&[b"MOVE", b"l", b"1"]), ":1\r\n");
2530 assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
2531 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2532 assert_eq!(
2533 f.run(&[b"LRANGE", b"l", b"0", b"-1"]),
2534 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
2535 );
2536 assert_eq!(f.run(&[b"MOVE", b"l", b"0"]), ":1\r\n");
2539 assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
2540 }
2541
2542 #[test]
2543 fn move_answers_zero_when_either_end_says_no() {
2544 let mut f = Fixture::new();
2545 assert_eq!(f.run(&[b"MOVE", b"nope", b"1"]), ":0\r\n");
2546 assert_eq!(f.run(&[b"SET", b"a", b"here"]), "+OK\r\n");
2547 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2548 assert_eq!(f.run(&[b"SET", b"a", b"there"]), "+OK\r\n");
2549 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
2550 assert_eq!(f.run(&[b"MOVE", b"a", b"1"]), ":0\r\n");
2553 assert_eq!(f.run(&[b"GET", b"a"]), "$4\r\nhere\r\n");
2554 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2555 assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nthere\r\n");
2556 }
2557
2558 #[test]
2559 fn move_refuses_a_database_that_is_not_one_and_the_one_it_is_on() {
2560 let mut f = Fixture::new();
2561 assert_eq!(
2562 f.run(&[b"MOVE", b"a", b"0"]),
2563 "-ERR source and destination objects are the same\r\n"
2564 );
2565 assert_eq!(
2566 f.run(&[b"MOVE", b"a", b"99"]),
2567 "-ERR DB index is out of range\r\n"
2568 );
2569 assert_eq!(
2570 f.run(&[b"MOVE", b"a", b"-1"]),
2571 "-ERR DB index is out of range\r\n"
2572 );
2573 assert_eq!(
2574 f.run(&[b"MOVE", b"a", b"x"]),
2575 "-ERR value is not an integer or out of range\r\n"
2576 );
2577 }
2578
2579 #[test]
2580 fn swapdb_swaps_what_two_connections_would_see() {
2581 let mut f = Fixture::new();
2582 assert_eq!(f.run(&[b"SET", b"k", b"zero"]), "+OK\r\n");
2583 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2584 assert_eq!(f.run(&[b"SET", b"k", b"one"]), "+OK\r\n");
2585 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
2586
2587 assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
2588 assert_eq!(f.run(&[b"GET", b"k"]), "$3\r\none\r\n");
2590 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2591 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
2592 assert_eq!(f.run(&[b"SWAPDB", b"1", b"1"]), "+OK\r\n");
2594 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
2595 }
2596
2597 #[test]
2602 fn a_wide_server_puts_its_databases_on_its_own_clock() {
2603 let mut f = Fixture::striped(8);
2604 f.server.set_clock_ms(1_700_000_000_000);
2605 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"100"]), "+OK\r\n");
2606 assert_eq!(f.run(&[b"EXPIRETIME", b"k"]), ":1700000100\r\n");
2607 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
2608 f.server.set_clock_ms(1_700_000_050_000);
2609 assert_eq!(f.run(&[b"TTL", b"k"]), ":50\r\n");
2610 }
2611
2612 #[test]
2617 fn swapdb_swaps_every_stripe_of_a_wide_database() {
2618 let mut f = Fixture::striped(4);
2619 for i in 0..16u32 {
2620 let key = format!("k{i}");
2621 assert_eq!(f.run(&[b"SET", key.as_bytes(), b"zero"]), "+OK\r\n");
2622 }
2623 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2624 assert_eq!(f.run(&[b"SET", b"only", b"one"]), "+OK\r\n");
2625 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
2626
2627 assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
2628 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
2629 assert_eq!(f.run(&[b"GET", b"only"]), "$3\r\none\r\n");
2630 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
2631 assert_eq!(f.run(&[b"DBSIZE"]), ":16\r\n");
2632 for i in 0..16u32 {
2633 let key = format!("k{i}");
2634 assert_eq!(f.run(&[b"GET", key.as_bytes()]), "$4\r\nzero\r\n");
2635 }
2636 }
2637
2638 #[test]
2639 fn swapdb_says_which_index_it_could_not_read() {
2640 let mut f = Fixture::new();
2641 assert_eq!(
2642 f.run(&[b"SWAPDB", b"x", b"1"]),
2643 "-ERR invalid first DB index\r\n"
2644 );
2645 assert_eq!(
2646 f.run(&[b"SWAPDB", b"0", b"y"]),
2647 "-ERR invalid second DB index\r\n"
2648 );
2649 assert_eq!(
2653 f.run(&[b"SWAPDB", b"99999999999999", b"1"]),
2654 "-ERR invalid first DB index\r\n"
2655 );
2656 assert_eq!(
2657 f.run(&[b"SWAPDB", b"0", b"99"]),
2658 "-ERR DB index is out of range\r\n"
2659 );
2660 assert_eq!(
2661 f.run(&[b"SWAPDB", b"-1", b"0"]),
2662 "-ERR DB index is out of range\r\n"
2663 );
2664 }
2665
2666 #[test]
2667 fn wait_answers_zero_replicas_without_waiting() {
2668 let mut f = Fixture::new();
2669 assert_eq!(f.run(&[b"SET", b"a", b"v"]), "+OK\r\n");
2670 assert_eq!(f.run(&[b"WAIT", b"0", b"0"]), ":0\r\n");
2671 assert_eq!(f.run(&[b"WAIT", b"3", b"1000"]), ":0\r\n");
2674 assert_eq!(f.run(&[b"WAIT", b"-1", b"0"]), ":0\r\n");
2677 assert_eq!(
2678 f.run(&[b"WAIT", b"x", b"0"]),
2679 "-ERR value is not an integer or out of range\r\n"
2680 );
2681 assert_eq!(
2682 f.run(&[b"WAIT", b"0", b"-1"]),
2683 "-ERR timeout is negative\r\n"
2684 );
2685 assert_eq!(
2686 f.run(&[b"WAIT", b"0", b"1.5"]),
2687 "-ERR timeout is not an integer or out of range\r\n"
2688 );
2689 }
2690
2691 #[test]
2692 fn waitaof_answers_two_zeroes_and_refuses_a_local_wait() {
2693 let mut f = Fixture::new();
2694 assert_eq!(f.run(&[b"WAITAOF", b"0", b"0", b"0"]), "*2\r\n:0\r\n:0\r\n");
2695 assert_eq!(
2696 f.run(&[b"WAITAOF", b"1", b"0", b"0"]),
2697 "-ERR WAITAOF cannot be used when numlocal is set but appendonly is disabled.\r\n"
2698 );
2699 assert_eq!(
2700 f.run(&[b"WAITAOF", b"2", b"0", b"0"]),
2701 "-ERR value is out of range, value must between 0 and 1\r\n"
2702 );
2703 assert_eq!(
2704 f.run(&[b"WAITAOF", b"0", b"-1", b"0"]),
2705 "-ERR value is out of range, must be positive\r\n"
2706 );
2707 assert_eq!(
2710 f.run(&[b"WAITAOF", b"1", b"0", b"-5"]),
2711 "-ERR timeout is negative\r\n"
2712 );
2713 }
2714
2715 fn payload(reply: &[u8]) -> Vec<u8> {
2719 let head = reply.windows(2).position(|w| w == b"\r\n").unwrap();
2720 reply[head + 2..reply.len() - 2].to_vec()
2721 }
2722
2723 #[test]
2724 fn a_value_survives_a_dump_and_a_restore() {
2725 let mut f = Fixture::new();
2726 f.run(&[b"SET", b"s", b"hello"]);
2727 f.run(&[b"RPUSH", b"l", b"a", b"b", b"c"]);
2728 f.run(&[b"SADD", b"t", b"1", b"2", b"3"]);
2729 f.run(&[b"SADD", b"u", b"x", b"y"]);
2730 f.run(&[b"HSET", b"h", b"f", b"1", b"g", b"2"]);
2731 f.run(&[b"ZADD", b"z", b"1.5", b"a", b"2.5", b"b"]);
2732
2733 for key in [&b"s"[..], b"l", b"t", b"u", b"h", b"z"] {
2734 let mut copy = key.to_vec();
2735 copy.push(b'2');
2736 let bytes = payload(&f.raw(&[b"DUMP", key]));
2737 assert_eq!(f.run(&[b"RESTORE", ©, b"0", &bytes]), "+OK\r\n");
2738 assert_eq!(f.run(&[b"TYPE", ©]), f.run(&[b"TYPE", key]));
2739 }
2740
2741 assert_eq!(f.run(&[b"GET", b"s2"]), "$5\r\nhello\r\n");
2742 assert_eq!(
2743 f.run(&[b"LRANGE", b"l2", b"0", b"-1"]),
2744 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
2745 );
2746 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"t2"])), ["1", "2", "3"]);
2747 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"u2"])), ["x", "y"]);
2748 assert_eq!(f.run(&[b"HGET", b"h2", b"g"]), "$1\r\n2\r\n");
2749 assert_eq!(f.run(&[b"ZSCORE", b"z2", b"b"]), "$3\r\n2.5\r\n");
2750 assert_eq!(
2753 f.run(&[b"OBJECT", b"ENCODING", b"t2"]),
2754 f.run(&[b"OBJECT", b"ENCODING", b"t"])
2755 );
2756 }
2757
2758 #[test]
2759 fn a_dumped_hash_keeps_its_field_deadlines() {
2760 let mut f = Fixture::new();
2761 f.run(&[b"HSET", b"h", b"keep", b"1", b"go", b"2"]);
2762 assert_eq!(
2763 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"go"]),
2764 "*1\r\n:1\r\n"
2765 );
2766 let bytes = payload(&f.raw(&[b"DUMP", b"h"]));
2767 assert_eq!(f.run(&[b"RESTORE", b"h2", b"0", &bytes]), "+OK\r\n");
2768 assert_eq!(
2769 f.run(&[b"HTTL", b"h2", b"FIELDS", b"2", b"keep", b"go"]),
2770 "*2\r\n:-1\r\n:100\r\n"
2771 );
2772 }
2773
2774 #[test]
2775 fn dump_leaves_the_deadline_behind_and_restore_is_given_a_new_one() {
2776 let mut f = Fixture::new();
2777 f.run(&[b"SET", b"a", b"v", b"EX", b"100"]);
2778 let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
2779 assert_eq!(f.run(&[b"RESTORE", b"b", b"0", &bytes]), "+OK\r\n");
2780 assert_eq!(f.run(&[b"TTL", b"b"]), ":-1\r\n");
2781 assert_eq!(f.run(&[b"RESTORE", b"c", b"5000", &bytes]), "+OK\r\n");
2782 assert_eq!(f.run(&[b"TTL", b"c"]), ":5\r\n");
2783 assert_eq!(
2786 f.run(&[b"RESTORE", b"d", b"1", &bytes, b"ABSTTL"]),
2787 "+OK\r\n"
2788 );
2789 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
2790 }
2791
2792 #[test]
2793 fn dump_answers_nothing_for_a_key_that_is_not_there() {
2794 let mut f = Fixture::new();
2795 assert_eq!(f.run(&[b"DUMP", b"nope"]), "$-1\r\n");
2796 f.run(&[b"SET", b"gone", b"v", b"PX", b"10"]);
2797 f.advance(50);
2798 assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
2799 }
2800
2801 #[test]
2802 fn restore_refuses_a_key_that_is_there_unless_it_is_told_to_replace() {
2803 let mut f = Fixture::new();
2804 f.run(&[b"SET", b"a", b"first"]);
2805 f.run(&[b"SET", b"b", b"second"]);
2806 let bytes = payload(&f.raw(&[b"DUMP", b"b"]));
2807 assert_eq!(
2808 f.run(&[b"RESTORE", b"a", b"0", &bytes]),
2809 "-BUSYKEY Target key name already exists.\r\n"
2810 );
2811 assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nfirst\r\n");
2812 assert_eq!(
2813 f.run(&[b"RESTORE", b"a", b"0", &bytes, b"REPLACE"]),
2814 "+OK\r\n"
2815 );
2816 assert_eq!(f.run(&[b"GET", b"a"]), "$6\r\nsecond\r\n");
2817 }
2818
2819 #[test]
2823 fn restore_asks_about_the_key_before_it_looks_at_the_bytes() {
2824 let mut f = Fixture::new();
2825 f.run(&[b"SET", b"a", b"v"]);
2826 assert_eq!(
2827 f.run(&[b"RESTORE", b"a", b"0", b"rubbish"]),
2828 "-BUSYKEY Target key name already exists.\r\n"
2829 );
2830 assert_eq!(
2833 f.run(&[b"RESTORE", b"a", b"0", b"rubbish", b"FREQ", b"300"]),
2834 "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
2835 );
2836 }
2837
2838 #[test]
2839 fn restore_can_tell_a_bad_footer_from_bad_bytes() {
2840 let mut f = Fixture::new();
2841 f.run(&[b"SET", b"a", b"hello"]);
2842 let good = payload(&f.raw(&[b"DUMP", b"a"]));
2843
2844 let mut flipped = good.clone();
2845 flipped[2] ^= 0x40;
2846 assert_eq!(
2847 f.run(&[b"RESTORE", b"b", b"0", &flipped]),
2848 "-ERR DUMP payload version or checksum are wrong\r\n"
2849 );
2850 assert_eq!(
2851 f.run(&[b"RESTORE", b"b", b"0", b"short"]),
2852 "-ERR DUMP payload version or checksum are wrong\r\n"
2853 );
2854 let mut truncated = good[..1].to_vec();
2858 truncated.extend_from_slice(&good[good.len() - 10..good.len() - 8]);
2859 let crc = yo_common::crc::crc64(0, &truncated);
2860 truncated.extend_from_slice(&crc.to_le_bytes());
2861 assert_eq!(
2862 f.run(&[b"RESTORE", b"b", b"0", &truncated]),
2863 "-ERR Bad data format\r\n"
2864 );
2865 assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
2866 }
2867
2868 #[test]
2869 fn restore_checks_the_three_numbers_a_client_can_get_wrong() {
2870 let mut f = Fixture::new();
2871 f.run(&[b"SET", b"a", b"v"]);
2872 let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
2873 assert_eq!(
2874 f.run(&[b"RESTORE", b"b", b"-1", &bytes]),
2875 "-ERR Invalid TTL value, must be >= 0\r\n"
2876 );
2877 assert_eq!(
2878 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"-1"]),
2879 "-ERR Invalid IDLETIME value, must be >= 0\r\n"
2880 );
2881 assert_eq!(
2882 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ", b"256"]),
2883 "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
2884 );
2885 assert_eq!(
2887 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"90"]),
2888 "+OK\r\n"
2889 );
2890 assert_eq!(
2891 f.run(&[b"RESTORE", b"c", b"0", &bytes, b"FREQ", b"200", b"REPLACE"]),
2892 "+OK\r\n"
2893 );
2894 }
2895
2896 #[test]
2900 fn restore_takes_idletime_or_freq_and_not_both() {
2901 let mut f = Fixture::new();
2902 f.run(&[b"SET", b"a", b"v"]);
2903 let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
2904 assert_eq!(
2905 f.run(&[
2906 b"RESTORE",
2907 b"b",
2908 b"0",
2909 &bytes,
2910 b"IDLETIME",
2911 b"1",
2912 b"FREQ",
2913 b"2"
2914 ]),
2915 "-ERR syntax error\r\n"
2916 );
2917 assert_eq!(
2918 f.run(&[
2919 b"RESTORE",
2920 b"b",
2921 b"0",
2922 &bytes,
2923 b"FREQ",
2924 b"2",
2925 b"IDLETIME",
2926 b"1"
2927 ]),
2928 "-ERR syntax error\r\n"
2929 );
2930 assert_eq!(
2931 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ"]),
2932 "-ERR syntax error\r\n"
2933 );
2934 assert_eq!(
2935 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"NOSUCH"]),
2936 "-ERR syntax error\r\n"
2937 );
2938 }
2939
2940 #[test]
2941 fn copy_checks_its_options_before_it_looks_for_anything() {
2942 let mut f = Fixture::new();
2943 assert_eq!(
2946 f.run(&[b"COPY", b"a", b"b", b"DB", b"99"]),
2947 "-ERR DB index is out of range\r\n"
2948 );
2949 assert_eq!(
2950 f.run(&[b"COPY", b"a", b"b", b"DB", b"-1"]),
2951 "-ERR DB index is out of range\r\n"
2952 );
2953 assert_eq!(
2954 f.run(&[b"COPY", b"a", b"b", b"DB", b"x"]),
2955 "-ERR value is not an integer or out of range\r\n"
2956 );
2957 assert_eq!(
2958 f.run(&[b"COPY", b"a", b"b", b"nonsense"]),
2959 "-ERR syntax error\r\n"
2960 );
2961 assert_eq!(
2962 f.run(&[b"COPY", b"a", b"a"]),
2963 "-ERR source and destination objects are the same\r\n"
2964 );
2965 assert_eq!(
2967 f.run(&[b"COPY", b"a", b"b", b"dB", b"1", b"rEpLaCe", b"db", b"2"]),
2968 ":0\r\n"
2969 );
2970 }
2971
2972 #[test]
2973 fn time_is_two_bulk_strings_and_moves() {
2974 let mut f = Fixture::new();
2975 let first = f.run(&[b"TIME"]);
2976 assert!(first.starts_with("*2\r\n$"), "got {first}");
2977 let parts: Vec<&str> = first.split("\r\n").collect();
2978 let secs: i64 = parts[2].parse().expect("seconds as decimal text");
2979 let micros: i64 = parts[4].parse().expect("microseconds as decimal text");
2980 assert!(secs > 1_700_000_000, "a real wall clock, got {secs}");
2981 assert!((0..1_000_000).contains(µs), "got {micros}");
2982 assert_ne!(first, f.run(&[b"TIME"]));
2986 }
2987
2988 #[test]
2989 fn a_keyspace_scan_walks_every_key_once() {
2990 let n = if cfg!(miri) { 96 } else { 500 };
2994 let mut f = Fixture::new();
2995 for i in 0..n {
2996 f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
2997 }
2998
2999 let mut seen: Vec<String> = Vec::new();
3000 let mut cursor = "0".to_owned();
3001 let mut calls = 0;
3002 loop {
3003 let (next, keys) = scan_reply(&f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"32"]));
3004 seen.extend(keys);
3005 cursor = next;
3006 calls += 1;
3007 assert!(calls < 10_000, "the cursor is not advancing");
3008 if cursor == "0" {
3009 break;
3010 }
3011 }
3012
3013 seen.sort();
3014 seen.dedup();
3015 assert_eq!(seen.len(), n, "every key once and only once");
3016 assert!(calls > 1, "{n} keys came back in one batch");
3019 }
3020
3021 #[test]
3022 fn a_scan_narrows_by_pattern_and_by_type() {
3023 let mut f = Fixture::new();
3024 f.run(&[b"SET", b"str", b"v"]);
3025 f.run(&[b"SADD", b"members", b"a"]);
3026 f.run(&[b"HSET", b"fields", b"f", b"v"]);
3027
3028 let all = |f: &mut Fixture, args: &[&[u8]]| {
3029 let mut out: Vec<String> = Vec::new();
3030 let mut cursor = "0".to_owned();
3031 loop {
3032 let mut line: Vec<&[u8]> = vec![b"SCAN", cursor.as_bytes()];
3033 line.extend_from_slice(args);
3034 let (next, keys) = scan_reply(&f.run(&line));
3035 out.extend(keys);
3036 cursor = next;
3037 if cursor == "0" {
3038 break;
3039 }
3040 }
3041 out.sort();
3042 out
3043 };
3044
3045 assert_eq!(all(&mut f, &[]), ["fields", "members", "str"]);
3046 assert_eq!(all(&mut f, &[b"MATCH", b"*e*"]), ["fields", "members"]);
3047 assert_eq!(all(&mut f, &[b"TYPE", b"set"]), ["members"]);
3048 assert_eq!(all(&mut f, &[b"TYPE", b"HASH"]), ["fields"]);
3050 assert!(all(&mut f, &[b"TYPE", b"list"]).is_empty());
3052 assert!(all(&mut f, &[b"TYPE", b"banana"]).is_empty());
3053 assert!(all(&mut f, &[b"MATCH", b"str*", b"TYPE", b"set"]).is_empty());
3055 }
3056
3057 #[test]
3058 fn a_scan_says_what_is_wrong_with_it() {
3059 let mut f = Fixture::new();
3060 assert_eq!(f.run(&[b"SCAN", b"nope"]), "-ERR invalid cursor\r\n");
3061 assert_eq!(f.run(&[b"SCAN", b"-1"]), "-ERR invalid cursor\r\n");
3062 assert_eq!(f.run(&[b"SCAN", b"0", b"MATCH"]), "-ERR syntax error\r\n");
3063 assert_eq!(
3064 f.run(&[b"SCAN", b"0", b"COUNT", b"0"]),
3065 "-ERR syntax error\r\n"
3066 );
3067 assert_eq!(
3068 f.run(&[b"SCAN", b"0", b"COUNT", b"x"]),
3069 "-ERR value is not an integer or out of range\r\n"
3070 );
3071 assert_eq!(
3072 f.run(&[b"SCAN", b"0", b"WAT", b"1"]),
3073 "-ERR syntax error\r\n"
3074 );
3075 assert!(f.run(&[b"SCAN", b"18446744073709551615"]).starts_with("*2"));
3080 }
3081
3082 #[test]
3083 fn keys_and_randomkey_look_at_the_whole_database() {
3084 let mut f = Fixture::new();
3085 assert_eq!(f.run(&[b"KEYS", b"*"]), "*0\r\n");
3086 assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
3087
3088 for name in ["one", "two", "three"] {
3089 f.run(&[b"SET", name.as_bytes(), b"v"]);
3090 }
3091 assert_eq!(sorted(&f.run(&[b"KEYS", b"*"])), ["one", "three", "two"]);
3092 assert_eq!(sorted(&f.run(&[b"KEYS", b"t*"])), ["three", "two"]);
3093 assert_eq!(f.run(&[b"KEYS", b"nothing"]), "*0\r\n");
3094
3095 for _ in 0..50 {
3096 let got = f.run(&[b"RANDOMKEY"]);
3097 assert!(
3098 ["$3\r\none\r\n", "$3\r\ntwo\r\n", "$5\r\nthree\r\n"].contains(&got.as_str()),
3099 "got {got}"
3100 );
3101 }
3102 }
3103
3104 #[test]
3105 fn a_walk_does_not_answer_keys_that_have_expired() {
3106 let mut f = Fixture::new();
3107 f.run(&[b"SET", b"alive", b"v"]);
3108 f.run(&[b"SET", b"dead", b"v", b"PX", b"1"]);
3109 f.server.advance_clock_ms(2);
3110 assert_eq!(
3111 f.run(&[b"DBSIZE"]),
3112 ":2\r\n",
3113 "nothing has collected it yet"
3114 );
3115
3116 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$5\r\nalive\r\n");
3117 let (_, keys) = scan_reply(&f.run(&[b"SCAN", b"0", b"COUNT", b"1000"]));
3118 assert_eq!(keys, ["alive"]);
3119 for _ in 0..20 {
3120 assert_eq!(f.run(&[b"RANDOMKEY"]), "$5\r\nalive\r\n");
3121 }
3122 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
3125 }
3126
3127 #[test]
3128 fn a_key_deadline_goes_on_and_comes_back_in_all_four_units() {
3129 let mut f = Fixture::new();
3130 f.run(&[b"SET", b"k", b"v"]);
3131 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "there and no deadline");
3132 assert_eq!(f.run(&[b"TTL", b"nosuch"]), ":-2\r\n", "not there at all");
3133
3134 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100"]), ":1\r\n");
3135 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
3136 let ms = int(&f.run(&[b"PTTL", b"k"]));
3137 assert!((99_000..=100_000).contains(&ms), "got {ms}");
3138
3139 let at = int(&f.run(&[b"EXPIRETIME", b"k"]));
3141 let at_ms = int(&f.run(&[b"PEXPIRETIME", b"k"]));
3142 assert_eq!(at, (at_ms + 500) / 1000);
3143 assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
3144
3145 assert_eq!(f.run(&[b"PERSIST", b"k"]), ":1\r\n");
3146 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
3147 assert_eq!(
3148 f.run(&[b"PERSIST", b"k"]),
3149 ":0\r\n",
3150 "nothing to take off the second time"
3151 );
3152 assert_eq!(f.run(&[b"PERSIST", b"nosuch"]), ":0\r\n");
3153 assert_eq!(
3154 f.run(&[b"GET", b"k"]),
3155 "$1\r\nv\r\n",
3156 "and the value went through all of that untouched"
3157 );
3158 }
3159
3160 #[test]
3161 fn every_type_can_be_given_a_deadline_and_it_is_the_same_deadline() {
3162 let mut f = Fixture::new();
3163 f.run(&[b"SET", b"str", b"v"]);
3164 f.run(&[b"SADD", b"set", b"a", b"b"]);
3165 f.run(&[b"HSET", b"hash", b"f", b"v"]);
3166
3167 for key in [b"str".as_slice(), b"set", b"hash"] {
3168 assert_eq!(f.run(&[b"EXPIRE", key, b"100"]), ":1\r\n");
3169 assert_eq!(f.run(&[b"TTL", key]), ":100\r\n");
3170 }
3171 assert_eq!(f.run(&[b"SCARD", b"set"]), ":2\r\n");
3174 assert_eq!(f.run(&[b"HGET", b"hash", b"f"]), "$1\r\nv\r\n");
3175 assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
3176 }
3177
3178 #[test]
3179 fn a_deadline_that_has_already_gone_deletes_the_key_now() {
3180 let mut f = Fixture::new();
3181 for key in [b"a".as_slice(), b"b", b"c", b"d"] {
3182 f.run(&[b"SET", key, b"v"]);
3183 }
3184 assert_eq!(f.run(&[b"EXPIRE", b"a", b"0"]), ":1\r\n");
3188 assert_eq!(f.run(&[b"EXPIRE", b"b", b"-1"]), ":1\r\n");
3189 assert_eq!(f.run(&[b"EXPIREAT", b"c", b"1"]), ":1\r\n");
3190 assert_eq!(f.run(&[b"PEXPIREAT", b"d", b"1"]), ":1\r\n");
3191 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3192 assert_eq!(
3193 f.run(&[b"EXPIRE", b"a", b"100"]),
3194 ":0\r\n",
3195 "and the key really went, so there is nothing to put a deadline on"
3196 );
3197 }
3198
3199 #[test]
3200 fn the_four_conditions_decide_whether_the_deadline_moves() {
3201 let mut f = Fixture::new();
3202 f.run(&[b"SET", b"k", b"v"]);
3203
3204 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX"]), ":0\r\n");
3205 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "and XX left it alone");
3206 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"GT"]), ":0\r\n");
3207 assert_eq!(
3208 f.run(&[b"EXPIRE", b"k", b"100", b"LT"]),
3209 ":1\r\n",
3210 "no deadline reads as infinitely far away, so LT passes where GT fails"
3211 );
3212
3213 assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"NX"]), ":0\r\n");
3214 assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"GT"]), ":0\r\n");
3215 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
3216 assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"LT"]), ":1\r\n");
3217 assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"GT"]), ":1\r\n");
3218 assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
3219
3220 assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"NX"]), ":0\r\n");
3223 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":1\r\n");
3224 assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"XX"]), ":1\r\n");
3225 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n", "and XX let it through");
3226 }
3227
3228 #[test]
3229 fn the_conditions_are_a_set_and_not_a_keyword() {
3230 let mut f = Fixture::new();
3231 f.run(&[b"SET", b"k", b"v"]);
3232
3233 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"nx"]), ":1\r\n");
3234 assert_eq!(
3235 f.run(&[b"EXPIRE", b"k", b"100", b"nx", b"nx"]),
3236 ":0\r\n",
3237 "the same keyword twice means it once, and NX now has a deadline to fail on"
3238 );
3239
3240 assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"xx", b"gt"]), ":1\r\n");
3243 assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
3244 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"gt", b"xx"]), ":0\r\n");
3245 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]), ":1\r\n");
3246 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
3247 f.run(&[b"PERSIST", b"k"]);
3248 assert_eq!(
3249 f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]),
3250 ":0\r\n",
3251 "where LT on its own would have taken it"
3252 );
3253 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"LT"]), ":1\r\n");
3254 }
3255
3256 #[test]
3257 fn a_key_is_gone_once_its_moment_passes() {
3258 let mut f = Fixture::new();
3259 f.run(&[b"SET", b"k", b"v"]);
3260 f.run(&[b"EXPIRE", b"k", b"100"]);
3261
3262 let at = int(&f.run(&[b"PEXPIRETIME", b"k"]));
3263 f.server.set_clock_ms(at as u64 + 1);
3264 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
3265 assert_eq!(f.run(&[b"TTL", b"k"]), ":-2\r\n");
3266 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
3267 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3268 }
3269
3270 #[test]
3271 fn the_expiry_commands_refuse_what_a_real_server_refuses() {
3272 let mut f = Fixture::new();
3273 f.run(&[b"SET", b"k", b"v"]);
3274 for (bad, want) in [
3275 (
3276 &[b"EXPIRE".as_slice(), b"k", b"soon"][..],
3277 "-ERR value is not an integer or out of range\r\n",
3278 ),
3279 (
3280 &[b"EXPIRE", b"k", b"100", b"MAYBE"],
3281 "-ERR Unsupported option MAYBE\r\n",
3282 ),
3283 (
3284 &[b"EXPIRE", b"k", b"100", b"NX", b"XX"],
3285 "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
3286 ),
3287 (
3288 &[b"EXPIRE", b"k", b"100", b"NX", b"GT"],
3289 "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
3290 ),
3291 (
3292 &[b"EXPIRE", b"k", b"100", b"GT", b"LT", b"GT"],
3293 "-ERR GT and LT options at the same time are not compatible\r\n",
3294 ),
3295 (
3298 &[b"EXPIRE", b"k", b"9223372036854775807"],
3299 "-ERR invalid expire time in 'expire' command\r\n",
3300 ),
3301 (
3302 &[b"EXPIREAT", b"k", b"9223372036854775807"],
3303 "-ERR invalid expire time in 'expireat' command\r\n",
3304 ),
3305 (
3306 &[b"PEXPIRE", b"k", b"9223372036854775807"],
3307 "-ERR invalid expire time in 'pexpire' command\r\n",
3308 ),
3309 ] {
3310 assert_eq!(f.run(bad), want, "for {bad:?}");
3311 }
3312 assert_eq!(
3313 f.run(&[b"TTL", b"k"]),
3314 ":-1\r\n",
3315 "and none of those put a deadline on anything"
3316 );
3317
3318 assert_eq!(
3322 f.run(&[b"PEXPIREAT", b"k", b"9223372036854775807"]),
3323 ":1\r\n"
3324 );
3325 assert_eq!(f.run(&[b"PEXPIRETIME", b"k"]), ":70368744177663\r\n");
3326 }
3327
3328 #[test]
3329 fn flushing_empties_this_database_or_every_one_of_them() {
3330 let mut f = Fixture::new();
3331 f.run(&[b"SELECT", b"0"]);
3332 f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
3333 f.run(&[b"SELECT", b"1"]);
3334 f.run(&[b"SET", b"c", b"3"]);
3335 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
3336 assert_eq!(f.run(&[b"FLUSHDB", b"async"]), "+OK\r\n");
3339 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3340 f.run(&[b"SELECT", b"0"]);
3342 assert_eq!(f.run(&[b"DBSIZE"]), ":2\r\n");
3343 assert_eq!(f.run(&[b"FLUSHALL", b"SYNC"]), "+OK\r\n");
3344 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3345 f.run(&[b"SELECT", b"1"]);
3346 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3347 assert_eq!(f.run(&[b"FLUSHALL", b"nope"]), "-ERR syntax error\r\n");
3350 assert_eq!(
3351 f.run(&[b"FLUSHDB", b"sync", b"sync"]),
3352 "-ERR syntax error\r\n"
3353 );
3354 }
3355
3356 #[test]
3357 fn the_script_cache_and_the_library_set_answer_for_being_empty() {
3358 let mut f = Fixture::new();
3359 assert_eq!(f.run(&[b"SCRIPT", b"FLUSH"]), "+OK\r\n");
3360 assert_eq!(f.run(&[b"SCRIPT", b"FLUSH", b"async"]), "+OK\r\n");
3361 assert_eq!(f.run(&[b"FUNCTION", b"FLUSH", b"SYNC"]), "+OK\r\n");
3362 assert_eq!(
3365 f.run(&[b"SCRIPT", b"EXISTS", b"aaaa", b"bbbb"]),
3366 "*2\r\n:0\r\n:0\r\n"
3367 );
3368 assert_eq!(f.run(&[b"FUNCTION", b"LIST"]), "*0\r\n");
3369 assert_eq!(
3370 f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"x", b"WITHCODE"]),
3371 "*0\r\n"
3372 );
3373 assert_eq!(
3374 f.run(&[b"FUNCTION", b"DELETE", b"nosuch"]),
3375 "-ERR Library not found\r\n"
3376 );
3377
3378 assert_eq!(
3381 f.run(&[b"SCRIPT", b"FLUSH", b"nope"]),
3382 "-ERR SCRIPT FLUSH only support SYNC|ASYNC option\r\n"
3383 );
3384 assert_eq!(
3385 f.run(&[b"FUNCTION", b"FLUSH", b"nope"]),
3386 "-ERR FUNCTION FLUSH only supports SYNC|ASYNC option\r\n"
3387 );
3388 assert_eq!(
3393 f.run(&[b"FUNCTION", b"FLUSH", b"sync", b"sync"]),
3394 "-ERR unknown subcommand or wrong number of arguments for 'FLUSH'. Try FUNCTION HELP.\r\n"
3395 );
3396 assert_eq!(
3397 f.run(&[b"FUNCTION", b"LIST", b"bogus"]),
3398 "-ERR Unknown argument bogus\r\n"
3399 );
3400 assert_eq!(
3401 f.run(&[b"SCRIPT", b"EXISTS"]),
3402 "-ERR wrong number of arguments for 'script|exists' command\r\n"
3403 );
3404
3405 assert_eq!(
3406 f.run(&[b"FUNCTION", b"NOPE"]),
3407 "-ERR unknown subcommand 'NOPE'. Try FUNCTION HELP.\r\n"
3408 );
3409 }
3410
3411 #[test]
3412 fn the_script_cache_holds_what_was_loaded_into_it() {
3413 let mut f = Fixture::new();
3414 let sha = b"e0e1f9fabfc9d4800c877a703b823ac0578ff8db";
3417 assert_eq!(
3418 f.run(&[b"SCRIPT", b"LOAD", b"return 1"]),
3419 "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
3420 );
3421 assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:1\r\n");
3422 assert_eq!(f.run(&[b"EVALSHA", sha, b"0"]), ":1\r\n");
3423 assert_eq!(
3426 f.run(&[b"SCRIPT", b"LOAD", b"return 1"]),
3427 "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
3428 );
3429 assert!(
3430 f.run(&[b"SCRIPT", b"LOAD", b"this is not lua"])
3431 .starts_with("-ERR Error compiling script"),
3432 );
3433
3434 assert_eq!(f.run(&[b"SCRIPT", b"FLUSH"]), "+OK\r\n");
3435 assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:0\r\n");
3436 assert_eq!(
3437 f.run(&[b"EVALSHA", sha, b"0"]),
3438 "-NOSCRIPT No matching script. Please use EVAL.\r\n"
3439 );
3440
3441 assert_eq!(f.run(&[b"EVAL", b"return 1", b"0"]), ":1\r\n");
3444 assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:1\r\n");
3445
3446 assert_eq!(
3449 f.run(&[b"SCRIPT", b"KILL"]),
3450 "-NOTBUSY No scripts in execution right now.\r\n"
3451 );
3452 assert_eq!(f.run(&[b"SCRIPT", b"DEBUG", b"NO"]), "+OK\r\n");
3453 assert_eq!(f.run(&[b"SCRIPT", b"DEBUG", b"yes"]), "+OK\r\n");
3454 assert_eq!(
3455 f.run(&[b"SCRIPT", b"DEBUG", b"maybe"]),
3456 "-ERR Use SCRIPT DEBUG YES/SYNC/NO\r\n"
3457 );
3458 }
3459
3460 #[test]
3461 fn eval_counts_its_keys_before_it_compiles_anything() {
3462 let mut f = Fixture::new();
3463 assert_eq!(
3464 f.run(&[b"EVAL", b"return 1"]),
3465 "-ERR wrong number of arguments for 'eval' command\r\n"
3466 );
3467 assert_eq!(
3468 f.run(&[b"EVAL", b"return 1", b"abc"]),
3469 "-ERR value is not an integer or out of range\r\n"
3470 );
3471 assert_eq!(
3472 f.run(&[b"EVAL", b"return 1", b"-1"]),
3473 "-ERR Number of keys can't be negative\r\n"
3474 );
3475 assert_eq!(
3476 f.run(&[b"EVAL", b"return 1", b"1"]),
3477 "-ERR Number of keys can't be greater than number of args\r\n"
3478 );
3479 assert_eq!(
3481 f.run(&[
3482 b"EVAL",
3483 b"return {KEYS[1],KEYS[2],ARGV[1]}",
3484 b"2",
3485 b"a",
3486 b"b",
3487 b"c"
3488 ]),
3489 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
3490 );
3491 assert_eq!(
3492 f.run(&[b"EVAL", b"return #KEYS", b"0", b"a", b"b"]),
3493 ":0\r\n"
3494 );
3495 assert_eq!(
3496 f.run(&[b"EVAL", b"return #ARGV", b"0", b"a", b"b"]),
3497 ":2\r\n"
3498 );
3499 }
3500
3501 #[test]
3502 fn a_lua_value_comes_back_as_the_reply_it_maps_to() {
3503 let mut f = Fixture::new();
3504 let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
3505
3506 assert_eq!(eval(&mut f, b"return 3.99"), ":3\r\n");
3509 assert_eq!(eval(&mut f, b"return -3.99"), ":-3\r\n");
3510 assert_eq!(eval(&mut f, b"return 0.5"), ":0\r\n");
3511 assert_eq!(eval(&mut f, b"return 2^63"), ":9223372036854775807\r\n");
3512 assert_eq!(eval(&mut f, b"return -2^63"), ":-9223372036854775808\r\n");
3513 assert_eq!(eval(&mut f, b"return 1/0"), ":9223372036854775807\r\n");
3514 assert_eq!(eval(&mut f, b"return 0/0"), ":0\r\n");
3515
3516 assert_eq!(eval(&mut f, b"return 'hello'"), "$5\r\nhello\r\n");
3517 assert_eq!(eval(&mut f, b"return true"), ":1\r\n");
3518 assert_eq!(eval(&mut f, b"return false"), "$-1\r\n");
3520 assert_eq!(eval(&mut f, b"return nil"), "$-1\r\n");
3521 assert_eq!(eval(&mut f, b"return"), "$-1\r\n");
3522 assert_eq!(eval(&mut f, b""), "$-1\r\n");
3523
3524 assert_eq!(eval(&mut f, b"return {}"), "*0\r\n");
3527 assert_eq!(eval(&mut f, b"return {1,2,nil,4}"), "*2\r\n:1\r\n:2\r\n");
3528 assert_eq!(
3529 eval(&mut f, b"return {1,'a',{2}}"),
3530 "*3\r\n:1\r\n$1\r\na\r\n*1\r\n:2\r\n"
3531 );
3532
3533 assert_eq!(eval(&mut f, b"return {ok='fine'}"), "+fine\r\n");
3535 assert_eq!(eval(&mut f, b"return {err='mine'}"), "-mine\r\n");
3536 assert_eq!(eval(&mut f, b"return {err='a', ok='b'}"), "-a\r\n");
3537 assert_eq!(eval(&mut f, b"return {ok='b', double=1.5}"), "+b\r\n");
3538 assert_eq!(eval(&mut f, b"return {ok='a\\r\\nb'}"), "+a b\r\n");
3541 assert_eq!(eval(&mut f, b"return {ok=1}"), "*0\r\n");
3544 assert_eq!(eval(&mut f, b"return {err={}}"), "*0\r\n");
3545 }
3546
3547 #[test]
3548 fn the_protocol_the_client_asked_for_is_the_one_a_table_answers_in() {
3549 let mut f = Fixture::new();
3550 assert_eq!(
3553 f.run(&[b"EVAL", b"return {double=3.5}", b"0"]),
3554 "$3\r\n3.5\r\n"
3555 );
3556 assert_eq!(
3557 f.run(&[b"EVAL", b"return {big_number='123'}", b"0"]),
3558 "$3\r\n123\r\n"
3559 );
3560 assert_eq!(
3561 f.run(&[b"EVAL", b"return {map={a='b'}}", b"0"]),
3562 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
3563 );
3564 assert_eq!(
3565 f.run(&[b"EVAL", b"return {set={a=true}}", b"0"]),
3566 "*1\r\n$1\r\na\r\n"
3567 );
3568 assert_eq!(f.run(&[b"EVAL", b"return false", b"0"]), "$-1\r\n");
3569
3570 f.out = Out::new(Proto::Resp3);
3571 assert_eq!(f.run(&[b"EVAL", b"return {double=3.5}", b"0"]), ",3.5\r\n");
3572 assert_eq!(
3573 f.run(&[b"EVAL", b"return {big_number='123'}", b"0"]),
3574 "(123\r\n"
3575 );
3576 assert_eq!(
3577 f.run(&[b"EVAL", b"return {map={a='b'}}", b"0"]),
3578 "%1\r\n$1\r\na\r\n$1\r\nb\r\n"
3579 );
3580 assert_eq!(
3581 f.run(&[b"EVAL", b"return {set={a=true}}", b"0"]),
3582 "~1\r\n$1\r\na\r\n"
3583 );
3584 assert_eq!(f.run(&[b"EVAL", b"return false", b"0"]), "_\r\n");
3585 }
3586
3587 #[test]
3588 fn a_reply_comes_back_into_lua_as_the_value_it_maps_to() {
3589 let mut f = Fixture::new();
3590 f.run(&[b"SET", b"s", b"hello"]);
3591 f.run(&[b"RPUSH", b"l", b"a", b"b"]);
3592 let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
3593
3594 assert_eq!(
3595 eval(&mut f, b"return type(redis.call('get','s'))"),
3596 "$6\r\nstring\r\n"
3597 );
3598 assert_eq!(
3599 eval(&mut f, b"return type(redis.call('llen','l'))"),
3600 "$6\r\nnumber\r\n"
3601 );
3602 assert_eq!(
3603 eval(&mut f, b"return type(redis.call('lrange','l',0,-1))"),
3604 "$5\r\ntable\r\n"
3605 );
3606 assert_eq!(
3609 eval(&mut f, b"return redis.call('set','s','v')['ok']"),
3610 "$2\r\nOK\r\n"
3611 );
3612 assert_eq!(
3615 eval(&mut f, b"return tostring(redis.call('get','nosuch'))"),
3616 "$5\r\nfalse\r\n"
3617 );
3618 assert_eq!(
3619 eval(
3620 &mut f,
3621 b"redis.setresp(3) return tostring(redis.call('get','nosuch'))"
3622 ),
3623 "$3\r\nnil\r\n"
3624 );
3625 assert_eq!(
3627 eval(&mut f, b"return tostring(redis.call('get','nosuch'))"),
3628 "$5\r\nfalse\r\n"
3629 );
3630 }
3631
3632 #[test]
3633 fn an_error_from_a_script_names_the_line_it_came_from() {
3634 let mut f = Fixture::new();
3635 assert_eq!(
3638 f.run(&[b"EVAL", b"error('boom')", b"0"]),
3639 "-ERR user_script:1: boom script: \
3640 82903a0434f1503e152f89c03c9acd881a0e8150, on @user_script:1.\r\n"
3641 );
3642 assert_eq!(
3644 f.run(&[b"EVAL", b"error('boom', 0)", b"0"]),
3645 "-ERR boom script: 90724e16396e5864c1184910ba6d7440461cee4f, on @user_script:1.\r\n"
3646 );
3647 assert!(
3649 f.run(&[b"EVAL", b"error({err='structured'})", b"0"])
3650 .starts_with("-structured script: "),
3651 );
3652 assert_eq!(
3655 f.run(&[b"EVAL", b"return this is not lua", b"0"]),
3656 "-ERR Error compiling script (new function): user_script:1: '<eof>' expected near 'is'\r\n"
3657 );
3658
3659 assert_eq!(
3662 f.run(&[
3663 b"EVAL",
3664 b"local a, b = pcall(function() error({err='z'}) end) return type(b) .. ':' .. tostring(b)",
3665 b"0"
3666 ]),
3667 "$8\r\nstring:z\r\n"
3668 );
3669 assert_eq!(
3670 f.run(&[
3671 b"EVAL",
3672 b"local a, b = pcall(function() error({a=1}) end) return type(b)",
3673 b"0"
3674 ]),
3675 "$5\r\ntable\r\n"
3676 );
3677 }
3678
3679 #[test]
3680 fn redis_call_refuses_what_it_cannot_run_and_pcall_hands_it_back() {
3681 let mut f = Fixture::new();
3682 let sentence = |f: &mut Fixture, body: &[u8]| {
3683 let reply = f.run(&[b"EVAL", body, b"0"]);
3684 reply.split(" script: ").next().unwrap().to_owned()
3685 };
3686
3687 assert_eq!(
3688 sentence(&mut f, b"return redis.call()"),
3689 "-ERR Please specify at least one argument for this redis lib call"
3690 );
3691 assert_eq!(
3692 sentence(&mut f, b"return redis.call('get', {})"),
3693 "-ERR Lua redis lib command arguments must be strings or integers"
3694 );
3695 assert_eq!(
3696 sentence(&mut f, b"return redis.call('nosuchcmd')"),
3697 "-ERR Unknown Redis command called from script"
3698 );
3699 assert_eq!(
3700 sentence(&mut f, b"return redis.call('get')"),
3701 "-ERR Wrong number of args calling Redis command from script"
3702 );
3703 for name in [
3707 &b"return redis.call('multi')"[..],
3708 b"return redis.call('exec')",
3709 b"return redis.call('watch','k')",
3710 b"return redis.call('subscribe','c')",
3711 b"return redis.call('debug','jmap')",
3712 b"return redis.call('eval','return 1',0)",
3713 b"return redis.call('config','get','maxmemory')",
3714 ] {
3715 assert_eq!(
3716 sentence(&mut f, name),
3717 "-ERR This Redis command is not allowed from script",
3718 "for {}",
3719 String::from_utf8_lossy(name)
3720 );
3721 }
3722 assert!(
3725 f.run(&[b"EVAL", b"return redis.call('config','help')", b"0"])
3726 .starts_with('*'),
3727 );
3728
3729 assert_eq!(
3732 f.run(&[
3733 b"EVAL",
3734 b"local x = redis.pcall('nosuchcmd') return x.err",
3735 b"0"
3736 ]),
3737 "$44\r\nERR Unknown Redis command called from script\r\n"
3738 );
3739 assert_eq!(
3741 f.run(&[b"EVAL", b"return redis.pcall('nosuchcmd')", b"0"]),
3742 "-ERR Unknown Redis command called from script\r\n"
3743 );
3744 }
3745
3746 #[test]
3747 fn a_read_only_script_is_stopped_at_the_write_and_not_at_the_door() {
3748 let mut f = Fixture::new();
3749 f.run(&[b"SET", b"k", b"v"]);
3750 assert_eq!(
3751 f.run(&[b"EVAL_RO", b"return redis.call('get', KEYS[1])", b"1", b"k"]),
3752 "$1\r\nv\r\n"
3753 );
3754 assert!(
3755 f.run(&[
3756 b"EVAL_RO",
3757 b"return redis.call('set', KEYS[1], 'x')",
3758 b"1",
3759 b"k"
3760 ])
3761 .starts_with("-ERR Write commands are not allowed from read-only scripts."),
3762 );
3763 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
3765 assert_eq!(
3766 f.run(&[
3767 b"EVAL",
3768 b"return redis.call('set', KEYS[1], 'x')",
3769 b"1",
3770 b"k"
3771 ]),
3772 "+OK\r\n"
3773 );
3774 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nx\r\n");
3775
3776 let sha = b"e0e1f9fabfc9d4800c877a703b823ac0578ff8db";
3778 f.run(&[b"SCRIPT", b"LOAD", b"return 1"]);
3779 assert_eq!(f.run(&[b"EVALSHA_RO", sha, b"0"]), ":1\r\n");
3780 }
3781
3782 #[test]
3783 fn a_script_cannot_leave_anything_behind_for_the_next_one() {
3784 let mut f = Fixture::new();
3785 for body in [&b"x = 1"[..], b"pcall = 1", b"redis = 1", b"redis.call = 1"] {
3788 let reply = f.run(&[b"EVAL", body, b"0"]);
3789 assert!(
3790 reply
3791 .starts_with("-ERR user_script:1: Attempt to modify a readonly table script: "),
3792 "{body:?} gave {reply}",
3793 );
3794 }
3795 for body in [
3798 &b"rawset(redis, 'call', 1)"[..],
3799 b"rawset(_G, 'zz', 1)",
3800 b"setmetatable(_G, {})",
3801 b"setmetatable(redis, {})",
3802 ] {
3803 let reply = f.run(&[b"EVAL", body, b"0"]);
3804 assert!(
3805 reply.starts_with("-ERR Attempt to modify a readonly table script: "),
3806 "{body:?} gave {reply}",
3807 );
3808 }
3809 assert!(
3812 f.run(&[b"EVAL", b"return nosuchglobal", b"0"])
3813 .contains("Script attempted to access nonexistent global variable 'nosuchglobal'"),
3814 );
3815 assert_eq!(
3818 f.run(&[b"EVAL", b"return tostring(redis.nosuchfield)", b"0"]),
3819 "$3\r\nnil\r\n"
3820 );
3821
3822 assert_eq!(f.run(&[b"EVAL", b"_G.pcall = 1 return 1", b"0"]), ":1\r\n");
3825 assert_eq!(
3826 f.run(&[b"EVAL", b"return type(pcall)", b"0"]),
3827 "$8\r\nfunction\r\n"
3828 );
3829 assert_eq!(
3830 f.run(&[b"EVAL", b"return type(redis.call)", b"0"]),
3831 "$8\r\nfunction\r\n"
3832 );
3833 }
3834
3835 #[test]
3836 fn a_script_can_walk_the_redis_table_it_is_not_allowed_to_write_to() {
3837 let mut f = Fixture::new();
3838 assert_eq!(
3842 f.run(&[
3843 b"EVAL",
3844 b"local n = 0 for k in pairs(redis) do n = n + 1 end return n",
3845 b"0",
3846 ]),
3847 ":23\r\n"
3848 );
3849 assert_eq!(
3850 f.run(&[
3851 b"EVAL",
3852 b"local t = {} for k in pairs(redis) do t[#t+1] = k end \
3853 table.sort(t) return table.concat(t, ' ')",
3854 b"0",
3855 ]),
3856 "$243\r\nLOG_DEBUG LOG_NOTICE LOG_VERBOSE LOG_WARNING REDIS_VERSION \
3857 REDIS_VERSION_NUM REPL_ALL REPL_AOF REPL_NONE REPL_REPLICA REPL_SLAVE \
3858 acl_check_cmd breakpoint call debug error_reply log pcall replicate_commands \
3859 set_repl setresp sha1hex status_reply\r\n"
3860 );
3861 assert_eq!(
3864 f.run(&[
3865 b"EVAL",
3866 b"local n = 0 for k, v in pairs(redis) do \
3867 if type(v) == 'function' then n = n + 1 end end return n",
3868 b"0",
3869 ]),
3870 ":12\r\n"
3871 );
3872 assert_eq!(
3874 f.run(&[b"EVAL", b"return type(next(redis))", b"0"]),
3875 "$6\r\nstring\r\n"
3876 );
3877 assert_eq!(
3878 f.run(&[b"EVAL", b"return type(rawget(redis, 'call'))", b"0"]),
3879 "$8\r\nfunction\r\n"
3880 );
3881 assert_eq!(
3882 f.run(&[
3883 b"EVAL",
3884 b"return tostring(rawget(redis, 'nosuchfield'))",
3885 b"0",
3886 ]),
3887 "$3\r\nnil\r\n"
3888 );
3889 for body in [&b"redis.call = 1"[..], b"rawset(redis, 'call', 1)"] {
3892 assert!(
3893 f.run(&[b"EVAL", body, b"0"])
3894 .contains("Attempt to modify a readonly table script: "),
3895 "{body:?}",
3896 );
3897 }
3898 assert_eq!(
3901 f.run(&[
3902 b"EVAL",
3903 b"local t = {a=1,b=2} local n = 0 for k in pairs(t) do n = n + 1 end return n",
3904 b"0",
3905 ]),
3906 ":2\r\n"
3907 );
3908 assert_eq!(
3909 f.run(&[b"EVAL", b"return tostring(next({}))", b"0"]),
3910 "$3\r\nnil\r\n"
3911 );
3912 assert_eq!(
3913 f.run(&[
3914 b"EVAL",
3915 b"local f for k, v in pairs(string) do if k == 'sub' then f = v end end \
3916 return type(f)",
3917 b"0",
3918 ]),
3919 "$8\r\nfunction\r\n"
3920 );
3921 }
3922
3923 #[test]
3924 fn a_script_gets_the_bit_library_a_real_server_carries() {
3925 let mut f = Fixture::new();
3926 for (body, want) in [
3929 ("bit.tobit(1)", ":1\r\n"),
3930 ("bit.tobit(2^32 + 1)", ":1\r\n"),
3931 ("bit.tobit(2^31)", ":-2147483648\r\n"),
3932 ("bit.tobit(0xffffffff)", ":-1\r\n"),
3933 ("bit.tobit(1.5)", ":2\r\n"),
3935 ("bit.tobit(2.5)", ":2\r\n"),
3936 ("bit.bnot(0)", ":-1\r\n"),
3937 ("bit.band(0xff, 0x0f)", ":15\r\n"),
3938 ("bit.band(1, 2, 3)", ":0\r\n"),
3939 ("bit.bor(1, 2, 4)", ":7\r\n"),
3940 ("bit.bxor(0xff, 0x0f)", ":240\r\n"),
3941 ("bit.lshift(1, 31)", ":-2147483648\r\n"),
3943 ("bit.lshift(1, 32)", ":1\r\n"),
3944 ("bit.lshift(1, 33)", ":2\r\n"),
3945 ("bit.rshift(-1, 1)", ":2147483647\r\n"),
3946 ("bit.arshift(-1, 1)", ":-1\r\n"),
3947 ("bit.rol(0x12345678, 8)", ":878082066\r\n"),
3948 ("bit.ror(0x12345678, 8)", ":2014458966\r\n"),
3949 ("bit.bswap(0x12345678)", ":2018915346\r\n"),
3950 ("bit.tobit('0x10')", ":16\r\n"),
3953 ] {
3954 let script = format!("return {body}");
3955 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
3956 }
3957 for (body, want) in [
3960 ("bit.tohex(1)", "00000001"),
3961 ("bit.tohex(-1)", "ffffffff"),
3962 ("bit.tohex(255, 2)", "ff"),
3963 ("bit.tohex(255, -8)", "000000FF"),
3964 ("bit.tohex(0x87654321, 4)", "4321"),
3965 ("bit.tohex(1, 0)", ""),
3966 ("bit.tohex(1, 9)", "00000001"),
3967 ] {
3968 let script = format!("return {body}");
3969 assert_eq!(
3970 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
3971 format!("${}\r\n{want}\r\n", want.len()),
3972 "{body}",
3973 );
3974 }
3975 for (body, want) in [
3978 (
3979 "return bit.band()",
3980 "bad argument #1 to 'band' (number expected, got no value)",
3981 ),
3982 (
3983 "return bit.band('x')",
3984 "bad argument #1 to 'band' (number expected, got string)",
3985 ),
3986 (
3987 "return bit.tobit(true)",
3988 "bad argument #1 to 'tobit' (number expected, got boolean)",
3989 ),
3990 (
3991 "return bit.lshift(1)",
3992 "bad argument #2 to 'lshift' (number expected, got no value)",
3993 ),
3994 ] {
3995 let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
3996 assert!(
3997 reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
3998 "{body} gave {reply}",
3999 );
4000 }
4001 assert_eq!(
4004 f.run(&[
4005 b"EVAL",
4006 b"local ok, e = pcall(bit.band, 'x') return tostring(e)",
4007 b"0",
4008 ]),
4009 "$52\r\nbad argument #1 to '?' (number expected, got string)\r\n"
4010 );
4011 assert_eq!(
4013 f.run(&[
4014 b"EVAL",
4015 b"local t = {} for k in pairs(bit) do t[#t+1] = k end \
4016 table.sort(t) return table.concat(t, ' ')",
4017 b"0",
4018 ]),
4019 "$66\r\narshift band bnot bor bswap bxor lshift rol ror rshift tobit tohex\r\n"
4020 );
4021 for body in [&b"bit.band = 1"[..], b"rawset(bit, 'zz', 1)"] {
4022 assert!(
4023 f.run(&[b"EVAL", body, b"0"])
4024 .contains("Attempt to modify a readonly table script: "),
4025 "{body:?}",
4026 );
4027 }
4028 }
4029
4030 #[test]
4031 fn a_script_gets_the_cjson_library_a_real_server_carries() {
4032 let mut f = Fixture::new();
4033 for (body, want) in [
4037 ("cjson.encode(nil)", "null"),
4038 ("cjson.encode(true)", "true"),
4039 ("cjson.encode(cjson.null)", "null"),
4040 ("cjson.encode(100)", "100"),
4041 ("cjson.encode(1/3)", "0.33333333333333"),
4042 ("cjson.encode(1e300)", "1e+300"),
4043 ("cjson.encode(2^53)", "9.007199254741e+15"),
4044 ("cjson.encode({})", "{}"),
4045 ("cjson.encode({1,2,3})", "[1,2,3]"),
4046 ("cjson.encode({a=1})", "{\"a\":1}"),
4047 ("cjson.encode({[1]=1,[3]=3})", "[1,null,3]"),
4048 ("cjson.encode({[0]=1})", "{\"0\":1}"),
4049 ("cjson.encode('a\\nb')", "\"a\\nb\""),
4050 ("cjson.encode('\\t\\\\')", "\"\\t\\\\\""),
4052 ("cjson.encode('\\11')", "\"\\u000b\""),
4053 (
4056 "cjson.encode(cjson.decode('[1,[2,{\"a\":null}]]'))",
4057 "[1,[2,{\"a\":null}]]",
4058 ),
4059 ("cjson.encode(cjson.decode('[]'))", "{}"),
4062 ] {
4063 let script = format!("return {body}");
4064 assert_eq!(
4065 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
4066 format!("${}\r\n{want}\r\n", want.len()),
4067 "{body}",
4068 );
4069 }
4070 for (body, want) in [
4073 ("cjson.decode('[1,2,3]')[2]", ":2\r\n"),
4074 ("cjson.decode('{\"a\":41}').a + 1", ":42\r\n"),
4075 ("cjson.decode('0x10')", ":16\r\n"),
4076 ("cjson.decode('+1')", ":1\r\n"),
4077 ("cjson.decode('01')", ":1\r\n"),
4078 ("cjson.decode(1) + 1", ":2\r\n"),
4079 ("cjson.decode([[\"\\u0041\"]]) == 'A' and 1 or 0", ":1\r\n"),
4081 ("cjson.decode('null') == cjson.null and 1 or 0", ":1\r\n"),
4082 ("cjson.decode('null') == nil and 1 or 0", ":0\r\n"),
4083 ] {
4084 let script = format!("return {body}");
4085 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
4086 }
4087 for (body, want) in [
4089 (
4090 "cjson.encode_number_precision(3) return cjson.encode(1/3)",
4091 "0.333",
4092 ),
4093 (
4094 "cjson.encode_invalid_numbers('null') return cjson.encode(1/0)",
4095 "null",
4096 ),
4097 (
4098 "cjson.encode_invalid_numbers(true) return cjson.encode(1/0)",
4099 "inf",
4100 ),
4101 (
4102 "cjson.encode_sparse_array(true) return cjson.encode({[1]=1,[100]=1})",
4103 "{\"1\":1,\"100\":1}",
4104 ),
4105 (
4106 "cjson.decode_array_with_array_mt(true) return cjson.encode(cjson.decode('[]'))",
4107 "[]",
4108 ),
4109 ("return tostring(cjson.encode_max_depth())", "1000"),
4110 ("return tostring(cjson.encode_keep_buffer(false))", "false"),
4111 ("return tostring(cjson.encode_sparse_array())", "false"),
4112 ("return tostring(cjson.encode_number_precision())", "14"),
4115 ] {
4116 assert_eq!(
4117 f.run(&[b"EVAL", body.as_bytes(), b"0"]),
4118 format!("${}\r\n{want}\r\n", want.len()),
4119 "{body}",
4120 );
4121 }
4122 for (body, want) in [
4124 (
4125 "return cjson.encode(1/0)",
4126 "Cannot serialise number: must not be NaN or Inf",
4127 ),
4128 (
4129 "return cjson.encode({[1]=1,[100]=1})",
4130 "Cannot serialise table: excessively sparse array",
4131 ),
4132 (
4133 "return cjson.encode({[true]=1})",
4134 "Cannot serialise boolean: table key must be a number or string",
4135 ),
4136 (
4137 "return cjson.encode(tostring)",
4138 "Cannot serialise function: type not supported",
4139 ),
4140 (
4141 "return cjson.encode()",
4142 "bad argument #1 to 'encode' (expected 1 argument)",
4143 ),
4144 (
4145 "return cjson.decode('[1,2')",
4146 "Expected comma or array end but found T_END at character 5",
4147 ),
4148 (
4149 "return cjson.decode('{\"a\" 1}')",
4150 "Expected colon but found T_NUMBER at character 6",
4151 ),
4152 (
4153 "return cjson.decode('tru')",
4154 "Expected value but found invalid token at character 1",
4155 ),
4156 (
4157 "return cjson.decode('[1] 2')",
4158 "Expected the end but found T_NUMBER at character 5",
4159 ),
4160 (
4161 "return cjson.encode_max_depth(0)",
4162 "bad argument #1 to 'encode_max_depth' (expected integer between 1 and 2147483647)",
4163 ),
4164 (
4165 "return cjson.encode_invalid_numbers('yes')",
4166 "bad argument #1 to 'encode_invalid_numbers' (invalid option 'yes')",
4167 ),
4168 (
4169 "return cjson.encode_max_depth(1, 2)",
4170 "bad argument #2 to 'encode_max_depth' (found too many arguments)",
4171 ),
4172 ] {
4173 let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
4174 assert!(
4175 reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
4176 "{body} gave {reply}",
4177 );
4178 }
4179 assert_eq!(
4182 f.run(&[
4183 b"EVAL",
4184 b"local n = cjson.new() n.encode_number_precision(3) \
4185 return cjson.encode(1/3) .. ' ' .. n.encode(1/3)",
4186 b"0",
4187 ]),
4188 "$22\r\n0.33333333333333 0.333\r\n"
4189 );
4190 let names = "_NAME _VERSION decode decode_array_with_array_mt decode_invalid_numbers \
4192 decode_max_depth encode encode_invalid_numbers encode_keep_buffer \
4193 encode_max_depth encode_number_precision encode_sparse_array new null";
4194 assert_eq!(
4195 f.run(&[
4196 b"EVAL",
4197 b"local t = {} for k in pairs(cjson) do t[#t+1] = k end \
4198 table.sort(t) return table.concat(t, ' ')",
4199 b"0",
4200 ]),
4201 format!("${}\r\n{names}\r\n", names.len())
4202 );
4203 for body in [&b"cjson.encode = 1"[..], b"rawset(cjson, 'zz', 1)"] {
4204 assert!(
4205 f.run(&[b"EVAL", body, b"0"])
4206 .contains("Attempt to modify a readonly table script: "),
4207 "{body:?}",
4208 );
4209 }
4210 }
4211
4212 #[test]
4213 fn a_script_gets_the_struct_library_a_real_server_carries() {
4214 let mut f = Fixture::new();
4215 for (body, want) in [
4218 ("#struct.pack('i4', 1)", ":4\r\n"),
4219 ("#struct.pack('l', 1)", ":8\r\n"),
4220 ("#struct.pack('d', 1)", ":8\r\n"),
4221 ("#struct.pack('f', 1)", ":4\r\n"),
4222 ("#struct.pack('s', 'abc')", ":4\r\n"),
4223 ("#struct.pack('c3', 'abcdef')", ":3\r\n"),
4224 ("#struct.pack('x')", ":1\r\n"),
4225 ("string.byte(struct.pack('i4', 1), 1)", ":1\r\n"),
4226 ("string.byte(struct.pack('>i4', 1), 4)", ":1\r\n"),
4227 ("string.byte(struct.pack('<i4', 1), 1)", ":1\r\n"),
4228 ("string.byte(struct.pack('i16', -1), 9)", ":0\r\n"),
4231 ("string.byte(struct.pack('i8', -1), 8)", ":255\r\n"),
4232 ("#struct.pack('c0', 'abcd')", ":4\r\n"),
4235 ("string.byte(struct.pack('s', 'a'), 2)", ":0\r\n"),
4236 ("string.byte(struct.pack('bxb', 1, 2), 2)", ":0\r\n"),
4237 ] {
4238 let script = format!("return {body}");
4239 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
4240 }
4241 for (body, want) in [
4244 ("struct.size('i')", ":4\r\n"),
4245 ("struct.size('l')", ":8\r\n"),
4246 ("struct.size('T')", ":8\r\n"),
4247 ("struct.size('h')", ":2\r\n"),
4248 ("struct.size('c10')", ":10\r\n"),
4249 ("struct.size('ic')", ":5\r\n"),
4250 ("struct.size('!8ic')", ":5\r\n"),
4251 ("struct.size('!4i')", ":4\r\n"),
4252 ("struct.size('bd')", ":9\r\n"),
4255 ("struct.size('!bd')", ":16\r\n"),
4256 ("struct.size('A')", ":0\r\n"),
4257 ("struct.size('7')", ":0\r\n"),
4258 ] {
4259 let script = format!("return {body}");
4260 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
4261 }
4262 for (body, want) in [
4265 ("select('#', struct.unpack('i4', '\\1\\0\\0\\0'))", ":2\r\n"),
4266 ("select(1, struct.unpack('i4', '\\1\\0\\0\\0'))", ":1\r\n"),
4267 ("select(2, struct.unpack('i4', '\\1\\0\\0\\0'))", ":5\r\n"),
4268 ("select(1, struct.unpack('i1', '\\255'))", ":-1\r\n"),
4269 ("select(1, struct.unpack('I1', '\\255'))", ":255\r\n"),
4270 (
4271 "select(1, struct.unpack('i4', struct.pack('i4', -70000)))",
4272 ":-70000\r\n",
4273 ),
4274 ("select(2, struct.unpack('i1', 'abc', 2))", ":3\r\n"),
4275 ("select('#', struct.unpack('bc0', '\\3abcd'))", ":2\r\n"),
4279 ("select(2, struct.unpack('bc0', '\\3abcd'))", ":5\r\n"),
4280 ] {
4281 let script = format!("return {body}");
4282 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
4283 }
4284 for (body, want) in [
4285 ("select(1, struct.unpack('bc0', '\\3abcd'))", "abc"),
4286 ("select(1, struct.unpack('s', 'ab\\0cd'))", "ab"),
4287 ("select(1, struct.unpack('c3', 'abcdef'))", "abc"),
4288 ] {
4289 let script = format!("return {body}");
4290 assert_eq!(
4291 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
4292 format!("${}\r\n{want}\r\n", want.len()),
4293 "{body}",
4294 );
4295 }
4296 for (body, want) in [
4299 (
4300 "return struct.pack()",
4301 "bad argument #1 to 'pack' (string expected, got no value)",
4302 ),
4303 (
4306 "return struct.pack('i4')",
4307 "bad argument #2 to 'pack' (number expected, got nil)",
4308 ),
4309 (
4312 "return struct.pack('c6', 'abc')",
4313 "bad argument #3 to 'pack' (string too short)",
4314 ),
4315 (
4316 "return struct.pack('A', 'x')",
4317 "bad argument #1 to 'pack' (invalid format option 'A')",
4318 ),
4319 (
4320 "return struct.pack('i33', 1)",
4321 "integral size 33 is larger than limit of 32",
4322 ),
4323 (
4324 "return struct.pack('!3i', 1)",
4325 "alignment 3 is not a power of 2",
4326 ),
4327 (
4328 "return struct.unpack()",
4329 "bad argument #1 to 'unpack' (string expected, got no value)",
4330 ),
4331 (
4332 "return struct.unpack('i4')",
4333 "bad argument #2 to 'unpack' (string expected, got no value)",
4334 ),
4335 (
4336 "return struct.unpack('i4', 'ab')",
4337 "bad argument #2 to 'unpack' (data string too short)",
4338 ),
4339 (
4340 "return struct.unpack('i1', 'abc', 0)",
4341 "bad argument #3 to 'unpack' (offset must be 1 or greater)",
4342 ),
4343 (
4344 "return struct.unpack('c0', 'abc')",
4345 "format 'c0' needs a previous size",
4346 ),
4347 (
4348 "return struct.unpack('s', 'abc')",
4349 "unfinished string in data",
4350 ),
4351 (
4352 "return struct.size()",
4353 "bad argument #1 to 'size' (string expected, got no value)",
4354 ),
4355 (
4356 "return struct.size('s')",
4357 "bad argument #1 to 'size' (option 's' has no fixed size)",
4358 ),
4359 (
4360 "return struct.size('c0')",
4361 "bad argument #1 to 'size' (option 'c0' has no fixed size)",
4362 ),
4363 ] {
4364 let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
4365 assert!(
4366 reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
4367 "{body} gave {reply}",
4368 );
4369 }
4370 let names = "pack size unpack";
4372 assert_eq!(
4373 f.run(&[
4374 b"EVAL",
4375 b"local t = {} for k in pairs(struct) do t[#t+1] = k end \
4376 table.sort(t) return table.concat(t, ' ')",
4377 b"0",
4378 ]),
4379 format!("${}\r\n{names}\r\n", names.len())
4380 );
4381 for body in [&b"struct.pack = 1"[..], b"rawset(struct, 'zz', 1)"] {
4382 assert!(
4383 f.run(&[b"EVAL", body, b"0"])
4384 .contains("Attempt to modify a readonly table script: "),
4385 "{body:?}",
4386 );
4387 }
4388 }
4389
4390 #[test]
4391 fn a_script_gets_the_cmsgpack_library_a_real_server_carries() {
4392 let mut f = Fixture::new();
4393 let hex = "local function hx(s) return (string.gsub(s, '.', \
4396 function(c) return string.format('%02x', string.byte(c)) end)) end ";
4397 for (body, want) in [
4398 ("cmsgpack.pack(nil)", "c0"),
4399 ("cmsgpack.pack(true)", "c3"),
4400 ("cmsgpack.pack(false)", "c2"),
4401 ("cmsgpack.pack(0)", "00"),
4402 ("cmsgpack.pack(127)", "7f"),
4403 ("cmsgpack.pack(128)", "cc80"),
4404 ("cmsgpack.pack(-1)", "ff"),
4405 ("cmsgpack.pack(-33)", "d0df"),
4406 ("cmsgpack.pack(65535)", "cdffff"),
4407 ("cmsgpack.pack(4294967296)", "cf0000000100000000"),
4408 ("cmsgpack.pack(2^53)", "cf0020000000000000"),
4409 ("cmsgpack.pack(-2^63)", "d38000000000000000"),
4410 ("cmsgpack.pack(2^64)", "ca5f800000"),
4413 ("cmsgpack.pack(1.5)", "ca3fc00000"),
4414 ("cmsgpack.pack(0.1)", "cb3fb999999999999a"),
4415 ("cmsgpack.pack('abc')", "a3616263"),
4416 ("cmsgpack.pack('')", "a0"),
4417 ("cmsgpack.pack({})", "90"),
4418 ("cmsgpack.pack({1, 2})", "920102"),
4419 ("cmsgpack.pack({a = 1})", "81a16101"),
4420 ("cmsgpack.pack(1, 'a', true)", "01a161c3"),
4421 (
4424 "(function() local t = {} local c = t \
4425 for i = 1, 20 do c.n = {} c = c.n end return cmsgpack.pack(t) end)()",
4426 "81a16e81a16e81a16e81a16e81a16e81a16e81a16e81a16e\
4427 81a16e81a16e81a16e81a16e81a16e81a16e81a16e81a16ec0",
4428 ),
4429 ] {
4430 let script = format!("{hex} return hx({body})");
4431 assert_eq!(
4432 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
4433 format!("${}\r\n{want}\r\n", want.len()),
4434 "{body}",
4435 );
4436 }
4437 for (body, want) in [
4441 ("cmsgpack.unpack(cmsgpack.pack(42))", 42),
4442 ("select('#', cmsgpack.unpack('\\1\\2\\3'))", 3),
4443 ("select(3, cmsgpack.unpack('\\1\\2\\3'))", 3),
4444 ("select('#', cmsgpack.unpack(''))", 0),
4445 ("select('#', cmsgpack.unpack_one('\\1\\2\\3'))", 2),
4446 ("select(1, cmsgpack.unpack_one('\\1\\2\\3'))", 1),
4447 ("select(2, cmsgpack.unpack_one('\\1\\2\\3'))", 1),
4448 ("select(1, cmsgpack.unpack_one('\\1\\2\\3', 2))", -1),
4449 ("select(1, cmsgpack.unpack_one('\\1'))", -1),
4450 ("select(1, cmsgpack.unpack_one('', 0))", -1),
4451 ("select('#', cmsgpack.unpack_limit('\\1\\2\\3', 2))", 3),
4452 ("select(1, cmsgpack.unpack_limit('\\1\\2\\3', 2))", 2),
4453 ("select('#', cmsgpack.unpack_limit('\\1\\2\\3', 0, 0))", 3),
4456 ("cmsgpack.unpack(cmsgpack.pack({1, 2, 3}))[2]", 2),
4457 ] {
4458 let script = format!("return {body}");
4459 assert_eq!(
4460 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
4461 format!(":{want}\r\n"),
4462 "{body}",
4463 );
4464 }
4465 for (body, want) in [
4466 ("cmsgpack.unpack(cmsgpack.pack({a = 'b'})).a", "b"),
4467 ("tostring(cmsgpack.unpack(cmsgpack.pack(1.5)))", "1.5"),
4468 ("tostring(cmsgpack.unpack(cmsgpack.pack(nil)))", "nil"),
4469 (
4470 "tostring(cmsgpack.unpack(string.char(0xcb, 0x7f, 0xf0, 0, 0, 0, 0, 0, 0)))",
4471 "inf",
4472 ),
4473 ("cmsgpack._NAME", "cmsgpack"),
4474 ("cmsgpack._VERSION", "lua-cmsgpack 0.4.0"),
4475 (
4476 "cmsgpack._COPYRIGHT",
4477 "Copyright (C) 2012, Salvatore Sanfilippo",
4478 ),
4479 (
4480 "cmsgpack._DESCRIPTION",
4481 "MessagePack C implementation for Lua",
4482 ),
4483 ] {
4484 let script = format!("return {body}");
4485 assert_eq!(
4486 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
4487 format!("${}\r\n{want}\r\n", want.len()),
4488 "{body}",
4489 );
4490 }
4491 for (body, want) in [
4492 (
4495 "return cmsgpack.pack()",
4496 "bad argument #0 to 'pack' (MessagePack pack needs input.)",
4497 ),
4498 (
4499 "return cmsgpack.unpack()",
4500 "bad argument #1 to 'unpack' (string expected, got no value)",
4501 ),
4502 (
4503 "return cmsgpack.unpack(string.char(193))",
4504 "Bad data format in input.",
4505 ),
4506 (
4507 "return cmsgpack.unpack(string.char(204))",
4508 "Missing bytes in input.",
4509 ),
4510 (
4511 "return cmsgpack.unpack(string.char(146, 1))",
4512 "Missing bytes in input.",
4513 ),
4514 (
4515 "return cmsgpack.unpack_one('\\1', 5)",
4516 "Start offset 5 greater than input length 1.",
4517 ),
4518 (
4519 "return cmsgpack.unpack_limit('\\1\\2', 1, 5)",
4520 "Start offset 5 greater than input length 2.",
4521 ),
4522 (
4525 "return cmsgpack.unpack_one('\\1', -1)",
4526 "Invalid request to unpack with offset of -1 and limit of 1.",
4527 ),
4528 (
4529 "return cmsgpack.unpack_limit('\\1', -1, 0)",
4530 "Invalid request to unpack with offset of 0 and limit of 1.",
4531 ),
4532 ] {
4533 let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
4534 assert!(
4535 reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
4536 "{body} gave {reply}",
4537 );
4538 }
4539 let names = "_COPYRIGHT _DESCRIPTION _NAME _VERSION pack unpack unpack_limit unpack_one";
4541 assert_eq!(
4542 f.run(&[
4543 b"EVAL",
4544 b"local t = {} for k in pairs(cmsgpack) do t[#t+1] = k end \
4545 table.sort(t) return table.concat(t, ' ')",
4546 b"0",
4547 ]),
4548 format!("${}\r\n{names}\r\n", names.len())
4549 );
4550 for body in [&b"cmsgpack.pack = 1"[..], b"rawset(cmsgpack, 'zz', 1)"] {
4551 assert!(
4552 f.run(&[b"EVAL", body, b"0"])
4553 .contains("Attempt to modify a readonly table script: "),
4554 "{body:?}",
4555 );
4556 }
4557 assert_eq!(
4560 f.run(&[
4561 b"EVAL",
4562 b"return cmsgpack.unpack(cmsgpack.pack(cmsgpack))._NAME",
4563 b"0",
4564 ]),
4565 "$8\r\ncmsgpack\r\n"
4566 );
4567 }
4568
4569 const LIB: &[u8] = b"#!lua name=mylib\n\
4575 local counter = 0\n\
4576 redis.register_function{function_name = 'ping', description = 'says pong',\n\
4577 callback = function(keys, args) return 'pong' end, flags = {'no-writes'}}\n\
4578 redis.register_function('count', function() counter = counter + 1 return counter end)\n\
4579 redis.register_function('echo', function(keys, args) return {keys, args} end)\n\
4580 redis.register_function('setit', function(keys, args) \
4581 return redis.call('SET', keys[1], args[1]) end)\n\
4582 redis.register_function('raise', function() error('boom') end)\n";
4583
4584 const OTHER: &[u8] = b"#!lua name=other\n\
4586 redis.register_function('twice', function(keys, args) return 2 end)\n";
4587
4588 #[test]
4589 fn a_library_is_loaded_once_and_called_by_name_forever_after() {
4590 let mut f = Fixture::new();
4591 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
4592 assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
4593 assert_eq!(f.run(&[b"FCALL", b"PiNg", b"0"]), "$4\r\npong\r\n");
4596 assert_eq!(
4600 f.run(&[b"FCALL", b"echo", b"1", b"k", b"a", b"b"]),
4601 "*2\r\n*1\r\n$1\r\nk\r\n*2\r\n$1\r\na\r\n$1\r\nb\r\n"
4602 );
4603 assert_eq!(f.run(&[b"FCALL", b"setit", b"1", b"s", b"v"]), "+OK\r\n");
4604 assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nv\r\n");
4605 assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":1\r\n");
4608 assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":2\r\n");
4609 assert_eq!(
4612 f.run(&[b"FCALL", b"raise", b"0"]),
4613 "-ERR user_function:8: boom script: raise, on @user_function:8.\r\n"
4614 );
4615 assert_eq!(
4618 f.run(&[b"FUNCTION", b"DELETE", b"MYLIB"]),
4619 "-ERR Library not found\r\n"
4620 );
4621 assert_eq!(f.run(&[b"FUNCTION", b"DELETE", b"mylib"]), "+OK\r\n");
4622 assert_eq!(
4623 f.run(&[b"FCALL", b"ping", b"0"]),
4624 "-ERR Function not found\r\n"
4625 );
4626 }
4627
4628 #[test]
4629 fn a_library_that_is_wrong_says_which_way_it_is_wrong() {
4630 let mut f = Fixture::new();
4631 for (code, want) in [
4632 (&b"return 1"[..], "ERR Missing library metadata"),
4633 (b"#!lua name=x", "ERR Invalid library metadata"),
4634 (b"#!\n", "ERR Library name was not given"),
4635 (b"#!lua\nx", "ERR Library name was not given"),
4636 (
4637 b"#!lua name=a name=b\nx",
4638 "ERR Invalid metadata value, name argument was given multiple times",
4639 ),
4640 (
4641 b"#!lua nome=a\nx",
4642 "ERR Invalid metadata value given: nome=a",
4643 ),
4644 (b"#!lua name=\"q\nx", "ERR Invalid library metadata"),
4645 (
4646 b"#!lua name=a-b\nx",
4647 "ERR Library names can only contain letters, numbers, or underscores(_) \
4648 and must be at least one character long",
4649 ),
4650 (b"#!zz name=x\nx", "ERR Engine 'zz' not found"),
4651 (
4652 b"#!lua name=c\nthis is not lua",
4653 "ERR Error compiling function: user_function:2: '=' expected near 'is'",
4654 ),
4655 (
4659 b"#!lua name=r\nerror('boom')",
4660 "ERR Error registering functions: ERR user_function:2: \
4661 Script attempted to access nonexistent global variable 'error'",
4662 ),
4663 (
4666 b"#!lua name=r\nredis.call('PING')",
4667 "ERR Error registering functions: ERR user_function:2: \
4668 Script attempted to access nonexistent global variable 'call'",
4669 ),
4670 (
4671 b"#!lua name=r\nx = 1",
4672 "ERR Error registering functions: ERR user_function:2: \
4673 Attempt to modify a readonly table",
4674 ),
4675 (b"#!lua name=n\nlocal x = 1", "ERR No functions registered"),
4676 ] {
4677 assert_eq!(
4678 f.run(&[b"FUNCTION", b"LOAD", code]),
4679 format!("-{want}\r\n"),
4680 "{}",
4681 String::from_utf8_lossy(code),
4682 );
4683 }
4684 }
4685
4686 #[test]
4687 fn register_function_turns_away_every_call_it_cannot_make_sense_of() {
4688 let mut f = Fixture::new();
4689 for (call, want) in [
4690 (
4691 &b"redis.register_function()"[..],
4692 "wrong number of arguments to redis.register_function",
4693 ),
4694 (
4695 b"redis.register_function('a', function() end, 1)",
4696 "wrong number of arguments to redis.register_function",
4697 ),
4698 (
4699 b"redis.register_function('a')",
4700 "calling redis.register_function with a single argument is only \
4701 applicable to Lua table (representing named arguments).",
4702 ),
4703 (
4704 b"redis.register_function({foo = 'a'})",
4705 "unknown argument given to redis.register_function",
4706 ),
4707 (
4708 b"redis.register_function({callback = function() end})",
4709 "redis.register_function must get a function name argument",
4710 ),
4711 (
4712 b"redis.register_function({function_name = 'a'})",
4713 "redis.register_function must get a callback argument",
4714 ),
4715 (
4716 b"redis.register_function({function_name = {}, callback = function() end})",
4717 "function_name argument given to redis.register_function must be a string",
4718 ),
4719 (
4720 b"redis.register_function({function_name = 'a', description = {}, \
4721 callback = function() end})",
4722 "description argument given to redis.register_function must be a string",
4723 ),
4724 (
4725 b"redis.register_function({function_name = 'a', callback = 1})",
4726 "callback argument given to redis.register_function must be a function",
4727 ),
4728 (
4729 b"redis.register_function({function_name = 'a', callback = function() end, \
4730 flags = 1})",
4731 "flags argument to redis.register_function must be a table \
4732 representing function flags",
4733 ),
4734 (
4735 b"redis.register_function({function_name = 'a', callback = function() end, \
4736 flags = {'zz'}})",
4737 "unknown flag given",
4738 ),
4739 (
4740 b"redis.register_function({}, function() end)",
4741 "first argument to redis.register_function must be a string",
4742 ),
4743 (
4744 b"redis.register_function('a', 1)",
4745 "second argument to redis.register_function must be a function",
4746 ),
4747 (
4748 b"redis.register_function('a-b', function() end)",
4749 "Library names can only contain letters, numbers, or underscores(_) \
4750 and must be at least one character long",
4751 ),
4752 (
4753 b"redis.register_function('d', function() end) \
4754 redis.register_function('d', function() end)",
4755 "Function already exists in the library",
4756 ),
4757 ] {
4758 let mut code = b"#!lua name=e\n".to_vec();
4759 code.extend_from_slice(call);
4760 assert_eq!(
4764 f.run(&[b"FUNCTION", b"LOAD", &code]),
4765 format!("-ERR Error registering functions: ERR {want}\r\n"),
4766 "{}",
4767 String::from_utf8_lossy(call),
4768 );
4769 }
4770 assert_eq!(
4773 f.run(&[
4774 b"FUNCTION",
4775 b"LOAD",
4776 b"#!lua name=n\nredis.register_function(12, function() return 1 end)",
4777 ]),
4778 "$1\r\nn\r\n"
4779 );
4780 assert_eq!(f.run(&[b"FCALL", b"12", b"0"]), ":1\r\n");
4781 assert_eq!(
4784 f.run(&[
4785 b"FUNCTION",
4786 b"LOAD",
4787 b"#!lua name=c\nredis.register_function('d', function() return 1 end) \
4788 redis.register_function('D', function() return 2 end)",
4789 ]),
4790 "$1\r\nc\r\n"
4791 );
4792 }
4793
4794 #[test]
4795 fn a_library_cannot_take_a_name_another_library_already_has() {
4796 let mut f = Fixture::new();
4797 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
4798 assert_eq!(
4799 f.run(&[b"FUNCTION", b"LOAD", LIB]),
4800 "-ERR Library 'mylib' already exists\r\n"
4801 );
4802 assert_eq!(
4806 f.run(&[
4807 b"FUNCTION",
4808 b"LOAD",
4809 b"#!lua name=other\nredis.register_function('PING', function() return 1 end)",
4810 ]),
4811 "-ERR Function PING already exists\r\n"
4812 );
4813 assert_eq!(
4816 f.run(&[b"FUNCTION", b"LOAD", b"REPLACE", LIB]),
4817 "$5\r\nmylib\r\n"
4818 );
4819 assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":1\r\n");
4822 assert_eq!(
4823 f.run(&[b"FUNCTION", b"LOAD", b"NOPE", LIB]),
4824 "-ERR Unknown option given: NOPE\r\n"
4825 );
4826 assert_eq!(
4829 f.run(&[b"FUNCTION", b"LOAD", b"REPLACE"]),
4830 "-ERR Missing library metadata\r\n"
4831 );
4832 assert_eq!(
4833 f.run(&[b"FUNCTION", b"LOAD"]),
4834 "-ERR wrong number of arguments for 'function|load' command\r\n"
4835 );
4836 }
4837
4838 #[test]
4839 fn fcall_checks_the_name_before_it_looks_at_anything_else() {
4840 let mut f = Fixture::new();
4841 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
4842 for (args, want) in [
4843 (&[&b"nosuch"[..], b"x"][..], "ERR Function not found"),
4844 (&[b"ping", b"x"], "ERR Bad number of keys provided"),
4845 (&[b"ping", b"1.5"], "ERR Bad number of keys provided"),
4846 (&[b"ping", b"+1"], "ERR Bad number of keys provided"),
4847 (
4848 &[b"ping", b"99999999999999999999"],
4849 "ERR Bad number of keys provided",
4850 ),
4851 (
4852 &[b"ping", b"3", b"a"],
4853 "ERR Number of keys can't be greater than number of args",
4854 ),
4855 (&[b"ping", b"-1"], "ERR Number of keys can't be negative"),
4856 ] {
4857 let mut wire: Vec<&[u8]> = vec![b"FCALL"];
4858 wire.extend_from_slice(args);
4859 assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
4860 }
4861 assert_eq!(
4864 f.run(&[b"FCALL_RO", b"setit", b"1", b"s", b"v"]),
4865 "-ERR Can not execute a script with write flag using *_ro command.\r\n"
4866 );
4867 assert_eq!(f.run(&[b"FCALL_RO", b"ping", b"0"]), "$4\r\npong\r\n");
4868 assert_eq!(
4869 f.run(&[b"FCALL_RO", b"nosuch", b"0"]),
4870 "-ERR Function not found\r\n"
4871 );
4872 assert_eq!(
4875 f.run(&[
4876 b"FUNCTION",
4877 b"LOAD",
4878 b"#!lua name=w\nredis.register_function{function_name = 'w', \
4879 flags = {'no-writes'}, callback = function(keys) \
4880 return redis.call('SET', keys[1], 'x') end}",
4881 ]),
4882 "$1\r\nw\r\n"
4883 );
4884 assert!(
4885 f.run(&[b"FCALL", b"w", b"1", b"k"])
4886 .starts_with("-ERR Write commands are not allowed from read-only scripts."),
4887 );
4888 }
4889
4890 #[test]
4891 fn a_function_gets_the_globals_a_script_gets_minus_the_ones_only_eval_has() {
4892 let mut f = Fixture::new();
4893 let names = "LOG_DEBUG LOG_NOTICE LOG_VERBOSE LOG_WARNING REDIS_VERSION \
4896 REDIS_VERSION_NUM REPL_ALL REPL_AOF REPL_NONE REPL_REPLICA REPL_SLAVE \
4897 acl_check_cmd call error_reply log pcall set_repl setresp sha1hex \
4898 status_reply";
4899 let globals = "_G _VERSION assert bit cjson cmsgpack collectgarbage coroutine error \
4900 gcinfo getmetatable ipairs load loadstring math next os pairs pcall \
4901 rawequal rawget rawset redis select setmetatable string struct table \
4902 tonumber tostring type unpack xpcall";
4903 assert_eq!(
4904 f.run(&[
4905 b"FUNCTION",
4906 b"LOAD",
4907 b"#!lua name=g\n\
4908 local function sorted(t) local o = {} for k in pairs(t) do o[#o+1] = k end \
4909 table.sort(o) return table.concat(o, ' ') end\n\
4910 redis.register_function('names', function() return sorted(redis) end)\n\
4911 redis.register_function('globals', function() return sorted(_G) end)\n\
4912 redis.register_function('keysg', function() return KEYS[1] end)\n\
4913 redis.register_function('zzz', function() return tostring(redis.zzz) end)\n\
4914 redis.register_function('wr', function() rawset(_G, 'x', 1) end)\n\
4915 redis.register_function('gwr', function() _G.pcall = 1 end)\n",
4916 ]),
4917 "$1\r\ng\r\n"
4918 );
4919 assert_eq!(
4920 f.run(&[b"FCALL", b"names", b"0"]),
4921 format!("${}\r\n{names}\r\n", names.len())
4922 );
4923 assert_eq!(
4924 f.run(&[b"FCALL", b"globals", b"0"]),
4925 format!("${}\r\n{globals}\r\n", globals.len())
4926 );
4927 assert!(
4930 f.run(&[b"FCALL", b"keysg", b"1", b"k"])
4931 .contains("nonexistent global variable 'KEYS'"),
4932 );
4933 assert_eq!(f.run(&[b"FCALL", b"zzz", b"0"]), "$3\r\nnil\r\n");
4936 for name in [&b"wr"[..], b"gwr"] {
4939 assert!(
4940 f.run(&[b"FCALL", name, b"0"])
4941 .contains("Attempt to modify a readonly table"),
4942 "{}",
4943 String::from_utf8_lossy(name),
4944 );
4945 }
4946 }
4947
4948 #[test]
4949 fn function_list_says_what_every_library_registered() {
4950 let mut f = Fixture::new();
4951 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
4952 f.out = Out::new(Proto::Resp3);
4955 let listed = f.run(&[b"FUNCTION", b"LIST"]);
4956 assert!(listed.starts_with("*1\r\n%3\r\n$12\r\nlibrary_name\r\n$5\r\nmylib\r\n"));
4957 assert!(listed.contains("$6\r\nengine\r\n$3\r\nLUA\r\n"));
4958 assert!(listed.contains(
4959 "%3\r\n$4\r\nname\r\n$4\r\nping\r\n\
4960 $11\r\ndescription\r\n$9\r\nsays pong\r\n$5\r\nflags\r\n~1\r\n+no-writes\r\n"
4961 ));
4962 assert!(listed.contains(
4965 "$4\r\nname\r\n$5\r\ncount\r\n$11\r\ndescription\r\n_\r\n$5\r\nflags\r\n~0\r\n"
4966 ));
4967 assert!(!listed.contains("library_code"));
4968 assert!(
4969 f.run(&[b"FUNCTION", b"LIST", b"WITHCODE"])
4970 .contains("library_code")
4971 );
4972 assert!(
4975 f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"MY*"])
4976 .starts_with("*1\r\n")
4977 );
4978 assert_eq!(
4979 f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"zz*"]),
4980 "*0\r\n"
4981 );
4982 f.out = Out::new(Proto::Resp2);
4985 assert!(f.run(&[b"FUNCTION", b"LIST"]).starts_with("*1\r\n*6\r\n"));
4986 for (args, want) in [
4987 (&[&b"ZZ"[..]][..], "ERR Unknown argument ZZ"),
4988 (&[b"WITHCODE", b"WITHCODE"], "ERR Unknown argument WITHCODE"),
4989 (
4990 &[b"LIBRARYNAME", b"a", b"LIBRARYNAME", b"b"],
4991 "ERR Unknown argument LIBRARYNAME",
4992 ),
4993 (&[b"LIBRARYNAME"], "ERR library name argument was not given"),
4994 ] {
4995 let mut wire: Vec<&[u8]> = vec![b"FUNCTION", b"LIST"];
4996 wire.extend_from_slice(args);
4997 assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
4998 }
4999 }
5000
5001 #[test]
5002 fn function_stats_counts_what_is_loaded_and_says_nothing_is_running() {
5003 let mut f = Fixture::new();
5004 f.out = Out::new(Proto::Resp3);
5005 assert_eq!(
5006 f.run(&[b"FUNCTION", b"STATS"]),
5007 "%2\r\n$14\r\nrunning_script\r\n_\r\n$7\r\nengines\r\n%1\r\n$3\r\nLUA\r\n\
5008 %2\r\n$15\r\nlibraries_count\r\n:0\r\n$15\r\nfunctions_count\r\n:0\r\n"
5009 );
5010 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5011 assert!(
5012 f.run(&[b"FUNCTION", b"STATS"])
5013 .ends_with("libraries_count\r\n:1\r\n$15\r\nfunctions_count\r\n:5\r\n"),
5014 );
5015 assert_eq!(f.run(&[b"FUNCTION", b"FLUSH"]), "+OK\r\n");
5016 assert!(
5017 f.run(&[b"FUNCTION", b"STATS"])
5018 .ends_with(":0\r\n$15\r\nfunctions_count\r\n:0\r\n")
5019 );
5020 }
5021
5022 #[test]
5023 fn every_function_subcommand_complains_about_its_own_arity() {
5024 let mut f = Fixture::new();
5025 for (args, want) in [
5026 (
5027 &[&b"STATS"[..], b"X"][..],
5028 "ERR wrong number of arguments for 'function|stats' command",
5029 ),
5030 (
5031 &[b"KILL", b"X"],
5032 "ERR wrong number of arguments for 'function|kill' command",
5033 ),
5034 (
5035 &[b"HELP", b"X"],
5036 "ERR wrong number of arguments for 'function|help' command",
5037 ),
5038 (
5039 &[b"DELETE"],
5040 "ERR wrong number of arguments for 'function|delete' command",
5041 ),
5042 (
5043 &[b"DELETE", b"a", b"b"],
5044 "ERR wrong number of arguments for 'function|delete' command",
5045 ),
5046 (
5047 &[b"DUMP", b"X"],
5048 "ERR wrong number of arguments for 'function|dump' command",
5049 ),
5050 (
5051 &[b"RESTORE"],
5052 "ERR wrong number of arguments for 'function|restore' command",
5053 ),
5054 (
5057 &[b"RESTORE", b"a", b"FLUSH", b"X"],
5058 "ERR unknown subcommand or wrong number of arguments for 'RESTORE'. \
5059 Try FUNCTION HELP.",
5060 ),
5061 (
5062 &[b"RESTORE", b"a", b"ZZ"],
5063 "ERR Wrong restore policy given, value should be either FLUSH, APPEND \
5064 or REPLACE.",
5065 ),
5066 (
5069 &[b"FLUSH", b"SYNC", b"X"],
5070 "ERR unknown subcommand or wrong number of arguments for 'FLUSH'. \
5071 Try FUNCTION HELP.",
5072 ),
5073 (
5074 &[b"FLUSH", b"ZZ"],
5075 "ERR FUNCTION FLUSH only supports SYNC|ASYNC option",
5076 ),
5077 (&[b"ZZ"], "ERR unknown subcommand 'ZZ'. Try FUNCTION HELP."),
5078 ] {
5079 let mut wire: Vec<&[u8]> = vec![b"FUNCTION"];
5080 wire.extend_from_slice(args);
5081 assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
5082 }
5083 assert_eq!(
5084 f.run(&[b"FUNCTION"]),
5085 "-ERR wrong number of arguments for 'function' command\r\n"
5086 );
5087 assert_eq!(
5088 f.run(&[b"FUNCTION", b"KILL"]),
5089 "-NOTBUSY No scripts in execution right now.\r\n"
5090 );
5091 }
5092
5093 #[test]
5099 fn a_library_survives_a_dump_and_a_restore() {
5100 let mut f = Fixture::new();
5101 let empty = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
5102 assert_eq!(empty.len(), 10);
5103 assert_eq!(f.run(&[b"FUNCTION", b"RESTORE", &empty]), "+OK\r\n");
5104
5105 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5106 let full = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
5107 assert!(full.len() > empty.len());
5108
5109 assert_eq!(
5112 f.run(&[b"FUNCTION", b"RESTORE", &full]),
5113 "-ERR Library mylib already exists\r\n"
5114 );
5115 assert_eq!(
5116 f.run(&[b"FUNCTION", b"RESTORE", &full, b"REPLACE"]),
5117 "+OK\r\n"
5118 );
5119 assert_eq!(
5120 f.run(&[b"FUNCTION", b"RESTORE", &full, b"FLUSH"]),
5121 "+OK\r\n"
5122 );
5123 assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
5125
5126 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", OTHER]), "$5\r\nother\r\n");
5129 assert_eq!(
5130 f.run(&[b"FUNCTION", b"RESTORE", &full, b"FLUSH"]),
5131 "+OK\r\n"
5132 );
5133 assert_eq!(
5134 f.run(&[b"FUNCTION", b"DELETE", b"other"]),
5135 "-ERR Library not found\r\n"
5136 );
5137 }
5138
5139 #[test]
5146 fn a_restore_that_fails_changes_nothing() {
5147 let mut f = Fixture::new();
5148 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5149 let good = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
5150
5151 let reseal = |body: &[u8], version: u16| {
5155 let mut out = body.to_vec();
5156 out.extend_from_slice(&version.to_le_bytes());
5157 let crc = yo_common::crc::crc64(0, &out);
5158 out.extend_from_slice(&crc.to_le_bytes());
5159 out
5160 };
5161 let body = &good[..good.len() - 10];
5162
5163 let mut torn = good.clone();
5164 let n = torn.len();
5165 torn[n - 1] ^= 0xff;
5166 let future = reseal(body, 999);
5167 let mut pre_ga = body.to_vec();
5170 pre_ga[0] = 246;
5171 let pre_ga = reseal(&pre_ga, yo_kv::rdb::VERSION);
5172 let mut other = body.to_vec();
5173 other[0] = 0;
5174 let other = reseal(&other, yo_kv::rdb::VERSION);
5175 let mut cut = body.to_vec();
5177 cut.truncate(body.len() - 1);
5178 let cut = reseal(&cut, yo_kv::rdb::VERSION);
5179
5180 for (bytes, want) in [
5181 (vec![], "ERR DUMP payload version or checksum are wrong"),
5182 (
5183 b"0123456789".to_vec(),
5184 "ERR DUMP payload version or checksum are wrong",
5185 ),
5186 (torn, "ERR DUMP payload version or checksum are wrong"),
5187 (future, "ERR DUMP payload version or checksum are wrong"),
5188 (pre_ga, "ERR Pre-GA function format not supported"),
5189 (other, "ERR given type is not a function"),
5190 (cut, "ERR Failed loading library payload"),
5191 ] {
5192 assert_eq!(
5193 f.run(&[b"FUNCTION", b"RESTORE", &bytes]),
5194 format!("-{want}\r\n")
5195 );
5196 }
5197
5198 assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
5200 let again = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
5201 assert_eq!(again, good);
5202 }
5203
5204 #[test]
5207 fn a_restore_will_not_take_a_function_name_off_a_library_it_keeps() {
5208 let mut f = Fixture::new();
5209 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5210 let full = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
5211 let clash =
5213 b"#!lua name=cl\nredis.register_function('ping', function() return 'other' end)"
5214 .as_slice();
5215 assert_eq!(f.run(&[b"FUNCTION", b"FLUSH"]), "+OK\r\n");
5216 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", clash]), "$2\r\ncl\r\n");
5217 assert_eq!(
5218 f.run(&[b"FUNCTION", b"RESTORE", &full, b"REPLACE"]),
5219 "-ERR Function ping already exists\r\n"
5220 );
5221 assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$5\r\nother\r\n");
5223 }
5224
5225 #[test]
5226 fn command_getkeys_reads_the_key_count_out_of_a_script_call() {
5227 let mut f = Fixture::new();
5228 assert_eq!(
5229 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"1", b"k"]),
5230 "*1\r\n$1\r\nk\r\n"
5231 );
5232 assert_eq!(
5233 f.run(&[
5234 b"COMMAND", b"GETKEYS", b"EVALSHA", b"abc", b"2", b"k1", b"k2"
5235 ]),
5236 "*2\r\n$2\r\nk1\r\n$2\r\nk2\r\n"
5237 );
5238 assert_eq!(
5241 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL_RO", b"return 1", b"0", b"a"]),
5242 "*0\r\n"
5243 );
5244 assert_eq!(
5247 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"3", b"k"]),
5248 "*0\r\n"
5249 );
5250 assert_eq!(
5251 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"-1"]),
5252 "*0\r\n"
5253 );
5254 assert_eq!(
5255 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"abc"]),
5256 "*0\r\n"
5257 );
5258 assert_eq!(
5260 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1"]),
5261 "-ERR Invalid number of arguments specified for command\r\n"
5262 );
5263 }
5264
5265 #[test]
5266 fn the_helpers_on_the_redis_table_answer_the_way_they_are_documented() {
5267 let mut f = Fixture::new();
5268 let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
5269
5270 assert_eq!(
5271 eval(&mut f, b"return redis.sha1hex('')"),
5272 "$40\r\nda39a3ee5e6b4b0d3255bfef95601890afd80709\r\n"
5273 );
5274 assert_eq!(
5275 eval(&mut f, b"return redis.sha1hex('return 1')"),
5276 "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
5277 );
5278 assert_eq!(
5281 eval(&mut f, b"return redis.error_reply('boom')"),
5282 "-ERR boom\r\n"
5283 );
5284 assert_eq!(
5285 eval(&mut f, b"return redis.error_reply('WRONGTYPE nope')"),
5286 "-WRONGTYPE nope\r\n"
5287 );
5288 assert_eq!(
5289 eval(&mut f, b"return redis.status_reply('fine')"),
5290 "+fine\r\n"
5291 );
5292 assert_eq!(
5295 eval(&mut f, b"return redis.error_reply(1)"),
5296 "-ERR wrong number or type of arguments\r\n"
5297 );
5298 assert_eq!(
5299 eval(&mut f, b"local x = redis.status_reply() return x.err"),
5300 "$37\r\nERR wrong number or type of arguments\r\n"
5301 );
5302
5303 assert_eq!(
5305 eval(
5306 &mut f,
5307 b"return redis.LOG_DEBUG .. redis.LOG_VERBOSE .. redis.LOG_NOTICE .. redis.LOG_WARNING"
5308 ),
5309 "$4\r\n0123\r\n"
5310 );
5311 assert_eq!(
5312 eval(
5313 &mut f,
5314 b"return redis.REPL_NONE .. redis.REPL_AOF .. redis.REPL_SLAVE .. redis.REPL_REPLICA .. redis.REPL_ALL"
5315 ),
5316 "$5\r\n01223\r\n"
5317 );
5318 assert_eq!(eval(&mut f, b"return redis.replicate_commands()"), ":1\r\n");
5320 assert_eq!(
5321 eval(&mut f, b"redis.set_repl(redis.REPL_ALL) return 1"),
5322 ":1\r\n"
5323 );
5324 assert_eq!(
5325 eval(&mut f, b"redis.log(redis.LOG_WARNING, 'x') return 1"),
5326 ":1\r\n"
5327 );
5328 assert_eq!(
5329 eval(&mut f, b"return redis.acl_check_cmd('get', 'k')"),
5330 ":1\r\n"
5331 );
5332 assert!(eval(&mut f, b"redis.setresp(4)").contains("RESP version must be 2 or 3."),);
5334 assert!(eval(&mut f, b"redis.set_repl(9)").contains("Invalid replication flags."));
5335 assert!(
5336 eval(&mut f, b"redis.log('x', 'y')")
5337 .contains("First argument must be a number (log level)."),
5338 );
5339 assert!(
5340 eval(&mut f, b"return redis.acl_check_cmd('nosuchcmd')")
5341 .contains("Invalid command passed to redis.acl_check_cmd()"),
5342 );
5343 assert!(
5344 eval(&mut f, b"return redis.acl_check_cmd('get')")
5345 .contains("Wrong number of args for redis.acl_check_cmd()"),
5346 );
5347 }
5348
5349 #[test]
5350 fn a_counter_is_an_integer_and_not_a_string_of_digits() {
5351 let mut f = Fixture::new();
5352 assert_eq!(f.run(&[b"INCR", b"c"]), ":1\r\n");
5353 assert_eq!(f.run(&[b"INCRBY", b"c", b"41"]), ":42\r\n");
5354 assert_eq!(f.run(&[b"DECRBY", b"c", b"2"]), ":40\r\n");
5355 assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\n40\r\n");
5358 assert_eq!(f.run(&[b"INCRBYFLOAT", b"c", b"0.5"]), "$4\r\n40.5\r\n");
5359 f.run(&[b"SET", b"k", b"hello"]);
5362 assert_eq!(
5363 f.run(&[b"INCR", b"k"]),
5364 "-ERR value is not an integer or out of range\r\n"
5365 );
5366 assert_eq!(
5367 f.run(&[b"INCRBYFLOAT", b"c", b"inf"]),
5368 "-ERR increment would produce NaN or Infinity\r\n"
5369 );
5370 }
5371
5372 #[test]
5377 fn the_newer_commands_reply_in_the_shapes_a_real_server_sends() {
5378 let mut f = Fixture::new();
5379 assert_eq!(f.run(&[b"SET", b"k", b"hello"]), "+OK\r\n");
5380 assert_eq!(f.run(&[b"DIGEST", b"k"]), "$16\r\n9555e8555c62dcfd\r\n");
5383 assert_eq!(f.run(&[b"DIGEST", b"nosuch"]), "$-1\r\n");
5384 assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"1"]), ":1\r\n");
5385 assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"2", b"NX"]), ":0\r\n");
5386 assert_eq!(f.run(&[b"GET", b"a"]), "$1\r\n1\r\n");
5387 assert_eq!(f.run(&[b"INCREX", b"n"]), "*2\r\n:1\r\n:1\r\n");
5388 assert_eq!(
5389 f.run(&[b"INCREX", b"n", b"BYINT", b"5", b"UBOUND", b"3"]),
5390 "*2\r\n:1\r\n:0\r\n",
5391 "a refused increment reports the value it left alone and applied nothing"
5392 );
5393 assert_eq!(
5394 f.run(&[
5395 b"INCREX",
5396 b"n",
5397 b"BYINT",
5398 b"5",
5399 b"UBOUND",
5400 b"3",
5401 b"SATURATE"
5402 ]),
5403 "*2\r\n:3\r\n:2\r\n"
5404 );
5405 assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"2"]), ":0\r\n");
5406 assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"1"]), ":1\r\n");
5407 }
5408
5409 #[test]
5410 fn the_same_answers_come_out_in_resp3_spelling() {
5411 let mut f = Fixture::new();
5412 assert!(f.run(&[b"HELLO", b"3"]).starts_with("%7\r\n"));
5413 assert_eq!(f.run(&[b"GET", b"nosuch"]), "_\r\n");
5414 assert_eq!(
5417 f.run(&[b"INCREX", b"c", b"BYFLOAT", b"1.5"]),
5418 "*2\r\n,1.5\r\n,1.5\r\n"
5419 );
5420 assert_eq!(f.run(&[b"INCRBYFLOAT", b"f", b"2.5"]), "$3\r\n2.5\r\n");
5421 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
5424 assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
5425 }
5426
5427 #[test]
5428 fn a_command_nobody_has_heard_of_is_an_error_and_not_a_closed_socket() {
5429 let mut f = Fixture::new();
5430 let (flow, reply) = f.flow(&[b"NOPE", b"a", b"b"]);
5431 assert_eq!(flow, Flow::Continue);
5432 assert_eq!(
5433 reply,
5434 "-ERR unknown command 'NOPE', with args beginning with: 'a' 'b' \r\n"
5435 );
5436 let reply = f.run(&[b"NO\r\n+PONG\r\nPE"]);
5439 assert_eq!(reply.matches("\r\n").count(), 1);
5440 }
5441
5442 #[test]
5443 fn arity_is_checked_before_the_command_is() {
5444 let mut f = Fixture::new();
5445 assert_eq!(
5446 f.run(&[b"GET"]),
5447 "-ERR wrong number of arguments for 'get' command\r\n"
5448 );
5449 assert_eq!(
5450 f.run(&[b"MSET", b"k"]),
5451 "-ERR wrong number of arguments for 'mset' command\r\n"
5452 );
5453 assert_eq!(
5457 f.run(&[b"PING", b"a", b"b"]),
5458 "-ERR wrong number of arguments for 'ping' command\r\n"
5459 );
5460 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
5461 assert_eq!(f.run(&[b"PING", b"hi"]), "$2\r\nhi\r\n");
5462 assert_eq!(
5464 f.run(&[b"DELEX", b"k", b"IFEQ"]),
5465 "-ERR wrong number of arguments for 'delex' command\r\n"
5466 );
5467 }
5468
5469 #[test]
5473 fn the_option_combinations_are_the_ones_a_real_server_accepts() {
5474 let mut f = Fixture::new();
5475 let syntax = "-ERR syntax error\r\n";
5476 assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"XX"]), syntax);
5477 assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"IFEQ", b"a"]), syntax);
5478 assert_eq!(
5479 f.run(&[b"SET", b"k", b"v", b"KEEPTTL", b"EX", b"5"]),
5480 syntax
5481 );
5482 assert_eq!(
5483 f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"PX", b"5"]),
5484 syntax
5485 );
5486 assert_eq!(f.run(&[b"SET", b"k", b"v", b"PERSIST"]), syntax);
5487 assert_eq!(
5489 f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"EX", b"100"]),
5490 "+OK\r\n"
5491 );
5492 assert_eq!(f.run(&[b"SET", b"k", b"v", b"XX", b"XX"]), "+OK\r\n");
5493 assert_eq!(f.run(&[b"SET", b"k", b"v", b"GET", b"GET"]), "$1\r\nv\r\n");
5494 assert_eq!(
5496 f.run(&[b"INCREX", b"n", b"BYINT", b"1", b"BYINT", b"2"]),
5497 syntax
5498 );
5499 assert_eq!(
5500 f.run(&[b"INCREX", b"n", b"ENX"]),
5501 "-ERR ENX flag requires an expiration\r\n"
5502 );
5503 assert_eq!(
5504 f.run(&[b"INCREX", b"n", b"UBOUND", b"abc"]),
5505 "-ERR UBOUND is not an integer or out of range\r\n"
5506 );
5507 assert_eq!(
5508 f.run(&[b"INCREX", b"n", b"LBOUND", b"10", b"UBOUND", b"5"]),
5509 "-ERR LBOUND can't be greater than UBOUND\r\n"
5510 );
5511 assert_eq!(
5512 f.run(&[b"LCS", b"a", b"b", b"LEN", b"IDX"]),
5513 "-ERR If you want both the length and indexes, please just use IDX.\r\n"
5514 );
5515 }
5516
5517 #[test]
5521 fn the_expiry_rules_are_redis_own() {
5522 let mut f = Fixture::new();
5523 let bad = "-ERR invalid expire time in 'set' command\r\n";
5524 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"0"]), bad);
5525 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"-1"]), bad);
5526 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"0"]), bad);
5527 assert_eq!(
5528 f.run(&[b"SET", b"k", b"v", b"EX", b"9999999999999999"]),
5529 bad
5530 );
5531 assert_eq!(
5532 f.run(&[b"SET", b"k", b"v", b"PX", b"99999999999999999999"]),
5533 "-ERR value is not an integer or out of range\r\n"
5534 );
5535 assert_eq!(
5536 f.run(&[b"SETEX", b"k", b"0", b"v"]),
5537 "-ERR invalid expire time in 'setex' command\r\n"
5538 );
5539 assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"0"]), "$-1\r\n");
5540 assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"abc"]), "$-1\r\n");
5541 assert_eq!(
5542 f.run(&[b"GETEX", b"nosuch", b"KEEPTTL"]),
5543 "-ERR syntax error\r\n",
5544 "the option list is still checked before the key is looked up"
5545 );
5546 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
5548 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"1"]), "+OK\r\n");
5549 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
5550 }
5551
5552 #[test]
5553 fn mset_takes_its_pairs_from_the_read_buffer() {
5554 let mut f = Fixture::new();
5555 assert_eq!(f.run(&[b"MSET", b"a", b"1", b"b", b"2"]), "+OK\r\n");
5556 assert_eq!(
5557 f.run(&[b"MGET", b"a", b"b", b"nosuch"]),
5558 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$-1\r\n"
5559 );
5560 assert_eq!(f.run(&[b"MSETNX", b"b", b"9", b"c", b"3"]), ":0\r\n");
5561 assert_eq!(f.run(&[b"MSETNX", b"c", b"3", b"d", b"4"]), ":1\r\n");
5562 assert_eq!(
5563 f.run(&[b"MSETEX", b"2", b"e", b"5"]),
5564 "-ERR wrong number of key-value pairs\r\n"
5565 );
5566 assert_eq!(
5567 f.run(&[b"MSETEX", b"0", b"e", b"5"]),
5568 "-ERR invalid numkeys value\r\n"
5569 );
5570 assert_eq!(
5571 f.run(&[b"MSETEX", b"abc", b"e", b"5"]),
5572 "-ERR invalid numkeys value\r\n"
5573 );
5574 }
5575
5576 #[test]
5577 fn lcs_answers_the_length_the_string_and_the_runs() {
5578 let mut f = Fixture::new();
5579 f.run(&[b"MSET", b"a", b"ohmytext", b"b", b"mynewtext"]);
5580 assert_eq!(f.run(&[b"LCS", b"a", b"b"]), "$6\r\nmytext\r\n");
5581 assert_eq!(f.run(&[b"LCS", b"a", b"b", b"LEN"]), ":6\r\n");
5582 assert_eq!(
5583 f.run(&[b"LCS", b"a", b"b", b"IDX", b"MINMATCHLEN", b"4"]),
5584 "*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"
5585 );
5586 assert_eq!(
5589 f.run(&[b"LCS", b"a", b"b", b"MINMATCHLEN", b"4", b"WITHMATCHLEN"]),
5590 "$6\r\nmytext\r\n"
5591 );
5592 }
5593
5594 #[test]
5595 fn select_moves_the_connection_and_the_databases_stay_apart() {
5596 let mut f = Fixture::new();
5597 f.run(&[b"SET", b"k", b"zero"]);
5598 assert_eq!(f.run(&[b"SELECT", b"4"]), "+OK\r\n");
5599 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
5600 f.run(&[b"SET", b"k", b"four"]);
5601 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
5602 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
5603 assert_eq!(
5604 f.run(&[b"SELECT", b"99"]),
5605 "-ERR DB index is out of range\r\n"
5606 );
5607 assert_eq!(
5608 f.run(&[b"SELECT", b"-1"]),
5609 "-ERR DB index is out of range\r\n"
5610 );
5611 assert_eq!(
5612 f.run(&[b"SELECT", b"abc"]),
5613 "-ERR value is not an integer or out of range\r\n"
5614 );
5615 f.run(&[b"SELECT", b"4"]);
5617 f.run(&[b"RESET"]);
5618 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
5619 }
5620
5621 #[test]
5622 fn hello_agrees_on_a_protocol_and_refuses_the_ones_that_do_not_exist() {
5623 let mut f = Fixture::new();
5624 let reply = f.run(&[b"HELLO"]);
5625 assert!(reply.starts_with("*14\r\n"), "{reply}");
5626 assert!(reply.contains("$5\r\nredis\r\n"), "{reply}");
5627 assert!(reply.contains("$5\r\n8.8.0\r\n"), "{reply}");
5628 assert!(
5629 reply.contains(":7\r\n"),
5630 "the connection id is in there: {reply}"
5631 );
5632 assert_eq!(
5633 f.run(&[b"HELLO", b"4"]),
5634 "-NOPROTO unsupported protocol version\r\n"
5635 );
5636 assert_eq!(
5637 f.run(&[b"HELLO", b"abc"]),
5638 "-ERR Protocol version is not an integer or out of range\r\n"
5639 );
5640 assert_eq!(
5641 f.run(&[b"HELLO", b"3", b"SETNAME"]),
5642 "-ERR Syntax error in HELLO option 'SETNAME'\r\n"
5643 );
5644 assert!(
5645 f.run(&[b"HELLO", b"3", b"SETNAME", b"bob"])
5646 .starts_with("%7\r\n")
5647 );
5648 assert_eq!(f.session.name(), b"bob");
5649 f.run(&[b"RESET"]);
5650 assert_eq!(f.session.name(), b"");
5651 }
5652
5653 #[test]
5654 fn command_describes_this_server_in_the_shape_a_driver_reads() {
5655 let mut f = Fixture::new();
5656 let count = format!(":{}\r\n", COMMANDS.len());
5657 assert_eq!(f.run(&[b"COMMAND", b"COUNT"]), count);
5658 let info = f.run(&[b"COMMAND", b"INFO", b"get"]);
5659 assert_eq!(
5660 info,
5661 "*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\
5662 *3\r\n+@read\r\n+@string\r\n+@fast\r\n*0\r\n*0\r\n*0\r\n"
5663 );
5664 assert_eq!(f.run(&[b"COMMAND", b"INFO", b"nosuch"]), "*1\r\n$-1\r\n");
5666 assert_eq!(
5667 f.run(&[b"COMMAND", b"LIST", b"FILTERBY", b"PATTERN", b"getr*"]),
5668 "*1\r\n$8\r\ngetrange\r\n"
5669 );
5670 assert_eq!(
5671 f.run(&[b"COMMAND", b"NOPE"]),
5672 "-ERR unknown subcommand 'NOPE'. Try COMMAND HELP.\r\n"
5673 );
5674 }
5675
5676 #[test]
5680 fn command_getkeys_finds_the_keys_including_the_hidden_ones() {
5681 let mut f = Fixture::new();
5682 assert_eq!(
5683 f.run(&[b"COMMAND", b"GETKEYS", b"get", b"k"]),
5684 "*1\r\n$1\r\nk\r\n"
5685 );
5686 assert_eq!(
5687 f.run(&[b"COMMAND", b"GETKEYS", b"mset", b"a", b"1", b"b", b"2"]),
5688 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
5689 );
5690 assert_eq!(
5691 f.run(&[
5692 b"COMMAND", b"GETKEYS", b"msetex", b"2", b"a", b"1", b"b", b"2"
5693 ]),
5694 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
5695 );
5696 assert_eq!(
5697 f.run(&[b"COMMAND", b"GETKEYS", b"ping"]),
5698 "-ERR The command has no key arguments\r\n"
5699 );
5700 assert_eq!(
5701 f.run(&[b"COMMAND", b"GETKEYS", b"set"]),
5702 "-ERR Invalid number of arguments specified for command\r\n"
5703 );
5704 }
5705
5706 #[test]
5707 fn config_answers_what_it_can_and_refuses_what_it_cannot() {
5708 let mut f = Fixture::new();
5709 assert_eq!(
5710 f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
5711 "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n"
5712 );
5713 let both = f.run(&[b"CONFIG", b"GET", b"maxmemory*", b"maxmemory"]);
5716 assert!(both.starts_with("*6\r\n"), "{both}");
5717 assert_eq!(f.run(&[b"CONFIG", b"GET", b"nosuch"]), "*0\r\n");
5718 assert_eq!(f.run(&[b"CONFIG", b"SET", b"appendonly", b"no"]), "+OK\r\n");
5719 assert_eq!(
5720 f.run(&[b"CONFIG", b"SET", b"appendonly", b"yes"]),
5721 "-ERR CONFIG SET failed (possibly related to argument 'appendonly') - can't set immutable config\r\n"
5722 );
5723 assert_eq!(
5724 f.run(&[b"CONFIG", b"SET", b"nosuch", b"1"]),
5725 "-ERR Unknown option or number of arguments for CONFIG SET - 'nosuch'\r\n"
5726 );
5727 assert_eq!(
5728 f.run(&[b"CONFIG", b"GET"]),
5729 "-ERR wrong number of arguments for 'config|get' command\r\n"
5730 );
5731 assert_eq!(
5735 f.run(&[b"CONFIG", b"SET", b"appendonly"]),
5736 "-ERR wrong number of arguments for 'config|set' command\r\n"
5737 );
5738 assert_eq!(
5739 f.run(&[b"CONFIG", b"SET", b"appendonly", b"no", b"maxmemory"]),
5740 "-ERR syntax error\r\n"
5741 );
5742 assert_eq!(f.run(&[b"CONFIG", b"RESETSTAT"]), "+OK\r\n");
5743 assert_eq!(
5744 f.run(&[b"CONFIG", b"REWRITE"]),
5745 "-ERR The server is running without a config file\r\n"
5746 );
5747 }
5748
5749 #[test]
5750 fn the_eviction_policy_reads_back_what_was_written_to_it() {
5751 let mut f = Fixture::new();
5752 assert_eq!(
5753 f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
5754 "*2\r\n$16\r\nmaxmemory-policy\r\n$10\r\nnoeviction\r\n"
5755 );
5756 assert_eq!(
5757 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"AllKeys-LFU"]),
5758 "+OK\r\n",
5759 "the name is matched without regard to case, like every other one"
5760 );
5761 assert_eq!(
5762 f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
5763 "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
5764 );
5765 assert!(
5767 f.run(&[b"INFO", b"memory"])
5768 .contains("maxmemory_policy:allkeys-lfu"),
5769 "INFO and CONFIG disagree about the policy"
5770 );
5771 assert_eq!(
5775 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"garbage"]),
5776 "-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"
5777 );
5778 assert_eq!(
5781 f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
5782 "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
5783 );
5784 f.run(&[
5785 b"CONFIG",
5786 b"SET",
5787 b"hash-max-listpack-entries",
5788 b"7",
5789 b"maxmemory-policy",
5790 b"nonsense",
5791 ]);
5792 assert_eq!(
5793 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
5794 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n"
5795 );
5796 }
5797
5798 #[test]
5799 fn the_three_eviction_numbers_read_back_too() {
5800 let mut f = Fixture::new();
5801 for (name, default, set) in [
5802 ("maxmemory-samples", "5", "12"),
5803 ("lfu-log-factor", "10", "3"),
5804 ("lfu-decay-time", "1", "60"),
5805 ] {
5806 let get = || {
5807 format!(
5808 "*2\r\n${}\r\n{name}\r\n${}\r\n{default}\r\n",
5809 name.len(),
5810 default.len()
5811 )
5812 };
5813 assert_eq!(f.run(&[b"CONFIG", b"GET", name.as_bytes()]), get());
5814 assert_eq!(
5815 f.run(&[b"CONFIG", b"SET", name.as_bytes(), set.as_bytes()]),
5816 "+OK\r\n"
5817 );
5818 assert_eq!(
5819 f.run(&[b"CONFIG", b"GET", name.as_bytes()]),
5820 format!(
5821 "*2\r\n${}\r\n{name}\r\n${}\r\n{set}\r\n",
5822 name.len(),
5823 set.len()
5824 )
5825 );
5826 assert_eq!(
5829 f.run(&[b"CONFIG", b"SET", name.as_bytes(), b"soon"]),
5830 format!(
5831 "-ERR CONFIG SET failed (possibly related to argument '{name}') - argument couldn't be parsed into an integer\r\n"
5832 )
5833 );
5834 }
5835 }
5836
5837 #[test]
5838 fn the_memory_limit_reads_back_in_bytes_whatever_the_unit_was() {
5839 let mut f = Fixture::new();
5840 assert_eq!(
5841 f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
5842 "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n",
5843 "no limit is the default"
5844 );
5845 for (typed, bytes) in [
5848 (&b"1024"[..], "1024"),
5849 (b"1k", "1000"),
5850 (b"1kb", "1024"),
5851 (b"1M", "1000000"),
5852 (b"1Mb", "1048576"),
5853 (b"1gb", "1073741824"),
5854 (b"100mb", "104857600"),
5855 ] {
5856 assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxmemory", typed]), "+OK\r\n");
5857 assert_eq!(
5858 f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
5859 format!("*2\r\n$9\r\nmaxmemory\r\n${}\r\n{bytes}\r\n", bytes.len()),
5860 "set {}",
5861 String::from_utf8_lossy(typed)
5862 );
5863 }
5864 assert!(
5865 f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
5866 "the report agrees with the setting"
5867 );
5868
5869 for bad in [&b"1tb"[..], b"-1", b"", b"lots"] {
5872 assert_eq!(
5873 f.run(&[b"CONFIG", b"SET", b"maxmemory", bad]),
5874 "-ERR CONFIG SET failed (possibly related to argument 'maxmemory') - argument must be a memory value\r\n",
5875 "refused {}",
5876 String::from_utf8_lossy(bad)
5877 );
5878 }
5879 assert!(
5880 f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
5881 "and the refusal left the old one alone"
5882 );
5883 }
5884
5885 #[test]
5886 fn a_write_is_refused_when_there_is_no_room_and_nothing_to_evict() {
5887 let mut f = Fixture::new();
5888 f.run(&[b"SET", b"here", b"already"]);
5889 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1"]);
5893 assert_eq!(
5894 f.run(&[b"SET", b"k", b"v"]),
5895 "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
5896 );
5897 assert_eq!(
5898 f.run(&[b"LPUSH", b"l", b"v"]),
5899 "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
5900 );
5901 assert_eq!(f.run(&[b"GET", b"here"]), "$7\r\nalready\r\n");
5903 assert_eq!(f.run(&[b"DEL", b"here"]), ":1\r\n");
5904 assert!(f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"));
5905
5906 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
5908 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
5909 }
5910
5911 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
5915 #[test]
5916 fn an_allkeys_policy_makes_room_instead_of_refusing() {
5917 let mut f = Fixture::new();
5918 let val = vec![b'v'; 256];
5919 for i in 0..24000u32 {
5920 let k = format!("key:{i:08}");
5921 f.run(&[b"SET", k.as_bytes(), &val]);
5922 }
5923 let full = f.server.memory_bytes();
5924 assert!(
5925 full > 3 * 1024 * 1024,
5926 "the arena is several segments: {full}"
5927 );
5928
5929 let limit = full - 2 * 1024 * 1024;
5933 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
5934 f.run(&[
5935 b"CONFIG",
5936 b"SET",
5937 b"maxmemory",
5938 limit.to_string().as_bytes(),
5939 ]);
5940
5941 for i in 0..2000u32 {
5945 let k = format!("new:{i:08}");
5946 assert_eq!(
5947 f.run(&[b"SET", k.as_bytes(), &val]),
5948 "+OK\r\n",
5949 "write {i} was refused"
5950 );
5951 f.server.refresh_memory();
5952 if f.server.memory_bytes() <= limit {
5953 break;
5954 }
5955 }
5956 assert!(
5957 f.server.memory_bytes() <= limit,
5958 "it never got under: {} against {limit}",
5959 f.server.memory_bytes()
5960 );
5961 let info = f.run(&[b"INFO", b"stats"]);
5962 assert!(!info.contains("evicted_keys:0"), "{info}");
5963 assert!(
5964 f.run(&[b"DBSIZE"]) != ":0\r\n",
5965 "and it did not empty the database to get there"
5966 );
5967 }
5968
5969 #[cfg_attr(miri, ignore = "an hour of commands, and they cannot come down")]
5980 #[test]
5981 fn the_running_total_and_the_walk_agree_on_a_mixed_keyspace() {
5982 let mut f = Fixture::new();
5989 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
5990 let big = vec![b'v'; 200];
5991
5992 for i in 0..400u32 {
5993 let n = i.to_string();
5994 let n = n.as_bytes();
5995 f.run(&[b"SADD", b"s", n]);
5996 f.run(&[b"SADD", b"s2", &big]);
5997 f.run(&[b"HSET", b"h", n, &big]);
5998 f.run(&[b"RPUSH", b"l", &big]);
5999 f.run(&[b"ZADD", b"z", n, n]);
6000 f.run(&[b"ARSET", b"a", n, &big]);
6001 if i % 7 == 0 {
6002 f.run(&[b"SREM", b"s", n]);
6003 f.run(&[b"HDEL", b"h", n]);
6004 f.run(&[b"LPOP", b"l"]);
6005 f.run(&[b"ZREM", b"z", n]);
6006 f.run(&[b"ARDEL", b"a", n]);
6007 }
6008 if i % 53 == 0 {
6009 f.run(&[b"DEL", b"s2"]);
6012 }
6013 assert_eq!(
6014 f.server.settled_memory(),
6015 f.server.memory_bytes(),
6016 "after round {i}"
6017 );
6018 }
6019
6020 assert_eq!(f.run(&[b"DBSIZE"]), ":6\r\n");
6023 assert!(
6024 f.server.memory_bytes() > 512 * 1024,
6025 "{}",
6026 f.server.memory_bytes()
6027 );
6028
6029 f.run(&[b"FLUSHALL"]);
6031 assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
6032 }
6033
6034 #[test]
6035 fn taking_the_limit_away_stops_the_counting_and_putting_it_back_starts_again() {
6036 let mut f = Fixture::new();
6041 for i in 0..200u32 {
6042 let n = i.to_string();
6043 f.run(&[b"SADD", b"s", n.as_bytes()]);
6044 f.run(&[b"HSET", b"h", n.as_bytes(), b"value"]);
6045 }
6046 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
6047 assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
6048
6049 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
6050 for i in 200..400u32 {
6051 let n = i.to_string();
6052 f.run(&[b"SADD", b"s", n.as_bytes()]);
6053 }
6054 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
6055 assert_eq!(
6056 f.server.settled_memory(),
6057 f.server.memory_bytes(),
6058 "the writes it was not watching are in the number it started from"
6059 );
6060 }
6061
6062 #[test]
6063 fn evicted_keys_and_expired_keys_are_different_numbers() {
6064 let mut f = Fixture::new();
6065 f.run(&[b"SET", b"gone", b"v", b"PX", b"1"]);
6068 f.server.advance_clock_ms(20);
6069 f.run(&[b"GET", b"gone"]);
6070 let info = f.run(&[b"INFO", b"stats"]);
6071 assert!(info.contains("expired_keys:1"), "{info}");
6072 assert!(info.contains("evicted_keys:0"), "{info}");
6073 }
6074
6075 #[test]
6076 fn the_object_subcommands_follow_the_policy() {
6077 let mut f = Fixture::new();
6078 f.run(&[b"SET", b"s", b"v"]);
6079 assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
6083 assert!(
6084 f.run(&[b"OBJECT", b"FREQ", b"s"])
6085 .starts_with("-ERR An LFU maxmemory policy is not selected"),
6086 );
6087
6088 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lfu"]);
6089 assert!(
6090 f.run(&[b"OBJECT", b"IDLETIME", b"s"])
6091 .starts_with("-ERR An LFU maxmemory policy is selected"),
6092 );
6093 assert!(
6098 f.run(&[b"OBJECT", b"FREQ", b"s"]).starts_with(':'),
6099 "FREQ should answer under an LFU policy"
6100 );
6101 }
6102
6103 #[test]
6104 fn object_says_which_rung_of_the_ladder_a_key_is_on() {
6105 let mut f = Fixture::new();
6106 f.run(&[b"SET", b"s", b"hello"]);
6107 f.run(&[b"SET", b"n", b"123"]);
6108 f.run(&[b"SADD", b"si", b"1", b"2", b"3"]);
6109 f.run(&[b"SADD", b"ss", b"a", b"b"]);
6110 f.run(&[b"HSET", b"h", b"f", b"v"]);
6111 for (key, want) in [
6112 (b"s".as_slice(), "embstr"),
6113 (b"n", "int"),
6114 (b"si", "intset"),
6115 (b"ss", "listpack"),
6116 (b"h", "listpack"),
6117 ] {
6118 let reply = f.run(&[b"OBJECT", b"ENCODING", key]);
6119 assert_eq!(reply, format!("${}\r\n{want}\r\n", want.len()));
6120 }
6121
6122 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"f"]);
6125 assert_eq!(
6126 f.run(&[b"OBJECT", b"ENCODING", b"h"]),
6127 "$10\r\nlistpackex\r\n"
6128 );
6129
6130 assert_eq!(f.run(&[b"OBJECT", b"REFCOUNT", b"s"]), ":1\r\n");
6131 assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
6132 assert!(f.run(&[b"OBJECT", b"HELP"]).starts_with("*14\r\n+OBJECT "));
6133 }
6134
6135 #[test]
6136 fn object_answers_nil_for_a_key_that_is_not_there() {
6137 let mut f = Fixture::new();
6138 for sub in [b"ENCODING".as_slice(), b"REFCOUNT", b"IDLETIME", b"FREQ"] {
6139 assert_eq!(
6140 f.run(&[b"OBJECT", sub, b"nokey"]),
6141 "$-1\r\n",
6142 "a nil and not an error, which is what 8.10.1 does"
6143 );
6144 }
6145 f.run(&[b"SET", b"s", b"v"]);
6148 assert!(
6149 f.run(&[b"OBJECT", b"FREQ", b"s"])
6150 .starts_with("-ERR An LFU maxmemory policy is not"),
6151 );
6152 assert_eq!(
6153 f.run(&[b"OBJECT", b"NOPE", b"s"]),
6154 "-ERR unknown subcommand 'NOPE'. Try OBJECT HELP.\r\n"
6155 );
6156 assert_eq!(
6157 f.run(&[b"OBJECT", b"ENCODING"]),
6158 "-ERR wrong number of arguments for 'object|encoding' command\r\n"
6159 );
6160 assert_eq!(
6161 f.run(&[b"OBJECT", b"ENCODING", b"s", b"extra"]),
6162 "-ERR wrong number of arguments for 'object|encoding' command\r\n"
6163 );
6164 assert_eq!(
6165 f.run(&[b"OBJECT"]),
6166 "-ERR wrong number of arguments for 'object' command\r\n"
6167 );
6168 }
6169
6170 #[test]
6171 fn config_moves_the_ladder_and_object_encoding_agrees() {
6172 let mut f = Fixture::new();
6173 assert_eq!(
6174 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
6175 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
6176 "512 and not the 128 everyone remembers, which is what 8.10.1 says"
6177 );
6178 assert_eq!(
6181 f.run(&[b"CONFIG", b"GET", b"hash-max-ziplist-entries"]),
6182 "*2\r\n$24\r\nhash-max-ziplist-entries\r\n$3\r\n512\r\n"
6183 );
6184 assert!(
6185 f.run(&[b"CONFIG", b"GET", b"hash-max-*"])
6186 .starts_with("*8\r\n")
6187 );
6188 assert!(
6189 f.run(&[b"CONFIG", b"GET", b"set-max-*"])
6190 .starts_with("*6\r\n")
6191 );
6192
6193 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2", b"c", b"3"]);
6194 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$8\r\nlistpack\r\n");
6195
6196 assert_eq!(
6197 f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"2"]),
6198 "+OK\r\n",
6199 "written under the old name and read back under the new one"
6200 );
6201 assert_eq!(
6202 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
6203 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n"
6204 );
6205 assert_eq!(
6206 f.run(&[b"OBJECT", b"ENCODING", b"h"]),
6207 "$8\r\nlistpack\r\n",
6208 "the hash that already exists is left exactly where it was"
6209 );
6210 f.run(&[b"HSET", b"h2", b"a", b"1", b"b", b"2", b"c", b"3"]);
6211 assert_eq!(
6212 f.run(&[b"OBJECT", b"ENCODING", b"h2"]),
6213 "$9\r\nhashtable\r\n",
6214 "and the next one built goes straight to a table"
6215 );
6216
6217 f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"2"]);
6219 f.run(&[b"SADD", b"s", b"1", b"2", b"3"]);
6220 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$8\r\nlistpack\r\n");
6221 f.run(&[b"CONFIG", b"SET", b"set-max-listpack-value", b"2"]);
6222 f.run(&[b"SADD", b"s2", b"abcdefgh"]);
6223 assert_eq!(
6224 f.run(&[b"OBJECT", b"ENCODING", b"s2"]),
6225 "$9\r\nhashtable\r\n"
6226 );
6227 }
6228
6229 #[test]
6230 fn config_set_takes_all_of_the_ladder_or_none_of_it() {
6231 let mut f = Fixture::new();
6232 assert_eq!(
6233 f.run(&[
6234 b"CONFIG",
6235 b"SET",
6236 b"hash-max-listpack-entries",
6237 b"7",
6238 b"set-max-listpack-entries",
6239 b"abc"
6240 ]),
6241 "-ERR CONFIG SET failed (possibly related to argument 'set-max-listpack-entries') - argument couldn't be parsed into an integer\r\n"
6242 );
6243 assert_eq!(
6244 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
6245 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
6246 "the pair in front of the bad one did not go in"
6247 );
6248 assert_eq!(
6251 f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"abc"]),
6252 "-ERR CONFIG SET failed (possibly related to argument 'hash-max-ziplist-entries') - argument couldn't be parsed into an integer\r\n"
6253 );
6254 assert_eq!(
6255 f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"-1"]),
6256 "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument must be between 0 and 9223372036854775807 inclusive\r\n"
6257 );
6258 assert_eq!(
6261 f.run(&[
6262 b"CONFIG",
6263 b"SET",
6264 b"set-max-intset-entries",
6265 b"99999999999999999999"
6266 ]),
6267 "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument couldn't be parsed into an integer\r\n"
6268 );
6269 assert_eq!(
6270 f.run(&[
6271 b"CONFIG",
6272 b"SET",
6273 b"set-max-intset-entries",
6274 b"9223372036854775807"
6275 ]),
6276 "+OK\r\n"
6277 );
6278 }
6279
6280 #[test]
6281 fn a_setting_moved_on_one_database_moved_on_all_of_them() {
6282 let mut f = Fixture::new();
6283 f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"1"]);
6284 f.run(&[b"SELECT", b"3"]);
6285 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
6286 assert_eq!(
6287 f.run(&[b"OBJECT", b"ENCODING", b"h"]),
6288 "$9\r\nhashtable\r\n",
6289 "these are one server wide number in Redis, whatever a Keyspace carries"
6290 );
6291 }
6292
6293 #[test]
6294 fn info_reports_the_numbers_it_can_stand_behind() {
6295 let mut f = Fixture::new();
6296 f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
6297 let all = f.run(&[b"INFO"]);
6298 assert!(all.contains("redis_version:8.8.0"), "{all}");
6299 assert!(
6300 all.contains(concat!("yo_version:", env!("CARGO_PKG_VERSION"))),
6301 "{all}"
6302 );
6303 assert!(all.contains("db0:keys=2,expires=0,avg_ttl=0"), "{all}");
6304 assert!(all.contains("role:master"), "{all}");
6305 let clients = f.run(&[b"INFO", b"clients"]);
6307 assert!(clients.contains("connected_clients:0"), "{clients}");
6308 assert!(!clients.contains("redis_version"), "{clients}");
6309 assert_eq!(f.run(&[b"INFO", b"nosuch"]), "$0\r\n\r\n");
6310 }
6311
6312 #[test]
6319 fn commandstats_is_asked_for_and_replication_is_not() {
6320 let mut f = Fixture::new();
6321 for arg in ["", "all", "default", "everything"] {
6322 let info = if arg.is_empty() {
6323 f.run(&[b"INFO"])
6324 } else {
6325 f.run(&[b"INFO", arg.as_bytes()])
6326 };
6327 assert!(info.contains("redis_version"), "{arg}: {info}");
6328 assert!(info.contains("used_cpu_user"), "{arg}: {info}");
6329 assert!(info.contains("used_memory"), "{arg}: {info}");
6330 assert!(!info.contains("sentinel_tilt"), "{arg}: {info}");
6331 let asked = arg == "all" || arg == "everything";
6332 assert_eq!(
6333 info.contains("rejected_calls"),
6334 asked,
6335 "{arg} should{} carry the command counters: {info}",
6336 if asked { "" } else { " not" }
6337 );
6338 }
6339
6340 let cpu = f.run(&[b"INFO", b"cpu"]);
6341 assert!(cpu.contains("used_cpu_user"), "{cpu}");
6342 assert!(!cpu.contains("used_memory"), "{cpu}");
6343
6344 let stats = f.run(&[b"INFO", b"commandSTATS"]);
6347 assert!(!stats.contains("used_memory"), "{stats}");
6348 assert!(stats.contains("rejected_calls"), "{stats}");
6349
6350 let pair = f.run(&[b"INFO", b"cpu", b"sentinel"]);
6352 assert!(pair.contains("used_cpu_user"), "{pair}");
6353 assert!(!pair.contains("master_repl_offset"), "{pair}");
6354
6355 let with_all = f.run(&[b"INFO", b"cpu", b"all"]);
6356 assert!(with_all.contains("used_memory"), "{with_all}");
6357 assert!(with_all.contains("master_repl_offset"), "{with_all}");
6358 assert!(with_all.contains("rejected_calls"), "{with_all}");
6359 assert_eq!(
6361 with_all.matches("used_cpu_user_children").count(),
6362 1,
6363 "{with_all}"
6364 );
6365
6366 let with_default = f.run(&[b"INFO", b"cpu", b"default"]);
6367 assert!(with_default.contains("used_memory"), "{with_default}");
6368 assert!(
6369 with_default.contains("master_repl_offset"),
6370 "{with_default}"
6371 );
6372 assert!(!with_default.contains("rejected_calls"), "{with_default}");
6373 assert_eq!(
6374 with_default.matches("used_cpu_user_children").count(),
6375 1,
6376 "{with_default}"
6377 );
6378 }
6379
6380 #[test]
6389 fn info_memory_reports_the_cap_and_the_quarter_of_it_that_gets_used() {
6390 let mut f = Fixture::new();
6391 let info = f.run(&[b"INFO", b"memory"]);
6392 for field in [
6393 "total_system_memory:",
6394 "mem_cgroup_limit:",
6395 "mem_limit:",
6396 "mem_budget:",
6397 ] {
6398 assert!(info.contains(field), "no {field} in {info}");
6399 }
6400
6401 let field = |name: &str| -> u64 {
6402 info.lines()
6403 .find_map(|l| l.strip_prefix(name))
6404 .unwrap_or_else(|| panic!("no {name} in {info}"))
6405 .trim()
6406 .parse()
6407 .unwrap_or_else(|_| panic!("{name} is not a number in {info}"))
6408 };
6409 let limit = field("mem_limit:");
6410 assert_eq!(field("mem_budget:"), limit / 4, "{info}");
6411 if limit != 0 {
6414 let host = field("total_system_memory:");
6415 let cgroup = field("mem_cgroup_limit:");
6416 assert!(
6417 limit == host || limit == cgroup,
6418 "the limit came from neither number: {info}"
6419 );
6420 }
6421 }
6422
6423 #[test]
6432 fn a_command_counts_what_it_did_separately_from_what_it_refused() {
6433 let mut f = Fixture::new();
6434 f.run(&[b"SET", b"k", b"v"]);
6435 f.run(&[b"SET", b"k", b"w"]);
6436 f.run(&[b"LPUSH", b"k", b"x"]);
6438 f.run(&[b"LPUSH", b"k"]);
6440
6441 let stats = f.run(&[b"INFO", b"commandstats"]);
6442 assert!(
6443 stats.contains("cmdstat_set:calls=2,rejected_calls=0,failed_calls=0"),
6444 "{stats}"
6445 );
6446 assert!(
6447 stats.contains("cmdstat_lpush:calls=1,rejected_calls=1,failed_calls=1"),
6448 "{stats}"
6449 );
6450 assert!(
6451 !stats.contains("cmdstat_zadd"),
6452 "a command nobody has sent has no row: {stats}"
6453 );
6454 }
6455
6456 #[test]
6460 fn the_active_sweep_reclaims_keys_no_client_comes_back_for() {
6461 let (dead, live) = if cfg!(miri) {
6468 (375, 125)
6469 } else {
6470 (3_000, 1_000)
6471 };
6472 let mut f = Fixture::new();
6473 for i in 0..dead {
6474 f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
6475 }
6476 for i in 0..live {
6477 f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
6478 }
6479 let all = format!(":{}\r\n", dead + live);
6480 assert_eq!(f.run(&[b"DBSIZE"]), all);
6481 f.advance(100);
6482 assert_eq!(
6483 f.run(&[b"DBSIZE"]),
6484 all,
6485 "DBSIZE counts records and nothing has read past the dead ones yet"
6486 );
6487
6488 let rest = format!(":{live}\r\n");
6490 let mut spent = 0;
6491 for _ in 0..2_000 {
6492 spent += f.server.expire_step(4096);
6493 if f.run(&[b"DBSIZE"]) == rest {
6494 break;
6495 }
6496 }
6497 assert_eq!(f.run(&[b"DBSIZE"]), rest, "spent {spent} looks");
6498 assert!(
6499 f.run(&[b"INFO", b"stats"])
6500 .contains(&format!("expired_keys:{dead}"))
6501 );
6502 for i in 0..live {
6503 assert_eq!(
6504 f.run(&[b"GET", format!("k{i}").as_bytes()]),
6505 "$1\r\nv\r\n",
6506 "it took a key that had no deadline"
6507 );
6508 }
6509 }
6510
6511 #[test]
6512 fn a_sweep_of_a_server_with_no_deadlines_anywhere_costs_nothing() {
6513 let n = if cfg!(miri) { 200 } else { 2_000 };
6517 let mut f = Fixture::new();
6518 for i in 0..n {
6519 f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
6520 }
6521 assert_eq!(f.server.expire_step(4096), 0);
6522 f.run(&[b"SELECT", b"3"]);
6524 f.run(&[b"SET", b"x", b"v", b"PX", b"50"]);
6525 f.advance(100);
6526 for _ in 0..64 {
6527 f.server.expire_step(4096);
6528 }
6529 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
6530 f.run(&[b"SELECT", b"0"]);
6531 assert_eq!(f.run(&[b"DBSIZE"]), format!(":{n}\r\n"));
6532 assert_eq!(f.server.expire_step(4096), 0, "and it is quiet again");
6533 }
6534
6535 #[test]
6538 fn the_sweep_the_loop_calls_runs_at_most_once_a_millisecond() {
6539 let mut f = Fixture::new();
6540 for i in 0..500u32 {
6541 f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
6542 }
6543 f.advance(100);
6544 let at = f.server.striped(0).now_ms();
6545 f.server.set_clock_ms(at);
6546 assert!(f.server.expire_slice(8) > 0, "the first one works");
6550 for _ in 0..1_000 {
6551 assert_eq!(
6552 f.server.expire_slice(8),
6553 0,
6554 "the millisecond has not moved and neither should this"
6555 );
6556 }
6557 assert!(
6558 f.server.striped(0).expires() > 400,
6559 "there is plenty left to take"
6560 );
6561 f.server.set_clock_ms(at + 1);
6562 assert!(f.server.expire_slice(8) > 0, "and then it goes again");
6563 }
6564
6565 #[test]
6568 fn info_keyspace_counts_the_keys_that_have_a_deadline() {
6569 let mut f = Fixture::new();
6570 f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
6571 assert!(
6572 f.run(&[b"INFO", b"keyspace"])
6573 .contains("db0:keys=3,expires=0"),
6574 "none of them has one yet"
6575 );
6576 f.run(&[b"EXPIRE", b"a", b"1000"]);
6577 f.run(&[b"EXPIRE", b"b", b"1000"]);
6578 let two = f.run(&[b"INFO", b"keyspace"]);
6579 assert!(two.contains("db0:keys=3,expires=2"), "{two}");
6580 f.run(&[b"PERSIST", b"a"]);
6581 f.run(&[b"DEL", b"b"]);
6582 let none = f.run(&[b"INFO", b"keyspace"]);
6583 assert!(none.contains("db0:keys=2,expires=0"), "{none}");
6584
6585 f.run(&[b"SELECT", b"1"]);
6587 f.run(&[b"SET", b"x", b"1", b"EX", b"1000"]);
6588 let both = f.run(&[b"INFO", b"keyspace"]);
6589 assert!(both.contains("db0:keys=2,expires=0"), "{both}");
6590 assert!(both.contains("db1:keys=1,expires=1"), "{both}");
6591 }
6592
6593 #[cfg(unix)]
6600 #[cfg_attr(miri, ignore = "no getrusage under Miri, so the number is fixed")]
6601 #[test]
6602 fn info_cpu_reports_processor_time_that_was_really_measured() {
6603 let mut f = Fixture::new();
6604 let cpu = f.run(&[b"INFO", b"cpu"]);
6605 assert!(cpu.contains("# CPU"), "{cpu}");
6606 assert!(cpu.contains("used_cpu_user:"), "{cpu}");
6608 assert!(cpu.contains("used_cpu_sys:"), "{cpu}");
6609 assert!(cpu.contains("used_cpu_user_children:0.000000"), "{cpu}");
6610 assert!(!cpu.contains("redis_version"), "{cpu}");
6611
6612 let before = used_cpu_user(&cpu);
6616 let mut n = 0u64;
6617 let mut rounds = 0;
6618 while used_cpu_user(&f.run(&[b"INFO", b"cpu"])) <= before {
6619 for i in 0..1_000_000u64 {
6620 n = n.wrapping_add(i.wrapping_mul(i));
6621 }
6622 rounds += 1;
6623 assert!(rounds < 1_000, "cpu time never moved, n is {n}");
6627 }
6628 }
6629
6630 #[cfg(unix)]
6632 fn used_cpu_user(info: &str) -> f64 {
6633 info.lines()
6634 .find_map(|l| l.strip_prefix("used_cpu_user:"))
6635 .expect("no used_cpu_user in the reply")
6636 .trim()
6637 .parse()
6638 .expect("used_cpu_user is not a number")
6639 }
6640
6641 #[test]
6647 fn a_command_that_fails_leaves_nothing_half_written() {
6648 let mut f = Fixture::new();
6649 let reply = f.run(&[b"SETRANGE", b"k", b"-1", b"x"]);
6650 assert_eq!(reply, "-ERR offset is out of range\r\n");
6651 assert!(!reply.contains(':'), "no integer went out in front of it");
6652 }
6653
6654 #[test]
6655 fn quit_answers_first_and_closes_after() {
6656 let mut f = Fixture::new();
6657 let (flow, reply) = f.flow(&[b"QUIT"]);
6658 assert_eq!(reply, "+OK\r\n");
6659 assert_eq!(flow, Flow::Close);
6660 }
6661
6662 #[test]
6669 fn shutdown_writes_nothing_and_sets_the_flag() {
6670 let mut f = Fixture::new();
6671 assert!(!f.server.stopping(), "nobody has asked yet");
6672
6673 let (flow, reply) = f.flow(&[b"SHUTDOWN"]);
6674 assert_eq!(reply, "");
6675 assert_eq!(flow, Flow::Close);
6676 assert!(f.server.stopping());
6677 }
6678
6679 #[test]
6687 fn shutdown_takes_the_flags_redis_takes() {
6688 for flags in [
6689 &[b"NOSAVE".as_slice()][..],
6690 &[b"SAVE"],
6691 &[b"NOW"],
6692 &[b"FORCE"],
6693 &[b"nosave"],
6694 &[b"NOW", b"NOW"],
6695 &[b"SAVE", b"SAVE"],
6696 &[b"NOSAVE", b"NOW", b"FORCE"],
6697 ] {
6698 let mut f = Fixture::new();
6699 let mut parts = vec![b"SHUTDOWN".as_slice()];
6700 parts.extend_from_slice(flags);
6701 let (flow, reply) = f.flow(&parts);
6702 assert_eq!(reply, "", "SHUTDOWN {flags:?} answered something");
6703 assert_eq!(flow, Flow::Close, "SHUTDOWN {flags:?} did not close");
6704 assert!(f.server.stopping(), "SHUTDOWN {flags:?} did not stop");
6705 }
6706
6707 for flags in [
6708 &[b"BOGUS".as_slice()][..],
6709 &[b"SAVE", b"NOSAVE"],
6710 &[b"NOSAVE", b"SAVE"],
6711 &[b"ABORT", b"NOW"],
6712 &[b"NOSAVE", b"ABORT"],
6713 &[b"NOW", b"FORCE", b"ABORT"],
6714 ] {
6715 let mut f = Fixture::new();
6716 let mut parts = vec![b"SHUTDOWN".as_slice()];
6717 parts.extend_from_slice(flags);
6718 assert_eq!(
6719 f.run(&parts),
6720 "-ERR syntax error\r\n",
6721 "SHUTDOWN {flags:?} was accepted"
6722 );
6723 assert!(!f.server.stopping(), "SHUTDOWN {flags:?} stopped anyway");
6724 }
6725 }
6726
6727 #[test]
6735 fn shutdown_abort_never_has_anything_to_abort() {
6736 let mut f = Fixture::new();
6737 for parts in [
6738 &[b"SHUTDOWN".as_slice(), b"ABORT"][..],
6739 &[b"SHUTDOWN", b"ABORT", b"ABORT"],
6740 ] {
6741 assert_eq!(f.run(parts), "-ERR No shutdown in progress.\r\n");
6742 assert!(!f.server.stopping(), "an abort stopped the server");
6743 }
6744 }
6745
6746 struct Backups {
6754 f: Fixture,
6755 dir: PathBuf,
6756 }
6757
6758 impl Backups {
6759 fn new(name: &str) -> Backups {
6760 let dir = std::env::temp_dir().join(format!("yo-backup-{name}-{}", std::process::id()));
6761 let _ = std::fs::remove_dir_all(&dir);
6762 std::fs::create_dir_all(&dir).expect("could not make a temporary directory");
6763 let mut f = Fixture::new();
6764 f.server.set_dir(dir.clone());
6765 Backups { f, dir }
6766 }
6767
6768 fn run(&mut self, parts: &[&[u8]]) -> String {
6769 self.f.run(parts)
6770 }
6771
6772 fn files(&self) -> Vec<String> {
6774 let mut names: Vec<String> = match std::fs::read_dir(self.dir.join("backupdir")) {
6775 Ok(entries) => entries
6776 .filter_map(|e| e.ok())
6777 .map(|e| e.file_name().to_string_lossy().into_owned())
6778 .collect(),
6779 Err(_) => Vec::new(),
6780 };
6781 names.sort();
6782 names
6783 }
6784
6785 fn read(&self, name: &str) -> Vec<u8> {
6786 std::fs::read(self.dir.join("backupdir").join(name)).expect("could not read")
6787 }
6788 }
6789
6790 impl Drop for Backups {
6791 fn drop(&mut self) {
6792 let _ = std::fs::remove_dir_all(&self.dir);
6793 }
6794 }
6795
6796 #[test]
6799 fn backup_walks_the_states_the_reference_walks() {
6800 let mut b = Backups::new("states");
6801 let status = |b: &mut Backups| b.run(&[b"BACKUP", b"STATUS"]);
6802
6803 assert!(status(&mut b).contains("idle"));
6804 assert!(b.files().is_empty(), "an idle server has written a backup");
6805
6806 assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
6807 assert!(status(&mut b).contains("incrementing"));
6808 assert_eq!(b.files(), ["appendonly.aof.1.base.rdb"]);
6809
6810 assert_eq!(b.run(&[b"BACKUP", b"SEAL"]), "+OK\r\n");
6811 assert!(status(&mut b).contains("sealed"));
6812 assert_eq!(
6813 b.files(),
6814 [
6815 "appendonly.aof.1.base.rdb",
6816 "appendonly.aof.1.incr.aof",
6817 "appendonly.aof.manifest",
6818 ]
6819 );
6820
6821 assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
6822 assert!(status(&mut b).contains("idle"));
6823 assert!(b.files().is_empty(), "cleanup left something behind");
6824 }
6825
6826 #[test]
6828 fn backup_refuses_the_moves_the_reference_refuses() {
6829 let mut b = Backups::new("refusals");
6830
6831 assert_eq!(
6832 b.run(&[b"BACKUP", b"SEAL"]),
6833 "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
6834 );
6835 assert_eq!(
6836 b.run(&[b"BACKUP", b"ABORT"]),
6837 "-ERR No backup in progress\r\n"
6838 );
6839 assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
6842
6843 b.run(&[b"BACKUP", b"START"]);
6844 assert_eq!(
6845 b.run(&[b"BACKUP", b"START"]),
6846 "-ERR A backup is already in progress, ABORT it first\r\n"
6847 );
6848 assert_eq!(
6849 b.run(&[b"BACKUP", b"CLEANUP"]),
6850 "-ERR Backup is in progress\r\n"
6851 );
6852
6853 b.run(&[b"BACKUP", b"SEAL"]);
6854 assert_eq!(
6855 b.run(&[b"BACKUP", b"START"]),
6856 "-ERR A sealed backup exists, CLEANUP it first\r\n"
6857 );
6858 assert_eq!(
6859 b.run(&[b"BACKUP", b"SEAL"]),
6860 "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
6861 );
6862 assert_eq!(
6863 b.run(&[b"BACKUP", b"ABORT"]),
6864 "-ERR No backup in progress\r\n"
6865 );
6866 }
6867
6868 #[test]
6875 fn backup_abort_removes_the_file_and_says_who_did_it() {
6876 let mut b = Backups::new("abort");
6877 b.run(&[b"BACKUP", b"START"]);
6878 assert_eq!(b.run(&[b"BACKUP", b"ABORT"]), "+OK\r\n");
6879
6880 let status = b.run(&[b"BACKUP", b"STATUS"]);
6881 assert!(status.contains("failed"), "{status}");
6882 assert!(status.contains("aborted by user"), "{status}");
6883 assert!(b.files().is_empty(), "abort left the base file behind");
6884 assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
6885
6886 assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
6888 assert_eq!(b.files(), ["appendonly.aof.2.base.rdb"]);
6889 let status = b.run(&[b"BACKUP", b"STATUS"]);
6890 assert!(status.contains("incrementing"), "{status}");
6891 assert!(!status.contains("aborted"), "the old error was kept");
6892 }
6893
6894 #[test]
6896 fn backup_list_names_the_files_that_are_pinned_so_far() {
6897 let mut b = Backups::new("list");
6898 assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
6899
6900 b.run(&[b"BACKUP", b"START"]);
6901 let base = b.dir.join("backupdir").join("appendonly.aof.1.base.rdb");
6902 let base = base.to_string_lossy().into_owned();
6903 assert_eq!(
6904 b.run(&[b"BACKUP", b"LIST"]),
6905 format!("*1\r\n${}\r\n{base}\r\n", base.len())
6906 );
6907
6908 b.run(&[b"BACKUP", b"SEAL"]);
6909 let listed = b.run(&[b"BACKUP", b"LIST"]);
6910 assert!(listed.starts_with("*3\r\n"), "{listed}");
6911 let names: Vec<&str> = listed
6914 .lines()
6915 .filter(|l| l.starts_with('/') || l.contains(":\\"))
6916 .collect();
6917 assert_eq!(names.len(), 3, "{listed}");
6918 assert!(names[0].ends_with("appendonly.aof.1.base.rdb"), "{listed}");
6919 assert!(names[1].ends_with("appendonly.aof.1.incr.aof"), "{listed}");
6920 assert!(names[2].ends_with("appendonly.aof.manifest"), "{listed}");
6921 }
6922
6923 #[test]
6931 fn a_backup_holds_the_dataset_as_it_was_at_start() {
6932 let mut b = Backups::new("contents");
6933 b.run(&[b"SET", b"bk", b"v1"]);
6934 b.run(&[b"BACKUP", b"START"]);
6935 b.run(&[b"SET", b"bk", b"v2"]);
6936 b.run(&[b"BACKUP", b"SEAL"]);
6937
6938 let base = b.read("appendonly.aof.1.base.rdb");
6939 assert!(base.starts_with(b"REDIS"), "not an RDB file");
6940 assert!(base.windows(2).any(|w| w == b"v1"), "the value is missing");
6941 assert!(
6942 !base.windows(2).any(|w| w == b"v2"),
6943 "the base file moved on after START"
6944 );
6945 let at = base
6950 .windows(8)
6951 .position(|w| w == b"aof-base")
6952 .expect("no aof-base aux field");
6953 assert_eq!(&base[at + 8..at + 10], b"\xc0\x01", "{:?}", &base[at..]);
6954
6955 assert!(b.read("appendonly.aof.1.incr.aof").is_empty());
6956 assert_eq!(
6957 String::from_utf8(b.read("appendonly.aof.manifest")).expect("the manifest is text"),
6958 "file appendonly.aof.1.base.rdb seq 1 type b\n\
6959 file appendonly.aof.1.incr.aof seq 1 type i startoffset 0 endoffset 0\n"
6960 );
6961 }
6962
6963 #[test]
6966 fn backup_status_is_a_map_on_resp3_and_a_flat_array_on_resp2() {
6967 let mut b = Backups::new("status");
6968 b.f.server.set_clock_ms(1_700_000_000_000);
6969
6970 assert_eq!(
6971 b.run(&[b"BACKUP", b"STATUS"]),
6972 "*8\r\n$5\r\nstate\r\n$4\r\nidle\r\n$5\r\nerror\r\n$0\r\n\r\n\
6973 $10\r\nstart_time\r\n:0\r\n$8\r\nend_time\r\n:0\r\n"
6974 );
6975
6976 b.f.out = Out::new(Proto::Resp3);
6977 b.run(&[b"BACKUP", b"START"]);
6978 assert_eq!(
6979 b.run(&[b"BACKUP", b"STATUS"]),
6980 "%4\r\n$5\r\nstate\r\n$12\r\nincrementing\r\n$5\r\nerror\r\n$0\r\n\r\n\
6981 $10\r\nstart_time\r\n:1700000000\r\n$8\r\nend_time\r\n:0\r\n"
6982 );
6983
6984 b.run(&[b"BACKUP", b"SEAL"]);
6985 let sealed = b.run(&[b"BACKUP", b"STATUS"]);
6986 assert!(sealed.contains("end_time\r\n:1700000000"), "{sealed}");
6987 }
6988
6989 #[test]
6992 fn a_sealed_backup_is_swept_away_after_the_timeout() {
6993 let mut b = Backups::new("ttl");
6994 b.f.server.set_clock_ms(1_000_000);
6995 assert_eq!(
6996 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"60"]),
6997 "+OK\r\n"
6998 );
6999 b.run(&[b"BACKUP", b"START"]);
7000 b.run(&[b"BACKUP", b"SEAL"]);
7001
7002 b.f.server.set_clock_ms(1_000_000 + 59_000);
7004 b.f.server.backup_expire();
7005 assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
7006 assert_eq!(b.files().len(), 3);
7007
7008 b.f.server.set_clock_ms(1_000_000 + 60_000);
7009 b.f.server.backup_expire();
7010 let status = b.run(&[b"BACKUP", b"STATUS"]);
7011 assert!(status.contains("idle"), "{status}");
7012 assert!(b.files().is_empty(), "the timeout left the files behind");
7013
7014 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"0"]);
7016 b.run(&[b"BACKUP", b"START"]);
7017 b.run(&[b"BACKUP", b"SEAL"]);
7018 b.f.server.set_clock_ms(9_000_000_000);
7019 b.f.server.backup_expire();
7020 assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
7021 }
7022
7023 #[test]
7026 fn the_backup_settings_behave_the_way_the_reference_does() {
7027 let mut b = Backups::new("config");
7028 let dir = b.dir.to_string_lossy().into_owned();
7029
7030 assert_eq!(
7031 b.run(&[b"CONFIG", b"GET", b"dir"]),
7032 format!("*2\r\n$3\r\ndir\r\n${}\r\n{dir}\r\n", dir.len())
7033 );
7034 assert_eq!(
7035 b.run(&[b"CONFIG", b"GET", b"backupdirname"]),
7036 "*2\r\n$13\r\nbackupdirname\r\n$9\r\nbackupdir\r\n"
7037 );
7038 assert_eq!(
7039 b.run(&[b"CONFIG", b"GET", b"backup-sealed-ttl"]),
7040 "*2\r\n$17\r\nbackup-sealed-ttl\r\n$1\r\n0\r\n"
7041 );
7042
7043 assert_eq!(
7046 b.run(&[b"CONFIG", b"SET", b"dir", dir.as_bytes()]),
7047 "-ERR CONFIG SET failed (possibly related to argument 'dir') - can't set protected config\r\n"
7048 );
7049 assert_eq!(
7050 b.run(&[b"CONFIG", b"SET", b"backupdirname", b"other"]),
7051 "-ERR CONFIG SET failed (possibly related to argument 'backupdirname') - can't set immutable config\r\n"
7052 );
7053 assert!(
7054 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"abc"])
7055 .contains("argument couldn't be parsed into an integer")
7056 );
7057 assert!(
7058 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"-1"])
7059 .contains("argument must be between 0 and 9223372036854775807 inclusive")
7060 );
7061 }
7062
7063 #[test]
7065 fn backup_help_is_the_text_the_reference_sends() {
7066 let mut f = Fixture::new();
7067 let help = f.run(&[b"BACKUP", b"HELP"]);
7068 assert!(help.starts_with("*17\r\n"), "{help}");
7069 assert!(
7070 help.contains("+BACKUP <subcommand> [<arg> [value] [opt] ...]. Subcommands are:\r\n")
7071 );
7072 assert!(help.contains("+ Start a new backup into the configured 'backupdirname'.\r\n"));
7073 assert!(help.contains("+ Freeze the current backup (BASE + INCR + manifest).\r\n"));
7074 assert!(help.contains("+ Return this help.\r\n+HELP\r\n+ Print this help.\r\n"));
7075 }
7076
7077 #[test]
7085 fn backup_refuses_what_it_cannot_read() {
7086 let mut f = Fixture::new();
7087 assert_eq!(
7088 f.run(&[b"BACKUP"]),
7089 "-ERR wrong number of arguments for 'backup' command\r\n"
7090 );
7091 assert_eq!(
7092 f.run(&[b"BACKUP", b"START", b"x"]),
7093 "-ERR wrong number of arguments for 'backup' command\r\n"
7094 );
7095 assert_eq!(
7096 f.run(&[b"BACKUP", b"NOPE"]),
7097 "-ERR unknown subcommand 'NOPE'. Try BACKUP HELP.\r\n"
7098 );
7099 }
7100
7101 #[test]
7102 fn the_command_counter_counts_every_command_including_the_bad_ones() {
7103 let mut f = Fixture::new();
7104 f.run(&[b"PING"]);
7105 f.run(&[b"NOPE"]);
7106 f.run(&[b"GET"]);
7107 assert_eq!(f.server.totals().commands, 3);
7108 }
7109
7110 #[test]
7111 fn what_a_thread_marked_is_taken_by_the_maintenance_turn() {
7112 let mut server = Server::new();
7113 server.set_threads(2);
7114 server.mine().turn.store(0, Relaxed);
7117 server.locals[1].mark(1 << 9);
7118 server.collect_marks();
7119 assert!(server.mine().wanted(9));
7120 assert_eq!(server.locals[1].dirty.load(Relaxed), 0);
7122 }
7123
7124 #[test]
7125 fn what_two_threads_counted_is_added_up_when_info_asks() {
7126 let mut server = Server::new();
7127 server.set_threads(2);
7128 let ping = lookup(b"PING").expect("PING is a command");
7132 for (at, calls) in [(0, 2), (1, 3)] {
7133 let counters = &server.locals[at];
7134 for _ in 0..calls {
7135 counters.stats.commands.bump();
7136 counters.cmdstats.at(ping).calls.bump();
7137 }
7138 counters.stats.opened();
7139 }
7140 assert_eq!(server.totals().commands, 5);
7141 assert_eq!(server.totals().clients, 2);
7142 assert_eq!(server.totals().connections, 2);
7143 let rows: Vec<_> = server.command_stats().collect();
7144 assert_eq!(rows.len(), 1);
7145 assert_eq!(rows[0].0, "ping");
7146 assert_eq!(rows[0].1.calls, 5);
7147 server.reset_stats();
7150 assert_eq!(server.totals().commands, 0);
7151 assert_eq!(server.totals().connections, 0);
7152 assert_eq!(server.totals().clients, 2);
7153 }
7154
7155 #[test]
7156 fn the_parked_count_says_what_the_waiter_list_says() {
7157 let mut f = Fixture::new();
7158 assert_eq!(f.server.parked(), 0);
7159 for client in 1..=3u64 {
7160 f.session = Session::new(client);
7161 assert_eq!(f.flow(&[b"BLPOP", b"q", b"0"]).0, Flow::Block);
7162 }
7163 assert_eq!(f.server.parked(), 3);
7164 assert_eq!(f.server.waiters().len(), 3);
7165
7166 f.server.forget_waiters(2);
7170 assert_eq!(f.server.parked(), f.server.waiters().len());
7171 f.server.forget_waiters(1);
7172 assert_eq!(f.server.parked(), f.server.waiters().len());
7173 f.run(&[b"RPUSH", b"q", b"v"]);
7174 let mut out = Out::new(Proto::Resp2);
7175 assert!(f.server.serve_waiter(3, 0, &mut out));
7176 f.server.forget_waiters(3);
7177 assert_eq!(f.server.parked(), 0);
7178 assert!(f.server.waiters().is_empty());
7179 }
7180
7181 #[test]
7182 fn a_set_goes_from_bytes_to_bytes() {
7183 let mut f = Fixture::new();
7184 assert_eq!(f.run(&[b"SADD", b"s", b"a", b"b", b"c"]), ":3\r\n");
7185 assert_eq!(f.run(&[b"SADD", b"s", b"b", b"d"]), ":1\r\n");
7186 assert_eq!(f.run(&[b"SCARD", b"s"]), ":4\r\n");
7187 assert_eq!(f.run(&[b"SISMEMBER", b"s", b"a"]), ":1\r\n");
7188 assert_eq!(f.run(&[b"SISMEMBER", b"s", b"z"]), ":0\r\n");
7189 assert_eq!(f.run(&[b"TYPE", b"s"]), "+set\r\n");
7190 assert_eq!(
7191 f.run(&[b"SMISMEMBER", b"s", b"a", b"z", b"d"]),
7192 "*3\r\n:1\r\n:0\r\n:1\r\n"
7193 );
7194 assert_eq!(f.run(&[b"SREM", b"s", b"a", b"z"]), ":1\r\n");
7195 assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
7196 }
7197
7198 #[test]
7199 fn a_set_command_at_a_key_that_is_not_there_answers_empty() {
7200 let mut f = Fixture::new();
7201 assert_eq!(f.run(&[b"SCARD", b"nope"]), ":0\r\n");
7202 assert_eq!(f.run(&[b"SISMEMBER", b"nope", b"a"]), ":0\r\n");
7203 assert_eq!(f.run(&[b"SREM", b"nope", b"a"]), ":0\r\n");
7204 assert_eq!(f.run(&[b"SMEMBERS", b"nope"]), "*0\r\n");
7205 assert_eq!(
7206 f.run(&[b"SMISMEMBER", b"nope", b"a", b"b"]),
7207 "*2\r\n:0\r\n:0\r\n"
7208 );
7209 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n", "and made nothing");
7210 }
7211
7212 #[test]
7213 fn smembers_answers_a_set_on_resp3_and_an_array_on_resp2() {
7214 let mut f = Fixture::new();
7218 f.run(&[b"SADD", b"s", b"one"]);
7219 assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$3\r\none\r\n");
7220
7221 f.run(&[b"HELLO", b"3"]);
7222 assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "~1\r\n$3\r\none\r\n");
7223 }
7224
7225 #[test]
7226 fn an_integer_member_comes_back_as_the_digits_it_never_stored() {
7227 let mut f = Fixture::new();
7230 f.run(&[b"SADD", b"s", b"42"]);
7231 assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$2\r\n42\r\n");
7232 assert_eq!(f.run(&[b"SISMEMBER", b"s", b"42"]), ":1\r\n");
7233 assert_eq!(
7234 f.run(&[b"SISMEMBER", b"s", b"042"]),
7235 ":0\r\n",
7236 "the member is the bytes and not the number they parse to"
7237 );
7238 }
7239
7240 #[test]
7241 fn the_wrong_command_at_the_wrong_type_says_so_both_ways() {
7242 let mut f = Fixture::new();
7243 f.run(&[b"SET", b"str", b"v"]);
7244 f.run(&[b"SADD", b"set", b"a"]);
7245
7246 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
7247 assert_eq!(f.run(&[b"SADD", b"str", b"a"]), wrong);
7248 assert_eq!(f.run(&[b"SCARD", b"str"]), wrong);
7249 assert_eq!(f.run(&[b"SMEMBERS", b"str"]), wrong);
7250 assert_eq!(f.run(&[b"SMISMEMBER", b"str", b"a"]), wrong);
7251 assert_eq!(f.run(&[b"GET", b"set"]), wrong);
7252 assert_eq!(f.run(&[b"APPEND", b"set", b"x"]), wrong);
7253 assert_eq!(f.run(&[b"INCR", b"set"]), wrong);
7254 assert_eq!(f.run(&[b"STRLEN", b"set"]), wrong);
7255
7256 assert_eq!(
7259 f.run(&[b"MGET", b"str", b"set", b"nope"]),
7260 "*3\r\n$1\r\nv\r\n$-1\r\n$-1\r\n"
7261 );
7262 assert_eq!(f.run(&[b"SET", b"set", b"now a string"]), "+OK\r\n");
7264 assert_eq!(f.run(&[b"TYPE", b"set"]), "+string\r\n");
7265 }
7266
7267 #[test]
7268 fn a_wrongtype_leaves_nothing_half_written() {
7269 let mut f = Fixture::new();
7273 f.run(&[b"SET", b"k", b"v"]);
7274 let reply = f.run(&[b"SMISMEMBER", b"k", b"a", b"b"]);
7275 assert!(reply.starts_with("-WRONGTYPE"), "got {reply}");
7276 assert!(!reply.contains('*'), "an array header went out in front");
7277 }
7278
7279 #[test]
7280 fn emptying_a_set_takes_the_key_with_it() {
7281 let mut f = Fixture::new();
7282 f.run(&[b"SADD", b"s", b"a", b"b"]);
7283 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
7284 assert_eq!(f.run(&[b"SREM", b"s", b"a", b"b"]), ":2\r\n");
7285 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
7286 assert_eq!(f.run(&[b"TYPE", b"s"]), "+none\r\n");
7287 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
7288 }
7289
7290 fn split_scan(reply: &str) -> (String, Vec<String>) {
7296 let mut lines = reply.split("\r\n");
7297 assert_eq!(lines.next(), Some("*2"), "got {reply}");
7298 lines.next().expect("the cursor header");
7299 let cursor = lines.next().expect("the cursor").to_owned();
7300 let header = lines.next().expect("the member header");
7301 let n: usize = header[1..].parse().expect("a member count");
7302 let mut members = Vec::with_capacity(n);
7303 for _ in 0..n {
7304 lines.next().expect("a member header");
7305 members.push(lines.next().expect("a member").to_owned());
7306 }
7307 (cursor, members)
7308 }
7309
7310 #[test]
7311 fn popping_takes_a_member_off_the_set_and_hands_it_back() {
7312 let mut f = Fixture::new();
7313 f.run(&[b"SADD", b"s", b"a", b"b", b"c", b"d"]);
7314
7315 let one = f.run(&[b"SPOP", b"s"]);
7316 assert!(
7317 ["$1\r\na\r\n", "$1\r\nb\r\n", "$1\r\nc\r\n", "$1\r\nd\r\n"].contains(&one.as_str()),
7318 "got {one}"
7319 );
7320 assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
7321
7322 let (_, rest) = ("", f.run(&[b"SPOP", b"s", b"3"]));
7324 assert!(rest.starts_with("*3\r\n"), "got {rest}");
7325 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
7326 assert_eq!(f.run(&[b"SPOP", b"s"]), "$-1\r\n");
7328 assert_eq!(f.run(&[b"SPOP", b"s", b"2"]), "*0\r\n");
7329 }
7330
7331 #[test]
7332 fn the_two_draws_disagree_about_the_reply_type_and_they_are_right_to() {
7333 let mut f = Fixture::new();
7338 f.run(&[b"HELLO", b"3"]);
7339 f.run(&[b"SADD", b"s", b"a", b"b", b"c"]);
7340
7341 assert!(f.run(&[b"SPOP", b"s", b"2"]).starts_with("~2\r\n"));
7342 assert!(f.run(&[b"SRANDMEMBER", b"s", b"1"]).starts_with("*1\r\n"));
7344
7345 f.run(&[b"SADD", b"one", b"z"]);
7349 assert_eq!(
7350 f.run(&[b"SRANDMEMBER", b"one", b"-3"]),
7351 "*3\r\n$1\r\nz\r\n$1\r\nz\r\n$1\r\nz\r\n"
7352 );
7353 }
7354
7355 #[test]
7356 fn drawing_a_member_removes_nothing_and_says_nil_at_a_missing_key() {
7357 let mut f = Fixture::new();
7358 f.run(&[b"SADD", b"s", b"only"]);
7359 assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
7360 assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
7361 assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n");
7362
7363 assert_eq!(f.run(&[b"SRANDMEMBER", b"nope"]), "$-1\r\n");
7364 assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"3"]), "*0\r\n");
7367 assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"-3"]), "*0\r\n");
7368 assert_eq!(f.run(&[b"SRANDMEMBER", b"s", b"9"]), "*1\r\n$4\r\nonly\r\n");
7370 }
7371
7372 #[test]
7373 fn a_pop_count_that_is_not_a_positive_number_says_so() {
7374 let mut f = Fixture::new();
7375 f.run(&[b"SADD", b"s", b"a"]);
7376 let bad = "-ERR value is out of range, must be positive\r\n";
7377 assert_eq!(f.run(&[b"SPOP", b"s", b"-1"]), bad);
7378 assert_eq!(f.run(&[b"SPOP", b"s", b"abc"]), bad);
7379 assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n", "and took nothing");
7380 assert_eq!(f.run(&[b"SPOP", b"s", b"0"]), "*0\r\n");
7382 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
7383 }
7384
7385 #[test]
7386 fn a_scan_walks_a_set_of_any_size_exactly_once() {
7387 let mut f = Fixture::new();
7388 let members: Vec<Vec<u8>> = (0..300).map(|i| format!("m{i}").into_bytes()).collect();
7389 let args: Vec<&[u8]> = [&b"SADD"[..], &b"s"[..]]
7390 .into_iter()
7391 .chain(members.iter().map(Vec::as_slice))
7392 .collect();
7393 f.run(&args);
7394
7395 let mut seen = Vec::new();
7396 let mut cursor = "0".to_owned();
7397 loop {
7398 let reply = f.run(&[b"SSCAN", b"s", cursor.as_bytes()]);
7399 let (next, got) = split_scan(&reply);
7400 seen.extend(got);
7401 cursor = next;
7402 if cursor == "0" {
7403 break;
7404 }
7405 }
7406 seen.sort();
7407 seen.dedup();
7408 assert_eq!(seen.len(), 300, "a walk saw a member twice or missed one");
7409
7410 f.run(&[b"SADD", b"small", b"a", b"b", b"c"]);
7413 let (cursor, got) = split_scan(&f.run(&[b"SSCAN", b"small", b"0", b"COUNT", b"1"]));
7414 assert_eq!(cursor, "0");
7415 assert_eq!(got.len(), 3);
7416 assert_eq!(f.run(&[b"SSCAN", b"nope", b"0"]), "*2\r\n$1\r\n0\r\n*0\r\n");
7418 }
7419
7420 #[test]
7421 fn a_scan_takes_match_and_count_and_refuses_anything_else() {
7422 let mut f = Fixture::new();
7423 f.run(&[b"SADD", b"s", b"aa", b"ab", b"ba", b"12", b"13"]);
7424
7425 let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"a*"]));
7426 let mut got = got;
7427 got.sort();
7428 assert_eq!(got, ["aa", "ab"]);
7429
7430 let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"1?"]));
7433 let mut got = got;
7434 got.sort();
7435 assert_eq!(got, ["12", "13"]);
7436
7437 assert_eq!(f.run(&[b"SSCAN", b"s", b"abc"]), "-ERR invalid cursor\r\n");
7438 assert_eq!(f.run(&[b"SSCAN", b"s", b"-1"]), "-ERR invalid cursor\r\n");
7439 assert_eq!(
7440 f.run(&[b"SSCAN", b"s", b"0", b"NOPE", b"1"]),
7441 "-ERR syntax error\r\n"
7442 );
7443 assert_eq!(
7446 f.run(&[b"SSCAN", b"s", b"0", b"COUNT", b"0"]),
7447 "-ERR syntax error\r\n"
7448 );
7449 }
7450
7451 #[test]
7452 fn moving_a_member_takes_it_off_one_set_and_puts_it_on_another() {
7453 let mut f = Fixture::new();
7454 f.run(&[b"SADD", b"src", b"a", b"b"]);
7455 f.run(&[b"SADD", b"dst", b"c"]);
7456
7457 assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"a"]), ":1\r\n");
7458 assert_eq!(f.run(&[b"SISMEMBER", b"src", b"a"]), ":0\r\n");
7459 assert_eq!(f.run(&[b"SISMEMBER", b"dst", b"a"]), ":1\r\n");
7460 assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"zz"]), ":0\r\n");
7462 assert_eq!(f.run(&[b"SCARD", b"dst"]), ":2\r\n");
7463
7464 assert_eq!(f.run(&[b"SMOVE", b"src", b"fresh", b"b"]), ":1\r\n");
7467 assert_eq!(f.run(&[b"EXISTS", b"src"]), ":0\r\n");
7468 assert_eq!(f.run(&[b"SMEMBERS", b"fresh"]), "*1\r\n$1\r\nb\r\n");
7469 }
7470
7471 #[test]
7472 fn moving_checks_the_types_in_the_order_redis_checks_them() {
7473 let mut f = Fixture::new();
7477 f.run(&[b"SET", b"str", b"v"]);
7478 f.run(&[b"SADD", b"set", b"a"]);
7479
7480 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
7481 assert_eq!(f.run(&[b"SMOVE", b"nope", b"str", b"a"]), ":0\r\n");
7482 assert_eq!(f.run(&[b"SMOVE", b"str", b"set", b"a"]), wrong);
7483 assert_eq!(f.run(&[b"SMOVE", b"set", b"str", b"a"]), wrong);
7484 assert_eq!(f.run(&[b"SPOP", b"str"]), wrong);
7485 assert_eq!(f.run(&[b"SRANDMEMBER", b"str"]), wrong);
7486 assert_eq!(f.run(&[b"SSCAN", b"str", b"0"]), wrong);
7487 assert_eq!(
7488 f.run(&[b"SISMEMBER", b"set", b"a"]),
7489 ":1\r\n",
7490 "and none of that moved anything"
7491 );
7492 }
7493
7494 #[test]
7495 fn a_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
7496 let mut f = Fixture::new();
7499 f.run(&[b"SADD", b"s", b"a"]);
7500 for bad in [
7501 &[b"SSCAN".as_slice(), b"s", b"abc"][..],
7502 &[b"SSCAN".as_slice(), b"s", b"0", b"COUNT", b"nope"][..],
7503 &[b"SSCAN".as_slice(), b"s", b"0", b"MATCH"][..],
7504 ] {
7505 let reply = f.run(bad);
7506 assert!(reply.starts_with("-ERR"), "got {reply}");
7507 assert!(!reply.contains('*'), "an array header went out in front");
7508 }
7509 }
7510
7511 #[test]
7512 fn a_hash_writes_reads_and_deletes_its_fields() {
7513 let mut f = Fixture::new();
7514 assert_eq!(f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]), ":2\r\n");
7515 assert_eq!(f.run(&[b"HSET", b"h", b"a", b"9"]), ":0\r\n", "a was there");
7516 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
7517 assert_eq!(f.run(&[b"HGET", b"h", b"nope"]), "$-1\r\n");
7518 assert_eq!(f.run(&[b"HGET", b"nokey", b"a"]), "$-1\r\n");
7519 assert_eq!(f.run(&[b"HLEN", b"h"]), ":2\r\n");
7520 assert_eq!(f.run(&[b"HEXISTS", b"h", b"a"]), ":1\r\n");
7521 assert_eq!(f.run(&[b"HEXISTS", b"h", b"nope"]), ":0\r\n");
7522 assert_eq!(f.run(&[b"HSTRLEN", b"h", b"a"]), ":1\r\n");
7523 assert_eq!(f.run(&[b"HSTRLEN", b"h", b"nope"]), ":0\r\n");
7524
7525 assert_eq!(f.run(&[b"HGET", b"h", b"2"]), "$-1\r\n");
7528
7529 assert_eq!(f.run(&[b"HDEL", b"h", b"a", b"nope"]), ":1\r\n");
7530 assert_eq!(f.run(&[b"HDEL", b"h", b"b"]), ":1\r\n");
7531 assert_eq!(
7532 f.run(&[b"EXISTS", b"h"]),
7533 ":0\r\n",
7534 "and losing the last field lost the key"
7535 );
7536 }
7537
7538 #[test]
7539 fn hgetall_answers_a_map_on_resp3_and_the_same_pairs_flat_on_resp2() {
7540 let mut f = Fixture::new();
7541 f.run(&[b"HSET", b"h", b"a", b"1"]);
7542 assert_eq!(f.run(&[b"HGETALL", b"h"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
7543 assert_eq!(f.run(&[b"HGETALL", b"nokey"]), "*0\r\n");
7544 assert_eq!(f.run(&[b"HKEYS", b"h"]), "*1\r\n$1\r\na\r\n");
7545 assert_eq!(f.run(&[b"HVALS", b"h"]), "*1\r\n$1\r\n1\r\n");
7546 assert_eq!(f.run(&[b"HKEYS", b"nokey"]), "*0\r\n");
7547
7548 f.run(&[b"HELLO", b"3"]);
7549 assert_eq!(f.run(&[b"HGETALL", b"h"]), "%1\r\n$1\r\na\r\n$1\r\n1\r\n");
7550 assert_eq!(
7551 f.run(&[b"HGETALL", b"nokey"]),
7552 "%0\r\n",
7553 "a missing key is the empty hash and never a nil"
7554 );
7555 assert_eq!(
7556 f.run(&[b"HKEYS", b"h"]),
7557 "*1\r\n$1\r\na\r\n",
7558 "and the two that answer one side stay arrays"
7559 );
7560 }
7561
7562 #[test]
7563 fn hmget_answers_once_per_field_and_hmset_answers_ok() {
7564 let mut f = Fixture::new();
7565 assert_eq!(f.run(&[b"HMSET", b"h", b"a", b"1", b"c", b"3"]), "+OK\r\n");
7566 assert_eq!(
7567 f.run(&[b"HMGET", b"h", b"a", b"b", b"c"]),
7568 "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n",
7569 "the reply is positional, so b is a nil and not a gap"
7570 );
7571 assert_eq!(
7572 f.run(&[b"HMGET", b"nokey", b"a", b"b"]),
7573 "*2\r\n$-1\r\n$-1\r\n",
7574 "and a missing key is all nils rather than an empty array"
7575 );
7576
7577 assert_eq!(f.run(&[b"HSETNX", b"h", b"a", b"9"]), ":0\r\n");
7578 assert_eq!(f.run(&[b"HSETNX", b"h", b"z", b"9"]), ":1\r\n");
7579 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
7580 }
7581
7582 #[test]
7583 fn a_hash_counts_up_and_says_so_when_it_cannot() {
7584 let mut f = Fixture::new();
7585 assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"5"]), ":5\r\n");
7586 assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"-7"]), ":-2\r\n");
7587 assert_eq!(f.run(&[b"HGET", b"h", b"n"]), "$2\r\n-2\r\n");
7588 assert_eq!(
7589 f.run(&[b"HINCRBYFLOAT", b"h", b"f", b"10.5"]),
7590 "$4\r\n10.5\r\n",
7591 "a bulk string and not a double, on both protocols"
7592 );
7593
7594 f.run(&[b"HSET", b"h", b"s", b"words"]);
7595 let bad = f.run(&[b"HINCRBY", b"h", b"s", b"1"]);
7596 assert!(
7597 bad.starts_with("-ERR hash value is not an integer"),
7598 "{bad}"
7599 );
7600 let bad = f.run(&[b"HINCRBY", b"h", b"n", b"nope"]);
7601 assert!(
7602 bad.starts_with("-ERR value is not an integer"),
7603 "a bad argument is not yet a hash value, {bad}"
7604 );
7605 assert_eq!(
7606 f.run(&[b"HGET", b"h", b"s"]),
7607 "$5\r\nwords\r\n",
7608 "and neither of them wrote anything"
7609 );
7610 }
7611
7612 #[test]
7613 fn a_hash_scan_walks_every_pair_once_and_novalues_drops_half_of_it() {
7614 let fields = if cfg!(miri) { 96 } else { 500 };
7619 let mut f = Fixture::new();
7620 for i in 0..fields {
7621 let field = format!("field-{i}");
7622 let value = format!("value-{i}");
7623 f.run(&[b"HSET", b"h", field.as_bytes(), value.as_bytes()]);
7624 }
7625
7626 let mut seen: Vec<String> = Vec::new();
7627 let mut cursor = "0".to_owned();
7628 loop {
7629 let reply = f.run(&[b"HSCAN", b"h", cursor.as_bytes(), b"COUNT", b"32"]);
7630 let (next, items) = scan_reply(&reply);
7631 assert_eq!(items.len() % 2, 0, "a pair went out half written");
7632 for pair in items.chunks(2) {
7633 assert_eq!(
7634 pair[0].strip_prefix("field-"),
7635 pair[1].strip_prefix("value-"),
7636 "a field came back with someone else's value"
7637 );
7638 seen.push(pair[0].clone());
7639 }
7640 cursor = next;
7641 if cursor == "0" {
7642 break;
7643 }
7644 }
7645 seen.sort();
7646 seen.dedup();
7647 assert_eq!(seen.len(), fields, "every field once and only once");
7648
7649 let (_, items) = scan_reply(&f.run(&[b"HSCAN", b"h", b"0", b"NOVALUES", b"COUNT", b"32"]));
7650 assert!(
7651 items.iter().all(|s| s.starts_with("field-")),
7652 "NOVALUES still sent the values"
7653 );
7654
7655 let last = fields - 1;
7656 let (_, one) = scan_reply(&f.run(&[
7657 b"HSCAN",
7658 b"h",
7659 b"0",
7660 b"MATCH",
7661 format!("field-{last}").as_bytes(),
7662 b"COUNT",
7663 b"1000",
7664 ]));
7665 assert_eq!(
7666 one,
7667 [format!("field-{last}"), format!("value-{last}")],
7668 "MATCH is on the field"
7669 );
7670 }
7671
7672 #[test]
7673 fn hrandfield_draws_what_it_was_asked_for_and_nests_values_on_resp3() {
7674 let mut f = Fixture::new();
7675 f.run(&[b"HSET", b"h", b"a", b"1"]);
7676 assert_eq!(f.run(&[b"HRANDFIELD", b"h"]), "$1\r\na\r\n");
7677 assert_eq!(f.run(&[b"HRANDFIELD", b"nokey"]), "$-1\r\n");
7678 assert_eq!(f.run(&[b"HRANDFIELD", b"nokey", b"3"]), "*0\r\n");
7679 assert_eq!(
7680 f.run(&[b"HRANDFIELD", b"h", b"3"]),
7681 "*1\r\n$1\r\na\r\n",
7682 "a positive count is capped at the size of the hash"
7683 );
7684 assert_eq!(
7685 f.run(&[b"HRANDFIELD", b"h", b"-3"]),
7686 "*3\r\n$1\r\na\r\n$1\r\na\r\n$1\r\na\r\n",
7687 "and a negative one repeats itself"
7688 );
7689 assert_eq!(
7690 f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
7691 "*2\r\n$1\r\na\r\n$1\r\n1\r\n",
7692 "flat on RESP2"
7693 );
7694
7695 f.run(&[b"HELLO", b"3"]);
7696 assert_eq!(
7697 f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
7698 "*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n",
7699 "and nested on RESP3, but still an array and never a map"
7700 );
7701 }
7702
7703 #[test]
7704 fn every_hash_command_says_wrongtype_and_writes_nothing() {
7705 let mut f = Fixture::new();
7706 f.run(&[b"SET", b"str", b"v"]);
7707 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
7708
7709 for cmd in [
7710 &[b"HSET".as_slice(), b"str", b"f", b"v"][..],
7711 &[b"HMSET".as_slice(), b"str", b"f", b"v"][..],
7712 &[b"HSETNX".as_slice(), b"str", b"f", b"v"][..],
7713 &[b"HGET".as_slice(), b"str", b"f"][..],
7714 &[b"HMGET".as_slice(), b"str", b"f"][..],
7715 &[b"HDEL".as_slice(), b"str", b"f"][..],
7716 &[b"HLEN".as_slice(), b"str"][..],
7717 &[b"HEXISTS".as_slice(), b"str", b"f"][..],
7718 &[b"HSTRLEN".as_slice(), b"str", b"f"][..],
7719 &[b"HGETALL".as_slice(), b"str"][..],
7720 &[b"HKEYS".as_slice(), b"str"][..],
7721 &[b"HVALS".as_slice(), b"str"][..],
7722 &[b"HINCRBY".as_slice(), b"str", b"f", b"1"][..],
7723 &[b"HINCRBYFLOAT".as_slice(), b"str", b"f", b"1"][..],
7724 &[b"HRANDFIELD".as_slice(), b"str"][..],
7725 &[b"HRANDFIELD".as_slice(), b"str", b"2"][..],
7726 &[b"HSCAN".as_slice(), b"str", b"0"][..],
7727 ] {
7728 let reply = f.run(cmd);
7729 assert_eq!(reply, wrong, "{:?}", cmd[0]);
7730 }
7731 assert_eq!(
7732 f.run(&[b"GET", b"str"]),
7733 "$1\r\nv\r\n",
7734 "and none of them touched the value"
7735 );
7736 }
7737
7738 #[test]
7739 fn a_hash_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
7740 let mut f = Fixture::new();
7741 f.run(&[b"HSET", b"h", b"f", b"v"]);
7742 for bad in [
7743 &[b"HSCAN".as_slice(), b"h", b"abc"][..],
7744 &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"nope"][..],
7745 &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"0"][..],
7746 &[b"HSCAN".as_slice(), b"h", b"0", b"MATCH"][..],
7747 ] {
7748 let reply = f.run(bad);
7749 assert!(reply.starts_with("-ERR"), "got {reply}");
7750 assert!(!reply.contains('*'), "an array header went out in front");
7751 }
7752 }
7753
7754 #[test]
7755 fn a_field_deadline_goes_on_and_comes_back_in_all_four_units() {
7756 let mut f = Fixture::new();
7757 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
7758 assert_eq!(
7759 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]),
7760 "*1\r\n:1\r\n"
7761 );
7762 assert_eq!(
7763 f.run(&[b"HTTL", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
7764 "*3\r\n:100\r\n:-1\r\n:-2\r\n",
7765 "one answer per field, and the two sentinels are TTL's own"
7766 );
7767
7768 let ms = int_reply(&f.run(&[b"HPTTL", b"h", b"FIELDS", b"1", b"a"]));
7771 assert!((99_000..=100_000).contains(&ms), "got {ms}");
7772 let at = int_reply(&f.run(&[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
7773 let at_ms = int_reply(&f.run(&[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
7774 assert_eq!(at, at_ms.div_euclid(1000) + i64::from(at_ms % 1000 != 0));
7775 assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
7776
7777 assert_eq!(
7778 f.run(&[b"HPERSIST", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
7779 "*3\r\n:1\r\n:-1\r\n:-2\r\n",
7780 "one for the deadline taken off, and it does not say what it was"
7781 );
7782 assert_eq!(
7783 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
7784 "*1\r\n:-1\r\n"
7785 );
7786 assert_eq!(
7787 f.run(&[b"HGET", b"h", b"a"]),
7788 "$1\r\n1\r\n",
7789 "and the field is still there with the value it had"
7790 );
7791 }
7792
7793 #[test]
7794 fn a_deadline_that_has_already_gone_deletes_the_field_now() {
7795 let mut f = Fixture::new();
7796 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
7797 assert_eq!(
7798 f.run(&[b"HEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"a"]),
7799 "*1\r\n:2\r\n",
7800 "two, and not one, because nothing was stored"
7801 );
7802 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
7803 assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
7804
7805 assert_eq!(
7806 f.run(&[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"]),
7807 "*1\r\n:2\r\n"
7808 );
7809 assert_eq!(
7810 f.run(&[b"EXISTS", b"h"]),
7811 ":0\r\n",
7812 "and the last field going took the key with it"
7813 );
7814
7815 f.run(&[b"HSET", b"h", b"a", b"1"]);
7818 assert_eq!(
7819 f.run(&[b"HEXPIRE", b"h", b"0", b"FIELDS", b"1", b"a"]),
7820 "*1\r\n:2\r\n"
7821 );
7822 }
7823
7824 #[test]
7825 fn a_field_is_gone_once_its_moment_passes() {
7826 let mut f = Fixture::new();
7827 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
7828 assert_eq!(
7829 f.run(&[b"HPEXPIRE", b"h", b"20", b"FIELDS", b"1", b"a"]),
7830 "*1\r\n:1\r\n"
7831 );
7832 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n", "not yet");
7833
7834 f.server.advance_clock_ms(60);
7838 assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
7839 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
7840 assert_eq!(
7841 f.run(&[b"HGETALL", b"h"]),
7842 "*2\r\n$1\r\nb\r\n$1\r\n2\r\n",
7843 "and the walks do not hand back a field that has expired"
7844 );
7845 }
7846
7847 #[test]
7848 fn a_missing_key_answers_the_no_field_sentinel_for_every_field() {
7849 let mut f = Fixture::new();
7850 for cmd in [
7851 &[
7852 b"HEXPIRE".as_slice(),
7853 b"nokey",
7854 b"100",
7855 b"FIELDS",
7856 b"2",
7857 b"a",
7858 b"b",
7859 ][..],
7860 &[b"HTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
7861 &[b"HPTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
7862 &[
7863 b"HEXPIRETIME".as_slice(),
7864 b"nokey",
7865 b"FIELDS",
7866 b"2",
7867 b"a",
7868 b"b",
7869 ][..],
7870 &[
7871 b"HPERSIST".as_slice(),
7872 b"nokey",
7873 b"FIELDS",
7874 b"2",
7875 b"a",
7876 b"b",
7877 ][..],
7878 ] {
7879 assert_eq!(f.run(cmd), "*2\r\n:-2\r\n:-2\r\n", "{:?}", cmd[0]);
7880 }
7881 }
7882
7883 #[test]
7884 fn writing_a_field_clears_the_deadline_that_was_on_it() {
7885 let mut f = Fixture::new();
7886 f.run(&[b"HSET", b"h", b"a", b"1"]);
7887 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]);
7888 f.run(&[b"HSET", b"h", b"a", b"2"]);
7889 assert_eq!(
7890 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
7891 "*1\r\n:-1\r\n",
7892 "Redis has done this since 7.4, and it is why HGETEX exists"
7893 );
7894 }
7895
7896 #[test]
7897 fn the_four_conditions_reach_the_store_the_way_they_were_written() {
7898 let mut f = Fixture::new();
7899 f.run(&[b"HSET", b"h", b"a", b"1"]);
7900 assert_eq!(
7901 f.run(&[b"HEXPIRE", b"h", b"100", b"XX", b"FIELDS", b"1", b"a"]),
7902 "*1\r\n:0\r\n",
7903 "XX on a field with no deadline changes nothing"
7904 );
7905 assert_eq!(
7906 f.run(&[b"HEXPIRE", b"h", b"100", b"NX", b"FIELDS", b"1", b"a"]),
7907 "*1\r\n:1\r\n"
7908 );
7909 assert_eq!(
7910 f.run(&[b"HEXPIRE", b"h", b"200", b"NX", b"FIELDS", b"1", b"a"]),
7911 "*1\r\n:0\r\n",
7912 "and NX will not move one that is already there"
7913 );
7914 assert_eq!(
7915 f.run(&[b"HEXPIRE", b"h", b"50", b"GT", b"FIELDS", b"1", b"a"]),
7916 "*1\r\n:0\r\n"
7917 );
7918 assert_eq!(
7919 f.run(&[b"HEXPIRE", b"h", b"500", b"GT", b"FIELDS", b"1", b"a"]),
7920 "*1\r\n:1\r\n"
7921 );
7922 assert_eq!(
7923 f.run(&[b"HEXPIRE", b"h", b"50", b"LT", b"FIELDS", b"1", b"a"]),
7924 "*1\r\n:1\r\n"
7925 );
7926 assert_eq!(
7927 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
7928 "*1\r\n:50\r\n"
7929 );
7930 }
7931
7932 #[test]
7933 fn the_field_ttl_family_leaves_nothing_half_written_on_a_bad_argument() {
7934 let mut f = Fixture::new();
7935 f.run(&[b"HSET", b"h", b"a", b"1"]);
7936 for (bad, want) in [
7937 (
7938 &[b"HEXPIRE".as_slice(), b"h", b"-1", b"FIELDS", b"1", b"a"][..],
7939 "-ERR invalid expire time, must be >= 0",
7940 ),
7941 (
7942 &[
7943 b"HEXPIRE".as_slice(),
7944 b"h",
7945 b"9999999999999999",
7946 b"FIELDS",
7947 b"1",
7948 b"a",
7949 ][..],
7950 "-ERR invalid expire time in 'hexpire' command",
7951 ),
7952 (
7953 &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELD", b"1", b"a"][..],
7954 "-ERR wrong number of arguments for 'hexpire' command",
7955 ),
7956 (
7957 &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"0", b"a"][..],
7958 "-ERR Parameter `numFields` should be greater than 0",
7959 ),
7960 (
7961 &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"2", b"a"][..],
7962 "-ERR wrong number of arguments",
7963 ),
7964 (
7965 &[b"HTTL".as_slice(), b"h", b"FIELDS", b"3", b"a", b"b"][..],
7966 "-ERR wrong number of arguments",
7967 ),
7968 ] {
7969 let reply = f.run(bad);
7970 assert!(reply.starts_with(want), "wanted {want}, got {reply}");
7971 assert!(!reply.contains('*'), "an array header went out in front");
7972 }
7973 assert_eq!(
7974 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
7975 "*1\r\n:-1\r\n",
7976 "and not one of them put a deadline on anything"
7977 );
7978 }
7979
7980 #[test]
7981 fn every_field_ttl_command_says_wrongtype_and_writes_nothing() {
7982 let mut f = Fixture::new();
7983 f.run(&[b"SET", b"str", b"v"]);
7984 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
7985
7986 for cmd in [
7987 &[b"HEXPIRE".as_slice(), b"str", b"100", b"FIELDS", b"1", b"f"][..],
7988 &[
7989 b"HPEXPIRE".as_slice(),
7990 b"str",
7991 b"100",
7992 b"FIELDS",
7993 b"1",
7994 b"f",
7995 ][..],
7996 &[
7997 b"HEXPIREAT".as_slice(),
7998 b"str",
7999 b"9999999999",
8000 b"FIELDS",
8001 b"1",
8002 b"f",
8003 ][..],
8004 &[
8005 b"HPEXPIREAT".as_slice(),
8006 b"str",
8007 b"9999999999999",
8008 b"FIELDS",
8009 b"1",
8010 b"f",
8011 ][..],
8012 &[b"HTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8013 &[b"HPTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8014 &[b"HEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8015 &[b"HPEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8016 &[b"HPERSIST".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8017 ] {
8018 assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
8019 }
8020 assert_eq!(
8021 f.run(&[b"GET", b"str"]),
8022 "$1\r\nv\r\n",
8023 "and none of them touched the value"
8024 );
8025 }
8026
8027 #[test]
8028 fn hgetdel_hands_the_value_out_and_then_takes_the_field() {
8029 let mut f = Fixture::new();
8030 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
8031 assert_eq!(
8032 f.run(&[b"HGETDEL", b"h", b"FIELDS", b"2", b"a", b"nope"]),
8033 "*2\r\n$1\r\n1\r\n$-1\r\n",
8034 "positional, so the field that was not there is a nil in its place"
8035 );
8036 assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
8037 assert_eq!(
8038 f.run(&[b"HGETDEL", b"nokey", b"FIELDS", b"1", b"a"]),
8039 "*1\r\n$-1\r\n"
8040 );
8041 assert_eq!(
8042 f.run(&[b"HGETDEL", b"h", b"FIELDS", b"1", b"b"]),
8043 "*1\r\n$1\r\n2\r\n"
8044 );
8045 assert_eq!(
8046 f.run(&[b"EXISTS", b"h"]),
8047 ":0\r\n",
8048 "and the last field took the key"
8049 );
8050 }
8051
8052 #[test]
8053 fn hgetex_reads_and_moves_the_deadline_in_one_command() {
8054 let mut f = Fixture::new();
8055 f.run(&[b"HSET", b"h", b"a", b"1"]);
8056 assert_eq!(
8057 f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]),
8058 "*1\r\n$1\r\n1\r\n"
8059 );
8060 assert_eq!(
8061 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8062 "*1\r\n:-1\r\n",
8063 "no option means leave it alone, which is the one place this is not GETEX"
8064 );
8065
8066 f.run(&[b"HGETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a"]);
8067 assert_eq!(
8068 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8069 "*1\r\n:100\r\n"
8070 );
8071 f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]);
8072 assert_eq!(
8073 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8074 "*1\r\n:100\r\n",
8075 "and a plain read really does leave it alone"
8076 );
8077 assert_eq!(
8078 f.run(&[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"a"]),
8079 "*1\r\n$1\r\n1\r\n"
8080 );
8081 assert_eq!(
8082 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8083 "*1\r\n:-1\r\n"
8084 );
8085
8086 assert_eq!(
8087 f.run(&[b"HGETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a"]),
8088 "*1\r\n$1\r\n1\r\n",
8089 "the value goes out before the deadline that has already gone is applied"
8090 );
8091 assert_eq!(f.run(&[b"EXISTS", b"h"]), ":0\r\n");
8092 assert_eq!(
8093 f.run(&[b"HGETEX", b"nokey", b"EX", b"100", b"FIELDS", b"1", b"a"]),
8094 "*1\r\n$-1\r\n"
8095 );
8096 }
8097
8098 #[test]
8099 fn hsetex_writes_all_of_it_or_none_of_it() {
8100 let mut f = Fixture::new();
8101 assert_eq!(
8102 f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"1"]),
8103 ":1\r\n"
8104 );
8105 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
8106 assert_eq!(
8107 f.run(&[
8108 b"HSETEX", b"h", b"FNX", b"FIELDS", b"2", b"a", b"9", b"new", b"9"
8109 ]),
8110 ":0\r\n",
8111 "FNX wants every field named to be missing"
8112 );
8113 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
8114 assert_eq!(
8115 f.run(&[b"HEXISTS", b"h", b"new"]),
8116 ":0\r\n",
8117 "and none of the list was written"
8118 );
8119 assert_eq!(
8120 f.run(&[
8121 b"HSETEX", b"h", b"FXX", b"FIELDS", b"2", b"a", b"9", b"nope", b"9"
8122 ]),
8123 ":0\r\n",
8124 "and FXX wants every one of them to be there"
8125 );
8126 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
8127 assert_eq!(
8128 f.run(&[b"HSETEX", b"h", b"FXX", b"FIELDS", b"1", b"a", b"9"]),
8129 ":1\r\n"
8130 );
8131 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
8132
8133 assert_eq!(
8134 f.run(&[b"HSETEX", b"gone", b"FXX", b"FIELDS", b"1", b"a", b"1"]),
8135 ":0\r\n"
8136 );
8137 assert_eq!(
8138 f.run(&[b"EXISTS", b"gone"]),
8139 ":0\r\n",
8140 "a key with no fields cannot meet FXX and is not created trying"
8141 );
8142 }
8143
8144 #[test]
8145 fn hsetex_clears_the_deadline_unless_it_is_told_to_keep_it() {
8146 let mut f = Fixture::new();
8147 f.run(&[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a", b"1"]);
8148 assert_eq!(
8149 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8150 "*1\r\n:100\r\n"
8151 );
8152
8153 f.run(&[b"HSETEX", b"h", b"KEEPTTL", b"FIELDS", b"1", b"a", b"2"]);
8154 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n2\r\n");
8155 assert_eq!(
8156 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8157 "*1\r\n:100\r\n",
8158 "KEEPTTL put back what the write cleared"
8159 );
8160
8161 f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"3"]);
8162 assert_eq!(
8163 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8164 "*1\r\n:-1\r\n",
8165 "and without it a write clears the deadline the way HSET does"
8166 );
8167
8168 assert_eq!(
8171 f.run(&[
8172 b"HSETEX", b"h", b"PX", b"100000", b"FXX", b"FIELDS", b"1", b"a", b"4"
8173 ]),
8174 ":1\r\n"
8175 );
8176 assert_eq!(
8177 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8178 "*1\r\n:100\r\n"
8179 );
8180
8181 assert_eq!(
8182 f.run(&[b"HSETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a", b"5"]),
8183 ":1\r\n",
8184 "written, and not the separate code the HEXPIRE family has for this"
8185 );
8186 assert_eq!(
8187 f.run(&[b"EXISTS", b"h"]),
8188 ":0\r\n",
8189 "and storing it and then removing it emptied the hash"
8190 );
8191 }
8192
8193 #[test]
8194 fn the_last_three_hash_commands_word_their_mistakes_their_own_way() {
8195 let mut f = Fixture::new();
8196 f.run(&[b"HSET", b"h", b"a", b"1"]);
8197 for (bad, want) in [
8198 (
8200 &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
8201 "-ERR Number of fields must be a positive integer",
8202 ),
8203 (
8204 &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
8205 "-ERR The `numfields` parameter must match the number of arguments",
8206 ),
8207 (
8208 &[b"HGETDEL".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
8209 "-ERR Mandatory argument FIELDS is missing or not at the right position",
8210 ),
8211 (
8213 &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
8214 "-ERR invalid number of fields",
8215 ),
8216 (
8217 &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
8218 "-ERR wrong number of arguments",
8219 ),
8220 (
8221 &[b"HGETEX".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
8222 "-ERR unknown argument: FIELD",
8223 ),
8224 (
8225 &[
8226 b"HGETEX".as_slice(),
8227 b"h",
8228 b"KEEPTTL",
8229 b"FIELDS",
8230 b"1",
8231 b"a",
8232 ][..],
8233 "-ERR unknown argument: KEEPTTL",
8234 ),
8235 (
8236 &[
8237 b"HGETEX".as_slice(),
8238 b"h",
8239 b"EX",
8240 b"100",
8241 b"PERSIST",
8242 b"FIELDS",
8243 b"1",
8244 b"a",
8245 ][..],
8246 "-ERR Only one of EX, PX, EXAT, PXAT or PERSIST arguments can be specified",
8247 ),
8248 (
8249 &[
8250 b"HSETEX".as_slice(),
8251 b"h",
8252 b"EX",
8253 b"1",
8254 b"KEEPTTL",
8255 b"FIELDS",
8256 b"1",
8257 b"a",
8258 b"1",
8259 ][..],
8260 "-ERR Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments can be specified",
8261 ),
8262 (
8263 &[
8264 b"HSETEX".as_slice(),
8265 b"h",
8266 b"FNX",
8267 b"FXX",
8268 b"FIELDS",
8269 b"1",
8270 b"a",
8271 b"1",
8272 ][..],
8273 "-ERR Only one of FXX or FNX arguments can be specified",
8274 ),
8275 (
8276 &[
8277 b"HSETEX".as_slice(),
8278 b"h",
8279 b"FIELDS",
8280 b"2",
8281 b"a",
8282 b"1",
8283 b"b",
8284 ][..],
8285 "-ERR wrong number of arguments",
8286 ),
8287 (
8288 &[
8289 b"HGETEX".as_slice(),
8290 b"h",
8291 b"EX",
8292 b"-1",
8293 b"FIELDS",
8294 b"1",
8295 b"a",
8296 ][..],
8297 "-ERR invalid expire time, must be >= 0",
8298 ),
8299 (
8300 &[
8301 b"HGETEX".as_slice(),
8302 b"h",
8303 b"PXAT",
8304 b"99999999999999",
8305 b"FIELDS",
8306 b"1",
8307 b"a",
8308 ][..],
8309 "-ERR invalid expire time in 'hgetex' command",
8310 ),
8311 (
8312 &[
8313 b"HSETEX".as_slice(),
8314 b"h",
8315 b"EX",
8316 b"abc",
8317 b"FIELDS",
8318 b"1",
8319 b"a",
8320 b"1",
8321 ][..],
8322 "-ERR value is not an integer or out of range",
8323 ),
8324 ] {
8325 let reply = f.run(bad);
8326 assert!(reply.starts_with(want), "wanted {want}, got {reply}");
8327 assert!(!reply.contains('*'), "an array header went out in front");
8328 }
8329 assert_eq!(
8330 f.run(&[b"HGET", b"h", b"a"]),
8331 "$1\r\n1\r\n",
8332 "and not one of them wrote anything"
8333 );
8334 assert_eq!(
8335 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8336 "*1\r\n:-1\r\n"
8337 );
8338 }
8339
8340 #[test]
8341 fn the_last_three_hash_commands_say_wrongtype_and_write_nothing() {
8342 let mut f = Fixture::new();
8343 f.run(&[b"SET", b"str", b"v"]);
8344 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8345 for cmd in [
8346 &[b"HGETDEL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8347 &[b"HGETEX".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8348 &[
8349 b"HGETEX".as_slice(),
8350 b"str",
8351 b"EX",
8352 b"100",
8353 b"FIELDS",
8354 b"1",
8355 b"f",
8356 ][..],
8357 &[b"HSETEX".as_slice(), b"str", b"FIELDS", b"1", b"f", b"v"][..],
8358 ] {
8359 assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
8360 }
8361 assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
8362 }
8363
8364 #[test]
8372 fn himport_writes_declared_values_into_sorted_fields() {
8373 let mut f = Fixture::new();
8374 assert_eq!(
8375 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"b", b"aa", b"a"]),
8376 "+OK\r\n"
8377 );
8378 assert_eq!(
8379 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2", b"3"]),
8380 "+OK\r\n"
8381 );
8382 assert_eq!(f.run(&[b"HKEYS", b"k"]), bulks(&["a", "b", "aa"]));
8383 assert_eq!(
8384 f.run(&[b"HGETALL", b"k"]),
8385 bulks(&["a", "3", "b", "1", "aa", "2"])
8386 );
8387 }
8388
8389 #[test]
8392 fn himport_set_replaces_the_whole_key() {
8393 let mut f = Fixture::new();
8394 f.run(&[b"HSET", b"k", b"gone", b"old", b"a", b"old"]);
8395 f.run(&[b"EXPIRE", b"k", b"100"]);
8396 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
8397 assert_eq!(
8398 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
8399 "+OK\r\n"
8400 );
8401 assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
8402 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
8403 }
8404
8405 #[test]
8408 fn himport_fieldsets_belong_to_the_connection_and_not_to_the_keyspace() {
8409 let mut f = Fixture::new();
8410 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a"]);
8411 f.run(&[b"SELECT", b"1"]);
8412 assert_eq!(
8413 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
8414 "+OK\r\n"
8415 );
8416 f.run(&[b"SELECT", b"0"]);
8417 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
8418 assert_eq!(
8419 f.run(&[b"HIMPORT", b"SET", b"k2", b"shape", b"1"]),
8420 "-ERR no such fieldset\r\n"
8421 );
8422 }
8423
8424 #[test]
8431 fn himport_complains_in_the_order_a_real_server_does() {
8432 let mut f = Fixture::new();
8433 f.run(&[b"SET", b"str", b"v"]);
8434 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
8435 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8436 assert_eq!(
8437 f.run(&[b"HIMPORT", b"SET", b"str", b"nope", b"1"]),
8438 wrong,
8439 "the type beats a missing fieldset"
8440 );
8441 assert_eq!(
8442 f.run(&[b"HIMPORT", b"SET", b"str", b"shape", b"1"]),
8443 wrong,
8444 "and it beats a value count that does not fit"
8445 );
8446 assert_eq!(
8447 f.run(&[b"HIMPORT", b"SET", b"k", b"nope", b"1"]),
8448 "-ERR no such fieldset\r\n"
8449 );
8450 for values in [&[b"1".as_slice()][..], &[b"1".as_slice(), b"2", b"3"][..]] {
8452 let mut line: Vec<&[u8]> = vec![b"HIMPORT", b"SET", b"k", b"shape"];
8453 line.extend_from_slice(values);
8454 assert_eq!(
8455 f.run(&line),
8456 "-ERR value count does not match fieldset field count\r\n",
8457 "{} values into two fields",
8458 values.len()
8459 );
8460 }
8461 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
8462 }
8463
8464 #[test]
8466 fn himport_checks_each_subcommand_count_under_its_own_name() {
8467 let mut f = Fixture::new();
8468 assert_eq!(
8469 f.run(&[b"HIMPORT"]),
8470 "-ERR wrong number of arguments for 'himport' command\r\n"
8471 );
8472 for (rest, name) in [
8473 (&["PREPARE"][..], "prepare"),
8474 (&["PREPARE", "fs"][..], "prepare"),
8475 (&["SET"][..], "set"),
8476 (&["SET", "k"][..], "set"),
8477 (&["SET", "k", "fs"][..], "set"),
8478 (&["DISCARD"][..], "discard"),
8479 (&["DISCARD", "a", "b"][..], "discard"),
8480 (&["DISCARDALL", "x"][..], "discardall"),
8481 ] {
8482 let mut line: Vec<&[u8]> = vec![b"HIMPORT"];
8483 line.extend(rest.iter().map(|a| a.as_bytes()));
8484 assert_eq!(
8485 f.run(&line),
8486 format!("-ERR wrong number of arguments for 'himport|{name}' command\r\n"),
8487 "HIMPORT {}",
8488 rest.join(" ")
8489 );
8490 }
8491 assert_eq!(
8492 f.run(&[b"HIMPORT", b"NOPE", b"x"]),
8493 "-ERR unknown subcommand 'NOPE'. Try HIMPORT HELP.\r\n"
8494 );
8495 }
8496
8497 #[test]
8500 fn a_failed_himport_prepare_leaves_the_old_fieldset_alone() {
8501 let mut f = Fixture::new();
8502 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
8503 assert_eq!(
8504 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"c", b"c"]),
8505 "-ERR duplicate field name in fieldset\r\n"
8506 );
8507 assert_eq!(
8508 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
8509 "+OK\r\n"
8510 );
8511 assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
8512 }
8513
8514 #[test]
8517 fn himport_prepare_replaces_and_the_discards_count() {
8518 let mut f = Fixture::new();
8519 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
8520 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"z"]);
8521 assert_eq!(
8522 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
8523 "+OK\r\n"
8524 );
8525 assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["z", "1"]));
8526
8527 assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":1\r\n");
8528 assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":0\r\n");
8529 f.run(&[b"HIMPORT", b"PREPARE", b"one", b"a"]);
8530 f.run(&[b"HIMPORT", b"PREPARE", b"two", b"a"]);
8531 assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":2\r\n");
8532 assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":0\r\n");
8533 }
8534
8535 fn int(reply: &str) -> i64 {
8541 let body = reply
8542 .strip_prefix(':')
8543 .and_then(|s| s.strip_suffix("\r\n"))
8544 .unwrap_or_else(|| panic!("wanted an integer, got {reply}"));
8545 body.parse().expect("an integer")
8546 }
8547
8548 fn int_reply(reply: &str) -> i64 {
8549 let body = reply
8550 .strip_prefix("*1\r\n:")
8551 .and_then(|s| s.strip_suffix("\r\n"))
8552 .unwrap_or_else(|| panic!("wanted one integer, got {reply}"));
8553 body.parse().expect("an integer")
8554 }
8555
8556 fn scan_reply(reply: &str) -> (String, Vec<String>) {
8558 let mut lines = reply.split("\r\n");
8559 assert_eq!(lines.next(), Some("*2"), "got {reply}");
8560 lines.next().expect("the cursor header");
8561 let cursor = lines.next().expect("a cursor").to_owned();
8562 let header = lines.next().expect("an item count");
8563 let n: usize = header[1..].parse().expect("a count");
8564 let mut items = Vec::with_capacity(n);
8565 for _ in 0..n {
8566 lines.next().expect("an item header");
8567 items.push(lines.next().expect("an item").to_owned());
8568 }
8569 (cursor, items)
8570 }
8571
8572 fn sorted(reply: &str) -> Vec<String> {
8575 let mut lines = reply.split("\r\n");
8576 let header = lines.next().expect("a header");
8577 assert!(
8578 header.starts_with('*') || header.starts_with('~'),
8579 "got {reply}"
8580 );
8581 let n: usize = header[1..].parse().expect("a member count");
8582 let mut got = Vec::with_capacity(n);
8583 for _ in 0..n {
8584 lines.next().expect("a member header");
8585 got.push(lines.next().expect("a member").to_owned());
8586 }
8587 got.sort();
8588 got
8589 }
8590
8591 #[test]
8592 fn the_algebra_answers_what_the_sets_share_and_do_not() {
8593 let mut f = Fixture::new();
8594 f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
8595 f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
8596 f.run(&[b"SADD", b"c", b"3", b"4", b"5"]);
8597
8598 assert_eq!(sorted(&f.run(&[b"SINTER", b"a", b"b", b"c"])), ["3"]);
8599 assert_eq!(
8600 sorted(&f.run(&[b"SUNION", b"a", b"b", b"c"])),
8601 ["1", "2", "3", "4", "5"]
8602 );
8603 assert_eq!(sorted(&f.run(&[b"SDIFF", b"a", b"b"])), ["1"]);
8604 assert_eq!(sorted(&f.run(&[b"SINTER", b"a"])), ["1", "2", "3"]);
8605
8606 assert_eq!(f.run(&[b"SINTER", b"a", b"nope"]), "*0\r\n");
8609 assert_eq!(sorted(&f.run(&[b"SUNION", b"a", b"nope"])), ["1", "2", "3"]);
8610 assert_eq!(f.run(&[b"SDIFF", b"nope", b"a"]), "*0\r\n");
8611 assert_eq!(f.run(&[b"DBSIZE"]), ":3\r\n", "and none of it made a key");
8612 }
8613
8614 #[test]
8615 fn the_algebra_answers_a_set_on_resp3_and_an_array_on_resp2() {
8616 let mut f = Fixture::new();
8617 f.run(&[b"SADD", b"a", b"x"]);
8618 assert_eq!(f.run(&[b"SINTER", b"a"]), "*1\r\n$1\r\nx\r\n");
8619 assert_eq!(f.run(&[b"SUNION", b"a"]), "*1\r\n$1\r\nx\r\n");
8620 assert_eq!(f.run(&[b"SDIFF", b"a"]), "*1\r\n$1\r\nx\r\n");
8621
8622 f.run(&[b"HELLO", b"3"]);
8623 assert_eq!(f.run(&[b"SINTER", b"a"]), "~1\r\n$1\r\nx\r\n");
8624 assert_eq!(f.run(&[b"SUNION", b"a"]), "~1\r\n$1\r\nx\r\n");
8625 assert_eq!(f.run(&[b"SDIFF", b"a"]), "~1\r\n$1\r\nx\r\n");
8626 assert_eq!(f.run(&[b"SINTER", b"nope"]), "~0\r\n");
8627 }
8628
8629 #[test]
8630 fn a_store_form_writes_a_key_and_answers_how_big_it_is() {
8631 let mut f = Fixture::new();
8632 f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
8633 f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
8634
8635 assert_eq!(f.run(&[b"SINTERSTORE", b"d", b"a", b"b"]), ":2\r\n");
8636 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["2", "3"]);
8637 assert_eq!(f.run(&[b"SUNIONSTORE", b"d", b"a", b"b"]), ":4\r\n");
8638 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["1", "2", "3", "4"]);
8639 assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"b"]), ":1\r\n");
8640 assert_eq!(f.run(&[b"SMEMBERS", b"d"]), "*1\r\n$1\r\n1\r\n");
8641
8642 assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"a"]), ":0\r\n");
8645 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
8646 assert_eq!(f.run(&[b"SINTERSTORE", b"a", b"a", b"b"]), ":2\r\n");
8647 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"a"])), ["2", "3"]);
8648
8649 f.run(&[b"SET", b"str", b"v"]);
8652 assert_eq!(f.run(&[b"SUNIONSTORE", b"str", b"b"]), ":3\r\n");
8653 assert_eq!(f.run(&[b"TYPE", b"str"]), "+set\r\n");
8654 }
8655
8656 #[test]
8657 fn sintercard_counts_without_building_and_stops_at_a_limit() {
8658 let mut f = Fixture::new();
8659 f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
8660 f.run(&[b"SADD", b"b", b"2", b"3", b"4", b"5"]);
8661
8662 assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"b"]), ":3\r\n");
8663 assert_eq!(
8664 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
8665 ":2\r\n"
8666 );
8667 assert_eq!(
8668 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
8669 ":3\r\n",
8670 "a limit of zero is no limit"
8671 );
8672 assert_eq!(f.run(&[b"SINTERCARD", b"1", b"a"]), ":4\r\n");
8673 assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"nope"]), ":0\r\n");
8674
8675 assert_eq!(
8677 f.run(&[b"SINTERCARD", b"0", b"a"]),
8678 "-ERR numkeys should be greater than 0\r\n"
8679 );
8680 assert_eq!(
8681 f.run(&[b"SINTERCARD", b"abc", b"a"]),
8682 "-ERR numkeys should be greater than 0\r\n"
8683 );
8684 assert_eq!(
8685 f.run(&[b"SINTERCARD", b"3", b"a", b"b"]),
8686 "-ERR Number of keys can't be greater than number of args\r\n"
8687 );
8688 assert_eq!(
8689 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"-1"]),
8690 "-ERR LIMIT can't be negative\r\n"
8691 );
8692 assert_eq!(
8693 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"NOPE", b"1"]),
8694 "-ERR syntax error\r\n"
8695 );
8696 f.run(&[b"SADD", b"LIMIT", b"2"]);
8698 assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"LIMIT"]), ":1\r\n");
8699 }
8700
8701 #[test]
8704 fn sunioncard_and_sdiffcard_count_without_building() {
8705 let mut f = Fixture::new();
8706 f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
8707 f.run(&[b"SADD", b"b", b"3", b"4", b"5", b"6"]);
8708
8709 assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"b"]), ":6\r\n");
8710 assert_eq!(
8711 f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
8712 ":2\r\n"
8713 );
8714 assert_eq!(
8715 f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
8716 ":6\r\n",
8717 "a limit of zero is no limit"
8718 );
8719 assert_eq!(f.run(&[b"SUNIONCARD", b"1", b"a"]), ":4\r\n");
8720 assert_eq!(
8721 f.run(&[b"SUNIONCARD", b"2", b"a", b"nope"]),
8722 ":4\r\n",
8723 "a missing key adds nothing to a union"
8724 );
8725
8726 assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"b"]), ":2\r\n");
8727 assert_eq!(
8728 f.run(&[b"SDIFFCARD", b"2", b"a", b"b", b"LIMIT", b"1"]),
8729 ":1\r\n"
8730 );
8731 assert_eq!(
8732 f.run(&[b"SDIFFCARD", b"2", b"b", b"a"]),
8733 ":2\r\n",
8734 "a difference is not symmetric"
8735 );
8736 assert_eq!(f.run(&[b"SDIFFCARD", b"1", b"a"]), ":4\r\n");
8737 assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"nope"]), ":4\r\n");
8738 assert_eq!(
8739 f.run(&[b"SDIFFCARD", b"2", b"nope", b"a"]),
8740 ":0\r\n",
8741 "nothing taken away from nothing"
8742 );
8743
8744 for name in [b"SUNIONCARD".as_slice(), b"SDIFFCARD".as_slice()] {
8747 assert_eq!(
8748 f.run(&[name, b"0", b"a"]),
8749 "-ERR numkeys should be greater than 0\r\n"
8750 );
8751 assert_eq!(
8752 f.run(&[name, b"abc", b"a"]),
8753 "-ERR numkeys should be greater than 0\r\n"
8754 );
8755 assert_eq!(
8756 f.run(&[name, b"-1", b"a"]),
8757 "-ERR numkeys should be greater than 0\r\n"
8758 );
8759 assert_eq!(
8760 f.run(&[name, b"3", b"a", b"b"]),
8761 "-ERR Number of keys can't be greater than number of args\r\n"
8762 );
8763 assert_eq!(
8764 f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"-1"]),
8765 "-ERR LIMIT can't be negative\r\n"
8766 );
8767 assert_eq!(
8768 f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"abc"]),
8769 "-ERR LIMIT can't be negative\r\n",
8770 "a LIMIT that is not a number gets the negative message too"
8771 );
8772 assert_eq!(
8773 f.run(&[name, b"2", b"a", b"b", b"NOPE", b"1"]),
8774 "-ERR syntax error\r\n"
8775 );
8776 assert_eq!(
8777 f.run(&[name, b"2", b"a", b"b", b"LIMIT"]),
8778 "-ERR syntax error\r\n"
8779 );
8780 assert_eq!(
8781 f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"1", b"X"]),
8782 "-ERR syntax error\r\n"
8783 );
8784 }
8785
8786 f.run(&[b"SADD", b"LIMIT", b"2"]);
8788 assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"LIMIT"]), ":4\r\n");
8789 assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"LIMIT"]), ":3\r\n");
8790 }
8791
8792 #[test]
8793 fn the_algebra_answers_wrongtype_before_it_writes_anything() {
8794 let mut f = Fixture::new();
8795 f.run(&[b"SADD", b"a", b"1"]);
8796 f.run(&[b"SADD", b"d", b"old"]);
8797 f.run(&[b"SET", b"str", b"v"]);
8798
8799 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8800 for bad in [
8801 &[b"SINTER".as_slice(), b"a", b"str"][..],
8802 &[b"SUNION".as_slice(), b"str"][..],
8803 &[b"SDIFF".as_slice(), b"a", b"str"][..],
8804 &[b"SINTERCARD".as_slice(), b"2", b"a", b"str"][..],
8805 &[b"SINTERSTORE".as_slice(), b"d", b"a", b"str"][..],
8806 &[b"SUNIONSTORE".as_slice(), b"d", b"str"][..],
8807 &[b"SDIFFSTORE".as_slice(), b"d", b"a", b"str"][..],
8808 ] {
8809 let reply = f.run(bad);
8810 assert_eq!(reply, wrong, "for {:?}", bad[0]);
8811 }
8812 assert_eq!(
8813 f.run(&[b"SMEMBERS", b"d"]),
8814 "*1\r\n$3\r\nold\r\n",
8815 "and the destination was left alone every time"
8816 );
8817 }
8818
8819 #[cfg_attr(miri, ignore = "the volume is the claim")]
8831 #[test]
8832 fn churning_sets_does_not_grow_the_server() {
8833 let mut f = Fixture::new();
8834 let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
8835 let args: Vec<&[u8]> = std::iter::once(&b"SADD"[..])
8836 .chain(std::iter::once(&b"s"[..]))
8837 .chain(members.iter().map(Vec::as_slice))
8838 .collect();
8839
8840 f.run(&args);
8841 f.run(&[b"DEL", b"s"]);
8842 f.server.compact_step();
8843 let after_first = f.server.memory_bytes();
8844
8845 for _ in 0..200 {
8846 f.run(&args);
8847 f.run(&[b"DEL", b"s"]);
8848 f.server.compact_step();
8849 }
8850 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
8851 assert!(
8852 f.server.memory_bytes() <= after_first * 2,
8853 "held {} after two hundred passes against {after_first} after one",
8854 f.server.memory_bytes()
8855 );
8856 }
8857
8858 #[test]
8866 fn a_bit_is_written_and_read_back_and_a_write_unpacks_an_int() {
8867 let mut f = Fixture::new();
8868 assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"1"]), ":0\r\n");
8869 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n\u{1}\r\n");
8870 assert_eq!(f.run(&[b"GETBIT", b"k", b"7"]), ":1\r\n");
8871 assert_eq!(f.run(&[b"GETBIT", b"k", b"6"]), ":0\r\n");
8872 assert_eq!(f.run(&[b"GETBIT", b"k", b"100"]), ":0\r\n");
8873 assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"0"]), ":1\r\n");
8874
8875 assert_eq!(f.run(&[b"SETBIT", b"nk", b"0", b"0"]), ":0\r\n");
8877 assert_eq!(f.run(&[b"STRLEN", b"nk"]), ":1\r\n");
8878 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"nk"]), "$3\r\nraw\r\n");
8879
8880 f.run(&[b"SET", b"num", b"12345"]);
8881 assert_eq!(f.run(&[b"GETBIT", b"num", b"1"]), ":0\r\n");
8882 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nint\r\n");
8883 assert_eq!(f.run(&[b"SETBIT", b"num", b"1", b"1"]), ":0\r\n");
8884 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nraw\r\n");
8885 assert_eq!(f.run(&[b"GET", b"num"]), "$5\r\nq2345\r\n");
8886 }
8887
8888 #[test]
8893 fn bits_are_counted_over_a_range_of_bytes_or_of_bits() {
8894 let mut f = Fixture::new();
8895 f.run(&[b"SET", b"mykey", b"foobar"]);
8896 assert_eq!(f.run(&[b"BITCOUNT", b"mykey"]), ":26\r\n");
8897 assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"0", b"0"]), ":4\r\n");
8898 assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"1", b"1"]), ":6\r\n");
8899 assert_eq!(
8900 f.run(&[b"BITCOUNT", b"mykey", b"1", b"1", b"BYTE"]),
8901 ":6\r\n"
8902 );
8903 assert_eq!(
8904 f.run(&[b"BITCOUNT", b"mykey", b"0", b"-5", b"BIT"]),
8905 ":25\r\n"
8906 );
8907 assert_eq!(
8908 f.run(&[b"BITCOUNT", b"mykey", b"5", b"30", b"BIT"]),
8909 ":17\r\n"
8910 );
8911 assert_eq!(f.run(&[b"BITCOUNT", b"nokey"]), ":0\r\n");
8912
8913 assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"10", b"20"]), ":0\r\n");
8916
8917 assert_eq!(
8919 f.run(&[b"BITCOUNT", b"mykey", b"0"]),
8920 "-ERR syntax error\r\n"
8921 );
8922 assert_eq!(
8923 f.run(&[b"BITCOUNT", b"mykey", b"0", b"1", b"NIB"]),
8924 "-ERR syntax error\r\n"
8925 );
8926 }
8927
8928 #[test]
8934 fn a_search_for_a_nought_past_the_end_answers_the_length_in_bits() {
8935 let mut f = Fixture::new();
8936 f.run(&[b"SET", b"ones", b"\xff\xff\xff"]);
8937 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0"]), ":24\r\n");
8938 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0"]), ":24\r\n");
8939 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"-1"]), ":-1\r\n");
8940 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"3"]), ":-1\r\n");
8941 assert_eq!(f.run(&[b"BITPOS", b"ones", b"1"]), ":0\r\n");
8942
8943 f.run(&[b"SET", b"mid", b"\x00\xff\xf0"]);
8944 assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"0"]), ":8\r\n");
8945 assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"2"]), ":16\r\n");
8946 assert_eq!(
8947 f.run(&[b"BITPOS", b"mid", b"1", b"0", b"-1", b"BIT"]),
8948 ":8\r\n"
8949 );
8950
8951 assert_eq!(f.run(&[b"BITPOS", b"gone", b"1"]), ":-1\r\n");
8954 assert_eq!(f.run(&[b"BITPOS", b"gone", b"0"]), ":0\r\n");
8955 }
8956
8957 #[test]
8959 fn the_eight_combinations_write_what_a_real_server_writes() {
8960 let mut f = Fixture::new();
8961 f.run(&[b"SET", b"a", b"abc"]);
8962 f.run(&[b"SET", b"b", b"abd"]);
8963 let cases: &[(&[u8], &str)] = &[
8964 (b"AND", "ab`"),
8965 (b"OR", "abg"),
8966 (b"XOR", "\u{0}\u{0}\u{7}"),
8967 (b"DIFF", "\u{0}\u{0}\u{3}"),
8968 (b"DIFF1", "\u{0}\u{0}\u{4}"),
8969 (b"ANDOR", "ab`"),
8970 (b"ONE", "\u{0}\u{0}\u{7}"),
8971 ];
8972 for (op, want) in cases {
8973 assert_eq!(f.run(&[b"BITOP", op, b"d", b"a", b"b"]), ":3\r\n", "{op:?}");
8974 assert_eq!(
8975 f.run(&[b"GET", b"d"]),
8976 format!("$3\r\n{want}\r\n"),
8977 "{op:?}"
8978 );
8979 }
8980 assert_eq!(f.run(&[b"BITOP", b"NOT", b"d", b"a"]), ":3\r\n");
8982 assert_eq!(f.raw(&[b"GET", b"d"]), b"$3\r\n\x9e\x9d\x9c\r\n".to_vec());
8983
8984 assert_eq!(f.run(&[b"BITOP", b"AND", b"d", b"a", b"gone"]), ":3\r\n");
8987 assert_eq!(f.run(&[b"GET", b"d"]), "$3\r\n\u{0}\u{0}\u{0}\r\n");
8988
8989 f.run(&[b"SET", b"dest", b"x"]);
8992 assert_eq!(f.run(&[b"BITOP", b"AND", b"dest", b"g1", b"g2"]), ":0\r\n");
8993 assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
8994 }
8995
8996 #[test]
8998 fn bitop_names_the_operation_in_its_own_complaints() {
8999 let mut f = Fixture::new();
9000 f.run(&[b"SET", b"a", b"abc"]);
9001 assert_eq!(
9002 f.run(&[b"BITOP", b"nope", b"d", b"a"]),
9003 "-ERR syntax error\r\n"
9004 );
9005 assert_eq!(
9006 f.run(&[b"BITOP", b"NOT", b"d", b"a", b"a"]),
9007 "-ERR BITOP NOT must be called with a single source key.\r\n"
9008 );
9009 for op in [&b"DIFF"[..], b"DIFF1", b"ANDOR"] {
9010 assert_eq!(
9011 f.run(&[b"BITOP", op, b"d", b"a"]),
9012 format!(
9013 "-ERR BITOP {} must be called with at least two source keys.\r\n",
9014 String::from_utf8_lossy(op)
9015 )
9016 );
9017 }
9018 f.run(&[b"LPUSH", b"l", b"x"]);
9019 assert_eq!(
9020 f.run(&[b"BITOP", b"AND", b"d", b"a", b"l"]),
9021 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
9022 );
9023 }
9024
9025 #[test]
9027 fn bitfield_reads_and_writes_packed_fields() {
9028 let mut f = Fixture::new();
9029 assert_eq!(f.run(&[b"BITFIELD", b"bf"]), "*0\r\n");
9030 assert_eq!(f.run(&[b"EXISTS", b"bf"]), ":0\r\n");
9031
9032 assert_eq!(
9033 f.run(&[
9034 b"BITFIELD",
9035 b"bf",
9036 b"INCRBY",
9037 b"u2",
9038 b"100",
9039 b"1",
9040 b"GET",
9041 b"u4",
9042 b"0"
9043 ]),
9044 "*2\r\n:1\r\n:0\r\n"
9045 );
9046 assert_eq!(f.run(&[b"STRLEN", b"bf"]), ":13\r\n");
9049
9050 assert_eq!(
9052 f.run(&[
9053 b"BITFIELD",
9054 b"bf",
9055 b"SET",
9056 b"u8",
9057 b"#0",
9058 b"255",
9059 b"GET",
9060 b"u8",
9061 b"#0"
9062 ]),
9063 "*2\r\n:0\r\n:255\r\n"
9064 );
9065
9066 assert_eq!(
9067 f.run(&[
9068 b"BITFIELD",
9069 b"bf",
9070 b"OVERFLOW",
9071 b"SAT",
9072 b"INCRBY",
9073 b"i8",
9074 b"0",
9075 b"120",
9076 b"INCRBY",
9077 b"i8",
9078 b"0",
9079 b"120"
9080 ]),
9081 "*2\r\n:119\r\n:127\r\n"
9082 );
9083 assert_eq!(
9084 f.run(&[
9085 b"BITFIELD",
9086 b"bf2",
9087 b"OVERFLOW",
9088 b"FAIL",
9089 b"INCRBY",
9090 b"u2",
9091 b"0",
9092 b"5"
9093 ]),
9094 "*1\r\n$-1\r\n"
9095 );
9096 assert_eq!(
9097 f.run(&[
9098 b"BITFIELD",
9099 b"bf3",
9100 b"OVERFLOW",
9101 b"WRAP",
9102 b"INCRBY",
9103 b"u2",
9104 b"0",
9105 b"5"
9106 ]),
9107 "*1\r\n:1\r\n"
9108 );
9109 assert_eq!(
9110 f.run(&[b"BITFIELD", b"bf3", b"GET", b"i64", b"0"]),
9111 "*1\r\n:4611686018427387904\r\n"
9112 );
9113 }
9114
9115 #[test]
9121 fn a_bad_bitfield_subcommand_leaves_the_key_alone() {
9122 let mut f = Fixture::new();
9123 let bad_type = "-ERR Invalid bitfield type. Use something like i16 u8. Note that u64 is not supported but i64 is.\r\n";
9124 assert_eq!(
9125 f.run(&[
9126 b"BITFIELD",
9127 b"bad",
9128 b"SET",
9129 b"u8",
9130 b"0",
9131 b"1",
9132 b"GET",
9133 b"u99",
9134 b"0"
9135 ]),
9136 bad_type
9137 );
9138 assert_eq!(f.run(&[b"EXISTS", b"bad"]), ":0\r\n");
9139 assert_eq!(
9140 f.run(&[b"BITFIELD", b"bad", b"GET", b"u64", b"0"]),
9141 bad_type
9142 );
9143 assert_eq!(
9144 f.run(&[b"BITFIELD", b"bad", b"GET"]),
9145 "-ERR syntax error\r\n"
9146 );
9147 assert_eq!(
9148 f.run(&[b"BITFIELD", b"bad", b"NOPE", b"u8", b"0"]),
9149 "-ERR syntax error\r\n"
9150 );
9151 assert_eq!(
9152 f.run(&[b"BITFIELD", b"bad", b"OVERFLOW"]),
9153 "-ERR syntax error\r\n"
9154 );
9155 assert_eq!(
9156 f.run(&[
9157 b"BITFIELD",
9158 b"bad",
9159 b"OVERFLOW",
9160 b"NOPE",
9161 b"GET",
9162 b"u8",
9163 b"0"
9164 ]),
9165 "-ERR Invalid OVERFLOW type specified\r\n"
9166 );
9167 assert_eq!(
9168 f.run(&[b"BITFIELD", b"bad", b"SET", b"u8", b"0", b"notanum"]),
9169 "-ERR value is not an integer or out of range\r\n"
9170 );
9171 for at in [&b"#-1"[..], b"abc"] {
9172 assert_eq!(
9173 f.run(&[b"BITFIELD", b"bad", b"GET", b"u8", at]),
9174 "-ERR bit offset is not an integer or out of range\r\n"
9175 );
9176 }
9177 }
9178
9179 #[test]
9181 fn bitfield_ro_answers_gets_and_refuses_the_rest() {
9182 let mut f = Fixture::new();
9183 f.run(&[b"SET", b"n", b"123"]);
9184 assert_eq!(
9185 f.run(&[b"BITFIELD_RO", b"n", b"GET", b"u8", b"0"]),
9186 "*1\r\n:49\r\n"
9187 );
9188 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"n"]), "$3\r\nint\r\n");
9190
9191 assert_eq!(
9193 f.run(&[
9194 b"BITFIELD_RO",
9195 b"n",
9196 b"OVERFLOW",
9197 b"SAT",
9198 b"GET",
9199 b"u8",
9200 b"0"
9201 ]),
9202 "*1\r\n:49\r\n"
9203 );
9204 for sub in [&b"SET"[..], b"INCRBY"] {
9205 assert_eq!(
9206 f.run(&[b"BITFIELD_RO", b"n", sub, b"u8", b"0", b"1"]),
9207 "-ERR BITFIELD_RO only supports the GET subcommand\r\n"
9208 );
9209 }
9210
9211 assert_eq!(
9212 f.run(&[b"BITFIELD_RO", b"gone", b"GET", b"u8", b"100"]),
9213 "*1\r\n:0\r\n"
9214 );
9215 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
9216 }
9217
9218 #[test]
9220 fn an_offset_off_the_end_of_the_world_is_refused() {
9221 let mut f = Fixture::new();
9222 let bad = "-ERR bit offset is not an integer or out of range\r\n";
9223 for arg in [&b"abc"[..], b"-1", b"4294967296"] {
9224 assert_eq!(f.run(&[b"SETBIT", b"k", arg, b"1"]), bad);
9225 assert_eq!(f.run(&[b"GETBIT", b"k", arg]), bad);
9226 }
9227 for arg in [&b"2"[..], b"-1"] {
9228 assert_eq!(
9229 f.run(&[b"BITPOS", b"k", arg]),
9230 "-ERR The bit argument must be 1 or 0.\r\n"
9231 );
9232 }
9233 assert_eq!(
9234 f.run(&[b"BITPOS", b"k", b"abc"]),
9235 "-ERR value is not an integer or out of range\r\n"
9236 );
9237 assert_eq!(
9238 f.run(&[b"BITPOS", b"k", b"0", b"5", b"BIT"]),
9239 "-ERR value is not an integer or out of range\r\n"
9240 );
9241 let bad_bit = "-ERR bit is not an integer or out of range\r\n";
9242 assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"2"]), bad_bit);
9243 assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"abc"]), bad_bit);
9244 }
9245
9246 #[test]
9248 fn every_bitmap_command_says_wrongtype() {
9249 let mut f = Fixture::new();
9250 f.run(&[b"LPUSH", b"l", b"x"]);
9251 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9252 let cases: &[&[&[u8]]] = &[
9253 &[b"SETBIT", b"l", b"0", b"1"],
9254 &[b"GETBIT", b"l", b"0"],
9255 &[b"BITCOUNT", b"l"],
9256 &[b"BITPOS", b"l", b"1"],
9257 &[b"BITOP", b"AND", b"d", b"l"],
9258 &[b"BITFIELD", b"l", b"GET", b"u8", b"0"],
9259 &[b"BITFIELD_RO", b"l", b"GET", b"u8", b"0"],
9260 ];
9261 for case in cases {
9262 assert_eq!(f.run(case), wrong, "{:?}", case[0]);
9263 }
9264 }
9265
9266 #[test]
9269 fn a_sketch_is_added_to_and_counted() {
9270 let mut f = Fixture::new();
9271 assert_eq!(f.run(&[b"PFADD", b"h"]), ":1\r\n");
9273 assert_eq!(f.run(&[b"PFADD", b"h"]), ":0\r\n");
9274 assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":0\r\n");
9275 assert_eq!(f.run(&[b"STRLEN", b"h"]), ":18\r\n");
9276 assert_eq!(f.run(&[b"TYPE", b"h"]), "+string\r\n");
9279 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$3\r\nraw\r\n");
9280
9281 assert_eq!(f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]), ":1\r\n");
9282 assert_eq!(f.run(&[b"PFADD", b"h", b"a"]), ":0\r\n");
9283 assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
9284 }
9285
9286 #[test]
9287 fn the_bytes_of_a_sketch_are_the_ones_a_real_server_writes() {
9288 let mut f = Fixture::new();
9289 f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
9290 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";
9292 let mut reply = b"$27\r\n".to_vec();
9293 reply.extend_from_slice(want);
9294 reply.extend_from_slice(b"\r\n");
9295 assert_eq!(f.raw(&[b"GET", b"h"]), reply);
9296 }
9297
9298 #[test]
9299 fn counting_several_keys_counts_their_union() {
9300 let mut f = Fixture::new();
9301 f.run(&[b"PFADD", b"a", b"x", b"y"]);
9302 f.run(&[b"PFADD", b"b", b"y", b"z"]);
9303 assert_eq!(f.run(&[b"PFCOUNT", b"a"]), ":2\r\n");
9304 assert_eq!(f.run(&[b"PFCOUNT", b"a", b"b"]), ":3\r\n");
9305 assert_eq!(f.run(&[b"PFCOUNT", b"gone"]), ":0\r\n");
9308 assert_eq!(f.run(&[b"PFCOUNT", b"a", b"gone"]), ":2\r\n");
9309 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
9310 }
9311
9312 #[test]
9313 fn a_merge_keeps_what_the_destination_had() {
9314 let mut f = Fixture::new();
9315 f.run(&[b"PFADD", b"a", b"x", b"y"]);
9316 f.run(&[b"PFADD", b"b", b"z"]);
9317 assert_eq!(f.run(&[b"PFMERGE", b"d", b"a", b"b"]), "+OK\r\n");
9318 assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":3\r\n");
9319 f.run(&[b"PFADD", b"c", b"w"]);
9321 assert_eq!(f.run(&[b"PFMERGE", b"d", b"c"]), "+OK\r\n");
9322 assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":4\r\n");
9323 assert_eq!(f.run(&[b"PFMERGE", b"fresh"]), "+OK\r\n");
9326 assert_eq!(f.run(&[b"PFCOUNT", b"fresh"]), ":0\r\n");
9327 }
9328
9329 #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
9338 #[test]
9339 fn the_debug_forms_answer_four_different_shapes() {
9340 let mut f = Fixture::new();
9341 f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
9342 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+sparse\r\n");
9343 assert_eq!(
9344 f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
9345 "$44\r\nZ:8436 v:1,1 Z:4274 v:2,1 Z:3068 v:1,1 Z:603\r\n"
9346 );
9347 assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":1\r\n");
9348 assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":0\r\n");
9349 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+dense\r\n");
9350 assert_eq!(f.run(&[b"STRLEN", b"h"]), ":12304\r\n");
9351 assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
9352 assert_eq!(
9354 f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
9355 "-ERR HLL encoding is not sparse\r\n"
9356 );
9357
9358 let reply = f.run(&[b"PFDEBUG", b"GETREG", b"h"]);
9360 assert!(reply.starts_with("*16384\r\n"), "{}", &reply[..16]);
9361 assert_eq!(reply.matches(":0\r\n").count(), 16381);
9362 assert_eq!(reply.matches(":1\r\n").count(), 2);
9363 assert_eq!(reply.matches(":2\r\n").count(), 1);
9364
9365 assert_eq!(f.run(&[b"PFSELFTEST"]), "+OK\r\n");
9366 }
9367
9368 #[test]
9369 fn a_string_that_is_not_a_sketch_is_refused_with_its_own_sentence() {
9370 let mut f = Fixture::new();
9371 f.run(&[b"SET", b"plain", b"not a sketch"]);
9372 let not_hll = "-WRONGTYPE Key is not a valid HyperLogLog string value.\r\n";
9373 assert_eq!(f.run(&[b"PFADD", b"plain", b"a"]), not_hll);
9374 assert_eq!(f.run(&[b"PFCOUNT", b"plain"]), not_hll);
9375 assert_eq!(f.run(&[b"PFMERGE", b"plain"]), not_hll);
9376 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"plain"]), not_hll);
9377
9378 f.run(&[b"RPUSH", b"l", b"x"]);
9381 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9382 assert_eq!(f.run(&[b"PFADD", b"l", b"a"]), wrong);
9383 assert_eq!(f.run(&[b"PFCOUNT", b"l"]), wrong);
9384 assert_eq!(f.run(&[b"PFMERGE", b"dest", b"l"]), wrong);
9385 assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
9386 assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"l"]), wrong);
9387 }
9388
9389 #[test]
9390 fn pfdebug_has_its_own_complaints() {
9391 let mut f = Fixture::new();
9392 f.run(&[b"PFADD", b"h", b"a"]);
9393 assert_eq!(
9396 f.run(&[b"PFDEBUG", b"NOPE", b"h"]),
9397 "-ERR Unknown PFDEBUG subcommand 'NOPE'\r\n"
9398 );
9399 let gone = "-ERR The specified key does not exist\r\n";
9401 assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"missing"]), gone);
9402 assert_eq!(f.run(&[b"PFDEBUG", b"DECODE", b"missing"]), gone);
9403 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"missing"]), gone);
9404 assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"missing"]), gone);
9405 assert_eq!(
9406 f.run(&[b"PFDEBUG"]),
9407 "-ERR wrong number of arguments for 'pfdebug' command\r\n"
9408 );
9409 assert_eq!(
9410 f.run(&[b"PFSELFTEST", b"x"]),
9411 "-ERR wrong number of arguments for 'pfselftest' command\r\n"
9412 );
9413 }
9414
9415 #[test]
9416 fn a_sketch_whose_opcodes_do_not_add_up_says_so() {
9417 let mut f = Fixture::new();
9418 f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
9419 let reply = f.raw(&[b"GET", b"h"]);
9422 let short = reply[5..reply.len() - 3].to_vec();
9423 f.run(&[b"SET", b"h", &short]);
9424 assert_eq!(
9425 f.run(&[b"PFCOUNT", b"h"]),
9426 "-INVALIDOBJ Corrupted HLL object detected\r\n"
9427 );
9428 }
9429
9430 #[test]
9431 fn a_sketch_survives_a_dump_and_a_restore_in_both_encodings() {
9432 let mut f = Fixture::new();
9433 f.run(&[b"PFADD", b"small", b"a", b"b", b"c"]);
9436 if cfg!(miri) {
9443 f.run(&[b"PFADD", b"big", b"a", b"b", b"c"]);
9444 f.run(&[b"PFDEBUG", b"TODENSE", b"big"]);
9445 } else {
9446 for i in 0..10_000u32 {
9447 let ele = format!("e{i}");
9448 f.run(&[b"PFADD", b"big", ele.as_bytes()]);
9449 }
9450 }
9451 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"small"]), "+sparse\r\n");
9452 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"big"]), "+dense\r\n");
9453
9454 for key in [&b"small"[..], b"big"] {
9455 let mut copy = key.to_vec();
9456 copy.push(b'2');
9457 let bytes = payload(&f.raw(&[b"DUMP", key]));
9458 assert_eq!(f.run(&[b"RESTORE", ©, b"0", &bytes]), "+OK\r\n");
9459 assert_eq!(f.raw(&[b"GET", ©]), f.raw(&[b"GET", key]));
9462 assert_eq!(
9463 f.run(&[b"PFDEBUG", b"ENCODING", ©]),
9464 f.run(&[b"PFDEBUG", b"ENCODING", key])
9465 );
9466 assert_eq!(f.run(&[b"PFCOUNT", ©]), f.run(&[b"PFCOUNT", key]));
9467 }
9468 assert_eq!(f.run(&[b"PFCOUNT", b"small2"]), ":3\r\n");
9469 assert_eq!(f.run(&[b"STRLEN", b"big2"]), ":12304\r\n");
9470 }
9471
9472 fn bulk(s: &str) -> String {
9476 format!("${}\r\n{s}\r\n", s.len())
9477 }
9478
9479 fn bulks(parts: &[&str]) -> String {
9482 let mut s = format!("*{}\r\n", parts.len());
9483 for p in parts {
9484 s.push_str(&format!("${}\r\n{p}\r\n", p.len()));
9485 }
9486 s
9487 }
9488
9489 #[test]
9490 fn a_list_is_pushed_from_both_ends_and_the_left_one_reverses() {
9491 let mut f = Fixture::new();
9492 assert_eq!(f.run(&[b"LPUSH", b"k", b"a", b"b", b"c"]), ":3\r\n");
9496 assert_eq!(
9497 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
9498 bulks(&["c", "b", "a"])
9499 );
9500 assert_eq!(f.run(&[b"RPUSH", b"k", b"d"]), ":4\r\n");
9501 assert_eq!(f.run(&[b"LLEN", b"k"]), ":4\r\n");
9502 assert_eq!(f.run(&[b"LPOP", b"k"]), "$1\r\nc\r\n");
9503 assert_eq!(f.run(&[b"RPOP", b"k"]), "$1\r\nd\r\n");
9504 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "a"]));
9505 assert_eq!(f.run(&[b"TYPE", b"k"]), "+list\r\n");
9506 }
9507
9508 #[test]
9509 fn the_x_pushes_refuse_to_bring_a_list_back_to_life() {
9510 let mut f = Fixture::new();
9511 assert_eq!(f.run(&[b"LPUSHX", b"k", b"a"]), ":0\r\n");
9512 assert_eq!(f.run(&[b"RPUSHX", b"k", b"a"]), ":0\r\n");
9513 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
9514 f.run(&[b"RPUSH", b"k", b"a"]);
9515 assert_eq!(f.run(&[b"LPUSHX", b"k", b"z"]), ":2\r\n");
9516 assert_eq!(f.run(&[b"RPUSHX", b"k", b"y"]), ":3\r\n");
9517 assert_eq!(
9518 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
9519 bulks(&["z", "a", "y"])
9520 );
9521 }
9522
9523 #[test]
9526 fn an_empty_pop_is_a_different_nothing_with_a_count_and_without() {
9527 let mut f = Fixture::new();
9528 assert_eq!(f.run(&[b"LPOP", b"nope"]), "$-1\r\n");
9529 assert_eq!(f.run(&[b"LPOP", b"nope", b"2"]), "*-1\r\n");
9530 assert_eq!(f.run(&[b"RPOP", b"nope"]), "$-1\r\n");
9531 assert_eq!(f.run(&[b"RPOP", b"nope", b"2"]), "*-1\r\n");
9532 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
9533 assert_eq!(f.run(&[b"LPOP", b"k", b"0"]), "*0\r\n");
9536 assert_eq!(f.run(&[b"LPOP", b"k", b"1"]), bulks(&["a"]));
9537 assert_eq!(f.run(&[b"RPOP", b"k", b"9"]), bulks(&["c", "b"]));
9539 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
9540 }
9541
9542 #[test]
9543 fn a_pop_count_has_its_own_sentence_and_a_third_argument_is_an_arity_error() {
9544 let mut f = Fixture::new();
9545 f.run(&[b"RPUSH", b"k", b"a"]);
9546 let range = "-ERR value is out of range, must be positive\r\n";
9547 assert_eq!(f.run(&[b"LPOP", b"k", b"-1"]), range);
9548 assert_eq!(f.run(&[b"LPOP", b"k", b"abc"]), range);
9549 assert_eq!(f.run(&[b"RPOP", b"k", b"-1"]), range);
9550 assert_eq!(
9553 f.run(&[b"LPOP", b"k", b"1", b"2"]),
9554 "-ERR wrong number of arguments for 'lpop' command\r\n"
9555 );
9556 assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
9557 }
9558
9559 #[test]
9560 fn a_range_takes_negative_ends_and_clamps_the_ones_that_run_off() {
9561 let mut f = Fixture::new();
9562 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
9563 assert_eq!(
9564 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
9565 bulks(&["a", "b", "c"])
9566 );
9567 assert_eq!(f.run(&[b"LRANGE", b"k", b"-2", b"-1"]), bulks(&["b", "c"]));
9568 assert_eq!(f.run(&[b"LRANGE", b"k", b"1", b"1"]), bulks(&["b"]));
9569 assert_eq!(f.run(&[b"LRANGE", b"k", b"5", b"10"]), "*0\r\n");
9570 assert_eq!(f.run(&[b"LRANGE", b"k", b"2", b"1"]), "*0\r\n");
9571 assert_eq!(
9572 f.run(&[b"LRANGE", b"k", b"-100", b"100"]),
9573 bulks(&["a", "b", "c"])
9574 );
9575 assert_eq!(f.run(&[b"LRANGE", b"nope", b"0", b"-1"]), "*0\r\n");
9578 assert_eq!(
9579 f.run(&[b"LRANGE", b"k", b"a", b"b"]),
9580 "-ERR value is not an integer or out of range\r\n"
9581 );
9582 }
9583
9584 #[test]
9585 fn an_index_reads_and_writes_from_whichever_end_is_nearer() {
9586 let mut f = Fixture::new();
9587 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
9588 assert_eq!(f.run(&[b"LINDEX", b"k", b"0"]), "$1\r\na\r\n");
9589 assert_eq!(f.run(&[b"LINDEX", b"k", b"-1"]), "$1\r\nc\r\n");
9590 assert_eq!(f.run(&[b"LINDEX", b"k", b"99"]), "$-1\r\n");
9591 assert_eq!(f.run(&[b"LINDEX", b"nope", b"0"]), "$-1\r\n");
9592 assert_eq!(f.run(&[b"LSET", b"k", b"-1", b"z"]), "+OK\r\n");
9593 assert_eq!(
9594 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
9595 bulks(&["a", "b", "z"])
9596 );
9597 assert_eq!(
9600 f.run(&[b"LSET", b"k", b"99", b"z"]),
9601 "-ERR index out of range\r\n"
9602 );
9603 assert_eq!(
9604 f.run(&[b"LSET", b"nope", b"0", b"z"]),
9605 "-ERR no such key\r\n"
9606 );
9607 }
9608
9609 #[test]
9610 fn linsert_says_three_things_with_one_signed_number() {
9611 let mut f = Fixture::new();
9612 assert_eq!(
9615 f.run(&[b"LINSERT", b"nope", b"BEFORE", b"a", b"x"]),
9616 ":0\r\n"
9617 );
9618 f.run(&[b"RPUSH", b"k", b"a", b"b"]);
9619 assert_eq!(f.run(&[b"LINSERT", b"k", b"before", b"a", b"X"]), ":3\r\n");
9620 assert_eq!(f.run(&[b"LINSERT", b"k", b"AFTER", b"b", b"Y"]), ":4\r\n");
9621 assert_eq!(
9622 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
9623 bulks(&["X", "a", "b", "Y"])
9624 );
9625 assert_eq!(
9626 f.run(&[b"LINSERT", b"k", b"BEFORE", b"zz", b"x"]),
9627 ":-1\r\n"
9628 );
9629 assert_eq!(
9630 f.run(&[b"LINSERT", b"k", b"SIDEWAYS", b"a", b"x"]),
9631 "-ERR syntax error\r\n"
9632 );
9633 }
9634
9635 #[test]
9636 fn lrem_counts_in_three_directions_and_takes_the_key_when_it_empties() {
9637 let mut f = Fixture::new();
9638 f.run(&[b"RPUSH", b"k", b"a", b"b", b"a", b"c", b"a"]);
9639 assert_eq!(f.run(&[b"LREM", b"k", b"2", b"a"]), ":2\r\n");
9640 assert_eq!(
9641 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
9642 bulks(&["b", "c", "a"])
9643 );
9644 assert_eq!(f.run(&[b"LREM", b"k", b"-1", b"a"]), ":1\r\n");
9645 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
9646 assert_eq!(f.run(&[b"LREM", b"k", b"0", b"b"]), ":1\r\n");
9647 assert_eq!(f.run(&[b"LREM", b"k", b"0", b"c"]), ":1\r\n");
9648 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
9649 assert_eq!(f.run(&[b"LREM", b"nope", b"0", b"a"]), ":0\r\n");
9650 }
9651
9652 #[test]
9653 fn ltrim_keeps_a_window_and_an_empty_one_deletes_the_key() {
9654 let mut f = Fixture::new();
9655 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c", b"d"]);
9656 assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"-2"]), "+OK\r\n");
9657 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
9658 assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"0"]), "+OK\r\n");
9661 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
9662 assert_eq!(f.run(&[b"LTRIM", b"nope", b"0", b"-1"]), "+OK\r\n");
9663 }
9664
9665 #[test]
9666 fn lpos_walks_from_either_end_and_stops_where_it_is_told() {
9667 let mut f = Fixture::new();
9668 f.run(&[b"RPUSH", b"p", b"a", b"b", b"c", b"a", b"b", b"c", b"a"]);
9669 assert_eq!(f.run(&[b"LPOS", b"p", b"a"]), ":0\r\n");
9670 assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1"]), ":6\r\n");
9671 assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"2"]), ":3\r\n");
9672 assert_eq!(
9673 f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"2"]),
9674 "*2\r\n:0\r\n:3\r\n"
9675 );
9676 assert_eq!(
9677 f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1", b"COUNT", b"0"]),
9678 "*3\r\n:6\r\n:3\r\n:0\r\n"
9679 );
9680 assert_eq!(
9683 f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"0", b"MAXLEN", b"3"]),
9684 "*1\r\n:0\r\n"
9685 );
9686 assert_eq!(f.run(&[b"LPOS", b"p", b"zz"]), "$-1\r\n");
9689 assert_eq!(f.run(&[b"LPOS", b"p", b"zz", b"COUNT", b"0"]), "*0\r\n");
9690 assert_eq!(f.run(&[b"LPOS", b"nope", b"a"]), "$-1\r\n");
9691 assert_eq!(f.run(&[b"LPOS", b"nope", b"a", b"COUNT", b"2"]), "*0\r\n");
9692 }
9693
9694 #[test]
9695 fn lpos_words_its_three_mistakes_the_way_redis_does() {
9696 let mut f = Fixture::new();
9697 f.run(&[b"RPUSH", b"p", b"a"]);
9698 assert_eq!(
9701 f.run(&[b"LPOS", b"p", b"a", b"RANK", b"0"]),
9702 "-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"
9703 );
9704 assert_eq!(
9705 f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"-1"]),
9706 "-ERR COUNT can't be negative\r\n"
9707 );
9708 assert_eq!(
9709 f.run(&[b"LPOS", b"p", b"a", b"MAXLEN", b"-1"]),
9710 "-ERR MAXLEN can't be negative\r\n"
9711 );
9712 assert_eq!(
9713 f.run(&[b"LPOS", b"p", b"a", b"RANK"]),
9714 "-ERR syntax error\r\n"
9715 );
9716 assert_eq!(
9717 f.run(&[b"LPOS", b"p", b"a", b"FOO", b"1"]),
9718 "-ERR syntax error\r\n"
9719 );
9720 }
9721
9722 #[test]
9723 fn a_move_takes_from_one_end_and_gives_to_another_even_on_one_key() {
9724 let mut f = Fixture::new();
9725 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
9726 assert_eq!(f.run(&[b"RPOPLPUSH", b"k", b"d"]), "$1\r\nc\r\n");
9727 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
9728 assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c"]));
9729 assert_eq!(
9730 f.run(&[b"LMOVE", b"k", b"d", b"LEFT", b"RIGHT"]),
9731 "$1\r\na\r\n"
9732 );
9733 assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c", "a"]));
9734 f.run(&[b"DEL", b"r"]);
9737 f.run(&[b"RPUSH", b"r", b"1", b"2", b"3"]);
9738 assert_eq!(f.run(&[b"RPOPLPUSH", b"r", b"r"]), "$1\r\n3\r\n");
9739 assert_eq!(
9740 f.run(&[b"LRANGE", b"r", b"0", b"-1"]),
9741 bulks(&["3", "1", "2"])
9742 );
9743 assert_eq!(
9744 f.run(&[b"LMOVE", b"nope", b"d", b"LEFT", b"LEFT"]),
9745 "$-1\r\n"
9746 );
9747 assert_eq!(
9748 f.run(&[b"LMOVE", b"r", b"d", b"LEFT", b"SIDEWAYS"]),
9749 "-ERR syntax error\r\n"
9750 );
9751 }
9752
9753 #[test]
9754 fn a_move_checks_the_destination_before_it_takes_anything() {
9755 let mut f = Fixture::new();
9756 f.run(&[b"RPUSH", b"k", b"a", b"b"]);
9757 f.run(&[b"SET", b"str", b"v"]);
9758 assert_eq!(
9759 f.run(&[b"LMOVE", b"k", b"str", b"LEFT", b"LEFT"]),
9760 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
9761 );
9762 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
9764 }
9765
9766 #[test]
9767 fn a_block_move_orders_the_block_by_the_ends_and_the_ordering_word() {
9768 for (from, to, order, want) in [
9772 ("LEFT", "RIGHT", "OBO", ["a", "b"]),
9773 ("LEFT", "RIGHT", "BULK", ["a", "b"]),
9774 ("LEFT", "LEFT", "OBO", ["b", "a"]),
9775 ("LEFT", "LEFT", "BULK", ["a", "b"]),
9776 ("RIGHT", "LEFT", "OBO", ["d", "e"]),
9777 ("RIGHT", "LEFT", "BULK", ["d", "e"]),
9778 ("RIGHT", "RIGHT", "OBO", ["e", "d"]),
9779 ("RIGHT", "RIGHT", "BULK", ["d", "e"]),
9780 ] {
9781 let mut f = Fixture::new();
9782 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c", b"d", b"e"]);
9783 let how = format!("{from} {to} {order}");
9784 let reply = f.run(&[
9785 b"LMOVEM",
9786 b"s",
9787 b"d",
9788 from.as_bytes(),
9789 to.as_bytes(),
9790 b"COUNT",
9791 b"2",
9792 order.as_bytes(),
9793 ]);
9794 assert_eq!(reply, bulks(&want), "the reply for {how}");
9795 assert_eq!(
9796 f.run(&[b"LRANGE", b"d", b"0", b"-1"]),
9797 bulks(&want),
9798 "the destination for {how}"
9799 );
9800 }
9801 }
9802
9803 #[test]
9804 fn a_block_move_of_one_needs_no_count_at_all() {
9805 let mut f = Fixture::new();
9806 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
9807 assert_eq!(
9808 f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT"]),
9809 bulks(&["a"])
9810 );
9811 assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["b", "c"]));
9812 assert_eq!(
9815 f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT"]),
9816 "-ERR syntax error\r\n"
9817 );
9818 assert_eq!(
9819 f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"2"]),
9820 "-ERR syntax error\r\n"
9821 );
9822 }
9823
9824 #[test]
9825 fn a_block_move_with_exactly_takes_all_of_them_or_none() {
9826 let mut f = Fixture::new();
9827 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
9828 assert_eq!(
9832 f.run(&[
9833 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"EXACTLY", b"99", b"BULK"
9834 ]),
9835 "*-1\r\n"
9836 );
9837 assert_eq!(
9838 f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
9839 bulks(&["a", "b", "c"])
9840 );
9841 assert_eq!(
9843 f.run(&[
9844 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"99", b"BULK"
9845 ]),
9846 bulks(&["a", "b", "c"])
9847 );
9848 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
9849 assert_eq!(
9850 f.run(&[
9851 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
9852 ]),
9853 "*-1\r\n"
9854 );
9855 }
9856
9857 #[test]
9858 fn a_block_move_onto_itself_rotates_by_the_count() {
9859 let mut f = Fixture::new();
9860 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
9861 assert_eq!(
9862 f.run(&[
9863 b"LMOVEM", b"s", b"s", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
9864 ]),
9865 bulks(&["a", "b"])
9866 );
9867 assert_eq!(
9868 f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
9869 bulks(&["c", "a", "b"])
9870 );
9871 }
9872
9873 #[test]
9874 fn a_block_move_reads_the_count_before_the_ordering_word() {
9875 let mut f = Fixture::new();
9876 f.run(&[b"RPUSH", b"s", b"a", b"b"]);
9877 f.run(&[b"SET", b"str", b"v"]);
9878 let count = "-ERR count should be greater than 0\r\n";
9879 assert_eq!(
9880 f.run(&[
9881 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"NOPE"
9882 ]),
9883 count
9884 );
9885 assert_eq!(
9886 f.run(&[
9887 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"0", b"BULK"
9888 ]),
9889 count
9890 );
9891 assert_eq!(
9892 f.run(&[
9893 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"NOPE"
9894 ]),
9895 "-ERR syntax error\r\n"
9896 );
9897 assert_eq!(
9898 f.run(&[
9899 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"NOPE", b"abc", b"BULK"
9900 ]),
9901 "-ERR syntax error\r\n"
9902 );
9903 assert_eq!(
9906 f.run(&[
9907 b"LMOVEM", b"str", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"BULK"
9908 ]),
9909 count
9910 );
9911 assert_eq!(
9912 f.run(&[
9913 b"LMOVEM", b"s", b"str", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
9914 ]),
9915 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
9916 );
9917 assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["a", "b"]));
9918 }
9919
9920 #[test]
9921 fn lmpop_answers_from_the_first_key_that_has_anything() {
9922 let mut f = Fixture::new();
9923 f.run(&[b"RPUSH", b"b", b"1", b"2", b"3"]);
9924 assert_eq!(
9927 f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT", b"COUNT", b"2"]),
9928 "*2\r\n$1\r\nb\r\n*2\r\n$1\r\n1\r\n$1\r\n2\r\n"
9929 );
9930 assert_eq!(
9931 f.run(&[b"LMPOP", b"2", b"a", b"b", b"RIGHT"]),
9932 "*2\r\n$1\r\nb\r\n*1\r\n$1\r\n3\r\n"
9933 );
9934 assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
9935 assert_eq!(f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT"]), "*-1\r\n");
9938 }
9939
9940 #[test]
9941 fn lmpop_has_its_own_words_for_a_count_and_for_a_key_count() {
9942 let mut f = Fixture::new();
9943 f.run(&[b"RPUSH", b"k", b"a"]);
9944 assert_eq!(
9945 f.run(&[b"LMPOP", b"0", b"k", b"LEFT"]),
9946 "-ERR numkeys should be greater than 0\r\n"
9947 );
9948 assert_eq!(
9949 f.run(&[b"LMPOP", b"-1", b"k", b"LEFT"]),
9950 "-ERR numkeys should be greater than 0\r\n"
9951 );
9952 assert_eq!(
9953 f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"0"]),
9954 "-ERR count should be greater than 0\r\n"
9955 );
9956 assert_eq!(
9959 f.run(&[b"LMPOP", b"3", b"k", b"LEFT"]),
9960 "-ERR syntax error\r\n"
9961 );
9962 assert_eq!(
9963 f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"1", b"x"]),
9964 "-ERR syntax error\r\n"
9965 );
9966 assert_eq!(
9967 f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"FOO", b"1"]),
9968 "-ERR syntax error\r\n"
9969 );
9970 assert_eq!(
9971 f.run(&[b"LMPOP", b"1", b"k", b"SIDEWAYS"]),
9972 "-ERR syntax error\r\n"
9973 );
9974 assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
9975 }
9976
9977 #[test]
9978 fn every_list_command_says_wrongtype_and_writes_nothing() {
9979 let mut f = Fixture::new();
9980 f.run(&[b"SET", b"str", b"v"]);
9981 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9982 for cmd in [
9983 &[b"LPUSH".as_slice(), b"str", b"a"][..],
9984 &[b"RPUSH", b"str", b"a"],
9985 &[b"LPUSHX", b"str", b"a"],
9986 &[b"RPUSHX", b"str", b"a"],
9987 &[b"LPOP", b"str"],
9988 &[b"LPOP", b"str", b"2"],
9989 &[b"RPOP", b"str"],
9990 &[b"LLEN", b"str"],
9991 &[b"LRANGE", b"str", b"0", b"-1"],
9992 &[b"LINDEX", b"str", b"0"],
9993 &[b"LSET", b"str", b"0", b"a"],
9994 &[b"LINSERT", b"str", b"BEFORE", b"a", b"b"],
9995 &[b"LREM", b"str", b"0", b"a"],
9996 &[b"LTRIM", b"str", b"0", b"-1"],
9997 &[b"LPOS", b"str", b"a"],
9998 &[b"LPOS", b"str", b"a", b"COUNT", b"0"],
9999 &[b"RPOPLPUSH", b"str", b"d"],
10000 &[b"LMOVE", b"str", b"d", b"LEFT", b"LEFT"],
10001 &[b"LMPOP", b"1", b"str", b"LEFT"],
10002 ] {
10003 assert_eq!(f.run(cmd), wrong, "{:?}", String::from_utf8_lossy(cmd[0]));
10004 }
10005 assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
10006 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
10007 }
10008
10009 #[test]
10013 fn a_timeout_has_three_ways_of_being_wrong() {
10014 let mut f = Fixture::new();
10015 let not_float = "-ERR timeout is not a float or out of range\r\n";
10016 let range = "-ERR timeout is out of range\r\n";
10017 for (bad, want) in [
10018 (&[b"BLPOP".as_slice(), b"k", b"abc"][..], not_float),
10019 (&[b"BLPOP", b"k", b"nan"], not_float),
10020 (&[b"BLPOP", b"k", b""], not_float),
10021 (&[b"BLPOP", b"k", b" 1"], not_float),
10024 (&[b"BLPOP", b"k", b"1 "], not_float),
10025 (&[b"BLPOP", b"k", b"-1"], "-ERR timeout is negative\r\n"),
10026 (&[b"BLPOP", b"k", b"-0.1"], "-ERR timeout is negative\r\n"),
10027 (&[b"BLPOP", b"k", b"1e400"], range),
10030 (&[b"BLPOP", b"k", b"inf"], range),
10031 (&[b"BLPOP", b"k", b"9999999999999999"], range),
10032 (&[b"BRPOP", b"k", b"abc"], not_float),
10033 (
10034 &[b"BLMOVE", b"a", b"b", b"LEFT", b"RIGHT", b"abc"],
10035 not_float,
10036 ),
10037 (
10038 &[b"BRPOPLPUSH", b"a", b"b", b"-1"],
10039 "-ERR timeout is negative\r\n",
10040 ),
10041 (&[b"BLMPOP", b"abc", b"1", b"k", b"LEFT"], not_float),
10042 ] {
10043 assert_eq!(f.run(bad), want, "for {bad:?}");
10044 }
10045 }
10046
10047 #[test]
10050 fn a_zero_timeout_waits_and_the_smallest_positive_one_does_not() {
10051 let mut f = Fixture::new();
10052 for timeout in [b"0".as_slice(), b"0.0", b"-0.0"] {
10053 let (flow, out) = f.flow(&[b"BLPOP", b"k", timeout]);
10054 assert_eq!(flow, Flow::Block, "for {timeout:?}");
10055 assert!(out.is_empty(), "for {timeout:?}");
10056 }
10057 let (flow, out) = f.flow(&[b"BLPOP", b"k", b"0.0000001"]);
10061 assert_eq!(flow, Flow::Block);
10062 assert!(out.is_empty());
10063 }
10064
10065 #[test]
10066 fn a_blocking_command_that_can_be_answered_answers_like_the_one_it_wraps() {
10067 let mut f = Fixture::new();
10068 f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
10069
10070 assert_eq!(
10073 f.flow(&[b"BLPOP", b"nope", b"L", b"0"]),
10074 (Flow::Continue, "*2\r\n$1\r\nL\r\n$1\r\na\r\n".to_owned())
10075 );
10076 assert_eq!(
10077 f.run(&[b"BRPOP", b"L", b"0"]),
10078 "*2\r\n$1\r\nL\r\n$1\r\ne\r\n"
10079 );
10080 assert_eq!(
10081 f.run(&[
10082 b"BLMPOP", b"0", b"2", b"nope", b"L", b"LEFT", b"COUNT", b"2"
10083 ]),
10084 "*2\r\n$1\r\nL\r\n*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
10085 );
10086 assert_eq!(
10087 f.run(&[b"BLMOVE", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
10088 "$1\r\nd\r\n"
10089 );
10090 assert_eq!(
10091 f.run(&[b"EXISTS", b"L"]),
10092 ":0\r\n",
10093 "and the key went with it"
10094 );
10095 assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nd\r\n");
10096 f.run(&[b"RPUSH", b"D", b"x"]);
10099 assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"D", b"0"]), "$1\r\nx\r\n");
10100 assert_eq!(
10101 f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
10102 "*2\r\n$1\r\nx\r\n$1\r\nd\r\n"
10103 );
10104 }
10105
10106 #[test]
10107 fn blmpop_reads_its_count_and_its_key_count_the_way_lmpop_does() {
10108 let mut f = Fixture::new();
10109 f.run(&[b"RPUSH", b"k", b"a"]);
10110 for (bad, want) in [
10111 (
10112 &[b"BLMPOP".as_slice(), b"0", b"0", b"k", b"LEFT"][..],
10113 "-ERR numkeys should be greater than 0\r\n",
10114 ),
10115 (
10116 &[b"BLMPOP", b"0", b"-1", b"k", b"LEFT"],
10117 "-ERR numkeys should be greater than 0\r\n",
10118 ),
10119 (
10122 &[b"BLMPOP", b"0", b"2", b"k", b"LEFT"],
10123 "-ERR syntax error\r\n",
10124 ),
10125 (
10126 &[b"BLMPOP", b"0", b"1", b"k", b"SIDEWAYS"],
10127 "-ERR syntax error\r\n",
10128 ),
10129 (
10130 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT"],
10131 "-ERR syntax error\r\n",
10132 ),
10133 (
10134 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"2", b"x"],
10135 "-ERR syntax error\r\n",
10136 ),
10137 (
10140 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"0"],
10141 "-ERR count should be greater than 0\r\n",
10142 ),
10143 (
10144 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"abc"],
10145 "-ERR count should be greater than 0\r\n",
10146 ),
10147 ] {
10148 assert_eq!(f.run(bad), want, "for {bad:?}");
10149 }
10150 assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n", "and none of them popped");
10151 }
10152
10153 #[test]
10154 fn a_blocking_move_reads_its_directions_before_its_timeout() {
10155 let mut f = Fixture::new();
10156 assert_eq!(
10159 f.run(&[b"BLMOVE", b"a", b"b", b"UP", b"DOWN", b"abc"]),
10160 "-ERR syntax error\r\n"
10161 );
10162 assert_eq!(
10163 f.run(&[b"BLMOVE", b"a", b"b", b"LEFT", b"DOWN", b"0.05"]),
10164 "-ERR syntax error\r\n"
10165 );
10166 }
10167
10168 #[test]
10172 fn a_blocking_block_move_that_can_be_answered_answers_like_lmovem() {
10173 let mut f = Fixture::new();
10174 f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
10175 assert_eq!(
10176 f.flow(&[b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
10177 (Flow::Continue, "*1\r\n$1\r\na\r\n".to_owned())
10178 );
10179 assert_eq!(
10180 f.run(&[
10181 b"BLMOVEM", b"L", b"D", b"RIGHT", b"RIGHT", b"0", b"COUNT", b"2", b"OBO"
10182 ]),
10183 bulks(&["e", "d"])
10184 );
10185 assert_eq!(
10186 f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
10187 bulks(&["a", "e", "d"])
10188 );
10189 assert_eq!(
10191 f.run(&[
10192 b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"2", b"BULK"
10193 ]),
10194 bulks(&["b", "c"])
10195 );
10196 assert_eq!(f.run(&[b"EXISTS", b"L"]), ":0\r\n", "and the key went");
10197 }
10198
10199 #[test]
10203 fn a_blocking_block_move_waits_for_the_whole_block_only_under_exactly() {
10204 let mut f = Fixture::new();
10205 f.run(&[b"RPUSH", b"L", b"a", b"b"]);
10206 assert_eq!(
10208 f.flow(&[
10209 b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"COUNT", b"3", b"BULK"
10210 ]),
10211 (Flow::Continue, bulks(&["a", "b"]))
10212 );
10213
10214 f.run(&[b"RPUSH", b"L", b"a", b"b"]);
10215 assert_eq!(
10218 f.flow(&[
10219 b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"3", b"BULK"
10220 ])
10221 .0,
10222 Flow::Block
10223 );
10224 assert_eq!(f.run(&[b"LRANGE", b"L", b"0", b"-1"]), bulks(&["a", "b"]));
10225 }
10226
10227 #[test]
10228 fn a_blocking_block_move_reads_its_directions_then_its_timeout_then_its_count() {
10229 let mut f = Fixture::new();
10230 let syntax = "-ERR syntax error\r\n";
10231 assert_eq!(
10233 f.run(&[
10234 b"BLMOVEM", b"a", b"b", b"UP", b"DOWN", b"abc", b"NOPE", b"x", b"y"
10235 ]),
10236 syntax
10237 );
10238 assert_eq!(
10240 f.run(&[
10241 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"abc", b"COUNT", b"abc", b"BULK"
10242 ]),
10243 "-ERR timeout is not a float or out of range\r\n"
10244 );
10245 assert_eq!(
10246 f.run(&[
10247 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"-1", b"COUNT", b"1", b"BULK"
10248 ]),
10249 "-ERR timeout is negative\r\n"
10250 );
10251 assert_eq!(
10253 f.run(&[
10254 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"abc", b"NOPE"
10255 ]),
10256 "-ERR count should be greater than 0\r\n"
10257 );
10258 assert_eq!(
10259 f.run(&[
10260 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"1", b"NOPE"
10261 ]),
10262 syntax
10263 );
10264 assert_eq!(
10267 f.run(&[b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT"]),
10268 syntax
10269 );
10270 assert_eq!(
10271 f.run(&[
10272 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"2"
10273 ]),
10274 syntax
10275 );
10276 }
10277
10278 #[test]
10281 fn a_blocking_command_errors_on_a_wrong_type_rather_than_waiting_on_it() {
10282 let mut f = Fixture::new();
10283 f.run(&[b"SET", b"S", b"v"]);
10284 f.run(&[b"RPUSH", b"D", b"x"]);
10285 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10286
10287 assert_eq!(f.run(&[b"BLPOP", b"S", b"0"]), wrong);
10288 assert_eq!(f.run(&[b"BLPOP", b"E", b"S", b"0"]), wrong);
10291 assert_eq!(f.run(&[b"BRPOP", b"S", b"0"]), wrong);
10292 assert_eq!(f.run(&[b"BLMPOP", b"0", b"1", b"S", b"LEFT"]), wrong);
10293 assert_eq!(f.run(&[b"BRPOPLPUSH", b"S", b"D", b"0"]), wrong);
10294 assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"S", b"0"]), wrong);
10297 assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nx\r\n");
10298 assert_eq!(
10299 f.run(&[b"BLMOVEM", b"S", b"D", b"LEFT", b"RIGHT", b"0"]),
10300 wrong
10301 );
10302 assert_eq!(
10303 f.run(&[b"BLMOVEM", b"D", b"S", b"LEFT", b"RIGHT", b"0"]),
10304 wrong
10305 );
10306
10307 assert_eq!(
10311 f.flow(&[b"BLMOVE", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
10312 .0,
10313 Flow::Block
10314 );
10315 assert_eq!(
10319 f.flow(&[b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
10320 .0,
10321 Flow::Block
10322 );
10323 f.run(&[b"RPUSH", b"E", b"1", b"2"]);
10324 assert_eq!(
10325 f.flow(&[
10326 b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1", b"EXACTLY", b"3", b"BULK"
10327 ])
10328 .0,
10329 Flow::Block
10330 );
10331 }
10332
10333 #[cfg_attr(miri, ignore = "the volume is the claim")]
10338 #[test]
10339 fn churning_lists_does_not_grow_the_server() {
10340 let mut f = Fixture::new();
10341 let vals: Vec<Vec<u8>> = (0..200).map(|i| format!("v{i}").into_bytes()).collect();
10342 let args: Vec<&[u8]> = [&b"RPUSH"[..], &b"k"[..]]
10343 .into_iter()
10344 .chain(vals.iter().map(Vec::as_slice))
10345 .collect();
10346
10347 f.run(&args);
10348 f.run(&[b"DEL", b"k"]);
10349 f.server.compact_step();
10350 let after_first = f.server.memory_bytes();
10351
10352 for _ in 0..200 {
10353 f.run(&args);
10354 f.run(&[b"LTRIM", b"k", b"1", b"0"]);
10355 f.server.compact_step();
10356 }
10357 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
10358 assert!(
10359 f.server.memory_bytes() <= after_first * 2,
10360 "held {} after two hundred passes against {after_first} after one",
10361 f.server.memory_bytes()
10362 );
10363 }
10364
10365 #[test]
10368 fn a_sorted_set_takes_scores_and_gives_them_back() {
10369 let mut f = Fixture::new();
10370 assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]), ":2\r\n");
10371 assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]), ":1\r\n");
10372 assert_eq!(f.run(&[b"ZCARD", b"z"]), ":3\r\n");
10373 assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$1\r\n2\r\n");
10374 assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "$-1\r\n");
10375 assert_eq!(f.run(&[b"ZSCORE", b"nokey", b"b"]), "$-1\r\n");
10376 assert_eq!(
10377 f.run(&[b"ZMSCORE", b"z", b"a", b"nope", b"c"]),
10378 "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n"
10379 );
10380 assert_eq!(f.run(&[b"ZREM", b"z", b"a", b"nope"]), ":1\r\n");
10381 assert_eq!(f.run(&[b"ZCARD", b"z"]), ":2\r\n");
10382 assert_eq!(f.run(&[b"ZREM", b"z", b"b", b"c"]), ":2\r\n");
10384 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
10385 }
10386
10387 #[test]
10388 fn a_score_is_a_double_on_resp3_and_digits_on_resp2() {
10389 let mut f = Fixture::new();
10390 f.run(&[b"ZADD", b"z", b"1.5", b"a", b"inf", b"b", b"-inf", b"c"]);
10391 assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$3\r\n1.5\r\n");
10392 assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$3\r\ninf\r\n");
10393 assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), "$4\r\n-inf\r\n");
10394
10395 f.out = Out::new(Proto::Resp3);
10396 assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), ",1.5\r\n");
10397 assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), ",inf\r\n");
10398 assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), ",-inf\r\n");
10399 assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "_\r\n");
10400 }
10401
10402 #[test]
10403 fn the_zadd_options_gate_what_gets_written() {
10404 let mut f = Fixture::new();
10405 f.run(&[b"ZADD", b"z", b"5", b"a"]);
10406 assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"9", b"a"]), ":0\r\n");
10408 assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$1\r\n5\r\n");
10409 assert_eq!(f.run(&[b"ZADD", b"z", b"XX", b"9", b"new"]), ":0\r\n");
10410 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":1\r\n");
10411 assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"3", b"a"]), ":0\r\n");
10413 assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"7", b"a"]), ":1\r\n");
10414 assert_eq!(f.run(&[b"ZADD", b"z", b"LT", b"CH", b"9", b"a"]), ":0\r\n");
10415 assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"1", b"b"]), ":1\r\n");
10417 assert_eq!(
10418 f.run(&[b"ZADD", b"z", b"CH", b"2", b"a", b"2", b"c"]),
10419 ":2\r\n"
10420 );
10421 }
10422
10423 #[test]
10424 fn zadd_incr_answers_a_score_or_nothing_at_all() {
10425 let mut f = Fixture::new();
10426 assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"5", b"m"]), "$1\r\n5\r\n");
10427 assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"2", b"m"]), "$1\r\n7\r\n");
10428 assert_eq!(
10431 f.run(&[b"ZADD", b"z", b"NX", b"INCR", b"2", b"m"]),
10432 "$-1\r\n"
10433 );
10434 assert_eq!(
10435 f.run(&[b"ZADD", b"z", b"XX", b"INCR", b"2", b"gone"]),
10436 "$-1\r\n"
10437 );
10438 assert_eq!(
10439 f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"-1", b"m"]),
10440 "$-1\r\n"
10441 );
10442 assert_eq!(
10443 f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"1", b"m"]),
10444 "$1\r\n8\r\n"
10445 );
10446 assert_eq!(f.run(&[b"ZINCRBY", b"z", b"2", b"m"]), "$2\r\n10\r\n");
10447 assert_eq!(f.run(&[b"ZINCRBY", b"z", b"1", b"fresh"]), "$1\r\n1\r\n");
10448 }
10449
10450 #[test]
10451 fn the_two_infinities_will_not_be_added_together() {
10452 let mut f = Fixture::new();
10453 f.run(&[b"ZADD", b"z", b"inf", b"m"]);
10454 let nan = "-ERR resulting score is not a number (NaN)\r\n";
10455 assert_eq!(f.run(&[b"ZINCRBY", b"z", b"-inf", b"m"]), nan);
10456 assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"-inf", b"m"]), nan);
10457 assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\ninf\r\n");
10458 assert_eq!(f.run(&[b"ZINCRBY", b"gone", b"1", b"m"]), "$1\r\n1\r\n");
10460 }
10461
10462 #[test]
10463 fn zadd_says_its_mistakes_the_way_redis_says_them() {
10464 let mut f = Fixture::new();
10465 assert_eq!(
10468 f.run(&[b"ZADD", b"z", b"NX", b"XX"]),
10469 "-ERR syntax error\r\n"
10470 );
10471 assert_eq!(
10472 f.run(&[b"ZADD", b"z", b"NX", b"XX", b"1", b"a"]),
10473 "-ERR XX and NX options at the same time are not compatible\r\n"
10474 );
10475 let gtlt = "-ERR GT, LT, and/or NX options at the same time are not compatible\r\n";
10476 assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"GT", b"1", b"a"]), gtlt);
10477 assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"LT", b"1", b"a"]), gtlt);
10478 assert_eq!(
10479 f.run(&[b"ZADD", b"z", b"INCR", b"1", b"a", b"2", b"b"]),
10480 "-ERR INCR option supports a single increment-element pair\r\n"
10481 );
10482 assert_eq!(
10484 f.run(&[b"ZADD", b"z", b"1", b"a", b"2"]),
10485 "-ERR syntax error\r\n"
10486 );
10487 assert_eq!(
10489 f.run(&[b"ZADD", b"z", b"1", b"a", b"nonsense", b"b"]),
10490 "-ERR value is not a valid float\r\n"
10491 );
10492 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
10493 }
10494
10495 #[test]
10496 fn a_rank_says_where_a_member_sits_from_either_end() {
10497 let mut f = Fixture::new();
10498 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
10499 assert_eq!(f.run(&[b"ZRANK", b"z", b"a"]), ":0\r\n");
10500 assert_eq!(f.run(&[b"ZRANK", b"z", b"c"]), ":2\r\n");
10501 assert_eq!(f.run(&[b"ZREVRANK", b"z", b"c"]), ":0\r\n");
10502 assert_eq!(f.run(&[b"ZREVRANK", b"z", b"a"]), ":2\r\n");
10503 assert_eq!(
10505 f.run(&[b"ZRANK", b"z", b"b", b"WITHSCORE"]),
10506 "*2\r\n:1\r\n$1\r\n2\r\n"
10507 );
10508 assert_eq!(f.run(&[b"ZRANK", b"z", b"nope"]), "$-1\r\n");
10509 assert_eq!(f.run(&[b"ZRANK", b"z", b"nope", b"WITHSCORE"]), "*-1\r\n");
10510 assert_eq!(f.run(&[b"ZRANK", b"nokey", b"a", b"WITHSCORE"]), "*-1\r\n");
10511 assert_eq!(
10514 f.run(&[b"ZRANK", b"z", b"b", b"bogus"]),
10515 "-ERR syntax error\r\n"
10516 );
10517 assert_eq!(
10518 f.run(&[b"ZREVRANK", b"z", b"b", b"WITHSCORE", b"more"]),
10519 "-ERR wrong number of arguments for 'zrevrank' command\r\n"
10520 );
10521 }
10522
10523 #[test]
10524 fn the_two_counts_read_their_two_kinds_of_bound() {
10525 let mut f = Fixture::new();
10526 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
10527 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"-inf", b"+inf"]), ":3\r\n");
10528 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"2", b"3"]), ":2\r\n");
10529 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"3"]), ":2\r\n");
10530 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"(3"]), ":1\r\n");
10531 assert_eq!(f.run(&[b"ZCOUNT", b"nokey", b"-inf", b"+inf"]), ":0\r\n");
10532 assert_eq!(
10533 f.run(&[b"ZCOUNT", b"z", b"bogus", b"3"]),
10534 "-ERR min or max is not a float\r\n"
10535 );
10536
10537 f.run(&[b"ZADD", b"l", b"0", b"a", b"0", b"b", b"0", b"c"]);
10538 assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"-", b"+"]), ":3\r\n");
10539 assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"[a", b"(c"]), ":2\r\n");
10540 assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"(a", b"+"]), ":2\r\n");
10541 assert_eq!(
10544 f.run(&[b"ZLEXCOUNT", b"l", b"a", b"c"]),
10545 "-ERR min or max not valid string range item\r\n"
10546 );
10547 }
10548
10549 #[test]
10555 fn one_range_command_selects_by_rank_or_score_or_name() {
10556 let mut f = Fixture::new();
10557 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
10558 assert_eq!(
10559 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
10560 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
10561 );
10562 assert_eq!(
10563 f.run(&[b"ZRANGE", b"z", b"-2", b"-1"]),
10564 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
10565 );
10566 assert_eq!(f.run(&[b"ZRANGE", b"z", b"5", b"9"]), "*0\r\n");
10567 assert_eq!(f.run(&[b"ZRANGE", b"nokey", b"0", b"-1"]), "*0\r\n");
10568 assert_eq!(
10571 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"REV"]),
10572 "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
10573 );
10574 assert_eq!(
10575 f.run(&[b"ZRANGE", b"z", b"(1", b"+inf", b"BYSCORE"]),
10576 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
10577 );
10578 assert_eq!(
10581 f.run(&[b"ZRANGE", b"z", b"+inf", b"(1", b"BYSCORE", b"REV"]),
10582 "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
10583 );
10584 assert_eq!(
10585 f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX"]),
10586 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
10587 );
10588 assert_eq!(
10589 f.run(&[b"ZRANGE", b"z", b"+", b"-", b"BYLEX", b"REV"]),
10590 "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
10591 );
10592 }
10593
10594 #[test]
10597 fn the_older_range_spellings_name_their_high_end_first() {
10598 let mut f = Fixture::new();
10599 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
10600 assert_eq!(
10601 f.run(&[b"ZREVRANGE", b"z", b"0", b"-1"]),
10602 "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
10603 );
10604 assert_eq!(
10605 f.run(&[b"ZREVRANGE", b"z", b"0", b"0", b"WITHSCORES"]),
10606 "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
10607 );
10608 assert_eq!(
10609 f.run(&[b"ZRANGEBYSCORE", b"z", b"(1", b"3"]),
10610 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
10611 );
10612 assert_eq!(
10613 f.run(&[b"ZREVRANGEBYSCORE", b"z", b"3", b"(1"]),
10614 "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
10615 );
10616 assert_eq!(f.run(&[b"ZREVRANGEBYSCORE", b"z", b"(1", b"3"]), "*0\r\n");
10620 assert_eq!(
10621 f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"(c"]),
10622 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
10623 );
10624 assert_eq!(
10625 f.run(&[b"ZREVRANGEBYLEX", b"z", b"(c", b"[a"]),
10626 "*2\r\n$1\r\nb\r\n$1\r\na\r\n"
10627 );
10628 for cmd in [
10631 &[b"ZREVRANGE".as_slice(), b"z", b"0", b"-1", b"BYSCORE"][..],
10632 &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"REV"],
10633 &[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"BYLEX"],
10634 ] {
10635 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{:?}", cmd[0]);
10636 }
10637 }
10638
10639 #[test]
10642 fn limit_and_withscores_are_read_by_all_of_them_and_refused_afterwards() {
10643 let mut f = Fixture::new();
10644 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
10645 assert_eq!(
10646 f.run(&[
10647 b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"1", b"1"
10648 ]),
10649 "*1\r\n$1\r\nb\r\n"
10650 );
10651 assert_eq!(
10653 f.run(&[
10654 b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"-1", b"2"
10655 ]),
10656 "*0\r\n"
10657 );
10658 assert_eq!(
10659 f.run(&[
10660 b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"0", b"-1"
10661 ]),
10662 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
10663 );
10664 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";
10666 assert_eq!(
10667 f.run(&[
10668 b"ZRANGEBYSCORE",
10669 b"z",
10670 b"1",
10671 b"3",
10672 b"WITHSCORES",
10673 b"LIMIT",
10674 b"0",
10675 b"2"
10676 ]),
10677 both
10678 );
10679 assert_eq!(
10680 f.run(&[
10681 b"ZRANGEBYSCORE",
10682 b"z",
10683 b"1",
10684 b"3",
10685 b"LIMIT",
10686 b"0",
10687 b"2",
10688 b"WITHSCORES"
10689 ]),
10690 both
10691 );
10692 let needs_by = "-ERR syntax error, LIMIT is only supported in combination with either BYSCORE or BYLEX\r\n";
10695 assert_eq!(
10696 f.run(&[
10697 b"ZREVRANGE",
10698 b"z",
10699 b"0",
10700 b"-1",
10701 b"WITHSCORES",
10702 b"LIMIT",
10703 b"0",
10704 b"1"
10705 ]),
10706 needs_by
10707 );
10708 assert_eq!(
10709 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"LIMIT", b"0", b"1"]),
10710 needs_by
10711 );
10712 let not_bylex = "-ERR syntax error, WITHSCORES not supported in combination with BYLEX\r\n";
10713 assert_eq!(
10714 f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX", b"WITHSCORES"]),
10715 not_bylex
10716 );
10717 assert_eq!(
10718 f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"WITHSCORES"]),
10719 not_bylex
10720 );
10721 for cmd in [
10724 &[
10725 b"ZRANGE".as_slice(),
10726 b"z",
10727 b"0",
10728 b"-1",
10729 b"BYSCORE",
10730 b"BYLEX",
10731 ][..],
10732 &[b"ZRANGE", b"z", b"0", b"-1", b"junk"],
10733 &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"0"],
10734 ] {
10735 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
10736 }
10737 assert_eq!(
10738 f.run(&[b"ZRANGEBYSCORE", b"z", b"bad", b"3"]),
10739 "-ERR min or max is not a float\r\n"
10740 );
10741 assert_eq!(
10742 f.run(&[b"ZRANGEBYLEX", b"z", b"a", b"[c"]),
10743 "-ERR min or max not valid string range item\r\n"
10744 );
10745 assert_eq!(
10746 f.run(&[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"a", b"2"]),
10747 "-ERR value is not an integer or out of range\r\n"
10748 );
10749 }
10750
10751 #[test]
10754 fn withscores_nests_on_resp3_and_flattens_on_resp2() {
10755 let mut f = Fixture::new();
10756 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
10757 assert_eq!(
10758 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
10759 "*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"
10760 );
10761 f.out = Out::new(Proto::Resp3);
10762 assert_eq!(
10763 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
10764 "*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"
10765 );
10766 assert_eq!(
10767 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
10768 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
10769 );
10770 }
10771
10772 #[test]
10774 fn a_range_store_writes_the_window_into_another_key() {
10775 let mut f = Fixture::new();
10776 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
10777 assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1"]), ":3\r\n");
10778 assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"5", b"9"]), ":0\r\n");
10781 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
10782 assert_eq!(
10783 f.run(&[b"ZRANGESTORE", b"d", b"z", b"(1", b"+inf", b"BYSCORE"]),
10784 ":2\r\n"
10785 );
10786 assert_eq!(
10787 f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
10788 "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
10789 );
10790 assert_eq!(f.run(&[b"ZRANGESTORE", b"z", b"z", b"1", b"2"]), ":2\r\n");
10793 assert_eq!(
10794 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
10795 "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
10796 );
10797 assert_eq!(
10800 f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1", b"WITHSCORES"]),
10801 "-ERR syntax error\r\n"
10802 );
10803 }
10804
10805 #[test]
10808 fn the_three_removals_share_their_window_with_the_reads() {
10809 let mut f = Fixture::new();
10810 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
10811 assert_eq!(f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"0"]), ":1\r\n");
10812 assert_eq!(
10813 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
10814 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
10815 );
10816 assert_eq!(
10817 f.run(&[b"ZREMRANGEBYSCORE", b"z", b"(2", b"+inf"]),
10818 ":1\r\n"
10819 );
10820 assert_eq!(f.run(&[b"ZRANGE", b"z", b"0", b"-1"]), "*1\r\n$1\r\nb\r\n");
10821 assert_eq!(f.run(&[b"ZREMRANGEBYLEX", b"z", b"-", b"+"]), ":1\r\n");
10823 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
10824 assert_eq!(
10825 f.run(&[b"ZREMRANGEBYRANK", b"nokey", b"0", b"-1"]),
10826 ":0\r\n"
10827 );
10828 assert_eq!(
10829 f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"x"]),
10830 "-ERR value is not an integer or out of range\r\n"
10831 );
10832 }
10833
10834 #[test]
10836 fn the_three_algebra_commands_combine_scores_and_order_the_answer_once() {
10837 let mut f = Fixture::new();
10838 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
10839 f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
10840 assert_eq!(
10841 f.run(&[b"ZUNION", b"2", b"z", b"y"]),
10842 "*4\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\nd\r\n"
10843 );
10844 assert_eq!(
10847 f.run(&[b"ZUNION", b"2", b"z", b"y", b"WITHSCORES"]),
10848 "*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"
10849 );
10850 assert_eq!(
10851 f.run(&[
10852 b"ZUNION",
10853 b"2",
10854 b"z",
10855 b"y",
10856 b"WEIGHTS",
10857 b"2",
10858 b"3",
10859 b"WITHSCORES"
10860 ]),
10861 "*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"
10862 );
10863 assert_eq!(
10864 f.run(&[
10865 b"ZUNION",
10866 b"2",
10867 b"z",
10868 b"y",
10869 b"AGGREGATE",
10870 b"MIN",
10871 b"WITHSCORES"
10872 ]),
10873 "*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"
10874 );
10875 assert_eq!(
10876 f.run(&[
10877 b"ZUNION",
10878 b"2",
10879 b"z",
10880 b"y",
10881 b"AGGREGATE",
10882 b"MAX",
10883 b"WITHSCORES"
10884 ]),
10885 "*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"
10886 );
10887 assert_eq!(
10888 f.run(&[b"ZINTER", b"2", b"z", b"y", b"WITHSCORES"]),
10889 "*2\r\n$1\r\nb\r\n$2\r\n12\r\n"
10890 );
10891 assert_eq!(
10892 f.run(&[b"ZDIFF", b"2", b"z", b"y", b"WITHSCORES"]),
10893 "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nc\r\n$1\r\n3\r\n"
10894 );
10895 assert_eq!(f.run(&[b"ZUNION", b"1", b"nokey"]), "*0\r\n");
10896 f.run(&[b"SADD", b"p", b"a", b"d"]);
10899 assert_eq!(
10900 f.run(&[b"ZUNION", b"2", b"z", b"p", b"WITHSCORES"]),
10901 "*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"
10902 );
10903 for cmd in [
10906 &[
10907 b"ZDIFF".as_slice(),
10908 b"2",
10909 b"z",
10910 b"y",
10911 b"WEIGHTS",
10912 b"1",
10913 b"1",
10914 ][..],
10915 &[b"ZDIFF", b"2", b"z", b"y", b"AGGREGATE", b"MIN"],
10916 ] {
10917 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
10918 }
10919 }
10920
10921 #[test]
10923 fn the_algebra_counts_its_keys_and_says_so_when_the_count_is_wrong() {
10924 let mut f = Fixture::new();
10925 f.run(&[b"ZADD", b"z", b"1", b"a"]);
10926 f.run(&[b"ZADD", b"y", b"2", b"b"]);
10927 assert_eq!(
10929 f.run(&[b"ZUNION", b"0", b"z"]),
10930 "-ERR at least 1 input key is needed for 'zunion' command\r\n"
10931 );
10932 assert_eq!(
10933 f.run(&[b"ZUNION", b"-1", b"z"]),
10934 "-ERR at least 1 input key is needed for 'zunion' command\r\n"
10935 );
10936 assert_eq!(
10937 f.run(&[b"ZINTERCARD", b"0", b"z"]),
10938 "-ERR at least 1 input key is needed for 'zintercard' command\r\n"
10939 );
10940 assert_eq!(
10943 f.run(&[b"ZUNION", b"3", b"z", b"y"]),
10944 "-ERR syntax error\r\n"
10945 );
10946 assert_eq!(
10947 f.run(&[b"ZUNION", b"x", b"z"]),
10948 "-ERR value is not an integer or out of range\r\n"
10949 );
10950 assert_eq!(
10953 f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"1"]),
10954 "-ERR syntax error\r\n"
10955 );
10956 assert_eq!(
10957 f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"a", b"b"]),
10958 "-ERR weight value is not a float\r\n"
10959 );
10960 assert_eq!(
10961 f.run(&[b"ZUNION", b"2", b"z", b"y", b"AGGREGATE", b"NOPE"]),
10962 "-ERR syntax error\r\n"
10963 );
10964 }
10965
10966 #[test]
10968 fn the_algebra_stores_answer_a_count_and_delete_an_empty_destination() {
10969 let mut f = Fixture::new();
10970 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
10971 f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
10972 assert_eq!(f.run(&[b"ZUNIONSTORE", b"d", b"2", b"z", b"y"]), ":4\r\n");
10973 assert_eq!(
10974 f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
10975 "*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"
10976 );
10977 assert_eq!(f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"y"]), ":1\r\n");
10978 assert_eq!(f.run(&[b"ZDIFFSTORE", b"d", b"2", b"z", b"y"]), ":2\r\n");
10979 assert_eq!(
10982 f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"nokey"]),
10983 ":0\r\n"
10984 );
10985 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
10986 assert_eq!(f.run(&[b"ZUNIONSTORE", b"z", b"2", b"z", b"y"]), ":4\r\n");
10988 assert_eq!(f.run(&[b"ZCARD", b"z"]), ":4\r\n");
10989 for cmd in [
10990 &[
10991 b"ZUNIONSTORE".as_slice(),
10992 b"d",
10993 b"2",
10994 b"z",
10995 b"y",
10996 b"WITHSCORES",
10997 ][..],
10998 &[
10999 b"ZDIFFSTORE",
11000 b"d",
11001 b"2",
11002 b"z",
11003 b"y",
11004 b"WEIGHTS",
11005 b"1",
11006 b"1",
11007 ],
11008 ] {
11009 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
11010 }
11011 }
11012
11013 #[test]
11015 fn intercard_counts_and_stops_at_its_limit() {
11016 let mut f = Fixture::new();
11017 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11018 f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"c", b"30", b"d"]);
11019 assert_eq!(f.run(&[b"ZINTERCARD", b"2", b"z", b"y"]), ":2\r\n");
11020 assert_eq!(
11022 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"0"]),
11023 ":2\r\n"
11024 );
11025 assert_eq!(
11026 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"1"]),
11027 ":1\r\n"
11028 );
11029 let bad = "-ERR LIMIT can't be negative\r\n";
11032 assert_eq!(
11033 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"-1"]),
11034 bad
11035 );
11036 assert_eq!(
11037 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"x"]),
11038 bad
11039 );
11040 for cmd in [
11041 &[b"ZINTERCARD".as_slice(), b"3", b"z", b"y"][..],
11042 &[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT"],
11043 &[b"ZINTERCARD", b"2", b"z", b"y", b"junk", b"1"],
11044 ] {
11045 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
11046 }
11047 }
11048
11049 #[test]
11051 fn a_draw_answers_one_member_or_an_array_of_them() {
11052 let mut f = Fixture::new();
11053 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11054 assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "$-1\r\n");
11057 assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
11058 assert_eq!(f.run(&[b"ZRANDMEMBER", b"z", b"0"]), "*0\r\n");
11059 assert!(f.run(&[b"ZRANDMEMBER", b"z"]).starts_with("$1\r\n"));
11060 let all = f.run(&[b"ZRANDMEMBER", b"z", b"10"]);
11063 assert!(all.starts_with("*3\r\n"), "{all}");
11064 for m in ["a", "b", "c"] {
11065 assert!(all.contains(m), "{all}");
11066 }
11067 assert!(
11070 f.run(&[b"ZRANDMEMBER", b"z", b"-5"]).starts_with("*5\r\n"),
11071 "five draws with replacement"
11072 );
11073 assert!(
11074 f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"])
11075 .starts_with("*4\r\n"),
11076 "two pairs, flat on RESP2"
11077 );
11078 f.out = Out::new(Proto::Resp3);
11079 let got = f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"]);
11080 assert!(got.starts_with("*2\r\n*2\r\n"), "{got}");
11081 assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "_\r\n");
11082 f.out = Out::new(Proto::Resp2);
11083 assert_eq!(
11084 f.run(&[b"ZRANDMEMBER", b"z", b"2", b"junk"]),
11085 "-ERR syntax error\r\n"
11086 );
11087 assert_eq!(
11088 f.run(&[b"ZRANDMEMBER", b"z", b"x"]),
11089 "-ERR value is not an integer or out of range\r\n"
11090 );
11091 }
11092
11093 #[test]
11095 fn a_sorted_set_scan_answers_pairs_of_strings_on_both_protocols() {
11096 let mut f = Fixture::new();
11097 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11098 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";
11099 assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
11100 assert_eq!(f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"10"]), all);
11101 assert_eq!(
11102 f.run(&[b"ZSCAN", b"z", b"0", b"MATCH", b"a*"]),
11103 "*2\r\n$1\r\n0\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
11104 );
11105 assert_eq!(
11106 f.run(&[b"ZSCAN", b"nokey", b"0"]),
11107 "*2\r\n$1\r\n0\r\n*0\r\n"
11108 );
11109 f.out = Out::new(Proto::Resp3);
11112 assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
11113 f.out = Out::new(Proto::Resp2);
11114 assert_eq!(
11115 f.run(&[b"ZSCAN", b"z", b"0", b"NOVALUES"]),
11116 "-ERR NOVALUES option can only be used in HSCAN\r\n"
11117 );
11118 assert_eq!(f.run(&[b"ZSCAN", b"z", b"-1"]), "-ERR invalid cursor\r\n");
11119 assert_eq!(
11120 f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"0"]),
11121 "-ERR syntax error\r\n"
11122 );
11123 }
11124
11125 #[test]
11127 fn a_sorted_set_pop_changes_shape_when_it_is_given_a_count() {
11128 let mut f = Fixture::new();
11129 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11130 assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
11132 assert_eq!(f.run(&[b"ZPOPMAX", b"z"]), "*2\r\n$1\r\nc\r\n$1\r\n3\r\n");
11133 f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
11134 assert_eq!(
11136 f.run(&[b"ZPOPMIN", b"z", b"2"]),
11137 "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
11138 );
11139 assert_eq!(f.run(&[b"ZPOPMIN", b"nokey"]), "*0\r\n");
11142 assert_eq!(f.run(&[b"ZPOPMIN", b"nokey", b"2"]), "*0\r\n");
11143 assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"0"]), "*0\r\n");
11144 assert_eq!(
11146 f.run(&[b"ZPOPMIN", b"z", b"9"]),
11147 "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
11148 );
11149 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
11150
11151 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]);
11152 f.out = Out::new(Proto::Resp3);
11153 assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n,1\r\n");
11154 assert_eq!(
11155 f.run(&[b"ZPOPMIN", b"z", b"1"]),
11156 "*1\r\n*2\r\n$1\r\nb\r\n,2\r\n"
11157 );
11158 f.out = Out::new(Proto::Resp2);
11159 let bad = "-ERR value is out of range, must be positive\r\n";
11162 assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"x"]), bad);
11163 assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"-1"]), bad);
11164 assert_eq!(
11165 f.run(&[b"ZPOPMIN", b"z", b"1", b"2"]),
11166 "-ERR syntax error\r\n"
11167 );
11168 }
11169
11170 #[test]
11172 fn a_multi_key_pop_names_the_key_that_answered_and_nests_its_pairs() {
11173 let mut f = Fixture::new();
11174 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11175 assert_eq!(
11176 f.run(&[b"ZMPOP", b"2", b"nokey", b"z", b"MIN"]),
11177 "*2\r\n$1\r\nz\r\n*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
11178 );
11179 assert_eq!(
11182 f.run(&[b"ZMPOP", b"1", b"z", b"MAX", b"COUNT", b"2"]),
11183 "*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"
11184 );
11185 assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "*-1\r\n");
11187 f.out = Out::new(Proto::Resp3);
11188 assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "_\r\n");
11189 f.out = Out::new(Proto::Resp2);
11190 let numkeys = "-ERR numkeys should be greater than 0\r\n";
11191 for bad in [
11192 &[b"ZMPOP".as_slice(), b"0", b"z", b"MIN"][..],
11193 &[b"ZMPOP", b"-1", b"z", b"MIN"],
11194 &[b"ZMPOP", b"x", b"z", b"MIN"],
11195 ] {
11196 assert_eq!(f.run(bad), numkeys, "{:?}", bad[1]);
11197 }
11198 let count = "-ERR count should be greater than 0\r\n";
11199 for bad in [
11200 &[b"ZMPOP".as_slice(), b"1", b"z", b"MIN", b"COUNT", b"0"][..],
11201 &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"-1"],
11202 &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"x"],
11203 ] {
11204 assert_eq!(f.run(bad), count, "{:?}", bad[5]);
11205 }
11206 let syntax = "-ERR syntax error\r\n";
11207 for bad in [
11208 &[b"ZMPOP".as_slice(), b"2", b"z", b"MIN"][..],
11211 &[b"ZMPOP", b"1", b"z", b"SIDEWAYS"],
11212 &[b"ZMPOP", b"1", b"z", b"MIN", b"junk"],
11213 &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"1", b"junk"],
11214 ] {
11215 assert_eq!(f.run(bad), syntax, "{bad:?}");
11216 }
11217 }
11218
11219 #[test]
11222 fn the_sorted_set_pops_that_wait_answer_like_the_ones_they_wrap() {
11223 let mut f = Fixture::new();
11224 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11225 assert_eq!(
11226 f.flow(&[b"BZPOPMIN", b"nokey", b"z", b"0"]),
11227 (
11228 Flow::Continue,
11229 "*3\r\n$1\r\nz\r\n$1\r\na\r\n$1\r\n1\r\n".to_owned()
11230 )
11231 );
11232 assert_eq!(
11233 f.run(&[b"BZPOPMAX", b"z", b"0"]),
11234 "*3\r\n$1\r\nz\r\n$1\r\nc\r\n$1\r\n3\r\n"
11235 );
11236 f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
11237 assert_eq!(
11238 f.run(&[
11239 b"BZMPOP", b"0", b"2", b"nokey", b"z", b"MIN", b"COUNT", b"2"
11240 ]),
11241 "*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"
11242 );
11243 f.out = Out::new(Proto::Resp3);
11244 assert_eq!(
11245 f.run(&[b"BZPOPMIN", b"z", b"0"]),
11246 "*3\r\n$1\r\nz\r\n$1\r\nc\r\n,3\r\n"
11247 );
11248 f.out = Out::new(Proto::Resp2);
11249 assert_eq!(
11251 f.flow(&[b"BZPOPMIN", b"z", b"0"]),
11252 (Flow::Block, String::new())
11253 );
11254 assert_eq!(
11255 f.flow(&[b"BZMPOP", b"0", b"1", b"z", b"MIN"]),
11256 (Flow::Block, String::new())
11257 );
11258 assert_eq!(
11261 f.run(&[b"BZMPOP", b"abc", b"0", b"z", b"MIN"]),
11262 "-ERR timeout is not a float or out of range\r\n"
11263 );
11264 assert_eq!(
11265 f.run(&[b"BZMPOP", b"0", b"0", b"z", b"MIN"]),
11266 "-ERR numkeys should be greater than 0\r\n"
11267 );
11268 assert_eq!(
11269 f.run(&[b"BZPOPMIN", b"z", b"-1"]),
11270 "-ERR timeout is negative\r\n"
11271 );
11272 }
11273
11274 #[test]
11278 fn a_parked_sorted_set_client_waits_for_a_member_and_not_for_a_key() {
11279 let mut f = Fixture::new();
11280 assert_eq!(f.flow(&[b"BZPOPMIN", b"z", b"0"]).0, Flow::Block);
11281 assert_eq!(f.server.parked(), 1);
11282 f.run(&[b"SET", b"z", b"v"]);
11285 let mut out = Out::new(Proto::Resp2);
11286 assert!(!f.server.serve_waiter(7, 0, &mut out));
11287 assert!(out.as_slice().is_empty());
11288 f.run(&[b"DEL", b"z"]);
11289 f.run(&[b"ZADD", b"z", b"5", b"m"]);
11290 assert!(f.server.serve_waiter(7, 0, &mut out));
11291 assert_eq!(
11292 core::str::from_utf8(out.as_slice()).expect("ascii"),
11293 "*3\r\n$1\r\nz\r\n$1\r\nm\r\n$1\r\n5\r\n"
11294 );
11295 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
11298 }
11299
11300 #[test]
11301 fn every_sorted_set_command_says_wrongtype_and_writes_nothing() {
11302 let mut f = Fixture::new();
11303 f.run(&[b"SET", b"s", b"v"]);
11304 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
11305 for cmd in [
11306 &[b"ZADD".as_slice(), b"s", b"1", b"a"][..],
11307 &[b"ZINCRBY", b"s", b"1", b"a"],
11308 &[b"ZCARD", b"s"],
11309 &[b"ZSCORE", b"s", b"a"],
11310 &[b"ZMSCORE", b"s", b"a"],
11311 &[b"ZREM", b"s", b"a"],
11312 &[b"ZRANK", b"s", b"a"],
11313 &[b"ZREVRANK", b"s", b"a"],
11314 &[b"ZCOUNT", b"s", b"1", b"2"],
11315 &[b"ZLEXCOUNT", b"s", b"-", b"+"],
11316 &[b"ZRANGE", b"s", b"0", b"-1"],
11317 &[b"ZREVRANGE", b"s", b"0", b"-1"],
11318 &[b"ZRANGEBYSCORE", b"s", b"1", b"2"],
11319 &[b"ZREVRANGEBYSCORE", b"s", b"2", b"1"],
11320 &[b"ZRANGEBYLEX", b"s", b"-", b"+"],
11321 &[b"ZREVRANGEBYLEX", b"s", b"+", b"-"],
11322 &[b"ZRANGESTORE", b"d", b"s", b"0", b"-1"],
11323 &[b"ZREMRANGEBYRANK", b"s", b"0", b"-1"],
11324 &[b"ZREMRANGEBYSCORE", b"s", b"1", b"2"],
11325 &[b"ZREMRANGEBYLEX", b"s", b"-", b"+"],
11326 &[b"ZUNION", b"1", b"s"],
11327 &[b"ZINTER", b"1", b"s"],
11328 &[b"ZDIFF", b"1", b"s"],
11329 &[b"ZUNIONSTORE", b"d", b"1", b"s"],
11330 &[b"ZINTERSTORE", b"d", b"1", b"s"],
11331 &[b"ZDIFFSTORE", b"d", b"1", b"s"],
11332 &[b"ZINTERCARD", b"1", b"s"],
11333 &[b"ZRANDMEMBER", b"s"],
11334 &[b"ZSCAN", b"s", b"0"],
11335 &[b"ZPOPMIN", b"s"],
11336 &[b"ZPOPMAX", b"s", b"2"],
11337 &[b"ZMPOP", b"1", b"s", b"MIN"],
11338 &[b"BZPOPMIN", b"s", b"0"],
11339 &[b"BZPOPMAX", b"s", b"0"],
11340 &[b"BZMPOP", b"0", b"1", b"s", b"MIN"],
11341 ] {
11342 assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
11343 }
11344 assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nv\r\n");
11345 }
11346
11347 #[cfg_attr(miri, ignore = "the volume is the claim")]
11352 #[test]
11353 fn churning_sorted_sets_does_not_grow_the_server() {
11354 let mut f = Fixture::new();
11355 let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
11356 let scores: Vec<Vec<u8>> = (0..200).map(|i| format!("{i}").into_bytes()).collect();
11357 let mut args: Vec<&[u8]> = vec![b"ZADD", b"z"];
11358 for i in 0..200 {
11359 args.push(&scores[i]);
11360 args.push(&members[i]);
11361 }
11362
11363 f.run(&args);
11364 f.run(&[b"DEL", b"z"]);
11365 f.server.compact_step();
11366 let after_first = f.server.memory_bytes();
11367
11368 for _ in 0..200 {
11369 f.run(&args);
11370 f.run(&[b"DEL", b"z"]);
11371 f.server.compact_step();
11372 }
11373 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
11374 assert!(
11375 f.server.memory_bytes() <= after_first * 2,
11376 "held {} after two hundred passes against {after_first} after one",
11377 f.server.memory_bytes()
11378 );
11379 }
11380
11381 fn sicily(f: &mut Fixture) {
11389 f.run(&[
11390 b"GEOADD",
11391 b"Sicily",
11392 b"13.361389",
11393 b"38.115556",
11394 b"Palermo",
11395 b"15.087269",
11396 b"37.502669",
11397 b"Catania",
11398 ]);
11399 f.run(&[
11400 b"GEOADD",
11401 b"Sicily",
11402 b"13.583333",
11403 b"37.316667",
11404 b"Agrigento",
11405 ]);
11406 }
11407
11408 #[test]
11409 fn places_go_in_as_scores_and_come_back_as_positions() {
11410 let mut f = Fixture::new();
11411 assert_eq!(
11412 f.run(&[
11413 b"GEOADD",
11414 b"Sicily",
11415 b"13.361389",
11416 b"38.115556",
11417 b"Palermo",
11418 b"15.087269",
11419 b"37.502669",
11420 b"Catania"
11421 ]),
11422 ":2\r\n"
11423 );
11424 assert_eq!(f.run(&[b"TYPE", b"Sicily"]), "+zset\r\n");
11428 assert_eq!(
11429 f.run(&[b"ZSCORE", b"Sicily", b"Palermo"]),
11430 "$16\r\n3479099956230698\r\n"
11431 );
11432 assert_eq!(
11433 f.run(&[b"GEOPOS", b"Sicily", b"Palermo", b"NonExisting"]),
11434 "*2\r\n*2\r\n$18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n*-1\r\n"
11435 );
11436 assert_eq!(
11437 f.run(&[
11438 b"GEOHASH",
11439 b"Sicily",
11440 b"Palermo",
11441 b"Catania",
11442 b"NonExisting"
11443 ]),
11444 "*3\r\n$11\r\nsqc8b49rny0\r\n$11\r\nsqdtr74hyu0\r\n$-1\r\n"
11445 );
11446 assert_eq!(f.run(&[b"GEOPOS", b"nokey", b"a"]), "*1\r\n*-1\r\n");
11450 assert_eq!(f.run(&[b"GEOHASH", b"nokey", b"a"]), "*1\r\n$-1\r\n");
11451 }
11452
11453 #[test]
11454 fn a_distance_comes_back_with_four_places_in_whatever_unit_was_asked_for() {
11455 let mut f = Fixture::new();
11456 sicily(&mut f);
11457 assert_eq!(
11458 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania"]),
11459 "$11\r\n166274.1516\r\n"
11460 );
11461 assert_eq!(
11462 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"km"]),
11463 "$8\r\n166.2742\r\n"
11464 );
11465 assert_eq!(
11466 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"mi"]),
11467 "$8\r\n103.3182\r\n"
11468 );
11469 assert_eq!(
11473 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Foo"]),
11474 "$-1\r\n"
11475 );
11476 assert_eq!(f.run(&[b"GEODIST", b"nokey", b"a", b"b"]), "$-1\r\n");
11477 assert_eq!(
11478 f.run(&[b"GEODIST", b"nokey", b"a", b"b", b"parsecs"]),
11479 "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n"
11480 );
11481 assert_eq!(
11482 f.run(&[b"GEODIST", b"Sicily", b"a", b"b", b"km", b"extra"]),
11483 "-ERR syntax error\r\n"
11484 );
11485 }
11486
11487 #[test]
11488 fn a_search_finds_what_is_inside_it_nearest_first() {
11489 let mut f = Fixture::new();
11490 sicily(&mut f);
11491 let all = "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n";
11492 assert_eq!(
11493 f.run(&[
11494 b"GEOSEARCH",
11495 b"Sicily",
11496 b"FROMLONLAT",
11497 b"15",
11498 b"37",
11499 b"BYRADIUS",
11500 b"200",
11501 b"km",
11502 b"ASC"
11503 ]),
11504 all
11505 );
11506 assert_eq!(
11509 f.run(&[b"GEORADIUS", b"Sicily", b"15", b"37", b"200", b"km", b"ASC"]),
11510 all
11511 );
11512 assert_eq!(
11513 f.run(&[
11514 b"GEORADIUS_RO",
11515 b"Sicily",
11516 b"15",
11517 b"37",
11518 b"200",
11519 b"km",
11520 b"ASC"
11521 ]),
11522 all
11523 );
11524 assert_eq!(
11527 f.run(&[
11528 b"GEORADIUS",
11529 b"Sicily",
11530 b"15",
11531 b"37",
11532 b"200",
11533 b"km",
11534 b"DESC",
11535 b"COUNT",
11536 b"1"
11537 ]),
11538 "*1\r\n$7\r\nPalermo\r\n"
11539 );
11540 assert_eq!(
11541 f.run(&[
11542 b"GEORADIUS",
11543 b"Sicily",
11544 b"15",
11545 b"37",
11546 b"200",
11547 b"km",
11548 b"COUNT",
11549 b"1"
11550 ]),
11551 "*1\r\n$7\r\nCatania\r\n"
11552 );
11553 let empty = "*0\r\n";
11556 assert_eq!(
11557 f.run(&[
11558 b"GEOSEARCH",
11559 b"Sicily",
11560 b"FROMLONLAT",
11561 b"15",
11562 b"37",
11563 b"BYRADIUS",
11564 b"1",
11565 b"km"
11566 ]),
11567 empty
11568 );
11569 assert_eq!(
11570 f.run(&[
11571 b"GEOSEARCH",
11572 b"nokey",
11573 b"FROMLONLAT",
11574 b"15",
11575 b"37",
11576 b"BYRADIUS",
11577 b"1",
11578 b"km"
11579 ]),
11580 empty
11581 );
11582 assert_eq!(
11583 f.run(&[b"GEORADIUSBYMEMBER", b"nokey", b"m", b"1", b"km"]),
11584 empty
11585 );
11586 }
11587
11588 #[test]
11589 fn a_search_centred_on_a_member_starts_from_where_that_member_is() {
11590 let mut f = Fixture::new();
11591 sicily(&mut f);
11592 assert_eq!(
11593 f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Agrigento", b"100", b"km"]),
11594 "*2\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
11595 );
11596 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";
11599 assert_eq!(
11600 f.run(&[
11601 b"GEORADIUSBYMEMBER_RO",
11602 b"Sicily",
11603 b"Agrigento",
11604 b"100",
11605 b"km",
11606 b"WITHDIST"
11607 ]),
11608 with_dist
11609 );
11610 assert_eq!(
11611 f.run(&[
11612 b"GEOSEARCH",
11613 b"Sicily",
11614 b"FROMMEMBER",
11615 b"Agrigento",
11616 b"BYRADIUS",
11617 b"100",
11618 b"km",
11619 b"ASC",
11620 b"WITHDIST"
11621 ]),
11622 with_dist
11623 );
11624 assert_eq!(
11625 f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Nowhere", b"100", b"km"]),
11626 "-ERR could not decode requested zset member\r\n"
11627 );
11628 }
11629
11630 #[test]
11631 fn a_box_search_reports_the_distance_the_hash_and_the_coordinates() {
11632 let mut f = Fixture::new();
11633 sicily(&mut f);
11634 assert_eq!(
11638 f.run(&[
11639 b"GEOSEARCH",
11640 b"Sicily",
11641 b"FROMLONLAT",
11642 b"15",
11643 b"37",
11644 b"BYBOX",
11645 b"400",
11646 b"400",
11647 b"km",
11648 b"ASC",
11649 b"WITHCOORD",
11650 b"WITHDIST",
11651 b"WITHHASH"
11652 ]),
11653 "*3\r\n*4\r\n$7\r\nCatania\r\n$7\r\n56.4413\r\n:3479447370796909\r\n*2\r\n\
11654 $18\r\n15.087267458438873\r\n$17\r\n37.50266842333162\r\n\
11655 *4\r\n$9\r\nAgrigento\r\n$8\r\n130.4235\r\n:3479030013248308\r\n*2\r\n\
11656 $18\r\n13.583331406116486\r\n$18\r\n37.316668049938166\r\n\
11657 *4\r\n$7\r\nPalermo\r\n$8\r\n190.4424\r\n:3479099956230698\r\n*2\r\n\
11658 $18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n"
11659 );
11660 }
11661
11662 #[test]
11663 fn a_store_writes_the_hashes_and_a_storedist_writes_the_distances() {
11664 let mut f = Fixture::new();
11665 sicily(&mut f);
11666 let hashes = "*6\r\n$9\r\nAgrigento\r\n$16\r\n3479030013248308\r\n\
11667 $7\r\nPalermo\r\n$16\r\n3479099956230698\r\n\
11668 $7\r\nCatania\r\n$16\r\n3479447370796909\r\n";
11669 assert_eq!(
11670 f.run(&[
11671 b"GEOSEARCHSTORE",
11672 b"dst",
11673 b"Sicily",
11674 b"FROMLONLAT",
11675 b"15",
11676 b"37",
11677 b"BYRADIUS",
11678 b"200",
11679 b"km",
11680 b"ASC"
11681 ]),
11682 ":3\r\n"
11683 );
11684 assert_eq!(
11685 f.run(&[b"ZRANGE", b"dst", b"0", b"-1", b"WITHSCORES"]),
11686 hashes
11687 );
11688 assert_eq!(
11691 f.run(&[
11692 b"GEORADIUS",
11693 b"Sicily",
11694 b"15",
11695 b"37",
11696 b"200",
11697 b"km",
11698 b"STORE",
11699 b"dst3"
11700 ]),
11701 ":3\r\n"
11702 );
11703 assert_eq!(
11704 f.run(&[b"ZRANGE", b"dst3", b"0", b"-1", b"WITHSCORES"]),
11705 hashes
11706 );
11707 assert_eq!(
11716 f.run(&[
11717 b"GEOSEARCHSTORE",
11718 b"dst2",
11719 b"Sicily",
11720 b"FROMLONLAT",
11721 b"15",
11722 b"37",
11723 b"BYRADIUS",
11724 b"200",
11725 b"km",
11726 b"ASC",
11727 b"STOREDIST"
11728 ]),
11729 ":3\r\n"
11730 );
11731 for (member, want) in [
11732 ("Catania", 56.441_257_870_158_19),
11733 ("Agrigento", 130.423_487_067_147_14),
11734 ("Palermo", 190.442_429_847_757_92),
11735 ] {
11736 let reply = f.run(&[b"ZSCORE", b"dst2", member.as_bytes()]);
11737 let got: f64 = reply
11738 .trim_start_matches(|c: char| c != '\n')
11739 .trim()
11740 .parse()
11741 .unwrap_or_else(|_| panic!("{member} scored {reply:?}"));
11742 assert!(
11743 (got - want).abs() < 1e-9,
11744 "{member} scored {got} not {want}"
11745 );
11746 }
11747 assert_eq!(
11750 f.run(&[b"ZRANGE", b"dst2", b"0", b"-1"]),
11751 "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
11752 );
11753 assert_eq!(
11757 f.run(&[
11758 b"GEOSEARCHSTORE",
11759 b"dst",
11760 b"nokey",
11761 b"FROMLONLAT",
11762 b"15",
11763 b"37",
11764 b"BYRADIUS",
11765 b"200",
11766 b"km"
11767 ]),
11768 ":0\r\n"
11769 );
11770 assert_eq!(f.run(&[b"EXISTS", b"dst"]), ":0\r\n");
11771 }
11772
11773 #[test]
11774 fn the_gates_on_geoadd_are_the_ones_zadd_has() {
11775 let mut f = Fixture::new();
11776 sicily(&mut f);
11777 assert_eq!(
11780 f.run(&[
11781 b"GEOADD",
11782 b"Sicily",
11783 b"XX",
11784 b"CH",
11785 b"13.361389",
11786 b"38.115556",
11787 b"Palermo"
11788 ]),
11789 ":0\r\n"
11790 );
11791 assert_eq!(
11792 f.run(&[
11793 b"GEOADD",
11794 b"Sicily",
11795 b"NX",
11796 b"13.361389",
11797 b"38.9",
11798 b"Palermo"
11799 ]),
11800 ":0\r\n"
11801 );
11802 assert_eq!(
11803 f.run(&[
11804 b"GEOADD",
11805 b"Sicily",
11806 b"CH",
11807 b"13.361389",
11808 b"38.9",
11809 b"Palermo"
11810 ]),
11811 ":1\r\n"
11812 );
11813 assert_eq!(
11816 f.run(&[
11817 b"GEOADD",
11818 b"new",
11819 b"13.361389",
11820 b"38.115556",
11821 b"here",
11822 b"181",
11823 b"38",
11824 b"there"
11825 ]),
11826 "-ERR invalid longitude,latitude pair 181.000000,38.000000\r\n"
11827 );
11828 assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
11829 assert_eq!(
11830 f.run(&[b"GEOADD", b"new", b"x", b"38", b"here"]),
11831 "-ERR value is not a valid float\r\n"
11832 );
11833 assert_eq!(
11836 f.run(&[b"GEOADD", b"new", b"13", b"38", b"here", b"and"]),
11837 "-ERR syntax error\r\n"
11838 );
11839 assert_eq!(
11840 f.run(&[b"GEOADD", b"new", b"NX", b"XX", b"CH"]),
11841 "-ERR syntax error\r\n"
11842 );
11843 assert_eq!(
11844 f.run(&[b"GEOADD", b"new", b"CH", b"CH", b"CH", b"CH"]),
11845 "-ERR syntax error\r\n"
11846 );
11847 assert_eq!(
11848 f.run(&[b"GEOADD", b"new", b"NX", b"CH"]),
11849 "-ERR wrong number of arguments for 'geoadd' command\r\n"
11850 );
11851 }
11852
11853 #[test]
11856 fn every_way_a_search_can_be_written_wrong_has_its_own_sentence() {
11857 let mut f = Fixture::new();
11858 sicily(&mut f);
11859 let cases: &[(&[&[u8]], &str)] = &[
11860 (
11861 &[b"GEORADIUS", b"Sicily", b"15", b"37", b"x", b"km"],
11862 "-ERR need numeric radius\r\n",
11863 ),
11864 (
11865 &[b"GEORADIUS", b"Sicily", b"15", b"37", b"-1", b"km"],
11866 "-ERR radius cannot be negative\r\n",
11867 ),
11868 (
11869 &[b"GEORADIUS", b"Sicily", b"15", b"37", b"1", b"parsecs"],
11870 "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n",
11871 ),
11872 (
11873 &[b"GEORADIUS", b"Sicily", b"181", b"37", b"1", b"km"],
11874 "-ERR invalid longitude,latitude pair 181.000000,37.000000\r\n",
11875 ),
11876 (
11877 &[
11878 b"GEOSEARCH",
11879 b"Sicily",
11880 b"FROMLONLAT",
11881 b"15",
11882 b"37",
11883 b"BYBOX",
11884 b"x",
11885 b"1",
11886 b"km",
11887 ],
11888 "-ERR need numeric width\r\n",
11889 ),
11890 (
11891 &[
11892 b"GEOSEARCH",
11893 b"Sicily",
11894 b"FROMLONLAT",
11895 b"15",
11896 b"37",
11897 b"BYBOX",
11898 b"1",
11899 b"y",
11900 b"km",
11901 ],
11902 "-ERR need numeric height\r\n",
11903 ),
11904 (
11905 &[
11906 b"GEOSEARCH",
11907 b"Sicily",
11908 b"FROMLONLAT",
11909 b"15",
11910 b"37",
11911 b"BYBOX",
11912 b"-1",
11913 b"1",
11914 b"km",
11915 ],
11916 "-ERR height or width cannot be negative\r\n",
11917 ),
11918 (
11919 &[
11920 b"GEOSEARCH",
11921 b"Sicily",
11922 b"FROMLONLAT",
11923 b"15",
11924 b"37",
11925 b"BYRADIUS",
11926 b"1",
11927 b"km",
11928 b"ANY",
11929 ],
11930 "-ERR the ANY argument requires COUNT argument\r\n",
11931 ),
11932 (
11933 &[
11934 b"GEOSEARCH",
11935 b"Sicily",
11936 b"FROMLONLAT",
11937 b"15",
11938 b"37",
11939 b"BYRADIUS",
11940 b"1",
11941 b"km",
11942 b"COUNT",
11943 b"0",
11944 ],
11945 "-ERR COUNT must be > 0\r\n",
11946 ),
11947 (
11948 &[
11949 b"GEOSEARCH",
11950 b"Sicily",
11951 b"BYRADIUS",
11952 b"1",
11953 b"km",
11954 b"BYBOX",
11955 b"1",
11956 b"1",
11957 b"km",
11958 ],
11959 "-ERR syntax error\r\n",
11960 ),
11961 (
11962 &[
11963 b"GEOSEARCH",
11964 b"Sicily",
11965 b"FROMMEMBER",
11966 b"Palermo",
11967 b"FROMLONLAT",
11968 b"1",
11969 b"2",
11970 b"BYRADIUS",
11971 b"1",
11972 b"km",
11973 ],
11974 "-ERR syntax error\r\n",
11975 ),
11976 (
11979 &[
11980 b"geosearch",
11981 b"Sicily",
11982 b"BYRADIUS",
11983 b"1",
11984 b"km",
11985 b"ASC",
11986 b"WITHDIST",
11987 ],
11988 "-ERR exactly one of FROMMEMBER or FROMLONLAT can be specified for geosearch\r\n",
11989 ),
11990 (
11991 &[
11992 b"GEOSEARCH",
11993 b"Sicily",
11994 b"FROMLONLAT",
11995 b"15",
11996 b"37",
11997 b"ASC",
11998 b"WITHDIST",
11999 ],
12000 "-ERR exactly one of BYRADIUS and BYBOX can be specified for GEOSEARCH\r\n",
12001 ),
12002 (
12005 &[
12006 b"GEOSEARCHSTORE",
12007 b"d",
12008 b"Sicily",
12009 b"FROMLONLAT",
12010 b"15",
12011 b"37",
12012 b"BYRADIUS",
12013 b"1",
12014 b"km",
12015 b"WITHCOORD",
12016 ],
12017 "-ERR GEOSEARCHSTORE is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
12018 ),
12019 (
12020 &[
12021 b"GEORADIUS",
12022 b"Sicily",
12023 b"15",
12024 b"37",
12025 b"1",
12026 b"km",
12027 b"WITHDIST",
12028 b"STORE",
12029 b"d",
12030 ],
12031 "-ERR STORE option in GEORADIUS is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
12032 ),
12033 (
12036 &[
12037 b"GEORADIUS_RO",
12038 b"Sicily",
12039 b"15",
12040 b"37",
12041 b"1",
12042 b"km",
12043 b"STORE",
12044 b"d",
12045 ],
12046 "-ERR syntax error\r\n",
12047 ),
12048 (
12049 &[
12050 b"GEOSEARCH",
12051 b"Sicily",
12052 b"FROMLONLAT",
12053 b"15",
12054 b"37",
12055 b"BYRADIUS",
12056 b"1",
12057 b"km",
12058 b"STOREDIST",
12059 ],
12060 "-ERR syntax error\r\n",
12061 ),
12062 ];
12063 for (parts, want) in cases {
12064 assert_eq!(&f.run(parts), want, "{:?}", parts[0]);
12065 }
12066 }
12067
12068 #[test]
12071 fn every_geo_command_says_wrongtype() {
12072 let mut f = Fixture::new();
12073 f.run(&[b"SET", b"s", b"v"]);
12074 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12075 let cases: &[&[&[u8]]] = &[
12076 &[b"GEOADD", b"s", b"13", b"38", b"m"],
12077 &[b"GEOPOS", b"s", b"m"],
12078 &[b"GEOHASH", b"s", b"m"],
12079 &[b"GEODIST", b"s", b"a", b"b"],
12080 &[
12081 b"GEOSEARCH",
12082 b"s",
12083 b"FROMLONLAT",
12084 b"15",
12085 b"37",
12086 b"BYRADIUS",
12087 b"1",
12088 b"km",
12089 ],
12090 &[
12091 b"GEOSEARCHSTORE",
12092 b"d",
12093 b"s",
12094 b"FROMLONLAT",
12095 b"15",
12096 b"37",
12097 b"BYRADIUS",
12098 b"1",
12099 b"km",
12100 ],
12101 &[b"GEORADIUS", b"s", b"15", b"37", b"1", b"km"],
12102 &[b"GEORADIUS_RO", b"s", b"15", b"37", b"1", b"km"],
12103 &[b"GEORADIUSBYMEMBER", b"s", b"m", b"1", b"km"],
12104 &[b"GEORADIUSBYMEMBER_RO", b"s", b"m", b"1", b"km"],
12105 ];
12106 for case in cases {
12107 assert_eq!(f.run(case), wrong, "{:?}", case[0]);
12108 }
12109 assert_eq!(
12112 f.run(&[b"GEORADIUS", b"s", b"15", b"37", b"x", b"km"]),
12113 wrong
12114 );
12115 }
12116
12117 #[test]
12120 fn an_array_writes_at_any_index_and_reads_back_what_it_sent() {
12121 let mut f = Fixture::new();
12122 assert_eq!(
12125 f.run(&[b"ARSET", b"a", b"1000", b"x", b"y", b"z"]),
12126 ":3\r\n"
12127 );
12128 assert_eq!(f.run(&[b"ARSET", b"a", b"1000", b"X", b"Y"]), ":0\r\n");
12129 assert_eq!(f.run(&[b"ARGET", b"a", b"1000"]), "$1\r\nX\r\n");
12130 assert_eq!(f.run(&[b"ARGET", b"a", b"1002"]), "$1\r\nz\r\n");
12131 assert_eq!(f.run(&[b"ARGET", b"a", b"999"]), "$-1\r\n");
12133 assert_eq!(f.run(&[b"ARGET", b"nope", b"0"]), "$-1\r\n");
12134 assert_eq!(
12135 f.run(&[b"ARMGET", b"a", b"1002", b"999", b"1000"]),
12136 "*3\r\n$1\r\nz\r\n$-1\r\n$1\r\nX\r\n"
12137 );
12138 assert_eq!(f.run(&[b"ARMSET", b"a", b"5", b"p", b"5", b"q"]), ":1\r\n");
12140 assert_eq!(f.run(&[b"ARGET", b"a", b"5"]), "$1\r\nq\r\n");
12141 }
12142
12143 #[test]
12146 fn the_length_is_the_high_water_mark_and_the_count_is_the_population() {
12147 let mut f = Fixture::new();
12148 assert_eq!(f.run(&[b"ARLEN", b"nope"]), ":0\r\n");
12149 assert_eq!(f.run(&[b"ARCOUNT", b"nope"]), ":0\r\n");
12150 f.run(&[b"ARMSET", b"a", b"0", b"x", b"9", b"y"]);
12151 assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
12152 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
12153 assert_eq!(f.run(&[b"ARDEL", b"a", b"0"]), ":1\r\n");
12155 assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
12156 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":1\r\n");
12157
12158 f.run(&[b"ARSET", b"top", b"18446744073709551614", b"z"]);
12162 assert_eq!(f.run(&[b"ARLEN", b"top"]), ":18446744073709551615\r\n");
12163 assert_eq!(f.run(&[b"ARCOUNT", b"top"]), ":1\r\n");
12164 assert_eq!(
12167 f.run(&[b"ARSET", b"over", b"18446744073709551614", b"a", b"b"]),
12168 "-ERR array index overflow\r\n"
12169 );
12170 assert_eq!(f.run(&[b"EXISTS", b"over"]), ":0\r\n");
12171 }
12172
12173 #[test]
12176 fn a_range_read_answers_for_the_holes_too_and_is_capped_at_a_million() {
12177 let mut f = Fixture::new();
12178 f.run(&[b"ARSET", b"a", b"1", b"x"]);
12179 assert_eq!(
12180 f.run(&[b"ARGETRANGE", b"a", b"0", b"3"]),
12181 "*4\r\n$-1\r\n$1\r\nx\r\n$-1\r\n$-1\r\n"
12182 );
12183 assert_eq!(
12186 f.run(&[b"ARGETRANGE", b"a", b"3", b"0"]),
12187 "*4\r\n$-1\r\n$-1\r\n$1\r\nx\r\n$-1\r\n"
12188 );
12189 assert_eq!(
12191 f.run(&[b"ARGETRANGE", b"nope", b"0", b"1"]),
12192 "*2\r\n$-1\r\n$-1\r\n"
12193 );
12194 assert_eq!(
12198 f.run(&[b"ARGETRANGE", b"nope", b"0", b"18446744073709551614"]),
12199 "-ERR range exceeds maximum of 1000000 items\r\n"
12200 );
12201 }
12202
12203 #[test]
12206 fn a_bad_index_late_in_the_line_writes_none_of_the_earlier_ones() {
12207 let mut f = Fixture::new();
12208 assert_eq!(
12209 f.run(&[b"ARMSET", b"a", b"0", b"x", b"-1", b"y"]),
12210 "-ERR invalid array index\r\n"
12211 );
12212 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
12213 f.run(&[b"ARSET", b"a", b"0", b"x", b"y", b"z"]);
12214 assert_eq!(
12215 f.run(&[b"ARDEL", b"a", b"0", b"01"]),
12216 "-ERR invalid array index\r\n"
12217 );
12218 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":3\r\n");
12219 assert_eq!(
12222 f.run(&[b"ARGET", b"a", b"-1"]),
12223 "-ERR invalid array index\r\n"
12224 );
12225 assert_eq!(
12228 f.run(&[b"ARMSET", b"a", b"0", b"x", b"1"]),
12229 "-ERR wrong number of arguments for 'armset' command\r\n"
12230 );
12231 assert_eq!(
12232 f.run(&[b"ARDELRANGE", b"a", b"0", b"1", b"2"]),
12233 "-ERR wrong number of arguments for 'ardelrange' command\r\n"
12234 );
12235 }
12236
12237 #[test]
12238 fn a_range_delete_costs_the_elements_and_takes_the_key_when_it_empties() {
12239 let mut f = Fixture::new();
12240 f.run(&[b"ARSET", b"a", b"0", b"0", b"1", b"2", b"3", b"4"]);
12241 assert_eq!(f.run(&[b"ARDELRANGE", b"a", b"3", b"1"]), ":3\r\n");
12242 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
12243 assert_eq!(
12246 f.run(&[
12247 b"ARDELRANGE",
12248 b"a",
12249 b"100",
12250 b"200",
12251 b"0",
12252 b"18446744073709551614"
12253 ]),
12254 ":2\r\n"
12255 );
12256 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
12257 assert_eq!(f.run(&[b"ARDELRANGE", b"nope", b"0", b"1"]), ":0\r\n");
12258 assert_eq!(f.run(&[b"ARDEL", b"nope", b"0"]), ":0\r\n");
12259 }
12260
12261 #[test]
12264 fn a_value_comes_back_byte_for_byte_however_it_was_packed() {
12265 let mut f = Fixture::new();
12266 let long = vec![b'v'; 200];
12267 f.run(&[
12268 b"ARMSET", b"a", b"0", b"42", b"1", b"007", b"2", b"3.5", b"3", b"3.14", b"4",
12269 b"short", b"5", &long, b"6", b"-0",
12270 ]);
12271 assert_eq!(
12275 f.run(&[b"ARGETRANGE", b"a", b"0", b"6"]),
12276 format!(
12277 "*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",
12278 String::from_utf8_lossy(&long)
12279 )
12280 );
12281 }
12282
12283 #[test]
12284 fn an_array_is_a_type_and_an_encoding_a_client_can_see() {
12285 let mut f = Fixture::new();
12286 f.run(&[b"ARSET", b"a", b"0", b"x"]);
12287 assert_eq!(f.run(&[b"TYPE", b"a"]), "+array\r\n");
12288 assert_eq!(
12289 f.run(&[b"OBJECT", b"ENCODING", b"a"]),
12290 "$12\r\nsliced-array\r\n"
12291 );
12292 assert_eq!(f.run(&[b"EXPIRE", b"a", b"100"]), ":1\r\n");
12294 assert_eq!(f.run(&[b"PERSIST", b"a"]), ":1\r\n");
12295 assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":1\r\n");
12296 assert_eq!(f.run(&[b"ARGET", b"b", b"0"]), "$1\r\nx\r\n");
12297 assert_eq!(f.run(&[b"RENAME", b"a", b"c"]), "+OK\r\n");
12298 assert_eq!(f.run(&[b"ARCOUNT", b"c"]), ":1\r\n");
12299 }
12300
12301 #[test]
12302 fn every_array_command_refuses_a_key_holding_something_else() {
12303 let mut f = Fixture::new();
12304 f.run(&[b"SET", b"s", b"v"]);
12305 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12306 for cmd in [
12307 &[b"ARSET".as_ref(), b"s", b"0", b"x"][..],
12308 &[b"ARMSET".as_ref(), b"s", b"0", b"x"][..],
12309 &[b"ARGET".as_ref(), b"s", b"0"][..],
12310 &[b"ARMGET".as_ref(), b"s", b"0"][..],
12311 &[b"ARGETRANGE".as_ref(), b"s", b"0", b"1"][..],
12312 &[b"ARLEN".as_ref(), b"s"][..],
12313 &[b"ARCOUNT".as_ref(), b"s"][..],
12314 &[b"ARDEL".as_ref(), b"s", b"0"][..],
12315 &[b"ARDELRANGE".as_ref(), b"s", b"0", b"1"][..],
12316 &[b"ARINSERT".as_ref(), b"s", b"x"][..],
12317 &[b"ARRING".as_ref(), b"s", b"4", b"x"][..],
12318 &[b"ARNEXT".as_ref(), b"s"][..],
12319 &[b"ARSEEK".as_ref(), b"s", b"1"][..],
12320 &[b"ARLASTITEMS".as_ref(), b"s", b"1"][..],
12321 &[b"ARSCAN".as_ref(), b"s", b"0", b"1"][..],
12322 &[b"ARGREP".as_ref(), b"s", b"0", b"1", b"EXACT", b"v"][..],
12323 &[b"AROP".as_ref(), b"s", b"0", b"1", b"SUM"][..],
12324 &[b"ARINFO".as_ref(), b"s"][..],
12325 ] {
12326 assert_eq!(f.run(cmd), wrong, "{}", String::from_utf8_lossy(cmd[0]));
12327 }
12328 }
12329
12330 #[test]
12334 fn a_bad_index_reports_the_type_only_where_redis_reports_it() {
12335 let mut f = Fixture::new();
12336 f.run(&[b"SET", b"s", b"v"]);
12337 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12338 let bad = "-ERR invalid array index\r\n";
12339 assert_eq!(f.run(&[b"ARGET", b"s", b"-1"]), wrong);
12340 assert_eq!(f.run(&[b"ARMGET", b"s", b"0", b"-1"]), wrong);
12341 assert_eq!(f.run(&[b"ARSET", b"s", b"-1", b"x"]), bad);
12342 assert_eq!(f.run(&[b"ARDEL", b"s", b"-1"]), bad);
12343 assert_eq!(f.run(&[b"ARSCAN", b"s", b"-1", b"0"]), bad);
12344 assert_eq!(f.run(&[b"ARGREP", b"s", b"-1", b"0", b"EXACT", b"v"]), bad);
12345 f.run(&[b"ARSET", b"a", b"0", b"x"]);
12347 assert_eq!(f.run(&[b"ARGET", b"a", b"-1"]), bad);
12348 assert_eq!(f.run(&[b"ARGET", b"nope", b"-1"]), bad);
12349 }
12350
12351 #[test]
12352 fn an_append_follows_a_cursor_the_client_can_move() {
12353 let mut f = Fixture::new();
12354 assert_eq!(f.run(&[b"ARNEXT", b"nope"]), ":0\r\n");
12355 assert_eq!(f.run(&[b"ARINSERT", b"a", b"x", b"y"]), ":1\r\n");
12356 assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":2\r\n");
12357 assert_eq!(f.run(&[b"ARINSERT", b"a", b"z"]), ":2\r\n");
12358 assert_eq!(f.run(&[b"ARGET", b"a", b"2"]), "$1\r\nz\r\n");
12359
12360 assert_eq!(f.run(&[b"ARSEEK", b"nope", b"5"]), ":0\r\n");
12363 assert_eq!(f.run(&[b"EXISTS", b"nope"]), ":0\r\n");
12364 assert_eq!(f.run(&[b"ARSEEK", b"a", b"100"]), ":1\r\n");
12365 assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":100\r\n");
12366 assert_eq!(f.run(&[b"ARINSERT", b"a", b"far"]), ":100\r\n");
12367 assert_eq!(f.run(&[b"ARSEEK", b"a", b"0"]), ":1\r\n");
12368 assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":0\r\n");
12369
12370 assert_eq!(f.run(&[b"ARSEEK", b"a", b"18446744073709551615"]), ":1\r\n");
12373 assert_eq!(f.run(&[b"ARNEXT", b"a"]), "$-1\r\n");
12374 assert_eq!(
12375 f.run(&[b"ARINSERT", b"a", b"x"]),
12376 "-ERR insert index overflow\r\n"
12377 );
12378 assert_eq!(
12379 f.run(&[b"ARSET", b"a", b"18446744073709551615", b"x"]),
12380 "-ERR invalid array index\r\n"
12381 );
12382 }
12383
12384 #[test]
12385 fn a_ring_keeps_the_newest_and_renumbers_them_when_it_is_resized() {
12386 let mut f = Fixture::new();
12387 assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"a", b"b", b"c"]), ":2\r\n");
12388 assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"d", b"e"]), ":1\r\n");
12389 assert_eq!(f.run(&[b"ARLEN", b"r"]), ":3\r\n");
12390 assert_eq!(
12391 f.run(&[b"ARGETRANGE", b"r", b"0", b"2"]),
12392 "*3\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nc\r\n"
12393 );
12394 assert_eq!(f.run(&[b"ARRING", b"r", b"5", b"f"]), ":3\r\n");
12397 assert_eq!(
12398 f.run(&[b"ARGETRANGE", b"r", b"0", b"3"]),
12399 "*4\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nf\r\n"
12400 );
12401 assert_eq!(
12404 f.run(&[b"ARRING", b"r", b"0", b"x"]),
12405 "-ERR size must be positive\r\n"
12406 );
12407 assert_eq!(
12408 f.run(&[b"ARRING", b"r", b"big", b"x"]),
12409 "-ERR invalid size\r\n"
12410 );
12411 }
12412
12413 #[test]
12414 fn the_last_items_walk_back_from_the_cursor_and_report_the_holes() {
12415 let mut f = Fixture::new();
12416 assert_eq!(f.run(&[b"ARLASTITEMS", b"nope", b"5"]), "*0\r\n");
12417 f.run(&[b"ARRING", b"r", b"4", b"a", b"b", b"c", b"d", b"e"]);
12418 assert_eq!(
12419 f.run(&[b"ARLASTITEMS", b"r", b"3"]),
12420 "*3\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n"
12421 );
12422 assert_eq!(
12423 f.run(&[b"ARLASTITEMS", b"r", b"3", b"rev"]),
12424 "*3\r\n$1\r\ne\r\n$1\r\nd\r\n$1\r\nc\r\n"
12425 );
12426 assert_eq!(
12427 f.run(&[b"ARLASTITEMS", b"r", b"99"]),
12428 "*4\r\n$1\r\nb\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n",
12429 "more than there is gets what there is"
12430 );
12431 assert_eq!(f.run(&[b"ARLASTITEMS", b"r", b"0", b"junk"]), "*0\r\n");
12434 assert_eq!(
12435 f.run(&[b"ARLASTITEMS", b"r", b"1", b"junk"]),
12436 "-ERR syntax error\r\n"
12437 );
12438 assert_eq!(
12439 f.run(&[b"ARLASTITEMS", b"r", b"nine"]),
12440 "-ERR invalid COUNT\r\n"
12441 );
12442
12443 f.run(&[b"ARMSET", b"h", b"0", b"x", b"2", b"z"]);
12446 assert_eq!(
12447 f.run(&[b"ARLASTITEMS", b"h", b"5"]),
12448 "*2\r\n$-1\r\n$1\r\nz\r\n"
12449 );
12450 }
12451
12452 #[test]
12453 fn a_scan_answers_pairs_for_what_is_there_and_skips_what_is_not() {
12454 let mut f = Fixture::new();
12455 assert_eq!(f.run(&[b"ARSCAN", b"nope", b"0", b"10"]), "*0\r\n");
12456 f.run(&[b"ARMSET", b"a", b"0", b"x", b"7", b"y", b"1000000", b"z"]);
12457 assert_eq!(
12460 f.run(&[b"ARSCAN", b"a", b"0", b"18446744073709551614"]),
12461 "*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"
12462 );
12463 assert_eq!(
12464 f.run(&[
12465 b"ARSCAN",
12466 b"a",
12467 b"18446744073709551614",
12468 b"0",
12469 b"LIMIT",
12470 b"1"
12471 ]),
12472 "*1\r\n*2\r\n:1000000\r\n$1\r\nz\r\n"
12473 );
12474 assert_eq!(f.run(&[b"ARSCAN", b"a", b"1", b"6"]), "*0\r\n");
12475 assert_eq!(
12476 f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT", b"0"]),
12477 "-ERR LIMIT must be positive\r\n"
12478 );
12479 assert_eq!(
12480 f.run(&[b"ARSCAN", b"a", b"0", b"10", b"NOPE", b"1"]),
12481 "-ERR syntax error\r\n"
12482 );
12483 assert_eq!(
12484 f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT"]),
12485 "-ERR wrong number of arguments for 'arscan' command\r\n"
12486 );
12487 }
12488
12489 #[test]
12490 fn a_grep_answers_the_indexes_whose_elements_match() {
12491 let mut f = Fixture::new();
12492 assert_eq!(
12493 f.run(&[b"ARGREP", b"nope", b"0", b"10", b"EXACT", b"x"]),
12494 "*0\r\n"
12495 );
12496 f.run(&[b"ARSET", b"a", b"0", b"alpha", b"beta", b"gamma", b"ALPHA"]);
12497
12498 assert_eq!(
12501 f.run(&[b"ARGREP", b"a", b"-", b"+", b"GLOB", b"*a"]),
12502 "*3\r\n:0\r\n:1\r\n:2\r\n"
12503 );
12504 assert_eq!(
12505 f.run(&[b"ARGREP", b"a", b"+", b"-", b"GLOB", b"*a"]),
12506 "*3\r\n:2\r\n:1\r\n:0\r\n"
12507 );
12508 assert_eq!(
12509 f.run(&[b"ARGREP", b"a", b"1", b"2", b"GLOB", b"*a"]),
12510 "*2\r\n:1\r\n:2\r\n"
12511 );
12512
12513 assert_eq!(
12516 f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha"]),
12517 "*1\r\n:0\r\n"
12518 );
12519 assert_eq!(
12520 f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha", b"NOCASE"]),
12521 "*2\r\n:0\r\n:3\r\n"
12522 );
12523 assert_eq!(
12524 f.run(&[b"ARGREP", b"a", b"-", b"+", b"MATCH", b"mm"]),
12525 "*1\r\n:2\r\n"
12526 );
12527 assert_eq!(
12528 f.run(&[b"ARGREP", b"a", b"-", b"+", b"RE", b"^[bg]"]),
12529 "*2\r\n:1\r\n:2\r\n"
12530 );
12531
12532 let both: &[&[u8]] = &[
12535 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al",
12536 ];
12537 assert_eq!(f.run(both), "*2\r\n:0\r\n:1\r\n");
12538 assert_eq!(
12539 f.run(&[
12540 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND"
12541 ]),
12542 "*0\r\n"
12543 );
12544 assert_eq!(
12545 f.run(&[
12546 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND", b"OR"
12547 ]),
12548 "*2\r\n:0\r\n:1\r\n"
12549 );
12550
12551 assert_eq!(
12554 f.run(&[
12555 b"ARGREP",
12556 b"a",
12557 b"-",
12558 b"+",
12559 b"MATCH",
12560 b"a",
12561 b"WITHVALUES",
12562 b"LIMIT",
12563 b"2"
12564 ]),
12565 "*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"
12566 );
12567 assert_eq!(
12568 f.run(&[
12569 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"ALPHA", b"LIMIT", b"1"
12570 ]),
12571 "*1\r\n:3\r\n"
12572 );
12573 }
12574
12575 #[test]
12577 fn a_grep_reports_a_broken_command_the_way_redis_does() {
12578 let mut f = Fixture::new();
12579 f.run(&[b"ARSET", b"a", b"0", b"alpha"]);
12580 let syntax = "-ERR syntax error\r\n";
12581
12582 assert_eq!(
12585 f.run(&[b"ARGREP", b"a", b"-1", b"0", b"NOPE", b"x"]),
12586 "-ERR invalid array index\r\n"
12587 );
12588 assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOPE", b"x"]), syntax);
12589 assert_eq!(
12591 f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"EXACT"]),
12592 syntax
12593 );
12594 assert_eq!(
12595 f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT"]),
12596 syntax
12597 );
12598 assert_eq!(
12599 f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"WITHVALUES"]),
12600 syntax,
12601 "a command with no predicate in it at all"
12602 );
12603 assert_eq!(
12604 f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"0"]),
12605 "-ERR LIMIT must be positive\r\n"
12606 );
12607 assert_eq!(
12608 f.run(&[
12609 b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"nine"
12610 ]),
12611 "-ERR value is not an integer or out of range\r\n"
12612 );
12613 assert_eq!(
12614 f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b""]),
12615 "-ERR regular expression is empty\r\n"
12616 );
12617 assert_eq!(
12618 f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b"(a"]),
12619 "-ERR invalid regular expression: Missing ')'\r\n"
12620 );
12621 assert_eq!(
12622 f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", br"(a)\1"]),
12623 "-ERR regular expression backreferences are not supported\r\n"
12624 );
12625 let arity = "-ERR wrong number of arguments for 'argrep' command\r\n";
12628 assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT"]), arity);
12629 assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1"]), arity);
12630 }
12631
12632 #[test]
12633 fn an_op_reduces_a_range_to_one_number() {
12634 let mut f = Fixture::new();
12635 f.run(&[b"ARSET", b"a", b"0", b"1", b"2.5", b"word", b"-4"]);
12636 assert_eq!(
12637 f.run(&[b"AROP", b"a", b"0", b"10", b"SUM"]),
12638 "$4\r\n-0.5\r\n"
12639 );
12640 assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"min"]), "$2\r\n-4\r\n");
12641 assert_eq!(
12642 f.run(&[b"AROP", b"a", b"0", b"10", b"MAX"]),
12643 "$3\r\n2.5\r\n"
12644 );
12645 assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"USED"]), ":4\r\n");
12646 assert_eq!(
12647 f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH", b"word"]),
12648 ":1\r\n"
12649 );
12650 f.run(&[b"ARSET", b"t", b"0", b"0.1", b"0.2"]);
12653 assert_eq!(
12654 f.run(&[b"AROP", b"t", b"0", b"10", b"SUM"]),
12655 "$19\r\n0.30000000000000004\r\n"
12656 );
12657 assert_eq!(f.run(&[b"ZADD", b"z", b"0.3", b"m"]), ":1\r\n");
12658 assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\n0.3\r\n");
12659
12660 f.run(&[b"ARSET", b"w", b"0", b"word"]);
12663 assert_eq!(f.run(&[b"AROP", b"w", b"0", b"10", b"SUM"]), "$-1\r\n");
12664 assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"SUM"]), "$-1\r\n");
12665 assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"USED"]), ":0\r\n");
12666
12667 assert_eq!(
12668 f.run(&[b"AROP", b"a", b"0", b"10", b"NOPE"]),
12669 "-ERR unknown operation\r\n"
12670 );
12671 assert_eq!(
12672 f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH"]),
12673 "-ERR MATCH requires a value argument\r\n"
12674 );
12675 assert_eq!(
12676 f.run(&[b"AROP", b"a", b"0", b"10", b"SUM", b"extra"]),
12677 "-ERR wrong number of arguments for 'arop' command\r\n"
12678 );
12679 }
12680
12681 #[test]
12682 fn the_info_is_a_map_and_a_missing_key_is_an_error() {
12683 let mut f = Fixture::new();
12684 assert_eq!(f.run(&[b"ARINFO", b"nope"]), "-ERR no such key\r\n");
12685 f.run(&[b"ARINSERT", b"a", b"x", b"y"]);
12686 let short = f.run(&[b"ARINFO", b"a"]);
12687 assert!(
12688 short.starts_with("*14\r\n"),
12689 "seven pairs on RESP2: {short}"
12690 );
12691 assert!(short.contains("$5\r\ncount\r\n:2\r\n"), "{short}");
12692 assert!(
12693 short.contains("$17\r\nnext-insert-index\r\n:2\r\n"),
12694 "{short}"
12695 );
12696 assert!(short.contains("$10\r\nslice-size\r\n:4096\r\n"), "{short}");
12697 let full = f.run(&[b"ARINFO", b"a", b"full"]);
12698 assert!(full.starts_with("*24\r\n"), "twelve pairs: {full}");
12699 assert!(full.contains("$12\r\ndense-slices\r\n:0\r\n"), "{full}");
12702 assert!(full.contains("$13\r\nsparse-slices\r\n:1\r\n"), "{full}");
12703 assert!(
12704 full.contains("$14\r\navg-dense-size\r\n$1\r\n0\r\n"),
12705 "{full}"
12706 );
12707 assert_eq!(f.run(&[b"ARINFO", b"a", b"nope"]), "-ERR syntax error\r\n");
12708
12709 let mut g = Fixture::new();
12711 g.run(&[b"HELLO", b"3"]);
12712 g.run(&[b"ARINSERT", b"a", b"x"]);
12713 let map = g.run(&[b"ARINFO", b"a", b"FULL"]);
12714 assert!(map.starts_with("%12\r\n"), "{map}");
12715 assert!(map.contains("$5\r\ncount\r\n:1\r\n"), "{map}");
12716 assert!(map.contains("$14\r\navg-dense-size\r\n,0\r\n"), "{map}");
12717 }
12718
12719 #[test]
12720 fn a_double_on_the_wire_is_written_the_way_redis_writes_one() {
12721 let mut f = Fixture::new();
12722 for (score, want) in [
12725 ("3", "3"),
12726 ("3.5", "3.5"),
12727 ("0.3", "0.3"),
12728 ("1e30", "1e+30"),
12729 ("1e19", "1e+19"),
12730 ("1e-7", "1e-7"),
12731 ("0.000001", "0.000001"),
12732 ("4611686018427387904", "4611686018427387904"),
12733 ("-0", "-0"),
12734 ] {
12735 f.run(&[b"ZADD", b"z", score.as_bytes(), b"m"]);
12736 assert_eq!(
12737 f.run(&[b"ZSCORE", b"z", b"m"]),
12738 format!("${}\r\n{want}\r\n", want.len()),
12739 "score {score}"
12740 );
12741 }
12742
12743 let mut g = Fixture::new();
12746 g.run(&[b"HELLO", b"3"]);
12747 g.run(&[b"ZADD", b"z", b"1e30", b"m"]);
12748 assert_eq!(g.run(&[b"ZSCORE", b"z", b"m"]), ",1e+30\r\n");
12749 assert_eq!(
12754 g.run(&[b"INCRBYFLOAT", b"s", b"1e30"]),
12755 "$31\r\n1000000000000000000000000000000\r\n"
12756 );
12757 assert_eq!(g.run(&[b"INCRBYFLOAT", b"t", b"0.1"]), "$3\r\n0.1\r\n");
12758 assert_eq!(
12759 g.run(&[b"HINCRBYFLOAT", b"h", b"f", b"1e19"]),
12760 "$20\r\n10000000000000000000\r\n"
12761 );
12762 }
12763
12764 #[test]
12767 fn a_node_comes_back_with_the_fields_it_went_in_with() {
12768 let mut f = Fixture::new();
12769 assert_eq!(
12770 f.run(&[
12771 b"G.NADD", b"social", b"ada", b"name", b"Ada", b"born", b"1815"
12772 ]),
12773 ":1\r\n"
12774 );
12775 assert_eq!(
12781 f.run(&[b"G.NGET", b"social", b"ada"]),
12782 "*4\r\n$4\r\nborn\r\n$4\r\n1815\r\n$4\r\nname\r\n$3\r\nAda\r\n"
12783 );
12784 assert_eq!(
12787 f.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada Lovelace"]),
12788 ":0\r\n"
12789 );
12790 assert_eq!(
12791 f.run(&[b"G.NGET", b"social", b"ada"]),
12792 "*2\r\n$4\r\nname\r\n$12\r\nAda Lovelace\r\n"
12793 );
12794 assert_eq!(f.run(&[b"G.NADD", b"social", b"grace"]), ":1\r\n");
12797 assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
12798 assert_eq!(f.run(&[b"G.NGET", b"social", b"nobody"]), "$-1\r\n");
12799 assert_eq!(f.run(&[b"G.NGET", b"nokey", b"ada"]), "$-1\r\n");
12800
12801 assert_eq!(
12804 f.run(&[b"G.NADD", b"fresh", b"n", b"lonely"]),
12805 "-ERR syntax error\r\n"
12806 );
12807 assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
12808
12809 let mut g = Fixture::new();
12811 g.run(&[b"HELLO", b"3"]);
12812 g.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada"]);
12813 assert_eq!(
12814 g.run(&[b"G.NGET", b"social", b"ada"]),
12815 "%1\r\n$4\r\nname\r\n$3\r\nAda\r\n"
12816 );
12817 }
12818
12819 #[test]
12820 fn an_edge_creates_the_ends_it_needs() {
12821 let mut f = Fixture::new();
12822 assert_eq!(
12823 f.run(&[
12824 b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1843"
12825 ]),
12826 ":1\r\n"
12827 );
12828 assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "*0\r\n");
12830 assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
12831 assert_eq!(
12832 f.run(&[b"G.OUT", b"social", b"ada", b"FOLLOWS"]),
12833 "*2\r\n$1\r\n0\r\n*1\r\n$5\r\ngrace\r\n"
12834 );
12835 assert_eq!(
12836 f.run(&[b"G.IN", b"social", b"grace", b"FOLLOWS"]),
12837 "*2\r\n$1\r\n0\r\n*1\r\n$3\r\nada\r\n"
12838 );
12839 assert_eq!(
12842 f.run(&[
12843 b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1844"
12844 ]),
12845 ":0\r\n"
12846 );
12847 assert_eq!(f.run(&[b"G.DEG", b"social", b"ada", b"FOLLOWS"]), ":1\r\n");
12848 assert_eq!(
12850 f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"WORKS_WITH"]),
12851 ":1\r\n"
12852 );
12853 assert_eq!(
12854 f.run(&[b"G.DEG", b"social", b"ada", b"WORKS_WITH"]),
12855 ":1\r\n"
12856 );
12857
12858 assert_eq!(
12859 f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
12860 ":1\r\n"
12861 );
12862 assert_eq!(
12863 f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
12864 ":0\r\n"
12865 );
12866 assert_eq!(
12869 f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"NEVER"]),
12870 ":0\r\n"
12871 );
12872 assert_eq!(
12873 f.run(&[b"G.EDEL", b"social", b"ada", b"nobody", b"FOLLOWS"]),
12874 ":0\r\n"
12875 );
12876 assert_eq!(
12877 f.run(&[b"G.EDEL", b"nokey", b"ada", b"grace", b"FOLLOWS"]),
12878 ":0\r\n"
12879 );
12880 }
12881
12882 #[test]
12885 fn a_hop_answers_a_cursor_and_a_page() {
12886 let mut f = Fixture::new();
12887 for i in 0..25u32 {
12888 let dst = format!("n{i}");
12889 f.run(&[b"G.EADD", b"social", b"hub", dst.as_bytes(), b"FOLLOWS"]);
12890 }
12891 let first = f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS"]);
12893 assert!(first.starts_with("*2\r\n$2\r\n10\r\n*10\r\n"), "{first}");
12894
12895 let mut seen = 0;
12896 let mut cursor = String::from("0");
12897 loop {
12898 let page = f.run(&[
12899 b"G.OUT",
12900 b"social",
12901 b"hub",
12902 b"FOLLOWS",
12903 b"COUNT",
12904 b"7",
12905 b"CURSOR",
12906 cursor.as_bytes(),
12907 ]);
12908 let (head, rest) = page.split_once("\r\n*").expect("a cursor and a page");
12909 cursor = head
12910 .rsplit("\r\n")
12911 .next()
12912 .expect("the cursor line")
12913 .to_string();
12914 seen += rest
12915 .split_once("\r\n")
12916 .expect("the page length")
12917 .0
12918 .parse::<usize>()
12919 .expect("a length");
12920 if cursor == "0" {
12921 break;
12922 }
12923 }
12924 assert_eq!(seen, 25, "every neighbour once across the pages");
12925
12926 assert_eq!(
12929 f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"CURSOR", b"900"]),
12930 "*2\r\n$1\r\n0\r\n*0\r\n"
12931 );
12932 assert_eq!(
12933 f.run(&[b"G.OUT", b"social", b"hub", b"NEVER"]),
12934 "*2\r\n$1\r\n0\r\n*0\r\n"
12935 );
12936 assert_eq!(
12937 f.run(&[b"G.OUT", b"nokey", b"hub", b"FOLLOWS"]),
12938 "*2\r\n$1\r\n0\r\n*0\r\n"
12939 );
12940 assert_eq!(
12941 f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"COUNT", b"0"]),
12942 "-ERR COUNT must be a positive integer\r\n"
12943 );
12944 assert_eq!(
12945 f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"NOPE", b"1"]),
12946 "-ERR syntax error\r\n"
12947 );
12948 }
12949
12950 #[test]
12951 fn a_degree_counts_one_way_or_both() {
12952 let mut f = Fixture::new();
12953 f.run(&[b"G.EADD", b"social", b"a", b"b", b"F"]);
12954 f.run(&[b"G.EADD", b"social", b"a", b"c", b"F"]);
12955 f.run(&[b"G.EADD", b"social", b"d", b"a", b"F"]);
12956 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F"]), ":2\r\n");
12957 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"OUT"]), ":2\r\n");
12958 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"IN"]), ":1\r\n");
12959 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"BOTH"]), ":3\r\n");
12960 assert_eq!(f.run(&[b"G.DEG", b"social", b"nobody", b"F"]), ":0\r\n");
12961 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"NEVER"]), ":0\r\n");
12962 assert_eq!(f.run(&[b"G.DEG", b"nokey", b"a", b"F"]), ":0\r\n");
12963 assert_eq!(
12964 f.run(&[b"G.DEG", b"social", b"a", b"F", b"SIDEWAYS"]),
12965 "-ERR syntax error\r\n"
12966 );
12967 }
12968
12969 #[test]
12972 fn a_walk_reaches_each_node_once_however_many_ways_there_are() {
12973 let mut f = Fixture::new();
12974 for (src, dst) in [
12975 ("ada", "grace"),
12976 ("ada", "alan"),
12977 ("grace", "edsger"),
12978 ("alan", "edsger"),
12979 ("edsger", "barbara"),
12980 ] {
12981 f.run(&[b"G.EADD", b"social", src.as_bytes(), dst.as_bytes(), b"F"]);
12982 }
12983 assert_eq!(
12986 f.run(&[b"G.NEIGH", b"social", b"ada", b"F"]),
12987 "*3\r\n$5\r\ngrace\r\n$4\r\nalan\r\n$6\r\nedsger\r\n"
12988 );
12989 assert_eq!(
12990 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"1"]),
12991 "*2\r\n$5\r\ngrace\r\n$4\r\nalan\r\n"
12992 );
12993 let deep = f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"9"]);
12994 assert!(deep.starts_with("*4\r\n"), "the whole component: {deep}");
12995 assert!(deep.contains("$7\r\nbarbara\r\n"), "{deep}");
12996 assert_eq!(
12998 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"COUNT", b"1"]),
12999 "*1\r\n$5\r\ngrace\r\n"
13000 );
13001 assert_eq!(f.run(&[b"G.NEIGH", b"social", b"barbara", b"F"]), "*0\r\n");
13003 assert_eq!(f.run(&[b"G.NEIGH", b"social", b"ada", b"NEVER"]), "*0\r\n");
13004 assert_eq!(f.run(&[b"G.NEIGH", b"nokey", b"ada", b"F"]), "*0\r\n");
13005 assert_eq!(
13006 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"0"]),
13007 "-ERR DEPTH must be a positive integer\r\n"
13008 );
13009 assert_eq!(
13010 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"NOPE", b"1"]),
13011 "-ERR syntax error\r\n"
13012 );
13013 }
13014
13015 #[test]
13018 fn a_path_is_the_shortest_one_and_goes_over_any_label() {
13019 let mut f = Fixture::new();
13020 for i in 0..6u32 {
13023 let src = format!("n{i}");
13024 let dst = format!("n{}", i + 1);
13025 f.run(&[b"G.EADD", b"road", src.as_bytes(), dst.as_bytes(), b"STEP"]);
13026 }
13027 assert_eq!(
13028 f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
13029 "*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"
13030 );
13031 f.run(&[b"G.EADD", b"road", b"n0", b"n5", b"JUMP"]);
13032 assert_eq!(
13033 f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
13034 "*3\r\n$2\r\nn0\r\n$2\r\nn5\r\n$2\r\nn6\r\n"
13035 );
13036 assert_eq!(
13039 f.run(&[b"G.PATH", b"road", b"n2", b"n2"]),
13040 "*1\r\n$2\r\nn2\r\n"
13041 );
13042 assert_eq!(
13043 f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"MAXDEPTH", b"1"]),
13044 "*0\r\n"
13045 );
13046 assert_eq!(f.run(&[b"G.PATH", b"road", b"n6", b"n0"]), "*0\r\n");
13048 f.run(&[b"G.NADD", b"road", b"island"]);
13051 assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"island"]), "*0\r\n");
13052 assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"nobody"]), "*0\r\n");
13053 assert_eq!(f.run(&[b"G.PATH", b"nokey", b"n0", b"n6"]), "*0\r\n");
13054 assert_eq!(
13055 f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"NOPE", b"3"]),
13056 "-ERR syntax error\r\n"
13057 );
13058 }
13059
13060 #[test]
13064 fn the_keyspace_sees_a_graph_key_like_any_other() {
13065 let mut f = Fixture::new();
13066 f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"F"]);
13067 assert_eq!(f.run(&[b"TYPE", b"social"]), "+graph\r\n");
13068 assert_eq!(
13069 f.run(&[b"OBJECT", b"ENCODING", b"social"]),
13070 "$9\r\nadjacency\r\n"
13071 );
13072 assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
13073 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
13074 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$6\r\nsocial\r\n");
13075 let held = f.server.memory_bytes();
13079 for i in 0..200u32 {
13080 let dst = format!("n{i}");
13081 f.run(&[b"G.EADD", b"big", b"hub", dst.as_bytes(), b"F"]);
13082 }
13083 assert!(
13084 f.server.memory_bytes() > held,
13085 "two hundred edges cost something: {held} then {}",
13086 f.server.memory_bytes()
13087 );
13088 f.run(&[b"DEL", b"big"]);
13089
13090 assert_eq!(f.run(&[b"EXPIRE", b"social", b"100"]), ":1\r\n");
13093 assert_eq!(f.run(&[b"PERSIST", b"social"]), ":1\r\n");
13094 assert_eq!(f.run(&[b"RENAME", b"social", b"net"]), "+OK\r\n");
13095 assert_eq!(f.run(&[b"MOVE", b"net", b"1"]), ":1\r\n");
13096 assert_eq!(f.run(&[b"EXISTS", b"net"]), ":0\r\n");
13097 f.run(&[b"SELECT", b"1"]);
13098 assert_eq!(f.run(&[b"G.DEG", b"net", b"ada", b"F"]), ":1\r\n");
13099
13100 assert_eq!(f.run(&[b"DEL", b"net"]), ":1\r\n");
13101 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
13102 f.run(&[b"G.NADD", b"g", b"n"]);
13103 assert_eq!(f.run(&[b"FLUSHDB"]), "+OK\r\n");
13104 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
13105 }
13106
13107 #[test]
13110 fn a_graph_cannot_be_copied_or_dumped() {
13111 let mut f = Fixture::new();
13112 f.run(&[b"G.NADD", b"social", b"ada"]);
13113 assert_eq!(
13114 f.run(&[b"COPY", b"social", b"other"]),
13115 "-ERR COPY is not supported for a graph\r\n"
13116 );
13117 assert_eq!(
13118 f.run(&[b"COPY", b"social", b"other", b"DB", b"1"]),
13119 "-ERR COPY is not supported for a graph\r\n"
13120 );
13121 assert_eq!(
13122 f.run(&[b"DUMP", b"social"]),
13123 "-ERR DUMP is not supported for a graph\r\n"
13124 );
13125 assert_eq!(f.run(&[b"EXISTS", b"social", b"other"]), ":1\r\n");
13127 }
13128
13129 #[test]
13132 fn a_graph_and_a_string_are_the_wrong_type_for_each_other() {
13133 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13134 let mut f = Fixture::new();
13135 f.run(&[b"G.NADD", b"social", b"ada"]);
13136 assert_eq!(f.run(&[b"GET", b"social"]), wrong);
13137 assert_eq!(f.run(&[b"LPUSH", b"social", b"x"]), wrong);
13138 assert_eq!(f.run(&[b"SADD", b"social", b"x"]), wrong);
13139
13140 f.run(&[b"SET", b"str", b"v"]);
13141 for cmd in [
13142 vec![b"G.NADD".as_ref(), b"str", b"n"],
13143 vec![b"G.NGET".as_ref(), b"str", b"n"],
13144 vec![b"G.NDEL".as_ref(), b"str", b"n"],
13145 vec![b"G.EADD".as_ref(), b"str", b"a", b"b", b"F"],
13146 vec![b"G.EDEL".as_ref(), b"str", b"a", b"b", b"F"],
13147 vec![b"G.OUT".as_ref(), b"str", b"a", b"F"],
13148 vec![b"G.IN".as_ref(), b"str", b"a", b"F"],
13149 vec![b"G.DEG".as_ref(), b"str", b"a", b"F"],
13150 vec![b"G.NEIGH".as_ref(), b"str", b"a", b"F"],
13151 vec![b"G.PATH".as_ref(), b"str", b"a", b"b"],
13152 ] {
13153 assert_eq!(f.run(&cmd), wrong, "{:?}", cmd[0]);
13154 }
13155 }
13156
13157 #[test]
13160 fn a_graph_goes_when_its_last_node_does() {
13161 let mut f = Fixture::new();
13162 f.run(&[
13163 b"G.EADD", b"social", b"ada", b"grace", b"F", b"since", b"1843",
13164 ]);
13165 assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":1\r\n");
13166 assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "$-1\r\n");
13168 assert_eq!(
13169 f.run(&[b"G.DEG", b"social", b"grace", b"F", b"IN"]),
13170 ":0\r\n"
13171 );
13172 assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":0\r\n");
13173 assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
13174
13175 assert_eq!(f.run(&[b"G.NDEL", b"social", b"grace"]), ":1\r\n");
13176 assert_eq!(f.run(&[b"EXISTS", b"social"]), ":0\r\n");
13177 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
13178 assert_eq!(f.run(&[b"G.NDEL", b"nokey", b"ada"]), ":0\r\n");
13179
13180 f.run(&[b"G.NADD", b"social", b"first"]);
13183 f.run(&[b"G.NADD", b"social", b"second"]);
13184 f.run(&[b"G.NDEL", b"social", b"first"]);
13185 f.run(&[b"G.EADD", b"social", b"third", b"second", b"F"]);
13186 assert_eq!(
13187 f.run(&[b"G.OUT", b"social", b"third", b"F"]),
13188 "*2\r\n$1\r\n0\r\n*1\r\n$6\r\nsecond\r\n"
13189 );
13190 }
13191
13192 #[test]
13197 fn a_json_path_answers_a_set_and_a_legacy_path_answers_a_value() {
13198 let mut f = Fixture::new();
13199 let doc = br#"{"a":1,"b":{"c":true}}"#;
13200 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$", doc]), "+OK\r\n");
13201 assert_eq!(
13204 f.run(&[b"JSON.GET", b"doc"]),
13205 bulk(r#"{"a":1,"b":{"c":true}}"#)
13206 );
13207 assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.a"]), bulk("[1]"));
13208 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("1"));
13209 assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$..c"]), bulk("[true]"));
13210 assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.nope"]), bulk("[]"));
13213 assert_eq!(
13214 f.run(&[b"JSON.GET", b"doc", b".nope"]),
13215 "-ERR Path does not exist\r\n"
13216 );
13217 assert_eq!(f.run(&[b"JSON.GET", b"nokey"]), "$-1\r\n");
13218 assert_eq!(f.run(&[b"TYPE", b"doc"]), "+ReJSON-RL\r\n");
13221 assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":1\r\n");
13222 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"doc"]), bulk("raw"));
13223 assert_eq!(f.run(&[b"DEL", b"doc"]), ":1\r\n");
13224 assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
13225 }
13226
13227 #[test]
13233 fn the_two_json_errors_that_carry_no_prefix() {
13234 let mut f = Fixture::new();
13235 f.run(&[b"SET", b"plain", b"x"]);
13236 let wrong = "-Existing key has wrong Redis type\r\n";
13237 assert_eq!(f.run(&[b"JSON.GET", b"plain"]), wrong);
13238 assert_eq!(f.run(&[b"JSON.SET", b"plain", b"$", b"1"]), wrong);
13239 assert_eq!(f.run(&[b"JSON.DEL", b"plain"]), wrong);
13240 assert_eq!(f.run(&[b"JSON.TYPE", b"plain"]), wrong);
13241 assert_eq!(f.run(&[b"JSON.CLEAR", b"plain"]), wrong);
13242
13243 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"z":1},"b":{"z":2}}"#]);
13244 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.*.z", b"9"]), "+OK\r\n");
13248 assert_eq!(
13249 f.run(&[b"JSON.GET", b"doc"]),
13250 bulk(r#"{"a":{"z":9},"b":{"z":9}}"#)
13251 );
13252 assert_eq!(
13253 f.run(&[b"JSON.SET", b"doc", b"$.*.y", b"9"]),
13254 "-Err wrong static path\r\n"
13255 );
13256 }
13257
13258 #[test]
13260 fn json_set_creates_one_field_and_refuses_to_invent_the_rest() {
13261 let mut f = Fixture::new();
13262 assert_eq!(
13264 f.run(&[b"JSON.SET", b"new", b".a", b"1"]),
13265 "-ERR new objects must be created at the root\r\n"
13266 );
13267 assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
13268 assert_eq!(
13271 f.run(&[b"JSON.SET", b"new", b".a", b"1", b"NX"]),
13272 "-ERR new objects must be created at the root\r\n"
13273 );
13274 assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"XX"]), "$-1\r\n");
13275 assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"NX"]), "+OK\r\n");
13276
13277 f.run(&[
13278 b"JSON.SET",
13279 b"doc",
13280 b"$",
13281 br#"{"o":{},"arr":[1,2],"s":"x"}"#,
13282 ]);
13283 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"1"]), "+OK\r\n");
13285 assert_eq!(
13288 f.run(&[b"JSON.SET", b"doc", b"$.nope.made", b"1"]),
13289 "$-1\r\n"
13290 );
13291 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.s.made", b"1"]), "$-1\r\n");
13292 assert_eq!(
13294 f.run(&[b"JSON.SET", b"doc", b"$.arr[5]", b"9"]),
13295 "-ERR array index out of range\r\n"
13296 );
13297 assert_eq!(
13298 f.run(&[b"JSON.SET", b"doc", b"$.arr[2]", b"9"]),
13299 "-ERR array index out of range\r\n"
13300 );
13301 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.arr[1]", b"9"]), "+OK\r\n");
13302 assert_eq!(
13305 f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"2", b"NX"]),
13306 "$-1\r\n"
13307 );
13308 assert_eq!(
13309 f.run(&[b"JSON.SET", b"doc", b"$.gone", b"2", b"XX"]),
13310 "$-1\r\n"
13311 );
13312 assert_eq!(
13313 f.run(&[b"JSON.GET", b"doc"]),
13314 bulk(r#"{"o":{"made":1},"s":"x","arr":[1,9]}"#)
13315 );
13316 assert!(
13320 f.run(&[b"JSON.SET", b"doc", b"$.s", b"nope"])
13321 .starts_with("-this is not the start of a value")
13322 );
13323 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".s"]), bulk("\"x\""));
13324 }
13325
13326 #[test]
13329 fn the_json_commands_that_do_not_answer_text() {
13330 let mut f = Fixture::new();
13331 let doc = br#"{"a":1,"t":true,"o":{"x":1},"arr":[1,2],"f":1.5,"s":"x","n":null}"#;
13332 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
13333
13334 assert_eq!(f.run(&[b"JSON.TYPE", b"doc"]), bulk("object"));
13335 assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".a"]), bulk("integer"));
13336 assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".f"]), bulk("number"));
13337 assert_eq!(
13338 f.run(&[b"JSON.TYPE", b"doc", b"$.a"]),
13339 format!("*1\r\n{}", bulk("integer"))
13340 );
13341 assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".nope"]), "$-1\r\n");
13344 assert_eq!(f.run(&[b"JSON.TYPE", b"nokey"]), "$-1\r\n");
13345
13346 assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.t"]), "*1\r\n:0\r\n");
13349 assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b".t"]), bulk("true"));
13350 assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.a"]), "*1\r\n$-1\r\n");
13353 assert_eq!(
13354 f.run(&[b"JSON.TOGGLE", b"doc", b".a"]),
13355 "-ERR Path does not exist or not a bool\r\n"
13356 );
13357 assert_eq!(
13358 f.run(&[b"JSON.TOGGLE", b"doc", b".nope"]),
13359 "-ERR Path does not exist or not a bool\r\n"
13360 );
13361 assert_eq!(
13362 f.run(&[b"JSON.TOGGLE", b"nokey", b"$.a"]),
13363 "-ERR could not perform this operation on a key that doesn't exist\r\n"
13364 );
13365
13366 assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.s"]), ":0\r\n");
13369 assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":4\r\n");
13370 assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":0\r\n");
13371 assert_eq!(
13372 f.run(&[b"JSON.GET", b"doc"]),
13373 bulk(r#"{"a":0,"f":0,"n":null,"o":{},"s":"x","t":true,"arr":[]}"#)
13374 );
13375
13376 assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.nope"]), ":0\r\n");
13379 assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.a"]), ":1\r\n");
13380 assert_eq!(f.run(&[b"JSON.FORGET", b"doc", b"$.*"]), ":6\r\n");
13385 assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":0\r\n");
13386 assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
13387 assert_eq!(f.run(&[b"JSON.DEL", b"doc"]), ":0\r\n");
13388 assert_eq!(f.run(&[b"JSON.SET", b"empty", b"$", b"{}"]), "+OK\r\n");
13389 assert_eq!(f.run(&[b"EXISTS", b"empty"]), ":1\r\n");
13390 assert_eq!(f.run(&[b"JSON.GET", b"empty"]), bulk("{}"));
13391 assert_eq!(f.run(&[b"JSON.DEL", b"nokey"]), ":0\r\n");
13392 }
13393
13394 #[test]
13401 fn json_get_lays_out_the_wrapper_it_builds() {
13402 let mut f = Fixture::new();
13403 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[1,{"c":2}]}"#]);
13404
13405 assert_eq!(
13406 f.run(&[b"JSON.GET", b"doc", b"$.a", b"$.b"]),
13407 bulk(r#"{"$.a":[1],"$.b":[[1,{"c":2}]]}"#)
13408 );
13409 assert_eq!(
13411 f.run(&[b"JSON.GET", b"doc", b".a", b".b"]),
13412 bulk(r#"{".a":1,".b":[1,{"c":2}]}"#)
13413 );
13414 let fmt: &[&[u8]] = &[b"INDENT", b" ", b"NEWLINE", b"\n", b"SPACE", b" "];
13415 let mut one = vec![b"JSON.GET".as_slice(), b"doc"];
13416 one.extend_from_slice(fmt);
13417 one.push(b"$.b");
13418 assert_eq!(
13419 f.run(&one),
13420 bulk("[\n [\n 1,\n {\n \"c\": 2\n }\n ]\n]")
13421 );
13422 let mut two = vec![b"JSON.GET".as_slice(), b"doc"];
13423 two.extend_from_slice(fmt);
13424 two.push(b"$.a");
13425 two.push(b"$.nope");
13426 assert_eq!(
13427 f.run(&two),
13428 bulk("{\n \"$.a\": [\n 1\n ],\n \"$.nope\": []\n}")
13429 );
13430 let mut root = vec![b"JSON.GET".as_slice(), b"doc", b"SPACE", b" "];
13433 root.push(b".a");
13434 assert_eq!(f.run(&root), bulk("1"));
13435 }
13436
13437 #[test]
13440 fn json_mget_answers_once_per_key_whatever_is_under_them() {
13441 let mut f = Fixture::new();
13442 f.run(&[b"JSON.SET", b"one", b"$", br#"{"a":1}"#]);
13443 f.run(&[b"JSON.SET", b"two", b"$", br#"{"a":2}"#]);
13444 f.run(&[b"SET", b"plain", b"x"]);
13445 assert_eq!(
13446 f.run(&[b"JSON.MGET", b"one", b"two", b"$.a"]),
13447 format!("*2\r\n{}{}", bulk("[1]"), bulk("[2]"))
13448 );
13449 assert_eq!(
13452 f.run(&[b"JSON.MGET", b"one", b"nokey", b"plain", b".a"]),
13453 format!("*3\r\n{}$-1\r\n$-1\r\n", bulk("1"))
13454 );
13455 assert_eq!(f.run(&[b"JSON.MGET", b"one", b".nope"]), "*1\r\n$-1\r\n");
13458 }
13459
13460 #[test]
13468 fn the_json_commands_that_answer_a_size_disagree_about_every_failure() {
13469 let mut f = Fixture::new();
13470 let doc = br#"{"a":[1,2,3],"o":{"x":1,"y":2},"s":"hello","n":7}"#;
13471 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
13472
13473 assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b".a"]), ":3\r\n");
13474 assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b"$.a"]), "*1\r\n:3\r\n");
13475 assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".o"]), ":2\r\n");
13476 assert_eq!(f.run(&[b"JSON.STRLEN", b"doc", b".s"]), ":5\r\n");
13477 assert_eq!(
13478 f.run(&[b"JSON.OBJKEYS", b"doc", b".o"]),
13479 format!("*2\r\n{}{}", bulk("x"), bulk("y"))
13480 );
13481 assert_eq!(
13484 f.run(&[b"JSON.ARRLEN", b"doc", b"$.*"]),
13485 "*4\r\n:3\r\n$-1\r\n$-1\r\n$-1\r\n"
13486 );
13487
13488 assert_eq!(
13491 f.run(&[b"JSON.ARRLEN", b"doc", b".nope"]),
13492 "-ERR Path does not exist\r\n"
13493 );
13494 assert_eq!(
13495 f.run(&[b"JSON.STRLEN", b"doc", b".nope"]),
13496 "-ERR Path does not exist\r\n"
13497 );
13498 assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".nope"]), "$-1\r\n");
13499 assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b".nope"]), "$-1\r\n");
13502 assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b"$.nope"]), "*0\r\n");
13505
13506 assert_eq!(
13510 f.run(&[b"JSON.ARRLEN", b"doc", b".n"]),
13511 "-ERR Path does not exist or not an array\r\n"
13512 );
13513 assert_eq!(
13514 f.run(&[b"JSON.OBJKEYS", b"doc", b".n"]),
13515 "-ERR Path does not exist or not an object\r\n"
13516 );
13517 assert_eq!(
13518 f.run(&[b"JSON.OBJLEN", b"doc", b".n"]),
13519 "-WRONGTYPE wrong type of path value - expected object\r\n"
13520 );
13521 assert_eq!(
13522 f.run(&[b"JSON.STRLEN", b"doc", b".n"]),
13523 "-WRONGTYPE wrong type of path value - expected string\r\n"
13524 );
13525
13526 assert_eq!(f.run(&[b"JSON.ARRLEN", b"nokey", b".a"]), "$-1\r\n");
13529 assert_eq!(f.run(&[b"JSON.OBJLEN", b"nokey", b".a"]), "$-1\r\n");
13530 assert_eq!(f.run(&[b"JSON.STRLEN", b"nokey", b".a"]), "$-1\r\n");
13531 assert_eq!(f.run(&[b"JSON.OBJKEYS", b"nokey", b".a"]), "$-1\r\n");
13532 assert_eq!(
13533 f.run(&[b"JSON.ARRLEN", b"nokey", b"$.a"]),
13534 "-ERR could not perform this operation on a key that doesn't exist\r\n"
13535 );
13536 assert_eq!(
13538 f.run(&[b"JSON.OBJLEN", b"nokey", b"$.a"]),
13539 "-ERR Path does not exist or not an object\r\n"
13540 );
13541 }
13542
13543 #[test]
13549 fn the_json_array_writes_agree_on_the_errors_and_not_on_the_indexes() {
13550 let mut f = Fixture::new();
13551 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3],"n":7}"#]);
13552
13553 assert_eq!(f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"4"]), ":4\r\n");
13554 assert_eq!(
13555 f.run(&[b"JSON.ARRAPPEND", b"doc", b"$.a", b"5", b"6"]),
13556 "*1\r\n:6\r\n"
13557 );
13558 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[1,2,3,4,5,6]"));
13559
13560 assert_eq!(
13563 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-1", b"0"]),
13564 ":7\r\n"
13565 );
13566 assert_eq!(
13567 f.run(&[b"JSON.GET", b"doc", b".a"]),
13568 bulk("[1,2,3,4,5,0,6]")
13569 );
13570 assert_eq!(
13571 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"7", b"9"]),
13572 ":8\r\n"
13573 );
13574 assert_eq!(
13576 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"9", b"9"]),
13577 "-ERR index out of bounds\r\n"
13578 );
13579 assert_eq!(
13580 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-9", b"9"]),
13581 "-ERR index out of bounds\r\n"
13582 );
13583
13584 f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3,4,5]"]);
13587 assert_eq!(
13588 f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"1", b"3"]),
13589 ":3\r\n"
13590 );
13591 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[2,3,4]"));
13592 assert_eq!(
13593 f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"-2", b"99"]),
13594 ":2\r\n"
13595 );
13596 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[3,4]"));
13597 assert_eq!(
13598 f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"9", b"9"]),
13599 ":0\r\n"
13600 );
13601 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
13602
13603 f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3]"]);
13606 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), bulk("3"));
13607 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"0"]), bulk("1"));
13608 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"99"]), bulk("2"));
13609 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), "$-1\r\n");
13610
13611 for call in [
13614 &[&b"JSON.ARRAPPEND"[..], b"doc", b"PATH", b"1"][..],
13615 &[&b"JSON.ARRTRIM"[..], b"doc", b"PATH", b"1", b"1"][..],
13616 &[&b"JSON.ARRPOP"[..], b"doc", b"PATH", b"1"][..],
13617 &[&b"JSON.ARRINSERT"[..], b"doc", b"PATH", b"0", b"1"][..],
13618 ] {
13619 for path in [&b".n"[..], &b".nope"[..]] {
13620 let args: Vec<&[u8]> = call
13621 .iter()
13622 .map(|a| if *a == b"PATH" { path } else { *a })
13623 .collect();
13624 assert_eq!(
13625 f.run(&args),
13626 "-ERR Path does not exist or not an array\r\n",
13627 "{} {}",
13628 String::from_utf8_lossy(call[0]),
13629 String::from_utf8_lossy(path)
13630 );
13631 }
13632 }
13633
13634 assert_eq!(
13637 f.run(&[b"JSON.ARRAPPEND", b"nokey", b".a", b"1"]),
13638 "-ERR could not perform this operation on a key that doesn't exist\r\n"
13639 );
13640 assert_eq!(
13641 f.run(&[b"JSON.ARRPOP", b"nokey", b"$.a"]),
13642 "-ERR could not perform this operation on a key that doesn't exist\r\n"
13643 );
13644
13645 assert!(
13650 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"nope"])
13651 .starts_with("-this is not the start of a value")
13652 );
13653 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
13654 }
13655
13656 #[test]
13664 fn json_arrinsert_is_all_or_nothing_across_the_matches() {
13665 let mut f = Fixture::new();
13666 let doc = br#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#;
13667 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
13668 assert_eq!(
13669 f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"-2", b"0"]),
13670 "-ERR index out of bounds\r\n"
13671 );
13672 assert_eq!(
13673 f.run(&[b"JSON.GET", b"doc"]),
13674 bulk(r#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#)
13675 );
13676 assert_eq!(
13678 f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"0", b"0"]),
13679 "*3\r\n:4\r\n:3\r\n:2\r\n"
13680 );
13681 assert_eq!(
13682 f.run(&[b"JSON.GET", b"doc"]),
13683 bulk(r#"{"a":[0,1,2,3],"n":{"a":[0,9,8],"in":{"a":[0,1]}}}"#)
13684 );
13685 }
13686
13687 #[test]
13695 fn json_arrindex_has_an_exclusive_stop_and_a_start_that_cannot_run_off_the_end() {
13696 let mut f = Fixture::new();
13697 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3,1],"n":7}"#]);
13698
13699 assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"2"]), ":1\r\n");
13700 assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"9"]), ":-1\r\n");
13701 assert_eq!(
13702 f.run(&[b"JSON.ARRINDEX", b"doc", b"$.a", b"2"]),
13703 "*1\r\n:1\r\n"
13704 );
13705
13706 assert_eq!(
13709 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"0"]),
13710 ":3\r\n"
13711 );
13712 assert_eq!(
13715 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"3"]),
13716 ":-1\r\n"
13717 );
13718
13719 for start in [&b"4"[..], &b"5"[..], &b"-1"[..]] {
13722 assert_eq!(
13723 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", start]),
13724 ":3\r\n",
13725 "{}",
13726 String::from_utf8_lossy(start)
13727 );
13728 }
13729 assert_eq!(
13730 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"-100"]),
13731 ":0\r\n"
13732 );
13733 f.run(&[b"JSON.SET", b"doc", b"$.a", b"[]"]);
13736 assert_eq!(
13737 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1"]),
13738 ":-1\r\n"
13739 );
13740
13741 f.run(&[b"JSON.SET", b"doc", b"$.a", br#"[{"k":1},[1,2],"s"]"#]);
13745 assert_eq!(
13746 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", br#"{"k":1}"#]),
13747 ":0\r\n"
13748 );
13749 assert_eq!(
13750 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[1,2]"]),
13751 ":1\r\n"
13752 );
13753 assert_eq!(
13754 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[2,1]"]),
13755 ":-1\r\n"
13756 );
13757
13758 assert_eq!(
13762 f.run(&[b"JSON.ARRINDEX", b"doc", b".nope", b"1"]),
13763 "-ERR Path does not exist\r\n"
13764 );
13765 assert_eq!(
13766 f.run(&[b"JSON.ARRINDEX", b"doc", b".n", b"1"]),
13767 "-WRONGTYPE wrong type of path value - expected array\r\n"
13768 );
13769 assert_eq!(
13770 f.run(&[b"JSON.ARRINDEX", b"nokey", b".a", b"1"]),
13771 "-ERR Path does not exist\r\n"
13772 );
13773 assert_eq!(
13774 f.run(&[b"JSON.ARRINDEX", b"nokey", b"$.a", b"1"]),
13775 "-ERR Path does not exist\r\n"
13776 );
13777 }
13778
13779 #[test]
13782 fn the_json_number_family_answers_json_text_and_keeps_its_integers() {
13783 let mut f = Fixture::new();
13784 let doc = br#"{"i":7,"f":1.5,"neg":-2,"s":"ab"}"#;
13785 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
13786
13787 assert_eq!(
13790 f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2"]),
13791 bulk("9").as_str()
13792 );
13793 assert_eq!(
13795 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.i", b"2"]),
13796 bulk("[11]").as_str()
13797 );
13798 assert_eq!(
13801 f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2.0"]),
13802 bulk("13.0").as_str()
13803 );
13804 assert_eq!(
13805 f.run(&[b"JSON.TYPE", b"doc", b".i"]),
13806 bulk("number").as_str()
13807 );
13808 assert_eq!(
13809 f.run(&[b"JSON.NUMMULTBY", b"doc", b".f", b"2"]),
13810 bulk("3.0").as_str()
13811 );
13812 assert_eq!(
13813 f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"3"]),
13814 bulk("-8").as_str()
13815 );
13816 f.run(&[b"JSON.SET", b"doc", b"$.f", b"1.5"]);
13819 assert_eq!(
13820 f.run(&[b"JSON.NUMPOWBY", b"doc", b".f", b"0.5"]),
13821 bulk("1.224744871391589").as_str()
13822 );
13823 assert_eq!(
13824 f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"0.5"]),
13825 "-ERR result is not a number\r\n"
13826 );
13827 f.run(&[b"JSON.SET", b"doc", b"$.big", b"9223372036854775807"]);
13831 assert_eq!(
13832 f.run(&[b"JSON.NUMINCRBY", b"doc", b".big", b"1"]),
13833 "-ERR numeric overflow\r\n"
13834 );
13835 f.run(&[b"JSON.SET", b"doc", b"$.p", b"2"]);
13836 assert_eq!(
13837 f.run(&[b"JSON.NUMPOWBY", b"doc", b".p", b"-1"]),
13838 "-ERR numeric overflow\r\n"
13839 );
13840 f.run(&[b"JSON.SET", b"doc", b"$.huge", b"1e308"]);
13842 assert_eq!(
13843 f.run(&[b"JSON.NUMMULTBY", b"doc", b".huge", b"1e10"]),
13844 "-ERR result is not a number\r\n"
13845 );
13846
13847 assert_eq!(
13851 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"1"]),
13852 bulk("[null]").as_str()
13853 );
13854 assert_eq!(
13855 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.nope", b"1"]),
13856 bulk("[]").as_str()
13857 );
13858 assert_eq!(
13859 f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", b"1"]),
13860 "-ERR Path does not exist or does not contains a number\r\n"
13861 );
13862 assert_eq!(
13863 f.run(&[b"JSON.NUMINCRBY", b"doc", b".nope", b"1"]),
13864 "-ERR Path does not exist or does not contains a number\r\n"
13865 );
13866 assert_eq!(
13869 f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"true"]),
13870 "-bad input number\r\n"
13871 );
13872 assert_eq!(
13873 f.run(&[b"JSON.NUMINCRBY", b"nokey", b".i", b"1"]),
13874 "-ERR could not perform this operation on a key that doesn't exist\r\n"
13875 );
13876 assert_eq!(
13877 f.run(&[b"JSON.NUMINCRBY", b"nokey", b"$.i", b"1"]),
13878 "-ERR could not perform this operation on a key that doesn't exist\r\n"
13879 );
13880 }
13881
13882 #[test]
13885 fn json_strappend_reads_its_shape_off_the_argument_count() {
13886 let mut f = Fixture::new();
13887 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"s":"ab","n":1}"#]);
13888
13889 assert_eq!(
13890 f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""c""#]),
13891 ":3\r\n"
13892 );
13893 assert_eq!(
13894 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", br#""d""#]),
13895 "*1\r\n:4\r\n"
13896 );
13897 assert_eq!(
13900 f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""\u00e9""#]),
13901 ":6\r\n"
13902 );
13903 f.run(&[b"JSON.SET", b"str", b"$", br#""ab""#]);
13906 assert_eq!(f.run(&[b"JSON.STRAPPEND", b"str", br#""c""#]), ":3\r\n");
13907 assert_eq!(f.run(&[b"JSON.GET", b"str"]), bulk("\"abc\"").as_str());
13908
13909 assert_eq!(
13913 f.run(&[b"JSON.STRAPPEND", b"doc", b".s", b"5"]),
13914 "-WRONGTYPE wrong type of path value - expected string\r\n"
13915 );
13916 assert_eq!(
13917 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", br#""c""#]),
13918 "*1\r\n$-1\r\n"
13919 );
13920 assert_eq!(
13921 f.run(&[b"JSON.STRAPPEND", b"doc", b".n", br#""c""#]),
13922 "-ERR Path does not exist or not a string\r\n"
13923 );
13924 assert_eq!(
13925 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.nope", br#""c""#]),
13926 "*0\r\n"
13927 );
13928 assert_eq!(
13929 f.run(&[b"JSON.STRAPPEND", b"nokey", br#""c""#]),
13930 "-ERR could not perform this operation on a key that doesn't exist\r\n"
13931 );
13932 }
13933
13934 #[test]
13937 fn a_legacy_wildcard_write_touches_every_match_and_answers_only_one() {
13938 let mut f = Fixture::new();
13939 let three = br#"{"a":[[7],[7,7],[7,7,7]]}"#;
13942
13943 f.run(&[b"JSON.SET", b"doc", b"$", three]);
13944 assert_eq!(
13945 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
13946 ":4\r\n"
13947 );
13948 f.run(&[b"JSON.SET", b"doc", b"$", three]);
13949 assert_eq!(
13950 f.run(&[b"JSON.ARRINSERT", b"doc", b".a[*]", b"0", b"9"]),
13951 ":2\r\n"
13952 );
13953 f.run(&[b"JSON.SET", b"doc", b"$", three]);
13954 assert_eq!(
13955 f.run(&[b"JSON.ARRTRIM", b"doc", b".a[*]", b"0", b"1"]),
13956 ":1\r\n"
13957 );
13958 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[1,2,3],[4,5,6]]}"#]);
13959 assert_eq!(
13960 f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]", b"0"]),
13961 bulk("1").as_str()
13962 );
13963 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3]}"#]);
13964 assert_eq!(
13965 f.run(&[b"JSON.NUMINCRBY", b"doc", b".a[*]", b"10"]),
13966 bulk("13").as_str()
13967 );
13968 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["p","qq","rrr"]}"#]);
13969 assert_eq!(
13970 f.run(&[b"JSON.STRAPPEND", b"doc", b".a[*]", br#""z""#]),
13971 ":4\r\n"
13972 );
13973 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[true,false,true]}"#]);
13974 assert_eq!(
13975 f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
13976 bulk("false").as_str()
13977 );
13978 assert_eq!(
13981 f.run(&[b"JSON.GET", b"doc", b".a"]),
13982 bulk("[false,true,false]").as_str()
13983 );
13984
13985 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x",[1],[1,2]]}"#]);
13988 assert_eq!(
13989 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
13990 ":3\r\n"
13991 );
13992 assert_eq!(
13993 f.run(&[b"JSON.GET", b"doc", b".a"]),
13994 bulk(r#"["x",[1,9],[1,2,9]]"#).as_str()
13995 );
13996 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x","y"]}"#]);
13999 assert_eq!(
14000 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
14001 "-ERR Path does not exist or not an array\r\n"
14002 );
14003 assert_eq!(
14004 f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
14005 "-ERR Path does not exist or not a bool\r\n"
14006 );
14007 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[],[2,3]]}"#]);
14011 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]"]), "$-1\r\n");
14012 assert_eq!(
14013 f.run(&[b"JSON.GET", b"doc", b".a"]),
14014 bulk("[[],[2]]").as_str()
14015 );
14016 }
14017
14018 #[test]
14021 fn a_write_reaches_a_match_that_sits_inside_another_match() {
14022 let mut f = Fixture::new();
14023 let nested = br#"{"a":[{"a":[7]},{"a":[7,7]}]}"#;
14024
14025 f.run(&[b"JSON.SET", b"doc", b"$", nested]);
14026 assert_eq!(
14027 f.run(&[b"JSON.ARRAPPEND", b"doc", b"$..a", b"9"]),
14028 "*3\r\n:3\r\n:2\r\n:3\r\n"
14029 );
14030 assert_eq!(
14031 f.run(&[b"JSON.GET", b"doc", b"$"]),
14032 bulk(r#"[{"a":[{"a":[7,9]},{"a":[7,7,9]},9]}]"#).as_str()
14033 );
14034
14035 f.run(&[b"JSON.SET", b"doc", b"$", nested]);
14038 assert_eq!(
14039 f.run(&[b"JSON.ARRTRIM", b"doc", b"$..a", b"0", b"0"]),
14040 "*3\r\n:1\r\n:1\r\n:1\r\n"
14041 );
14042 assert_eq!(
14043 f.run(&[b"JSON.GET", b"doc", b"$"]),
14044 bulk(r#"[{"a":[{"a":[7]}]}]"#).as_str()
14045 );
14046
14047 f.run(&[b"JSON.SET", b"doc", b"$", nested]);
14050 assert_eq!(
14051 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$..a[0]", b"1"]),
14052 bulk("[null,8,8]").as_str()
14053 );
14054 }
14055
14056 #[test]
14059 fn a_bad_operand_is_not_the_answer_when_the_path_found_nothing_to_use_it_on() {
14060 let mut f = Fixture::new();
14061 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"n":7,"s":"t"}"#]);
14062
14063 assert_eq!(
14066 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", br#""x""#]),
14067 bulk("[null]").as_str()
14068 );
14069 assert_eq!(
14070 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"notjson"]),
14071 bulk("[null]").as_str()
14072 );
14073 assert_eq!(
14074 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.missing", b"notjson"]),
14075 bulk("[]").as_str()
14076 );
14077 assert_eq!(
14078 f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", br#""x""#]),
14079 "-ERR Path does not exist or does not contains a number\r\n"
14080 );
14081 assert_eq!(
14083 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.n", br#""x""#]),
14084 "-bad input number\r\n"
14085 );
14086
14087 assert_eq!(
14089 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", b"1"]),
14090 "*1\r\n$-1\r\n"
14091 );
14092 assert_eq!(
14093 f.run(&[b"JSON.STRAPPEND", b"doc", b".n", b"1"]),
14094 "-ERR Path does not exist or not a string\r\n"
14095 );
14096 assert_eq!(
14097 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", b"1"]),
14098 "-WRONGTYPE wrong type of path value - expected string\r\n"
14099 );
14100
14101 assert_eq!(
14103 f.run(&[b"JSON.NUMINCRBY", b"nope", b"$.a", br#""x""#]),
14104 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14105 );
14106 assert_eq!(
14107 f.run(&[b"JSON.STRAPPEND", b"nope", b"$.a", b"1"]),
14108 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14109 );
14110 }
14111
14112 #[test]
14115 fn a_merge_patch_adds_replaces_and_deletes_in_one_write() {
14116 let mut f = Fixture::new();
14117
14118 assert_eq!(
14122 f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"x":null,"y":1}"#]),
14123 "+OK\r\n"
14124 );
14125 assert_eq!(
14126 f.run(&[b"JSON.GET", b"doc", b"$"]),
14127 bulk(r#"[{"x":null,"y":1}]"#).as_str()
14128 );
14129
14130 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1,"c":2},"d":3}"#]);
14133 assert_eq!(
14134 f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"a":{"b":null,"e":4}}"#]),
14135 "+OK\r\n"
14136 );
14137 assert_eq!(
14138 f.run(&[b"JSON.GET", b"doc", b"$"]),
14139 bulk(r#"[{"a":{"c":2,"e":4},"d":3}]"#).as_str()
14140 );
14141
14142 assert_eq!(f.run(&[b"JSON.MERGE", b"doc", b"$.a", b"[1,2]"]), "+OK\r\n");
14144 assert_eq!(
14145 f.run(&[b"JSON.GET", b"doc", b"$"]),
14146 bulk(r#"[{"a":[1,2],"d":3}]"#).as_str()
14147 );
14148
14149 assert_eq!(
14153 f.run(&[b"JSON.MERGE", b"doc", b"$.d", br#"{"p":null,"q":9}"#]),
14154 "+OK\r\n"
14155 );
14156 assert_eq!(
14157 f.run(&[b"JSON.GET", b"doc", b"$"]),
14158 bulk(r#"[{"a":[1,2],"d":{"q":9}}]"#).as_str()
14159 );
14160
14161 assert_eq!(
14165 f.run(&[b"JSON.MERGE", b"doc", b"$.new", br#"{"z":null}"#]),
14166 "+OK\r\n"
14167 );
14168 assert_eq!(
14169 f.run(&[b"JSON.GET", b"doc", b"$.new"]),
14170 bulk(r#"[{"z":null}]"#).as_str()
14171 );
14172 assert_eq!(
14173 f.run(&[b"JSON.MERGE", b"doc", b"$.no.deep", b"1"]),
14174 "$-1\r\n"
14175 );
14176 assert_eq!(
14177 f.run(&[b"JSON.MERGE", b"doc", b"$.no.*", b"1"]),
14178 "-Err wrong static path\r\n"
14179 );
14180
14181 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"n":1},"b":{"n":2}}"#]);
14183 assert_eq!(
14184 f.run(&[b"JSON.MERGE", b"doc", b"$.*", br#"{"m":0}"#]),
14185 "+OK\r\n"
14186 );
14187 assert_eq!(
14188 f.run(&[b"JSON.GET", b"doc", b"$"]),
14189 bulk(r#"[{"a":{"m":0,"n":1},"b":{"m":0,"n":2}}]"#).as_str()
14190 );
14191
14192 assert_eq!(
14194 f.run(&[b"JSON.MERGE", b"doc", b"$", b"{}", b"more"]),
14195 "-ERR syntax error\r\n"
14196 );
14197 assert_eq!(
14198 f.run(&[b"JSON.MERGE", b"gone", b"$.a", b"1"]),
14199 "-ERR new objects must be created at the root\r\n"
14200 );
14201 f.run(&[b"SET", b"str", b"x"]);
14202 assert_eq!(
14203 f.run(&[b"JSON.MERGE", b"str", b"$", b"1"]),
14204 "-Existing key has wrong Redis type\r\n"
14205 );
14206 }
14207
14208 #[test]
14211 fn a_merge_down_a_descent_keeps_what_the_inner_match_did() {
14212 let mut f = Fixture::new();
14213 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
14214 assert_eq!(
14215 f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"m":1}"#]),
14216 "+OK\r\n"
14217 );
14218 assert_eq!(
14222 f.run(&[b"JSON.GET", b"doc", b"$"]),
14223 bulk(r#"[{"a":{"b":{"m":1},"m":1},"c":{"m":1}}]"#).as_str()
14224 );
14225
14226 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
14229 assert_eq!(
14230 f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"a":null}"#]),
14231 "+OK\r\n"
14232 );
14233 assert_eq!(
14234 f.run(&[b"JSON.GET", b"doc", b"$"]),
14235 bulk(r#"[{"a":{"b":{}},"c":{}}]"#).as_str()
14236 );
14237 }
14238
14239 #[test]
14242 fn a_filter_path_reads_and_writes_the_members_it_keeps() {
14243 let mut f = Fixture::new();
14244 let doc = br#"{"book":[{"t":"a","p":8},{"t":"b","p":13},{"t":"c","p":9}],"cap":10}"#;
14245 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14246
14247 assert_eq!(
14248 f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < 10)].t"]),
14249 bulk(r#"["a","c"]"#).as_str()
14250 );
14251 assert_eq!(
14254 f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < $.cap)].t"]),
14255 bulk(r#"["a","c"]"#).as_str()
14256 );
14257 assert_eq!(
14259 f.run(&[b"JSON.GET", b"doc", b"book[?(@.p < 10)].t"]),
14260 bulk(r#""a""#).as_str()
14261 );
14262 assert_eq!(
14263 f.run(&[b"JSON.TYPE", b"doc", b"$.book[?(@.p > 10)]"]),
14264 "*1\r\n$6\r\nobject\r\n"
14265 );
14266
14267 assert_eq!(
14271 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.book[?(@.p < 10)].p", b"1"]),
14272 bulk("[9,10]").as_str()
14273 );
14274 assert_eq!(
14275 f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].t", br#""B""#]),
14276 "+OK\r\n"
14277 );
14278 assert_eq!(
14279 f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].n", b"1"]),
14280 "-Err wrong static path\r\n"
14281 );
14282 assert_eq!(
14283 f.run(&[b"JSON.DEL", b"doc", b"$.book[?(@.p > 9)]"]),
14284 ":2\r\n"
14285 );
14286 assert_eq!(
14287 f.run(&[b"JSON.GET", b"doc", b"$"]),
14288 bulk(r#"[{"cap":10,"book":[{"p":9,"t":"a"}]}]"#).as_str()
14289 );
14290
14291 assert!(
14294 f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p <)]"])
14295 .starts_with("-ERR")
14296 );
14297 assert!(
14298 f.run(&[b"JSON.GET", b"nokey", b"$.book[?(@.p <)]"])
14299 .starts_with("-ERR")
14300 );
14301 }
14302
14303 #[test]
14306 fn a_filter_takes_the_membership_operators_and_the_methods_too() {
14307 let mut f = Fixture::new();
14308 let doc = br#"{"box":[{"t":"a","n":[1,2],"g":"x"},{"t":"b","n":[9],"g":"y"}]}"#;
14309 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14310
14311 for (path, want) in [
14312 (&b"$.box[?(@.g in [\"x\"])].t"[..], r#"["a"]"#),
14313 (b"$.box[?(@.g nin [\"x\"])].t", r#"["b"]"#),
14314 (b"$.box[?(@.n anyof [2,3])].t", r#"["a"]"#),
14315 (b"$.box[?(@.n subsetof [1,2,3])].t", r#"["a"]"#),
14316 (b"$.box[?(@.n size 2)].t", r#"["a"]"#),
14317 (b"$.box[?(@.n empty false)].t", r#"["a","b"]"#),
14318 (b"$.box[?(@.n.length() == 1)].t", r#"["b"]"#),
14319 (b"$.box[?(@.n.sum() > 5)].t", r#"["b"]"#),
14320 (b"$.box[?(@.n[0] + 1 == 2)].t", r#"["a"]"#),
14321 (b"$.box[?(@~ size 3)].t", r#"["a","b"]"#),
14322 (b"$.box[?(@.n~)].t", "[]"),
14323 (b"$.box[?(@.n sizeof 2)].t", r#"["a"]"#),
14324 (b"$.box[?(-@.n[0] == -9)].t", r#"["b"]"#),
14325 (b"$.box[?(1 in @.n)].t", r#"["a"]"#),
14326 (b"$.box[?(\"g\" in @~)].t", r#"["a","b"]"#),
14327 ] {
14328 assert_eq!(f.run(&[b"JSON.GET", b"doc", path]), bulk(want).as_str());
14329 }
14330
14331 assert_eq!(
14334 f.run(&[b"JSON.SET", b"doc", b"$.box[?(@.n size 1)].g", br#""z""#]),
14335 "+OK\r\n"
14336 );
14337 assert_eq!(
14338 f.run(&[b"JSON.GET", b"doc", b"$.box[?(@.g == \"z\")].t"]),
14339 bulk(r#"["b"]"#).as_str()
14340 );
14341 }
14342
14343 #[test]
14346 fn a_merge_onto_a_number_inside_an_array_is_a_merge_and_not_an_error() {
14347 let mut f = Fixture::new();
14348 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2]}"#]);
14349 assert_eq!(
14350 f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
14351 "+OK\r\n"
14352 );
14353 assert_eq!(
14354 f.run(&[b"JSON.GET", b"doc", b"$"]),
14355 bulk(r#"[{"a":[{"x":1},2]}]"#).as_str()
14356 );
14357 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,"s"]}"#]);
14360 assert_eq!(
14361 f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
14362 "+OK\r\n"
14363 );
14364 assert_eq!(
14365 f.run(&[b"JSON.GET", b"doc", b"$"]),
14366 bulk(r#"[{"a":[{"x":1},"s"]}]"#).as_str()
14367 );
14368 }
14369
14370 #[test]
14373 fn an_mset_writes_every_triple_it_can_and_checks_the_rest_up_front() {
14374 let mut f = Fixture::new();
14375 assert_eq!(
14376 f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b", b"$", b"2"]),
14377 "+OK\r\n"
14378 );
14379 assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[1]").as_str());
14380 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[2]").as_str());
14381
14382 assert_eq!(
14384 f.run(&[b"JSON.MSET", b"a", b"$", b"3", b"a", b"$", b"4"]),
14385 "+OK\r\n"
14386 );
14387 assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[4]").as_str());
14388
14389 f.run(&[b"JSON.SET", b"a", b"$", br#"{"n":1}"#]);
14394 assert_eq!(
14395 f.run(&[b"JSON.MSET", b"a", b"$.no.deep", b"9", b"b", b"$", b"5"]),
14396 "$-1\r\n"
14397 );
14398 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[5]").as_str());
14399 assert_eq!(
14400 f.run(&[b"JSON.MSET", b"b", b"$", b"6", b"a", b"$.no.deep", b"9"]),
14401 "$-1\r\n"
14402 );
14403 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
14404
14405 f.run(&[b"SET", b"str", b"x"]);
14410 assert_eq!(
14411 f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"b", b"$", b"notjson"]),
14412 "-this is not the start of a value, at byte 0 of the JSON text\r\n"
14413 );
14414 assert_eq!(
14415 f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"str", b"$", b"1"]),
14416 "-Existing key has wrong Redis type\r\n"
14417 );
14418 assert_eq!(
14419 f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"gone", b"$.x", b"1"]),
14420 "-ERR new objects must be created at the root\r\n"
14421 );
14422 assert_eq!(f.run(&[b"JSON.GET", b"a", b"$.n"]), bulk("[1]").as_str());
14423
14424 assert_eq!(
14429 f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$.no.*", b"9"]),
14430 "-Err wrong static path\r\n"
14431 );
14432 assert_eq!(
14433 f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$[0]", b"9"]),
14434 "-ERR array index out of range\r\n"
14435 );
14436 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
14437
14438 f.run(&[b"JSON.SET", b"c", b"$", br#"{"n":1}"#]);
14442 assert_eq!(
14443 f.run(&[b"JSON.MSET", b"c", b"$", br#"{"n":2}"#, b"c", b"$.n", b"3"]),
14444 "+OK\r\n"
14445 );
14446 assert_eq!(
14447 f.run(&[b"JSON.GET", b"c", b"$"]),
14448 bulk(r#"[{"n":3}]"#).as_str()
14449 );
14450
14451 assert_eq!(
14454 f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b"]),
14455 "-ERR wrong number of arguments for 'json.mset' command\r\n"
14456 );
14457 }
14458
14459 #[test]
14462 fn json_resp_answers_the_document_as_resp_types() {
14463 let mut f = Fixture::new();
14464 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[2,"c"]}"#]);
14465 assert_eq!(
14466 f.run(&[b"JSON.RESP", b"doc"]),
14467 "*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"
14468 );
14469 assert_eq!(
14471 f.run(&[b"JSON.RESP", b"doc", b"$.b"]),
14472 "*1\r\n*3\r\n+[\r\n:2\r\n$1\r\nc\r\n"
14473 );
14474
14475 f.run(&[
14476 b"JSON.SET",
14477 b"doc",
14478 b"$",
14479 br#"{"f":2.5,"t":true,"z":null,"e":[],"o":{}}"#,
14480 ]);
14481 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".e"]), "*1\r\n+[\r\n");
14482 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".o"]), "*1\r\n+{\r\n");
14483 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".f"]), bulk("2.5").as_str());
14486 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".t"]), "+true\r\n");
14487 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".z"]), "$-1\r\n");
14488
14489 assert_eq!(
14492 f.run(&[b"JSON.RESP", b"doc", b".nope"]),
14493 "-ERR Path does not exist\r\n"
14494 );
14495 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b"$.nope"]), "*0\r\n");
14496 assert_eq!(f.run(&[b"JSON.RESP", b"gone"]), "$-1\r\n");
14497 assert_eq!(f.run(&[b"JSON.RESP", b"gone", b"$"]), "$-1\r\n");
14498 }
14499
14500 #[test]
14504 fn json_debug_answers_a_byte_count_and_its_own_help() {
14505 let mut f = Fixture::new();
14506 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2],"s":"hello"}"#]);
14507 let one = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".s"]);
14508 assert!(one.starts_with(':'), "{one}");
14509 assert_eq!(
14510 f.run(&[b"JSON.DEBUG", b"memory", b"doc", b"$.s"]),
14511 format!("*1\r\n{one}")
14512 );
14513 let whole = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc"]);
14514 assert!(whole.starts_with(':') && whole.len() > one.len(), "{whole}");
14515
14516 assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone"]), ":0\r\n");
14520 assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone", b"$"]), "*0\r\n");
14521 assert_eq!(
14522 f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".nope"]),
14523 "-ERR Path does not exist\r\n"
14524 );
14525 assert_eq!(
14526 f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b"$.nope"]),
14527 "*0\r\n"
14528 );
14529
14530 assert_eq!(
14531 f.run(&[b"JSON.DEBUG", b"HELP"]),
14532 "*2\r\n$42\r\nMEMORY <key> [path] - reports memory usage\r\n\
14533 $34\r\nHELP - this message\r\n"
14534 );
14535 assert_eq!(
14536 f.run(&[b"JSON.DEBUG", b"NOPE"]),
14537 "-ERR unknown subcommand - try `JSON.DEBUG HELP`\r\n"
14538 );
14539 assert_eq!(
14540 f.run(&[b"JSON.DEBUG", b"MEMORY"]),
14541 "-ERR wrong number of arguments for 'json.debug' command\r\n"
14542 );
14543 }
14544
14545 #[test]
14550 fn the_first_vadd_decides_how_wide_the_set_is() {
14551 let mut f = Fixture::new();
14552 assert_eq!(
14553 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]),
14554 ":1\r\n"
14555 );
14556 assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
14557 assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
14558 assert_eq!(
14561 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"east"]),
14562 ":0\r\n"
14563 );
14564 assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
14565 assert_eq!(
14568 f.run(&[b"VADD", b"v", b"VALUES", b"3", b"1", b"0", b"0", b"up"]),
14569 "-ERR Vector dimension mismatch - got 3 but set has 2\r\n"
14570 );
14571 assert_eq!(
14574 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"0", b"nowhere"]),
14575 ":1\r\n"
14576 );
14577 assert_eq!(
14578 f.run(&[b"VEMB", b"v", b"nowhere"]),
14579 "*2\r\n$1\r\n0\r\n$1\r\n0\r\n"
14580 );
14581 assert_eq!(
14585 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"other", b"BIN"]),
14586 "-ERR asked quantization mismatch with existing vector set\r\n"
14587 );
14588 assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
14591 assert_eq!(f.run(&[b"VDIM", b"fresh"]), "-ERR key does not exist\r\n");
14592 assert_eq!(f.run(&[b"VCARD", b"fresh"]), ":0\r\n");
14593 }
14594
14595 #[test]
14598 fn vemb_gives_back_the_vector_and_vsim_gives_back_a_similarity() {
14599 let mut f = Fixture::new();
14600 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"4", b"a"]);
14601 assert_eq!(
14607 f.run(&[b"VEMB", b"v", b"a"]),
14608 "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
14609 );
14610 f.run(&[b"VADD", b"n", b"VALUES", b"2", b"3", b"4", b"a", b"NOQUANT"]);
14612 assert_eq!(
14613 f.run(&[b"VEMB", b"n", b"a"]),
14614 "*2\r\n$1\r\n3\r\n$1\r\n4\r\n"
14615 );
14616 f.run(&[b"VADD", b"b", b"VALUES", b"2", b"3", b"-4", b"a", b"BIN"]);
14619 assert_eq!(
14620 f.run(&[b"VEMB", b"b", b"a"]),
14621 "*2\r\n$1\r\n1\r\n$2\r\n-1\r\n"
14622 );
14623 assert_eq!(f.run(&[b"VEMB", b"v", b"nobody"]), "*-1\r\n");
14624 assert_eq!(f.run(&[b"VEMB", b"nokey", b"a"]), "*-1\r\n");
14625
14626 let mut f = Fixture::new();
14630 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"0", b"a"]);
14631 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"-1", b"0", b"opposite"]);
14632 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"7", b"across"]);
14633 assert_eq!(
14634 f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"2", b"0", b"WITHSCORES"]),
14635 "*6\r\n$1\r\na\r\n$1\r\n1\r\n$6\r\nacross\r\n$3\r\n0.5\r\n\
14636 $8\r\nopposite\r\n$1\r\n0\r\n"
14637 );
14638 assert_eq!(
14641 f.run(&[b"VSIM", b"v", b"ELE", b"a"]),
14642 "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
14643 );
14644 assert_eq!(f.run(&[b"VSIM", b"v", b"ELE", b"nobody"]), "*0\r\n");
14647 assert_eq!(f.run(&[b"VSIM", b"nokey", b"ELE", b"a"]), "*0\r\n");
14648 assert_eq!(
14651 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1"]),
14652 "*1\r\n$6\r\nacross\r\n"
14653 );
14654 assert_eq!(
14655 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"TRUTH"]),
14656 "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
14657 );
14658 assert_eq!(
14662 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1", b"EF", b"500"]),
14663 "*1\r\n$6\r\nacross\r\n"
14664 );
14665
14666 let mut g = Fixture::new();
14669 g.run(&[b"HELLO", b"3"]);
14670 g.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
14671 assert_eq!(
14672 g.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHSCORES"]),
14673 "%1\r\n$4\r\neast\r\n,1\r\n"
14674 );
14675 }
14676
14677 #[test]
14679 fn an_attribute_is_bytes_and_an_empty_one_takes_it_off() {
14680 let mut f = Fixture::new();
14681 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
14682 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
14683 assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]), ":1\r\n");
14684 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$7\r\n{\"k\":1}\r\n");
14685 assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"not json"]), ":1\r\n");
14688 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$8\r\nnot json\r\n");
14689 assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b""]), ":1\r\n");
14691 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
14692 assert_eq!(f.run(&[b"VSETATTR", b"v", b"nobody", b"{}"]), ":0\r\n");
14695 assert_eq!(f.run(&[b"VSETATTR", b"nokey", b"east", b"{}"]), ":0\r\n");
14696 assert_eq!(f.run(&[b"EXISTS", b"nokey"]), ":0\r\n");
14697 assert_eq!(f.run(&[b"VGETATTR", b"v", b"nobody"]), "$-1\r\n");
14700 assert_eq!(f.run(&[b"VISMEMBER", b"v", b"east"]), ":1\r\n");
14701 assert_eq!(f.run(&[b"VISMEMBER", b"v", b"nobody"]), ":0\r\n");
14702 assert_eq!(f.run(&[b"VISMEMBER", b"nokey", b"east"]), ":0\r\n");
14703
14704 f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
14706 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
14707 assert_eq!(
14708 f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHATTRIBS"]),
14709 "*4\r\n$4\r\neast\r\n$7\r\n{\"k\":1}\r\n$5\r\nnorth\r\n$-1\r\n"
14710 );
14711 }
14712
14713 #[test]
14716 fn vrem_takes_the_attribute_with_it() {
14717 let mut f = Fixture::new();
14718 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
14719 f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
14720 assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":1\r\n");
14721 assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":0\r\n");
14722 assert_eq!(f.run(&[b"VREM", b"nokey", b"east"]), ":0\r\n");
14723 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
14726
14727 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
14730 f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
14731 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
14732 f.run(&[b"VREM", b"v", b"east"]);
14733 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"between"]);
14734 assert_eq!(f.run(&[b"VGETATTR", b"v", b"between"]), "$-1\r\n");
14735 }
14736
14737 #[test]
14740 fn vinfo_says_partition_first() {
14741 let mut f = Fixture::new();
14742 f.run(&[
14743 b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east", b"M", b"32",
14744 ]);
14745 f.run(&[b"VSETATTR", b"v", b"east", b"{}"]);
14746 let info = f.run(&[b"VINFO", b"v"]);
14747 assert!(info.starts_with("*24\r\n$10\r\nindex-type\r\n$9\r\npartition\r\n"));
14748 assert!(info.contains("$6\r\nhnsw-m\r\n:32\r\n"), "{info}");
14751 assert!(info.contains("$10\r\nvector-dim\r\n:2\r\n"), "{info}");
14752 assert!(info.contains("$16\r\nattributes-count\r\n:1\r\n"), "{info}");
14753 assert!(
14756 info.contains("$10\r\nquant-type\r\n$4\r\nint8\r\n"),
14757 "{info}"
14758 );
14759 let mut f = Fixture::new();
14760 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north", b"BIN"]);
14761 assert!(
14762 f.run(&[b"VINFO", b"v"])
14763 .contains("$10\r\nquant-type\r\n$3\r\nbin\r\n")
14764 );
14765 assert_eq!(f.run(&[b"VINFO", b"nokey"]), "$-1\r\n");
14766 }
14767
14768 fn named() -> Fixture {
14770 let mut f = Fixture::new();
14771 for (i, name) in ["alpha", "beta", "gamma", "delta", "epsilon"]
14772 .iter()
14773 .enumerate()
14774 {
14775 let x = (i + 1).to_string();
14776 f.run(&[
14777 b"VADD",
14778 b"r",
14779 b"VALUES",
14780 b"2",
14781 x.as_bytes(),
14782 b"1",
14783 name.as_bytes(),
14784 ]);
14785 }
14786 f
14787 }
14788
14789 #[test]
14792 fn vrange_walks_the_names_and_not_the_vectors() {
14793 let mut f = named();
14794 assert_eq!(
14795 f.run(&[b"VRANGE", b"r", b"-", b"+"]),
14796 "*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"
14797 );
14798 assert_eq!(
14799 f.run(&[b"VRANGE", b"r", b"[a", b"[d"]),
14800 "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n",
14801 "the high end is a name and not a prefix, so delta is past it"
14802 );
14803 assert_eq!(
14804 f.run(&[b"VRANGE", b"r", b"(alpha", b"(gamma"]),
14805 "*3\r\n$4\r\nbeta\r\n$5\r\ndelta\r\n$7\r\nepsilon\r\n"
14806 );
14807 assert_eq!(
14808 f.run(&[b"VRANGE", b"r", b"[beta", b"[beta"]),
14809 "*1\r\n$4\r\nbeta\r\n"
14810 );
14811 assert_eq!(f.run(&[b"VRANGE", b"r", b"[z", b"+"]), "*0\r\n");
14812 f.run(&[b"VADD", b"r", b"VALUES", b"2", b"1", b"1", b"Beta"]);
14815 assert_eq!(
14816 f.run(&[b"VRANGE", b"r", b"-", b"[beta"]),
14817 "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
14818 );
14819 assert_eq!(f.run(&[b"VRANGE", b"nokey", b"-", b"+"]), "*0\r\n");
14820 }
14821
14822 #[test]
14825 fn a_vrange_count_of_zero_asks_for_nothing() {
14826 let mut f = named();
14827 assert_eq!(
14828 f.run(&[b"VRANGE", b"r", b"-", b"+", b"2"]),
14829 "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
14830 );
14831 assert_eq!(f.run(&[b"VRANGE", b"r", b"-", b"+", b"0"]), "*0\r\n");
14832 assert!(
14833 f.run(&[b"VRANGE", b"r", b"-", b"+", b"-1"])
14834 .starts_with("*5\r\n"),
14835 "a negative count is no limit at all"
14836 );
14837 }
14838
14839 #[test]
14842 fn vrange_says_which_end_it_could_not_read() {
14843 let mut f = named();
14844 assert_eq!(
14845 f.run(&[b"VRANGE", b"r", b"x", b"y"]),
14846 "-ERR invalid start range format\r\n"
14847 );
14848 assert_eq!(
14849 f.run(&[b"VRANGE", b"r", b"+", b"x"]),
14850 "-ERR invalid end range format\r\n",
14851 "the high end is spelled wrong, which is worth saying before the \
14852 low end being on the wrong side"
14853 );
14854 assert_eq!(
14855 f.run(&[b"VRANGE", b"r", b"+", b"-"]),
14856 "-ERR '-' can only be used as first argument, '+' only as second\r\n"
14857 );
14858 assert_eq!(
14861 f.run(&[b"VRANGE", b"r", b"[", b"+"]),
14862 "-ERR invalid start range format\r\n"
14863 );
14864 assert_eq!(
14865 f.run(&[b"VRANGE", b"r", b"x", b"+", b"z"]),
14866 "-ERR invalid COUNT value\r\n"
14867 );
14868 assert_eq!(
14869 f.run(&[b"VRANGE", b"r", b"-", b"+", b"2", b"extra"]),
14870 "-ERR wrong number of arguments for 'VRANGE' command\r\n"
14871 );
14872 f.run(&[b"SET", b"s", b"x"]);
14873 assert!(
14874 f.run(&[b"VRANGE", b"s", b"-", b"+"])
14875 .starts_with("-WRONGTYPE")
14876 );
14877 }
14878
14879 #[test]
14882 fn reduce_is_refused_and_not_ignored() {
14883 let mut f = Fixture::new();
14884 let reduce = f.run(&[
14885 b"VADD", b"v", b"REDUCE", b"1", b"VALUES", b"2", b"1", b"0", b"east",
14886 ]);
14887 assert!(
14888 reduce.starts_with("-ERR REDUCE is not supported."),
14889 "{reduce}"
14890 );
14891 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
14892 }
14893
14894 #[test]
14897 fn vsim_filter_reads_the_attributes() {
14898 let mut f = Fixture::new();
14899 for (name, x, y, attr) in [
14900 ("a", "1", "0", r#"{"lang":"en","year":1999}"#),
14901 ("b", "9", "1", r#"{"lang":"fr","year":2005}"#),
14902 ("c", "8", "2", r#"{"lang":"en","year":1970}"#),
14903 ("d", "7", "3", r#"{"lang":"en","year":2020}"#),
14904 ] {
14905 f.run(&[
14906 b"VADD",
14907 b"v",
14908 b"VALUES",
14909 b"2",
14910 x.as_bytes(),
14911 y.as_bytes(),
14912 name.as_bytes(),
14913 b"SETATTR",
14914 attr.as_bytes(),
14915 ]);
14916 }
14917 assert_eq!(
14920 f.run(&[
14921 b"VSIM",
14922 b"v",
14923 b"VALUES",
14924 b"2",
14925 b"9",
14926 b"1",
14927 b"COUNT",
14928 b"2",
14929 b"FILTER",
14930 b".lang == \"en\"",
14931 ]),
14932 "*2\r\n$1\r\na\r\n$1\r\nc\r\n"
14933 );
14934 assert_eq!(
14937 f.run(&[
14938 b"VSIM",
14939 b"v",
14940 b"VALUES",
14941 b"2",
14942 b"9",
14943 b"1",
14944 b"FILTER",
14945 b".lang == 'en' and .year > 1980",
14946 ]),
14947 "*2\r\n$1\r\na\r\n$1\r\nd\r\n"
14948 );
14949 assert_eq!(
14951 f.run(&[
14952 b"VSIM",
14953 b"v",
14954 b"VALUES",
14955 b"2",
14956 b"9",
14957 b"1",
14958 b"FILTER",
14959 b".lang in ['fr', 'de']",
14960 ]),
14961 "*1\r\n$1\r\nb\r\n"
14962 );
14963 assert_eq!(
14964 f.run(&[
14965 b"VSIM",
14966 b"v",
14967 b"VALUES",
14968 b"2",
14969 b"9",
14970 b"1",
14971 b"FILTER",
14972 b".rating > 3"
14973 ]),
14974 "*0\r\n"
14975 );
14976 assert_eq!(
14979 f.run(&[
14980 b"VSIM",
14981 b"v",
14982 b"VALUES",
14983 b"2",
14984 b"9",
14985 b"1",
14986 b"TRUTH",
14987 b"FILTER",
14988 b".year < 1980",
14989 ]),
14990 "*1\r\n$1\r\nc\r\n"
14991 );
14992 f.run(&[b"VSETATTR", b"v", b"b", r#"{"lang":"en"}"#.as_bytes()]);
14995 assert_eq!(
14996 f.run(&[
14997 b"VSIM",
14998 b"v",
14999 b"VALUES",
15000 b"2",
15001 b"9",
15002 b"1",
15003 b"COUNT",
15004 b"1",
15005 b"FILTER",
15006 b".lang == \"en\"",
15007 ]),
15008 "*1\r\n$1\r\nb\r\n"
15009 );
15010 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"9", b"2", b"b"]);
15013 assert_eq!(
15014 f.run(&[
15015 b"VSIM",
15016 b"v",
15017 b"VALUES",
15018 b"2",
15019 b"9",
15020 b"1",
15021 b"COUNT",
15022 b"1",
15023 b"FILTER",
15024 b".lang == \"en\"",
15025 ]),
15026 "*1\r\n$1\r\nb\r\n"
15027 );
15028
15029 let bad = f.run(&[b"VSIM", b"nokey", b"ELE", b"e", b"FILTER", b".k =="]);
15032 assert_eq!(bad, "-ERR invalid FILTER expression\r\n");
15033 assert_eq!(
15034 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"FILTER", b"junk"]),
15035 "-ERR invalid FILTER expression\r\n"
15036 );
15037 assert_eq!(
15040 f.run(&[
15041 b"VSIM",
15042 b"v",
15043 b"VALUES",
15044 b"2",
15045 b"9",
15046 b"1",
15047 b"COUNT",
15048 b"1",
15049 b"FILTER-EF",
15050 b"500",
15051 b"FILTER",
15052 b".lang == 'en'",
15053 ]),
15054 "*1\r\n$1\r\nb\r\n"
15055 );
15056 assert_eq!(
15057 f.run(&[
15058 b"VSIM",
15059 b"v",
15060 b"VALUES",
15061 b"2",
15062 b"9",
15063 b"1",
15064 b"COUNT",
15065 b"1",
15066 b"FILTER-EF",
15067 b"0"
15068 ]),
15069 "*1\r\n$1\r\nb\r\n"
15070 );
15071 assert_eq!(
15072 f.run(&[
15073 b"VSIM",
15074 b"v",
15075 b"VALUES",
15076 b"2",
15077 b"9",
15078 b"1",
15079 b"FILTER-EF",
15080 b"lots"
15081 ]),
15082 "-ERR EF must be a positive integer\r\n"
15083 );
15084 }
15085
15086 #[test]
15089 fn the_keyspace_sees_a_vector_set_key_like_any_other() {
15090 let mut f = Fixture::new();
15091 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15092 assert_eq!(f.run(&[b"TYPE", b"v"]), "+vectorset\r\n");
15093 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":1\r\n");
15094 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"v"]), "$6\r\nrabitq\r\n");
15095 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\nv\r\n");
15096 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
15097 assert_eq!(f.run(&[b"EXPIRE", b"v", b"100"]), ":1\r\n");
15098 assert_eq!(f.run(&[b"TTL", b"v"]), ":100\r\n");
15099 assert_eq!(f.run(&[b"PERSIST", b"v"]), ":1\r\n");
15100 assert_eq!(f.run(&[b"DEL", b"v"]), ":1\r\n");
15101 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
15102
15103 f.run(&[b"SET", b"s", b"1"]);
15105 assert_eq!(
15106 f.run(&[b"VADD", b"s", b"VALUES", b"2", b"1", b"0", b"east"]),
15107 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15108 );
15109 assert_eq!(
15110 f.run(&[b"VCARD", b"s"]),
15111 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15112 );
15113 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15114 assert_eq!(
15115 f.run(&[b"GET", b"v"]),
15116 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15117 );
15118 f.run(&[b"G.NADD", b"social", b"ada"]);
15122 assert_eq!(
15123 f.run(&[b"VCARD", b"social"]),
15124 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15125 );
15126 assert_eq!(
15127 f.run(&[b"G.NGET", b"v", b"ada"]),
15128 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15129 );
15130 }
15131
15132 #[test]
15135 fn vrandmember_has_the_two_shapes_srandmember_has() {
15136 let mut f = Fixture::new();
15137 for (i, name) in [&b"a"[..], b"b", b"c"].iter().enumerate() {
15138 let x = (i + 1).to_string();
15139 f.run(&[b"VADD", b"v", b"VALUES", b"2", x.as_bytes(), b"1", name]);
15140 }
15141 let one = f.run(&[b"VRANDMEMBER", b"v"]);
15143 assert!(one.starts_with("$1\r\n"), "{one}");
15144 let mut all = f.run(&[b"VRANDMEMBER", b"v", b"9"]);
15146 assert!(all.starts_with("*3\r\n"), "{all}");
15147 for name in ["a", "b", "c"] {
15148 assert!(all.contains(name), "{all} is missing {name}");
15149 }
15150 all = f.run(&[b"VRANDMEMBER", b"v", b"2"]);
15151 assert!(all.starts_with("*2\r\n"), "{all}");
15152 let many = f.run(&[b"VRANDMEMBER", b"v", b"-5"]);
15154 assert!(many.starts_with("*5\r\n"), "{many}");
15155 assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey"]), "$-1\r\n");
15157 assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
15158 }
15159
15160 #[test]
15163 fn vlinks_reports_one_layer_of_partition_neighbours() {
15164 let mut f = Fixture::new();
15165 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15166 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
15167 assert_eq!(
15170 f.run(&[b"VLINKS", b"v", b"east"]),
15171 "*1\r\n*1\r\n$5\r\nnorth\r\n"
15172 );
15173 assert_eq!(
15174 f.run(&[b"VLINKS", b"v", b"east", b"WITHSCORES"]),
15175 "*1\r\n*2\r\n$5\r\nnorth\r\n$3\r\n0.5\r\n"
15176 );
15177 assert_eq!(f.run(&[b"VLINKS", b"v", b"nobody"]), "*-1\r\n");
15178 assert_eq!(f.run(&[b"VLINKS", b"nokey", b"east"]), "*-1\r\n");
15179 }
15180
15181 #[test]
15184 fn fp32_and_values_are_the_same_vector() {
15185 let mut f = Fixture::new();
15186 let mut blob = Vec::new();
15187 for x in [3.0f32, 4.0] {
15188 blob.extend_from_slice(&x.to_le_bytes());
15189 }
15190 assert_eq!(f.run(&[b"VADD", b"v", b"FP32", &blob, b"a"]), ":1\r\n");
15191 assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
15192 assert_eq!(
15193 f.run(&[b"VEMB", b"v", b"a"]),
15194 "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
15195 );
15196 assert_eq!(
15202 f.run(&[b"VEMB", b"v", b"a", b"RAW"]),
15203 "*4\r\n+int8\r\n$2\r\n_\x7f\r\n$1\r\n5\r\n$17\r\n0.800000011920929\r\n"
15204 );
15205 assert_eq!(
15207 f.run(&[b"VADD", b"w", b"FP32", b"abc", b"a"]),
15208 "-ERR invalid vector specification\r\n"
15209 );
15210 assert_eq!(
15212 f.run(&[b"VADD", b"w", b"VALUES", b"4", b"1", b"0", b"a"]),
15213 "-ERR syntax error\r\n"
15214 );
15215 assert_eq!(f.run(&[b"EXISTS", b"w"]), ":0\r\n");
15216 }
15217
15218 #[test]
15223 fn bf_add_makes_the_filter_and_says_whether_it_was_new() {
15224 let mut f = Fixture::new();
15225 assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":1\r\n");
15226 assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":0\r\n");
15227 assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"hello"]), ":1\r\n");
15228 assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"never"]), ":0\r\n");
15229 assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":1\r\n");
15230 assert_eq!(
15233 f.run(&[b"BF.INFO", b"b"]),
15234 "*10\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
15235 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
15236 +Expansion rate\r\n:2\r\n"
15237 );
15238 assert_eq!(f.run(&[b"TYPE", b"b"]), "+MBbloom--\r\n");
15239 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"b"]), "$3\r\nraw\r\n");
15240 assert_eq!(f.run(&[b"BF.CARD", b"gone"]), ":0\r\n");
15243 assert_eq!(f.run(&[b"BF.INFO", b"gone"]), "-ERR not found\r\n");
15244 }
15245
15246 #[test]
15252 fn a_wrong_type_is_a_miss_to_the_two_that_only_read_bits() {
15253 let mut f = Fixture::new();
15254 f.run(&[b"SET", b"s", b"text"]);
15255 assert_eq!(f.run(&[b"BF.EXISTS", b"s", b"x"]), ":0\r\n");
15256 assert_eq!(f.run(&[b"BF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
15257 for cmd in [
15258 vec![&b"BF.ADD"[..], b"s", b"x"],
15259 vec![&b"BF.MADD"[..], b"s", b"x"],
15260 vec![&b"BF.CARD"[..], b"s"],
15261 vec![&b"BF.INFO"[..], b"s"],
15262 vec![&b"BF.DEBUG"[..], b"s"],
15263 vec![&b"BF.SCANDUMP"[..], b"s", b"0"],
15264 ] {
15265 let name = String::from_utf8_lossy(cmd[0]).into_owned();
15266 assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
15267 }
15268 assert_eq!(
15271 f.run(&[b"BF.RESERVE", b"s", b"abc", b"10"]),
15272 "-ERR bad error rate\r\n"
15273 );
15274 assert!(
15275 f.run(&[b"BF.RESERVE", b"s", b"0.01", b"10"])
15276 .starts_with("-WRONGTYPE")
15277 );
15278 }
15279
15280 #[test]
15283 fn a_full_filter_grows_a_link_and_a_fixed_one_says_no() {
15284 let mut f = Fixture::new();
15285 assert_eq!(f.run(&[b"BF.RESERVE", b"g", b"0.01", b"10"]), "+OK\r\n");
15286 for i in 0..10u32 {
15287 assert_eq!(
15288 f.run(&[b"BF.ADD", b"g", i.to_string().as_bytes()]),
15289 ":1\r\n"
15290 );
15291 }
15292 assert_eq!(f.run(&[b"BF.INFO", b"g", b"FILTERS"]), "*1\r\n:1\r\n");
15293 assert_eq!(f.run(&[b"BF.ADD", b"g", b"11"]), ":1\r\n");
15294 assert_eq!(f.run(&[b"BF.INFO", b"g", b"filters"]), "*1\r\n:2\r\n");
15295 assert_eq!(f.run(&[b"BF.INFO", b"g", b"CAPACITY"]), "*1\r\n:30\r\n");
15298 assert_eq!(
15299 f.run(&[b"BF.DEBUG", b"g"]),
15300 "*3\r\n$7\r\nsize:11\r\n\
15301 $71\r\nbytes:16 bits:128 hashes:8 hashwidth:64 capacity:10 size:10 ratio:0.005\r\n\
15302 $71\r\nbytes:32 bits:256 hashes:9 hashwidth:64 capacity:20 size:1 ratio:0.0025\r\n"
15303 );
15304
15305 assert_eq!(
15307 f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]),
15308 "+OK\r\n"
15309 );
15310 assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":1\r\n");
15311 assert_eq!(f.run(&[b"BF.ADD", b"n", b"b"]), ":1\r\n");
15312 assert_eq!(
15313 f.run(&[b"BF.ADD", b"n", b"c"]),
15314 "-ERR non scaling filter is full\r\n"
15315 );
15316 assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":0\r\n");
15319 assert_eq!(f.run(&[b"BF.INFO", b"n", b"EXPANSION"]), "*1\r\n$-1\r\n");
15322 f.run(&[b"BF.RESERVE", b"z", b"0.01", b"2", b"EXPANSION", b"0"]);
15323 assert_eq!(f.run(&[b"BF.INFO", b"z", b"EXPANSION"]), "*1\r\n$-1\r\n");
15324 assert_eq!(
15327 f.run(&[
15328 b"BF.RESERVE",
15329 b"q",
15330 b"0.01",
15331 b"2",
15332 b"NONSCALING",
15333 b"EXPANSION",
15334 b"2"
15335 ]),
15336 "-Nonscaling filters cannot expand\r\n"
15337 );
15338 }
15339
15340 #[test]
15343 fn madd_truncates_its_reply_at_the_item_that_did_not_fit() {
15344 let mut f = Fixture::new();
15345 f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]);
15346 assert_eq!(
15347 f.run(&[b"BF.MADD", b"n", b"a", b"b", b"c", b"d"]),
15348 "*3\r\n:1\r\n:1\r\n-ERR non scaling filter is full\r\n"
15349 );
15350 assert_eq!(
15351 f.run(&[b"BF.MEXISTS", b"n", b"a", b"c"]),
15352 "*2\r\n:1\r\n:0\r\n"
15353 );
15354 }
15355
15356 #[test]
15359 fn insert_is_a_reserve_and_a_madd_with_different_errors() {
15360 let mut f = Fixture::new();
15361 assert_eq!(
15362 f.run(&[
15363 b"BF.INSERT",
15364 b"i",
15365 b"CAPACITY",
15366 b"50",
15367 b"ERROR",
15368 b"0.001",
15369 b"ITEMS",
15370 b"a",
15371 b"b"
15372 ]),
15373 "*2\r\n:1\r\n:1\r\n"
15374 );
15375 assert_eq!(f.run(&[b"BF.INFO", b"i", b"CAPACITY"]), "*1\r\n:50\r\n");
15376 assert_eq!(
15378 f.run(&[b"BF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
15379 "-ERR not found\r\n"
15380 );
15381 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
15382 assert_eq!(
15385 f.run(&[b"BF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
15386 "-Bad capacity\r\n"
15387 );
15388 assert_eq!(
15389 f.run(&[b"BF.INSERT", b"i", b"ERROR", b"2", b"ITEMS", b"a"]),
15390 "-Bad error rate\r\n"
15391 );
15392 assert_eq!(
15393 f.run(&[b"BF.INSERT", b"i", b"EXPANSION", b"99999", b"ITEMS", b"a"]),
15394 "-Bad expansion\r\n"
15395 );
15396 assert_eq!(
15401 f.run(&[b"BF.INSERT", b"ns", b"NOSUCHTHING", b"ITEMS", b"a"]),
15402 "*1\r\n:1\r\n"
15403 );
15404 assert_eq!(f.run(&[b"BF.INFO", b"ns", b"EXPANSION"]), "*1\r\n$-1\r\n");
15405 assert_eq!(
15409 f.run(&[b"BF.INSERT", b"e1", b"E", b"4", b"ITEMS", b"a"]),
15410 "*1\r\n:1\r\n"
15411 );
15412 assert_eq!(f.run(&[b"BF.INFO", b"e1", b"EXPANSION"]), "*1\r\n:4\r\n");
15413 assert_eq!(
15414 f.run(&[b"BF.INSERT", b"e2", b"ER", b"0.5", b"ITEMS", b"a"]),
15415 "*1\r\n:1\r\n"
15416 );
15417 assert_eq!(
15418 f.run(&[b"BF.INSERT", b"gone", b"NOC", b"ITEMS", b"a"]),
15419 "-ERR not found\r\n"
15420 );
15421 assert_eq!(
15423 f.run(&[b"BF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
15424 "-Unknown argument received\r\n"
15425 );
15426 assert_eq!(
15428 f.run(&[b"BF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
15429 "*1\r\n:1\r\n"
15430 );
15431 assert!(
15433 f.run(&[b"BF.INSERT", b"i", b"ITEMS"])
15434 .contains("wrong number of arguments")
15435 );
15436 }
15437
15438 #[test]
15441 fn a_dump_replays_into_a_filter_that_answers_the_same() {
15442 let mut f = Fixture::new();
15443 f.run(&[b"BF.RESERVE", b"src", b"0.01", b"10"]);
15444 for i in 0..25u32 {
15445 f.run(&[b"BF.ADD", b"src", i.to_string().as_bytes()]);
15446 }
15447 assert_eq!(f.run(&[b"BF.INFO", b"src", b"FILTERS"]), "*1\r\n:2\r\n");
15448
15449 let mut iter = b"0".to_vec();
15452 let mut chunks = 0;
15453 loop {
15454 let raw = f.raw(&[b"BF.SCANDUMP", b"src", &iter]);
15455 let text = String::from_utf8_lossy(&raw).into_owned();
15456 let next = text
15457 .split("\r\n")
15458 .nth(1)
15459 .and_then(|n| n.strip_prefix(':'))
15460 .expect("a two element reply of an iterator and a chunk")
15461 .to_owned();
15462 let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
15463 let data = &body[body
15464 .windows(2)
15465 .position(|w| w == b"\r\n")
15466 .expect("a length line")
15467 + 2..body.len() - 2];
15468 if next == "0" {
15469 assert!(data.is_empty(), "the last chunk is empty");
15470 break;
15471 }
15472 let put = f.run(&[b"BF.LOADCHUNK", b"dst", next.as_bytes(), data]);
15473 assert_eq!(put, "+OK\r\n", "loading chunk {chunks}");
15474 iter = next.into_bytes();
15475 chunks += 1;
15476 }
15477 assert_eq!(chunks, 3, "a header and one chunk per link");
15478
15479 assert_eq!(f.run(&[b"BF.INFO", b"dst"]), f.run(&[b"BF.INFO", b"src"]));
15480 assert_eq!(f.run(&[b"BF.DEBUG", b"dst"]), f.run(&[b"BF.DEBUG", b"src"]));
15481 for i in 0..25u32 {
15482 assert_eq!(
15483 f.run(&[b"BF.EXISTS", b"dst", i.to_string().as_bytes()]),
15484 ":1\r\n"
15485 );
15486 }
15487
15488 assert_eq!(
15491 f.run(&[b"BF.LOADCHUNK", b"dst", b"1", b"anything"]),
15492 "-ERR received bad data\r\n"
15493 );
15494 assert_eq!(
15495 f.run(&[b"BF.LOADCHUNK", b"fresh", b"1", b"anything"]),
15496 "-ERR received bad data\r\n"
15497 );
15498 assert_eq!(
15500 f.run(&[b"BF.LOADCHUNK", b"dst", b"99999", b"x"]),
15501 "-ERR invalid offset - no link found\r\n"
15502 );
15503 assert_eq!(
15504 f.run(&[b"BF.LOADCHUNK", b"dst", b"nope", b"x"]),
15505 "-ERR Second argument must be numeric\r\n"
15506 );
15507 assert_eq!(
15510 f.run(&[b"BF.SCANDUMP", b"src", b"nope"]),
15511 "-Second argument must be numeric\r\n"
15512 );
15513 }
15514
15515 #[test]
15518 fn reserve_reads_its_numbers_the_way_string2ll_does() {
15519 let mut f = Fixture::new();
15520 for (args, want) in [
15521 (vec![&b"abc"[..], b"10"], "-ERR bad error rate\r\n"),
15522 (vec![&b"nan"[..], b"10"], "-ERR bad error rate\r\n"),
15523 (
15524 vec![&b"0"[..], b"10"],
15525 "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
15526 ),
15527 (
15528 vec![&b"1"[..], b"10"],
15529 "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
15530 ),
15531 (
15532 vec![&b"inf"[..], b"10"],
15533 "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
15534 ),
15535 (vec![&b"0.01"[..], b"+10"], "-ERR bad capacity\r\n"),
15536 (vec![&b"0.01"[..], b"1e2"], "-ERR bad capacity\r\n"),
15537 (vec![&b"0.01"[..], b"007"], "-ERR bad capacity\r\n"),
15538 (
15539 vec![&b"0.01"[..], b"0"],
15540 "-ERR capacity must be in the range [1, 1073741824]\r\n",
15541 ),
15542 (
15543 vec![&b"0.01"[..], b"1073741825"],
15544 "-ERR capacity must be in the range [1, 1073741824]\r\n",
15545 ),
15546 ] {
15547 let mut cmd = vec![&b"BF.RESERVE"[..], b"k"];
15548 cmd.extend(args.iter().copied());
15549 assert_eq!(f.run(&cmd), want, "{}", String::from_utf8_lossy(args[0]));
15550 }
15551 assert_eq!(
15552 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION"]),
15553 "-ERR no expansion\r\n"
15554 );
15555 assert_eq!(
15556 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"abc"]),
15557 "-ERR bad expansion\r\n"
15558 );
15559 assert_eq!(
15560 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"32769"]),
15561 "-ERR expansion must be in the range [0, 32768]\r\n"
15562 );
15563 assert_eq!(
15565 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"junk"]),
15566 "+OK\r\n"
15567 );
15568 assert_eq!(
15569 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10"]),
15570 "-ERR item exists\r\n"
15571 );
15572 assert_eq!(
15573 f.run(&[b"BF.INFO", b"k", b"nosuchfield"]),
15574 "-Invalid information value\r\n"
15575 );
15576 assert!(
15577 f.run(&[b"BF.INFO", b"k", b"CAPACITY", b"SIZE"])
15578 .contains("wrong number of arguments")
15579 );
15580 }
15581
15582 #[test]
15584 fn the_bloom_family_answers_in_resp3_spelling_too() {
15585 let mut f = Fixture::new();
15586 f.out.set_proto(Proto::Resp3);
15587 assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#t\r\n");
15588 assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#f\r\n");
15589 assert_eq!(f.run(&[b"BF.MADD", b"b", b"a", b"c"]), "*2\r\n#f\r\n#t\r\n");
15590 assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"a"]), "#t\r\n");
15591 assert_eq!(
15592 f.run(&[b"BF.MEXISTS", b"b", b"a", b"z"]),
15593 "*2\r\n#t\r\n#f\r\n"
15594 );
15595 assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":2\r\n");
15597 assert_eq!(
15598 f.run(&[b"BF.INFO", b"b"]),
15599 "%5\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
15600 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:2\r\n\
15601 +Expansion rate\r\n:2\r\n"
15602 );
15603 assert_eq!(
15606 f.run(&[b"BF.INFO", b"b", b"CAPACITY"]),
15607 "%1\r\n+Capacity\r\n:100\r\n"
15608 );
15609 }
15610
15611 fn cf_header(
15620 items: u64,
15621 buckets: u64,
15622 deletes: u64,
15623 filters: u64,
15624 geometry: [u16; 3],
15625 ) -> Vec<u8> {
15626 let mut out = Vec::with_capacity(38);
15627 for n in [items, buckets, deletes, filters] {
15628 out.extend_from_slice(&n.to_le_bytes());
15629 }
15630 for n in geometry {
15631 out.extend_from_slice(&n.to_le_bytes());
15632 }
15633 out
15634 }
15635
15636 #[test]
15640 fn cf_add_makes_the_filter_and_counts_the_copies() {
15641 let mut f = Fixture::new();
15642 assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
15643 assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
15644 assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":2\r\n");
15645 assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"hello"]), ":0\r\n");
15648 assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"other"]), ":1\r\n");
15649 assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"hello"]), ":1\r\n");
15650 assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"no"]), ":0\r\n");
15651 assert_eq!(
15652 f.run(&[b"CF.MEXISTS", b"d", b"hello", b"no"]),
15653 "*2\r\n:1\r\n:0\r\n"
15654 );
15655 assert_eq!(
15658 f.run(&[b"CF.INFO", b"d"]),
15659 "*16\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
15660 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:3\r\n\
15661 +Number of items deleted\r\n:0\r\n+Bucket size\r\n:2\r\n\
15662 +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
15663 );
15664 assert_eq!(
15665 f.run(&[b"CF.DEBUG", b"d"]),
15666 "$79\r\nbktsize:2 buckets:512 items:3 deletes:0 filters:1 \
15667 max_iterations:20 expansion:1\r\n"
15668 );
15669 assert_eq!(f.run(&[b"TYPE", b"d"]), "+MBbloomCF\r\n");
15670 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
15671
15672 assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
15674 assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":1\r\n");
15675 assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
15676 assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":0\r\n");
15677 assert_eq!(f.run(&[b"CF.COMPACT", b"d"]), "+OK\r\n");
15678
15679 assert_eq!(f.run(&[b"CF.INFO", b"gone"]), "-ERR not found\r\n");
15682 assert_eq!(f.run(&[b"CF.DEL", b"gone", b"x"]), "-Not found\r\n");
15683 assert_eq!(
15684 f.run(&[b"CF.COMPACT", b"gone"]),
15685 "-Cuckoo filter was not found\r\n"
15686 );
15687 assert_eq!(f.run(&[b"CF.EXISTS", b"gone", b"x"]), ":0\r\n");
15688 assert!(
15692 f.run(&[b"CF.COMPACT", b"a", b"b"])
15693 .contains("wrong number of arguments")
15694 );
15695 }
15696
15697 #[test]
15700 fn a_wrong_type_is_a_miss_to_the_four_that_only_read_fingerprints() {
15701 let mut f = Fixture::new();
15702 f.run(&[b"SET", b"s", b"text"]);
15703 assert_eq!(f.run(&[b"CF.EXISTS", b"s", b"x"]), ":0\r\n");
15704 assert_eq!(f.run(&[b"CF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
15705 assert_eq!(f.run(&[b"CF.COUNT", b"s", b"x"]), ":0\r\n");
15706 assert_eq!(f.run(&[b"CF.DEL", b"s", b"x"]), "-Not found\r\n");
15710 assert_eq!(
15711 f.run(&[b"CF.COMPACT", b"s"]),
15712 "-Cuckoo filter was not found\r\n"
15713 );
15714 for cmd in [
15715 vec![&b"CF.ADD"[..], b"s", b"x"],
15716 vec![&b"CF.ADDNX"[..], b"s", b"x"],
15717 vec![&b"CF.INSERT"[..], b"s", b"ITEMS", b"x"],
15718 vec![&b"CF.INSERTNX"[..], b"s", b"ITEMS", b"x"],
15719 vec![&b"CF.INFO"[..], b"s"],
15720 vec![&b"CF.DEBUG"[..], b"s"],
15721 vec![&b"CF.SCANDUMP"[..], b"s", b"0"],
15722 vec![&b"CF.LOADCHUNK"[..], b"s", b"2", b"x"],
15723 vec![&b"CF.RESERVE"[..], b"s", b"64"],
15724 ] {
15725 let name = String::from_utf8_lossy(cmd[0]).into_owned();
15726 assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
15727 }
15728 }
15729
15730 #[test]
15733 fn reserve_complains_about_its_options_in_the_order_it_looks_for_them() {
15734 let mut f = Fixture::new();
15735 assert_eq!(
15736 f.run(&[
15737 b"CF.RESERVE",
15738 b"r",
15739 b"64",
15740 b"BUCKETSIZE",
15741 b"1",
15742 b"MAXITERATIONS",
15743 b"7",
15744 b"EXPANSION",
15745 b"4"
15746 ]),
15747 "+OK\r\n"
15748 );
15749 assert_eq!(
15750 f.run(&[b"CF.DEBUG", b"r"]),
15751 "$77\r\nbktsize:1 buckets:64 items:0 deletes:0 filters:1 \
15752 max_iterations:7 expansion:4\r\n"
15753 );
15754 assert_eq!(f.run(&[b"CF.RESERVE", b"r", b"64"]), "-ERR item exists\r\n");
15755
15756 assert_eq!(f.run(&[b"CF.RESERVE", b"q", b"abc"]), "-Bad capacity\r\n");
15757 assert_eq!(
15758 f.run(&[b"CF.RESERVE", b"q", b"1"]),
15759 "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
15760 );
15761 assert_eq!(
15764 f.run(&[b"CF.RESERVE", b"q", b"7", b"BUCKETSIZE", b"4"]),
15765 "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
15766 );
15767 assert_eq!(
15768 f.run(&[b"CF.RESERVE", b"q", b"8", b"BUCKETSIZE", b"4"]),
15769 "+OK\r\n"
15770 );
15771
15772 assert_eq!(
15777 f.run(&[b"CF.RESERVE", b"q2", b"64", b"BUCKETSIZE", b"0"]),
15778 "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
15779 );
15780 assert_eq!(
15781 f.run(&[
15782 b"CF.RESERVE",
15783 b"q2",
15784 b"64",
15785 b"EXPANSION",
15786 b"xx",
15787 b"BUCKETSIZE",
15788 b"0"
15789 ]),
15790 "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
15791 );
15792 assert_eq!(
15793 f.run(&[
15794 b"CF.RESERVE",
15795 b"q2",
15796 b"64",
15797 b"MAXITERATIONS",
15798 b"0",
15799 b"BUCKETSIZE",
15800 b"0"
15801 ]),
15802 "-MAXITERATIONS: value must be in the range [1, 65535]\r\n"
15803 );
15804 assert_eq!(
15808 f.run(&[
15809 b"CF.RESERVE",
15810 b"q3",
15811 b"64",
15812 b"BUCKETSIZE",
15813 b"1",
15814 b"BUCKETSIZE",
15815 b"0"
15816 ]),
15817 "+OK\r\n"
15818 );
15819 assert_eq!(
15822 f.run(&[b"CF.RESERVE", b"q4", b"64", b"NOSUCH", b"9"]),
15823 "+OK\r\n"
15824 );
15825 assert_eq!(
15826 f.run(&[b"CF.DEBUG", b"q4"]),
15827 "$78\r\nbktsize:2 buckets:32 items:0 deletes:0 filters:1 \
15828 max_iterations:20 expansion:1\r\n"
15829 );
15830 assert!(
15833 f.run(&[b"CF.RESERVE", b"q5", b"64", b"BUCKETSIZE"])
15834 .contains("wrong number of arguments")
15835 );
15836 }
15837
15838 #[test]
15841 fn insert_checks_every_occurrence_and_matches_on_the_first_letter() {
15842 let mut f = Fixture::new();
15843 assert_eq!(
15844 f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"64", b"ITEMS", b"a", b"b"]),
15845 "*2\r\n:1\r\n:1\r\n"
15846 );
15847 assert_eq!(
15848 f.run(&[b"CF.DEBUG", b"i"]),
15849 "$78\r\nbktsize:2 buckets:32 items:2 deletes:0 filters:1 \
15850 max_iterations:20 expansion:1\r\n"
15851 );
15852 assert_eq!(
15855 f.run(&[b"CF.INSERTNX", b"i", b"ITEMS", b"a", b"c"]),
15856 "*2\r\n:0\r\n:1\r\n"
15857 );
15858 assert_eq!(
15859 f.run(&[b"CF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
15860 "-ERR not found\r\n"
15861 );
15862 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
15863
15864 assert_eq!(
15865 f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
15866 "-Bad capacity\r\n"
15867 );
15868 assert_eq!(
15871 f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"2", b"ITEMS", b"a"]),
15872 "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
15873 );
15874 assert_eq!(
15878 f.run(&[
15879 b"CF.INSERT",
15880 b"i",
15881 b"CAPACITY",
15882 b"8",
15883 b"CAPACITY",
15884 b"2",
15885 b"ITEMS",
15886 b"a"
15887 ]),
15888 "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
15889 );
15890 assert_eq!(
15894 f.run(&[b"CF.INSERT", b"i", b"NOSUCH", b"ITEMS", b"a"]),
15895 "*1\r\n:1\r\n"
15896 );
15897 assert_eq!(
15898 f.run(&[b"CF.INSERT", b"i", b"ITEMSXYZ", b"a"]),
15899 "*1\r\n:1\r\n"
15900 );
15901 assert_eq!(
15902 f.run(&[b"CF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
15903 "-Unknown argument received\r\n"
15904 );
15905 assert_eq!(
15907 f.run(&[b"CF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
15908 "*1\r\n:1\r\n"
15909 );
15910 assert!(
15912 f.run(&[b"CF.INSERT", b"i", b"ITEMS"])
15913 .contains("wrong number of arguments")
15914 );
15915 assert!(
15916 f.run(&[b"CF.INSERT", b"i", b"CAPACITY"])
15917 .contains("wrong number of arguments")
15918 );
15919 }
15920
15921 #[test]
15924 fn a_full_filter_and_one_that_ran_out_of_filters_answer_differently() {
15925 let mut f = Fixture::new();
15926 f.run(&[
15927 b"CF.RESERVE",
15928 b"s",
15929 b"4",
15930 b"BUCKETSIZE",
15931 b"1",
15932 b"EXPANSION",
15933 b"0",
15934 ]);
15935 for i in 0..4u32 {
15936 assert_eq!(
15937 f.run(&[b"CF.ADD", b"s", i.to_string().as_bytes()]),
15938 ":1\r\n"
15939 );
15940 }
15941 assert_eq!(f.run(&[b"CF.ADD", b"s", b"4"]), "-Filter is full\r\n");
15942 assert_eq!(f.run(&[b"CF.ADDNX", b"s", b"zz"]), "-Filter is full\r\n");
15943 assert_eq!(
15946 f.run(&[b"CF.INSERT", b"s", b"ITEMS", b"p", b"q"]),
15947 "*2\r\n:-1\r\n:-1\r\n"
15948 );
15949 assert_eq!(
15950 f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"0", b"q"]),
15951 "*2\r\n:0\r\n:-1\r\n"
15952 );
15953
15954 let full = cf_header(0, 4, 0, 32, [1, 20, 1]);
15960 assert_eq!(f.run(&[b"CF.LOADCHUNK", b"g", b"1", &full]), "+OK\r\n");
15961 assert_eq!(
15962 f.run(&[b"CF.ADD", b"g", b"q"]),
15963 "-Maximum expansions reached\r\n"
15964 );
15965 assert_eq!(
15966 f.run(&[b"CF.INFO", b"g"]),
15967 "*16\r\n+Size\r\n:680\r\n+Number of buckets\r\n:4\r\n\
15968 +Number of filters\r\n:32\r\n+Number of items inserted\r\n:0\r\n\
15969 +Number of items deleted\r\n:0\r\n+Bucket size\r\n:1\r\n\
15970 +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
15971 );
15972 }
15973
15974 #[test]
15978 fn a_cuckoo_dump_replays_into_a_filter_that_answers_the_same() {
15979 let mut f = Fixture::new();
15980 f.run(&[
15981 b"CF.RESERVE",
15982 b"src",
15983 b"8",
15984 b"BUCKETSIZE",
15985 b"2",
15986 b"EXPANSION",
15987 b"2",
15988 ]);
15989 for i in 0..40u32 {
15990 f.run(&[b"CF.ADD", b"src", i.to_string().as_bytes()]);
15991 }
15992 let mut pos = b"0".to_vec();
15996 let mut chunks = 0;
15997 loop {
15998 let raw = f.raw(&[b"CF.SCANDUMP", b"src", &pos]);
15999 let head = String::from_utf8_lossy(&raw[..raw.len().min(24)]).into_owned();
16000 let next = head
16001 .split("\r\n")
16002 .nth(1)
16003 .and_then(|n| n.strip_prefix(':'))
16004 .expect("a two element reply of a position and a chunk")
16005 .to_owned();
16006 if next == "0" {
16007 assert!(raw.ends_with(b"$-1\r\n"), "the walk ends on a nil");
16008 break;
16009 }
16010 let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
16011 let at = body
16012 .windows(2)
16013 .position(|w| w == b"\r\n")
16014 .expect("a length line")
16015 + 2;
16016 let data = &body[at..body.len() - 2];
16017 assert_eq!(
16018 f.run(&[b"CF.LOADCHUNK", b"dst", next.as_bytes(), data]),
16019 "+OK\r\n",
16020 "loading chunk {chunks}"
16021 );
16022 pos = next.into_bytes();
16023 chunks += 1;
16024 }
16025 assert!(chunks >= 2, "a header and at least one chunk");
16026
16027 assert_eq!(f.run(&[b"CF.INFO", b"dst"]), f.run(&[b"CF.INFO", b"src"]));
16028 assert_eq!(f.run(&[b"CF.DEBUG", b"dst"]), f.run(&[b"CF.DEBUG", b"src"]));
16029 for i in 0..40u32 {
16030 assert_eq!(
16031 f.run(&[b"CF.EXISTS", b"dst", i.to_string().as_bytes()]),
16032 ":1\r\n"
16033 );
16034 }
16035
16036 f.run(&[b"CF.RESERVE", b"empty", b"4", b"BUCKETSIZE", b"1"]);
16039 assert_eq!(
16040 f.run(&[b"CF.SCANDUMP", b"empty", b"0"]),
16041 "*2\r\n:0\r\n$-1\r\n"
16042 );
16043
16044 assert_eq!(
16048 f.run(&[b"CF.SCANDUMP", b"src", b"nope"]),
16049 "-Invalid position\r\n"
16050 );
16051 assert_eq!(
16052 f.run(&[b"CF.SCANDUMP", b"src", b"-1"]),
16053 "-Invalid position\r\n"
16054 );
16055 assert_eq!(
16056 f.run(&[b"CF.LOADCHUNK", b"dst", b"0", b"x"]),
16057 "-Invalid position\r\n"
16058 );
16059 assert_eq!(
16060 f.run(&[b"CF.LOADCHUNK", b"dst", b"99999", b"x"]),
16061 "-Couldn't load chunk!\r\n"
16062 );
16063 let good = cf_header(0, 8, 0, 1, [2, 20, 1]);
16065 assert_eq!(
16066 f.run(&[b"CF.LOADCHUNK", b"dst", b"1", &good]),
16067 "-ERR item exists\r\n"
16068 );
16069 assert_eq!(
16073 f.run(&[b"CF.LOADCHUNK", b"n1", b"1", b"short"]),
16074 "-Invalid header\r\n"
16075 );
16076 for (why, bad) in [
16077 ("no filters at all", cf_header(0, 8, 0, 0, [2, 20, 1])),
16078 ("no buckets", cf_header(0, 0, 0, 1, [2, 20, 1])),
16079 (
16080 "a bucket count that is not a power of two",
16081 cf_header(0, 3, 0, 1, [2, 20, 1]),
16082 ),
16083 ("an empty bucket", cf_header(0, 8, 0, 1, [0, 20, 1])),
16084 ("no kicks", cf_header(0, 8, 0, 1, [2, 0, 1])),
16085 (
16086 "a growth nobody could reach",
16087 cf_header(0, 8, 0, 1, [2, 20, 32769]),
16088 ),
16089 (
16090 "a chain that cannot grow and did",
16091 cf_header(0, 8, 0, 2, [2, 20, 0]),
16092 ),
16093 (
16096 "a filter count that wraps",
16097 cf_header(0, 8, 0, 65_536, [2, 20, 1]),
16098 ),
16099 ] {
16100 assert_eq!(
16101 f.run(&[b"CF.LOADCHUNK", b"bad", b"1", &bad]),
16102 "-Couldn't create filter!\r\n",
16103 "{why}"
16104 );
16105 }
16106 }
16107
16108 #[test]
16111 fn the_cuckoo_family_answers_in_resp3_spelling_too() {
16112 let mut f = Fixture::new();
16113 f.out.set_proto(Proto::Resp3);
16114 assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
16115 assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
16116 assert_eq!(f.run(&[b"CF.ADDNX", b"c", b"a"]), "#f\r\n");
16117 assert_eq!(f.run(&[b"CF.EXISTS", b"c", b"a"]), "#t\r\n");
16118 assert_eq!(
16119 f.run(&[b"CF.MEXISTS", b"c", b"a", b"z"]),
16120 "*2\r\n#t\r\n#f\r\n"
16121 );
16122 assert_eq!(f.run(&[b"CF.DEL", b"c", b"a"]), "#t\r\n");
16123 assert_eq!(f.run(&[b"CF.DEL", b"c", b"z"]), "#f\r\n");
16124 assert_eq!(f.run(&[b"CF.COUNT", b"c", b"a"]), ":1\r\n");
16126 assert_eq!(
16127 f.run(&[b"CF.INFO", b"c"]),
16128 "%8\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
16129 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
16130 +Number of items deleted\r\n:1\r\n+Bucket size\r\n:2\r\n\
16131 +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
16132 );
16133
16134 f.run(&[
16139 b"CF.RESERVE",
16140 b"s",
16141 b"4",
16142 b"BUCKETSIZE",
16143 b"1",
16144 b"EXPANSION",
16145 b"0",
16146 ]);
16147 assert_eq!(
16148 f.run(&[
16149 b"CF.INSERT",
16150 b"s",
16151 b"ITEMS",
16152 b"a",
16153 b"b",
16154 b"c",
16155 b"d",
16156 b"e",
16157 b"f"
16158 ]),
16159 "*6\r\n#t\r\n#t\r\n#t\r\n#f\r\n#f\r\n#f\r\n"
16160 );
16161 assert_eq!(
16162 f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"a", b"zz"]),
16163 "*2\r\n:0\r\n:-1\r\n"
16164 );
16165 assert_eq!(f.run(&[b"CF.ADD", b"s", b"zzz"]), "-Filter is full\r\n");
16166 assert_eq!(f.run(&[b"CF.SCANDUMP", b"c", b"9999"]), "*2\r\n:0\r\n_\r\n");
16169 }
16170
16171 #[test]
16176 fn a_sketch_is_made_from_a_size_or_from_an_error_rate() {
16177 let mut f = Fixture::new();
16178 assert_eq!(f.run(&[b"CMS.INITBYDIM", b"d", b"100", b"5"]), "+OK\r\n");
16179 assert_eq!(
16180 f.run(&[b"CMS.INFO", b"d"]),
16181 "*6\r\n+width\r\n:100\r\n+depth\r\n:5\r\n+count\r\n:0\r\n"
16182 );
16183 assert_eq!(f.run(&[b"TYPE", b"d"]), "+CMSk-TYPE\r\n");
16184 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
16185 assert_eq!(
16188 f.run(&[b"CMS.INITBYPROB", b"p", b"0.01", b"0.03"]),
16189 "+OK\r\n"
16190 );
16191 assert_eq!(
16192 f.run(&[b"CMS.INFO", b"p"]),
16193 "*6\r\n+width\r\n:200\r\n+depth\r\n:6\r\n+count\r\n:0\r\n"
16194 );
16195 assert_eq!(
16198 f.run(&[b"CMS.INITBYDIM", b"d", b"0", b"2"]),
16199 "-CMS: key already exists\r\n"
16200 );
16201 assert_eq!(
16202 f.run(&[b"CMS.INITBYDIM", b"new", b"0", b"2"]),
16203 "-CMS: invalid width\r\n"
16204 );
16205 assert_eq!(
16206 f.run(&[b"CMS.INITBYDIM", b"new", b"2", b"0"]),
16207 "-CMS: invalid depth\r\n"
16208 );
16209 assert_eq!(
16210 f.run(&[b"CMS.INITBYPROB", b"new", b"0", b"0.5"]),
16211 "-CMS: invalid overestimation value\r\n"
16212 );
16213 assert_eq!(
16214 f.run(&[b"CMS.INITBYPROB", b"new", b"0.1", b"1"]),
16215 "-CMS: invalid prob value\r\n"
16216 );
16217 assert_eq!(
16221 f.run(&[b"CMS.INITBYPROB", b"new", b"0.5", b"1e-46"]),
16222 "-CMS: invalid init arguments\r\n"
16223 );
16224 assert_eq!(
16228 f.run(&[b"CMS.INITBYDIM", b"new", b"268435457", b"1"]),
16229 "-CMS: Insufficient memory to create the key\r\n"
16230 );
16231 assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
16232 }
16233
16234 #[test]
16237 fn increments_are_parsed_whole_and_the_counters_saturate() {
16238 let mut f = Fixture::new();
16239 f.run(&[b"CMS.INITBYDIM", b"c", b"100", b"4"]);
16240 assert_eq!(
16241 f.run(&[b"CMS.INCRBY", b"c", b"a", b"3", b"b", b"4"]),
16242 "*2\r\n:3\r\n:4\r\n"
16243 );
16244 assert_eq!(
16247 f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"a", b"1"]),
16248 "*2\r\n:4\r\n:5\r\n"
16249 );
16250 assert_eq!(
16252 f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"x"]),
16253 "-CMS: Cannot parse number\r\n"
16254 );
16255 assert_eq!(
16256 f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"-1"]),
16257 "-CMS: Number cannot be negative\r\n"
16258 );
16259 assert_eq!(
16260 f.run(&[b"CMS.QUERY", b"c", b"a", b"b"]),
16261 "*2\r\n:5\r\n:4\r\n"
16262 );
16263 f.run(&[b"CMS.INCRBY", b"c", b"a", b"4294967295"]);
16266 assert_eq!(
16267 f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b", b"1"]),
16268 "*2\r\n-CMS: INCRBY overflow\r\n:5\r\n"
16269 );
16270 assert_eq!(f.run(&[b"CMS.QUERY", b"c", b"a"]), "*1\r\n:4294967295\r\n");
16271 f.run(&[b"CMS.INITBYDIM", b"w", b"4", b"1"]);
16274 f.run(&[b"CMS.INCRBY", b"w", b"x", b"9223372036854775807"]);
16275 f.run(&[b"CMS.INCRBY", b"w", b"x", b"1"]);
16276 assert_eq!(
16277 f.run(&[b"CMS.INFO", b"w"]),
16278 "*6\r\n+width\r\n:4\r\n+depth\r\n:1\r\n+count\r\n:-9223372036854775808\r\n"
16279 );
16280 assert!(
16283 f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b"])
16284 .contains("wrong number of arguments")
16285 );
16286 assert_eq!(
16287 f.run(&[b"CMS.INCRBY", b"nope", b"a", b"1"]),
16288 "-CMS: key does not exist\r\n"
16289 );
16290 assert_eq!(
16291 f.run(&[b"CMS.QUERY", b"nope", b"a"]),
16292 "-CMS: key does not exist\r\n"
16293 );
16294 }
16295
16296 #[test]
16299 fn a_merge_lands_whole_or_not_at_all() {
16300 let mut f = Fixture::new();
16301 for name in [&b"m1"[..], b"m2", b"dst"] {
16302 f.run(&[b"CMS.INITBYDIM", name, b"64", b"3"]);
16303 }
16304 f.run(&[b"CMS.INCRBY", b"m1", b"a", b"5"]);
16305 f.run(&[b"CMS.INCRBY", b"m2", b"a", b"7"]);
16306 assert_eq!(
16307 f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
16308 "+OK\r\n"
16309 );
16310 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
16311 assert_eq!(
16314 f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
16315 "+OK\r\n"
16316 );
16317 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
16318 assert_eq!(
16319 f.run(&[
16320 b"CMS.MERGE",
16321 b"dst",
16322 b"2",
16323 b"m1",
16324 b"m2",
16325 b"WEIGHTS",
16326 b"2",
16327 b"3"
16328 ]),
16329 "+OK\r\n"
16330 );
16331 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
16332 assert_eq!(
16335 f.run(&[
16336 b"CMS.MERGE",
16337 b"dst",
16338 b"1",
16339 b"m1",
16340 b"WEIGHTS",
16341 b"4611686018427387904"
16342 ]),
16343 "-CMS: MERGE overflow\r\n"
16344 );
16345 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
16346 f.run(&[b"CMS.INITBYDIM", b"wide", b"128", b"3"]);
16349 assert_eq!(
16350 f.run(&[b"CMS.MERGE", b"gone", b"1", b"m1"]),
16351 "-CMS: key does not exist\r\n"
16352 );
16353 assert_eq!(
16354 f.run(&[b"CMS.MERGE", b"dst", b"0", b"m1"]),
16355 "-CMS: Number of keys must be positive\r\n"
16356 );
16357 assert_eq!(
16358 f.run(&[b"CMS.MERGE", b"dst", b"3", b"m1"]),
16359 "-CMS: wrong number of keys\r\n"
16360 );
16361 assert_eq!(
16362 f.run(&[b"CMS.MERGE", b"dst", b"1", b"m1", b"WEIGHTS", b"1", b"2"]),
16363 "-CMS: wrong number of keys/weights\r\n"
16364 );
16365 assert_eq!(
16366 f.run(&[b"CMS.MERGE", b"dst", b"1", b"wide"]),
16367 "-CMS: width/depth is not equal\r\n"
16368 );
16369 assert_eq!(
16370 f.run(&[b"CMS.MERGE", b"dst", b"1", b"gone"]),
16371 "-CMS: key does not exist\r\n"
16372 );
16373 }
16374
16375 #[test]
16378 fn a_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
16379 let mut f = Fixture::new();
16380 f.run(&[b"SET", b"s", b"text"]);
16381 for cmd in [
16382 vec![&b"CMS.INITBYDIM"[..], b"s", b"8", b"2"],
16383 vec![&b"CMS.INCRBY"[..], b"s", b"a", b"1"],
16384 vec![&b"CMS.QUERY"[..], b"s", b"a"],
16385 vec![&b"CMS.INFO"[..], b"s"],
16386 vec![&b"CMS.MERGE"[..], b"s", b"1", b"s"],
16387 ] {
16388 let name = String::from_utf8_lossy(cmd[0]).into_owned();
16389 let reply = f.run(&cmd);
16390 assert!(
16393 reply.starts_with("-WRONGTYPE") || reply == "-CMS: key already exists\r\n",
16394 "{name}: {reply}"
16395 );
16396 }
16397 f.run(&[b"CMS.INITBYDIM", b"c", b"64", b"2"]);
16398 assert_eq!(
16402 f.run(&[b"COPY", b"c", b"c2"]),
16403 "-ERR not supported for this module key\r\n"
16404 );
16405 assert_eq!(
16406 f.run(&[b"DUMP", b"c"]),
16407 "-ERR DUMP is not supported for this module key\r\n"
16408 );
16409 f.run(&[b"G.NADD", b"g", b"a"]);
16411 assert_eq!(
16412 f.run(&[b"COPY", b"g", b"g2"]),
16413 "-ERR COPY is not supported for a graph\r\n"
16414 );
16415 assert_eq!(
16416 f.run(&[b"DUMP", b"g"]),
16417 "-ERR DUMP is not supported for a graph\r\n"
16418 );
16419 assert_eq!(f.run(&[b"EXPIRE", b"c", b"100"]), ":1\r\n");
16422 assert_eq!(f.run(&[b"PERSIST", b"c"]), ":1\r\n");
16423 assert_eq!(f.run(&[b"RENAME", b"c", b"c3"]), "+OK\r\n");
16424 assert_eq!(f.run(&[b"TYPE", b"c3"]), "+CMSk-TYPE\r\n");
16425 assert_eq!(f.run(&[b"DEL", b"c3"]), ":1\r\n");
16426 }
16427
16428 #[test]
16433 fn a_reserve_takes_three_arguments_or_six() {
16434 let mut f = Fixture::new();
16435 assert_eq!(f.run(&[b"TOPK.RESERVE", b"t", b"5"]), "+OK\r\n");
16436 assert_eq!(
16437 f.run(&[b"TOPK.INFO", b"t"]),
16438 "*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"
16439 );
16440 for cmd in [
16443 vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8"],
16444 vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8", b"7"],
16445 ] {
16446 assert!(f.run(&cmd).contains("wrong number of arguments"));
16447 }
16448 assert_eq!(
16449 f.run(&[b"TOPK.RESERVE", b"u", b"5", b"8", b"7", b"0.5"]),
16450 "+OK\r\n"
16451 );
16452 assert_eq!(
16455 f.run(&[b"TOPK.RESERVE", b"u", b"0", b"0", b"0", b"9"]),
16456 "-TopK: key already exists\r\n"
16457 );
16458 assert_eq!(
16459 f.run(&[b"TOPK.RESERVE", b"v", b"0"]),
16460 "-TopK: invalid k\r\n"
16461 );
16462 assert_eq!(
16463 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"0", b"7", b"0.9"]),
16464 "-TopK: invalid width\r\n"
16465 );
16466 assert_eq!(
16467 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"x", b"0.9"]),
16468 "-TopK: invalid depth\r\n"
16469 );
16470 assert_eq!(
16472 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"0"]),
16473 "-TopK: invalid decay value. must be '<= 1' & '> 0'\r\n"
16474 );
16475 assert_eq!(
16476 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"1"]),
16477 "+OK\r\n"
16478 );
16479 assert_eq!(
16481 f.run(&[
16482 b"TOPK.RESERVE",
16483 b"w",
16484 b"1",
16485 b"4294967295",
16486 b"4294967295",
16487 b"0.9"
16488 ]),
16489 "-ERR Insufficient memory to create topk data structure\r\n"
16490 );
16491 }
16492
16493 #[test]
16495 fn the_kept_set_is_what_query_and_list_answer_from() {
16496 let mut f = Fixture::new();
16497 f.run(&[b"TOPK.RESERVE", b"t", b"2", b"1000", b"5", b"0.9"]);
16498 assert_eq!(
16501 f.run(&[b"TOPK.ADD", b"t", b"a", b"b"]),
16502 "*2\r\n$-1\r\n$-1\r\n"
16503 );
16504 assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"a", b"10"]), "*1\r\n$-1\r\n");
16505 assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"c"]), "*1\r\n$1\r\nb\r\n");
16508 assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"c", b"5"]), "*1\r\n$-1\r\n");
16509 assert_eq!(
16510 f.run(&[b"TOPK.QUERY", b"t", b"a", b"b", b"c"]),
16511 "*3\r\n:1\r\n:0\r\n:1\r\n"
16512 );
16513 assert_eq!(
16515 f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
16516 "*3\r\n:11\r\n:1\r\n:6\r\n"
16517 );
16518 assert_eq!(f.run(&[b"TOPK.LIST", b"t"]), "*2\r\n$1\r\na\r\n$1\r\nc\r\n");
16519 assert_eq!(
16520 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"]),
16521 "*4\r\n$1\r\na\r\n:11\r\n$1\r\nc\r\n:6\r\n"
16522 );
16523 assert_eq!(
16526 f.run(&[b"TOPK.LIST", b"t", b"w"]),
16527 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
16528 );
16529 assert_eq!(
16530 f.run(&[b"TOPK.LIST", b"t", b""]),
16531 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
16532 );
16533 assert_eq!(
16534 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNTS"]),
16535 "-WITHCOUNT keyword expected\r\n"
16536 );
16537 assert_eq!(
16540 f.run(&[b"TOPK.LIST", b"missing", b"nope"]),
16541 "-WITHCOUNT keyword expected\r\n"
16542 );
16543 assert_eq!(
16544 f.run(&[b"TOPK.LIST", b"missing"]),
16545 "-TopK: key does not exist\r\n"
16546 );
16547 f.run(&[b"TOPK.RESERVE", b"z", b"3"]);
16549 assert_eq!(
16550 f.run(&[b"TOPK.INCRBY", b"z", b"nothing", b"0"]),
16551 "*1\r\n$-1\r\n"
16552 );
16553 assert_eq!(f.run(&[b"TOPK.QUERY", b"z", b"nothing"]), "*1\r\n:1\r\n");
16554 assert_eq!(f.run(&[b"TOPK.LIST", b"z"]), "*0\r\n");
16555 }
16556
16557 #[test]
16560 fn an_increment_is_applied_as_it_goes_and_stops_at_a_bad_one() {
16561 let mut f = Fixture::new();
16562 f.run(&[b"TOPK.RESERVE", b"t", b"5", b"1000", b"5", b"0.9"]);
16563 assert_eq!(
16567 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"3", b"b", b"-1", b"c", b"4"]),
16568 format!(
16569 "*2\r\n$-1\r\n-{}\r\n",
16570 "TopK: increment must be an integer greater or equal to 0 and smaller or equal to 100,000"
16571 )
16572 );
16573 assert_eq!(
16574 f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
16575 "*3\r\n:3\r\n:0\r\n:0\r\n"
16576 );
16577 assert_eq!(
16579 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100000"]),
16580 "*1\r\n$-1\r\n"
16581 );
16582 assert!(
16583 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100001"])
16584 .contains("smaller or equal to 100,000")
16585 );
16586 assert!(
16588 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"1", b"b"])
16589 .contains("wrong number of arguments")
16590 );
16591 assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:100003\r\n");
16592 }
16593
16594 #[test]
16596 fn a_query_is_a_bool_and_info_is_a_map_on_resp3() {
16597 let mut f = Fixture::new();
16598 f.run(&[b"HELLO", b"3"]);
16599 f.run(&[b"TOPK.RESERVE", b"t", b"2", b"8", b"7", b"0.5"]);
16600 f.run(&[b"TOPK.ADD", b"t", b"a"]);
16601 assert_eq!(
16602 f.run(&[b"TOPK.QUERY", b"t", b"a", b"b"]),
16603 "*2\r\n#t\r\n#f\r\n"
16604 );
16605 assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:1\r\n");
16607 assert_eq!(
16608 f.run(&[b"TOPK.INFO", b"t"]),
16609 "%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"
16610 );
16611 assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"a"]), "*1\r\n_\r\n");
16612 }
16613
16614 #[test]
16617 fn a_top_k_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
16618 let mut f = Fixture::new();
16619 f.run(&[b"SET", b"s", b"text"]);
16620 for cmd in [
16621 vec![&b"TOPK.RESERVE"[..], b"s", b"5"],
16622 vec![&b"TOPK.ADD"[..], b"s", b"a"],
16623 vec![&b"TOPK.INCRBY"[..], b"s", b"a", b"1"],
16624 vec![&b"TOPK.QUERY"[..], b"s", b"a"],
16625 vec![&b"TOPK.COUNT"[..], b"s", b"a"],
16626 vec![&b"TOPK.LIST"[..], b"s"],
16627 vec![&b"TOPK.INFO"[..], b"s"],
16628 ] {
16629 let name = String::from_utf8_lossy(cmd[0]).into_owned();
16630 let reply = f.run(&cmd);
16631 assert!(
16632 reply.starts_with("-WRONGTYPE") || reply == "-TopK: key already exists\r\n",
16633 "{name}: {reply}"
16634 );
16635 }
16636 f.run(&[b"TOPK.RESERVE", b"t", b"5"]);
16637 assert_eq!(
16638 f.run(&[b"COPY", b"t", b"t2"]),
16639 "-ERR not supported for this module key\r\n"
16640 );
16641 assert_eq!(
16642 f.run(&[b"DUMP", b"t"]),
16643 "-ERR DUMP is not supported for this module key\r\n"
16644 );
16645 assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
16646 assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
16647 assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
16648 assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TopK-TYPE\r\n");
16649 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
16650 assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
16651 assert_eq!(
16654 f.run(&[b"TOPK.INFO", b"t3"]),
16655 "-TopK: key does not exist\r\n"
16656 );
16657 }
16658
16659 #[test]
16664 fn a_create_takes_two_arguments_or_four_and_reads_the_last_one() {
16665 let mut f = Fixture::new();
16666 assert_eq!(f.run(&[b"TDIGEST.CREATE", b"t"]), "+OK\r\n");
16667 assert_eq!(
16669 f.run(&[b"TDIGEST.INFO", b"t"]),
16670 "*18\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:0\r\n\
16671 +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:0\r\n+Unmerged weight\r\n:0\r\n\
16672 +Observations\r\n:0\r\n+Total compressions\r\n:0\r\n+Memory usage\r\n:9840\r\n"
16673 );
16674 assert_eq!(
16675 f.run(&[b"TDIGEST.CREATE", b"t"]),
16676 "-ERR T-Digest: key already exists\r\n"
16677 );
16678 assert!(
16680 f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION"])
16681 .contains("wrong number of arguments")
16682 );
16683 assert_eq!(
16684 f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION", b"1000"]),
16685 "+OK\r\n"
16686 );
16687 assert_eq!(
16688 f.run(&[b"TDIGEST.CREATE", b"v", b"compression", b"1"]),
16689 "+OK\r\n"
16690 );
16691 assert_eq!(
16695 f.run(&[b"TDIGEST.CREATE", b"w", b"100", b"COMPRESSION"]),
16696 "-ERR T-Digest: error parsing compression parameter\r\n"
16697 );
16698 assert_eq!(
16699 f.run(&[b"TDIGEST.CREATE", b"w", b"NOPE", b"100"]),
16700 "-ERR T-Digest: wrong keyword\r\n"
16701 );
16702 assert_eq!(
16703 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"1.5"]),
16704 "-ERR T-Digest: error parsing compression parameter\r\n"
16705 );
16706 assert_eq!(
16707 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"0"]),
16708 "-ERR T-Digest: compression parameter needs to be a positive integer\r\n"
16709 );
16710 assert_eq!(
16713 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"357913942"]),
16714 "-ERR T-Digest: allocation failed\r\n"
16715 );
16716 assert_eq!(
16718 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"100000000"]),
16719 "-ERR T-Digest: allocation failed\r\n"
16720 );
16721 assert_eq!(
16724 f.run(&[b"TDIGEST.CREATE", b"t", b"COMPRESSION", b"0"]),
16725 "-ERR T-Digest: key already exists\r\n"
16726 );
16727 }
16728
16729 #[test]
16732 fn the_quantile_family_answers_what_the_module_answers() {
16733 let mut f = Fixture::new();
16734 f.run(&[b"TDIGEST.CREATE", b"s"]);
16735 assert_eq!(
16736 f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]),
16737 "+OK\r\n"
16738 );
16739 assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), "$1\r\n1\r\n");
16740 assert_eq!(f.run(&[b"TDIGEST.MAX", b"s"]), "$1\r\n4\r\n");
16741 assert_eq!(
16743 f.run(&[b"TDIGEST.CDF", b"s", b"1", b"2", b"3", b"4"]),
16744 "*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"
16745 );
16746 assert_eq!(
16747 f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"0.5", b"1"]),
16748 "*3\r\n$1\r\n1\r\n$1\r\n3\r\n$1\r\n4\r\n"
16749 );
16750 assert_eq!(
16753 f.run(&[b"TDIGEST.QUANTILE", b"s", b"0.5", b"0.1", b"0.9"]),
16754 "*3\r\n$1\r\n3\r\n$1\r\n1\r\n$1\r\n4\r\n"
16755 );
16756 assert_eq!(
16757 f.run(&[b"TDIGEST.RANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
16758 "*5\r\n:-1\r\n:0\r\n:2\r\n:3\r\n:4\r\n"
16759 );
16760 assert_eq!(
16761 f.run(&[b"TDIGEST.REVRANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
16762 "*5\r\n:4\r\n:3\r\n:1\r\n:0\r\n:-1\r\n"
16763 );
16764 assert_eq!(
16765 f.run(&[b"TDIGEST.BYRANK", b"s", b"0", b"1", b"3", b"4"]),
16766 "*4\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n4\r\n$3\r\ninf\r\n"
16767 );
16768 assert_eq!(
16769 f.run(&[b"TDIGEST.BYREVRANK", b"s", b"0", b"1", b"3", b"4"]),
16770 "*4\r\n$1\r\n4\r\n$1\r\n3\r\n$1\r\n1\r\n$4\r\n-inf\r\n"
16771 );
16772 assert_eq!(
16773 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0", b"1"]),
16774 "$3\r\n2.5\r\n"
16775 );
16776 assert_eq!(
16777 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.25", b"0.75"]),
16778 "$3\r\n2.5\r\n"
16779 );
16780 assert_eq!(
16782 f.run(&[b"TDIGEST.QUANTILE", b"s", b"1.1"]),
16783 "-ERR T-Digest: quantile should be in [0,1]\r\n"
16784 );
16785 assert_eq!(
16786 f.run(&[b"TDIGEST.QUANTILE", b"s", b"zzz"]),
16787 "-ERR T-Digest: error parsing quantile\r\n"
16788 );
16789 assert_eq!(
16790 f.run(&[b"TDIGEST.CDF", b"s", b"zzz"]),
16791 "-ERR T-Digest: error parsing cdf\r\n"
16792 );
16793 assert_eq!(
16794 f.run(&[b"TDIGEST.RANK", b"s", b"zzz"]),
16795 "-ERR T-Digest: error parsing value\r\n"
16796 );
16797 assert_eq!(
16798 f.run(&[b"TDIGEST.BYRANK", b"s", b"-1"]),
16799 "-ERR T-Digest: rank needs to be non negative\r\n"
16800 );
16801 assert_eq!(
16802 f.run(&[b"TDIGEST.BYRANK", b"s", b"1.5"]),
16803 "-ERR T-Digest: error parsing rank\r\n"
16804 );
16805 assert_eq!(
16808 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"zzz", b"0.9"]),
16809 "-ERR T-Digest: error parsing low_cut_percentile\r\n"
16810 );
16811 assert_eq!(
16812 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"zzz"]),
16813 "-ERR T-Digest: error parsing high_cut_percentile\r\n"
16814 );
16815 assert_eq!(
16816 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"1.1"]),
16817 "-ERR T-Digest: low_cut_percentile and high_cut_percentile should be in [0,1]\r\n"
16818 );
16819 assert_eq!(
16820 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.5", b"0.5"]),
16821 "-ERR T-Digest: low_cut_percentile should be lower than high_cut_percentile\r\n"
16822 );
16823 }
16824
16825 #[test]
16828 fn an_empty_digest_has_an_answer_for_everything() {
16829 let mut f = Fixture::new();
16830 f.run(&[b"TDIGEST.CREATE", b"e"]);
16831 assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
16832 assert_eq!(f.run(&[b"TDIGEST.MAX", b"e"]), "$3\r\nnan\r\n");
16833 assert_eq!(
16834 f.run(&[b"TDIGEST.QUANTILE", b"e", b"0", b"1"]),
16835 "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
16836 );
16837 assert_eq!(f.run(&[b"TDIGEST.CDF", b"e", b"0"]), "*1\r\n$3\r\nnan\r\n");
16838 assert_eq!(
16839 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"e", b"0.1", b"0.9"]),
16840 "$3\r\nnan\r\n"
16841 );
16842 assert_eq!(
16845 f.run(&[b"TDIGEST.RANK", b"e", b"0", b"1"]),
16846 "*2\r\n:-2\r\n:-2\r\n"
16847 );
16848 assert_eq!(
16849 f.run(&[b"TDIGEST.REVRANK", b"e", b"0", b"1"]),
16850 "*2\r\n:-2\r\n:-2\r\n"
16851 );
16852 assert_eq!(
16853 f.run(&[b"TDIGEST.BYRANK", b"e", b"0", b"5"]),
16854 "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
16855 );
16856 f.run(&[b"TDIGEST.ADD", b"e", b"1", b"2", b"3"]);
16858 assert_eq!(f.run(&[b"TDIGEST.RESET", b"e"]), "+OK\r\n");
16859 assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
16860 f.run(&[b"TDIGEST.CREATE", b"e2"]);
16863 assert_eq!(
16864 f.run(&[b"TDIGEST.INFO", b"e"]),
16865 f.run(&[b"TDIGEST.INFO", b"e2"])
16866 );
16867 }
16868
16869 #[test]
16872 fn a_sample_is_read_the_way_redis_reads_a_double() {
16873 let mut f = Fixture::new();
16874 f.run(&[b"TDIGEST.CREATE", b"a"]);
16875 for bad in [
16878 &b"nan"[..],
16879 b"1e400",
16880 b"-1e400",
16881 b"1e309",
16882 b"1e-400",
16883 b"",
16884 b" 1",
16885 b"1 ",
16886 b"1e",
16887 b"--1",
16888 ] {
16889 assert_eq!(
16890 f.run(&[b"TDIGEST.ADD", b"a", bad]),
16891 "-ERR T-Digest: error parsing val parameter\r\n",
16892 "{}",
16893 String::from_utf8_lossy(bad)
16894 );
16895 }
16896 for word in [&b"inf"[..], b"-inf", b"+INF", b"Infinity"] {
16899 assert_eq!(
16900 f.run(&[b"TDIGEST.ADD", b"a", word]),
16901 "-ERR T-Digest: val parameter needs to be a finite number\r\n",
16902 "{}",
16903 String::from_utf8_lossy(word)
16904 );
16905 }
16906 for good in [&b"0x10"[..], b".5", b"1.", b"1e-320", b"-0", b"0"] {
16909 assert_eq!(
16910 f.run(&[b"TDIGEST.ADD", b"a", good]),
16911 "+OK\r\n",
16912 "{}",
16913 String::from_utf8_lossy(good)
16914 );
16915 }
16916 assert!(
16918 f.run(&[b"TDIGEST.INFO", b"a"])
16919 .contains("Observations\r\n:6\r\n")
16920 );
16921 assert_eq!(
16924 f.run(&[b"TDIGEST.ADD", b"a", b"1", b"zzz"]),
16925 "-ERR T-Digest: error parsing val parameter\r\n"
16926 );
16927 assert!(
16928 f.run(&[b"TDIGEST.INFO", b"a"])
16929 .contains("Observations\r\n:6\r\n")
16930 );
16931 }
16932
16933 #[test]
16936 fn a_merge_sweeps_the_destination_between_its_inputs() {
16937 let mut f = Fixture::new();
16938 f.run(&[b"TDIGEST.CREATE", b"m1", b"COMPRESSION", b"100"]);
16939 f.run(&[b"TDIGEST.ADD", b"m1", b"1", b"2", b"3"]);
16940 f.run(&[b"TDIGEST.CREATE", b"m2", b"COMPRESSION", b"200"]);
16941 f.run(&[b"TDIGEST.ADD", b"m2", b"4", b"5", b"6"]);
16942 assert_eq!(
16943 f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"m2"]),
16944 "+OK\r\n"
16945 );
16946 let info = f.run(&[b"TDIGEST.INFO", b"d"]);
16951 assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
16952 assert!(info.contains("Merged nodes\r\n:3\r\n"), "{info}");
16953 assert!(info.contains("Unmerged nodes\r\n:3\r\n"), "{info}");
16954 assert!(info.contains("Total compressions\r\n:1\r\n"), "{info}");
16955 assert_eq!(f.run(&[b"TDIGEST.MIN", b"d"]), "$1\r\n1\r\n");
16956 assert_eq!(f.run(&[b"TDIGEST.MAX", b"d"]), "$1\r\n6\r\n");
16957 assert!(
16960 f.run(&[b"TDIGEST.INFO", b"m1"])
16961 .contains("Merged nodes\r\n:3\r\n")
16962 );
16963 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1"]);
16966 let info = f.run(&[b"TDIGEST.INFO", b"d"]);
16967 assert!(info.contains("Observations\r\n:9\r\n"), "{info}");
16968 assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
16969 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"OVERRIDE"]);
16972 let info = f.run(&[b"TDIGEST.INFO", b"d"]);
16973 assert!(info.contains("Observations\r\n:3\r\n"), "{info}");
16974 assert!(info.contains("Compression\r\n:100\r\n"), "{info}");
16975 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION", b"500"]);
16977 assert!(
16978 f.run(&[b"TDIGEST.INFO", b"d"])
16979 .contains("Compression\r\n:500\r\n")
16980 );
16981 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"d"]);
16983 assert!(
16984 f.run(&[b"TDIGEST.INFO", b"d"])
16985 .contains("Observations\r\n:12\r\n")
16986 );
16987 assert_eq!(
16989 f.run(&[b"TDIGEST.MERGE", b"d", b"zzz", b"m1"]),
16990 "-ERR T-Digest: error parsing numkeys\r\n"
16991 );
16992 assert_eq!(
16993 f.run(&[b"TDIGEST.MERGE", b"d", b"0", b"m1"]),
16994 "-ERR T-Digest: numkeys needs to be a positive integer\r\n"
16995 );
16996 assert!(
16997 f.run(&[b"TDIGEST.MERGE", b"d", b"3", b"m1", b"m2"])
16998 .contains("wrong number of arguments")
16999 );
17000 assert!(
17001 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION"])
17002 .contains("wrong number of arguments")
17003 );
17004 assert_eq!(
17005 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"NOPE"]),
17006 "-ERR T-Digest: wrong keyword\r\n"
17007 );
17008 assert_eq!(
17011 f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"gone"]),
17012 "-ERR T-Digest: key does not exist\r\n"
17013 );
17014 assert!(
17015 f.run(&[b"TDIGEST.INFO", b"d"])
17016 .contains("Observations\r\n:12\r\n")
17017 );
17018 assert_eq!(
17021 f.run(&[b"TDIGEST.MERGE", b"gone", b"1", b"gone"]),
17022 "-ERR T-Digest: key does not exist\r\n"
17023 );
17024 }
17025
17026 #[test]
17028 fn a_digest_answers_doubles_and_a_map_on_resp3() {
17029 let mut f = Fixture::new();
17030 f.run(&[b"HELLO", b"3"]);
17031 f.run(&[b"TDIGEST.CREATE", b"s"]);
17032 f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]);
17033 assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), ",1\r\n");
17034 assert_eq!(
17035 f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"1"]),
17036 "*2\r\n,1\r\n,4\r\n"
17037 );
17038 assert_eq!(f.run(&[b"TDIGEST.CDF", b"s", b"1"]), "*1\r\n,0.125\r\n");
17039 assert_eq!(f.run(&[b"TDIGEST.BYRANK", b"s", b"4"]), "*1\r\n,inf\r\n");
17041 assert_eq!(
17042 f.run(&[b"TDIGEST.BYREVRANK", b"s", b"4"]),
17043 "*1\r\n,-inf\r\n"
17044 );
17045 f.run(&[b"TDIGEST.CREATE", b"e"]);
17046 assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), ",nan\r\n");
17047 assert_eq!(f.run(&[b"TDIGEST.RANK", b"s", b"1"]), "*1\r\n:0\r\n");
17049 assert_eq!(
17052 f.run(&[b"TDIGEST.INFO", b"s"]),
17053 "%9\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:4\r\n\
17054 +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:4\r\n+Unmerged weight\r\n:0\r\n\
17055 +Observations\r\n:4\r\n+Total compressions\r\n:1\r\n+Memory usage\r\n:9840\r\n"
17056 );
17057 }
17058
17059 #[test]
17062 fn a_t_digest_is_a_module_key_to_the_rest_of_the_keyspace() {
17063 let mut f = Fixture::new();
17064 f.run(&[b"SET", b"s", b"text"]);
17065 for cmd in [
17066 vec![&b"TDIGEST.CREATE"[..], b"s"],
17067 vec![&b"TDIGEST.RESET"[..], b"s"],
17068 vec![&b"TDIGEST.ADD"[..], b"s", b"1"],
17069 vec![&b"TDIGEST.MIN"[..], b"s"],
17070 vec![&b"TDIGEST.MAX"[..], b"s"],
17071 vec![&b"TDIGEST.QUANTILE"[..], b"s", b"0.5"],
17072 vec![&b"TDIGEST.CDF"[..], b"s", b"1"],
17073 vec![&b"TDIGEST.TRIMMED_MEAN"[..], b"s", b"0.1", b"0.9"],
17074 vec![&b"TDIGEST.RANK"[..], b"s", b"1"],
17075 vec![&b"TDIGEST.REVRANK"[..], b"s", b"1"],
17076 vec![&b"TDIGEST.BYRANK"[..], b"s", b"0"],
17077 vec![&b"TDIGEST.BYREVRANK"[..], b"s", b"0"],
17078 vec![&b"TDIGEST.INFO"[..], b"s"],
17079 ] {
17080 let name = String::from_utf8_lossy(cmd[0]).into_owned();
17081 let reply = f.run(&cmd);
17082 assert!(reply.starts_with("-WRONGTYPE"), "{name}: {reply}");
17083 }
17084 f.run(&[b"TDIGEST.CREATE", b"t"]);
17086 assert!(
17087 f.run(&[b"TDIGEST.MERGE", b"s", b"1", b"t"])
17088 .starts_with("-WRONGTYPE")
17089 );
17090 assert!(
17091 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"s"])
17092 .starts_with("-WRONGTYPE")
17093 );
17094 assert_eq!(
17095 f.run(&[b"COPY", b"t", b"t2"]),
17096 "-ERR not supported for this module key\r\n"
17097 );
17098 assert_eq!(
17099 f.run(&[b"DUMP", b"t"]),
17100 "-ERR DUMP is not supported for this module key\r\n"
17101 );
17102 assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
17103 assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
17104 assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
17105 assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TDIS-TYPE\r\n");
17106 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
17107 assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
17108 assert_eq!(
17111 f.run(&[b"TDIGEST.INFO", b"t3"]),
17112 "-ERR T-Digest: key does not exist\r\n"
17113 );
17114 assert_eq!(
17117 f.run(&[b"TDIGEST.QUANTILE", b"t3", b"zzz"]),
17118 "-ERR T-Digest: key does not exist\r\n"
17119 );
17120 }
17121
17122 fn without_memory(reply: &str) -> String {
17132 let head = "+memoryUsage\r\n:";
17133 let at = reply.find(head).expect("every TS.INFO reports memory");
17134 let rest = &reply[at + head.len()..];
17135 let end = rest.find("\r\n").expect("and it is a whole number");
17136 format!("{}{}", &reply[..at + head.len()], &rest[end..])
17137 }
17138
17139 #[test]
17142 fn a_series_is_made_empty_and_reports_on_itself() {
17143 let mut f = Fixture::new();
17144 assert_eq!(f.run(&[b"TS.CREATE", b"t"]), "+OK\r\n");
17145 assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
17146 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t"]), "$3\r\nraw\r\n");
17147 assert_eq!(
17151 without_memory(&f.run(&[b"TS.INFO", b"t"])),
17152 "*28\r\n\
17153 +totalSamples\r\n:0\r\n\
17154 +memoryUsage\r\n:\r\n\
17155 +firstTimestamp\r\n:0\r\n\
17156 +lastTimestamp\r\n:0\r\n\
17157 +retentionTime\r\n:0\r\n\
17158 +chunkCount\r\n:1\r\n\
17159 +chunkSize\r\n:4096\r\n\
17160 +chunkType\r\n+compressed\r\n\
17161 +duplicatePolicy\r\n+block\r\n\
17162 +labels\r\n*0\r\n\
17163 +sourceKey\r\n$-1\r\n\
17164 +rules\r\n*0\r\n\
17165 +ignoreMaxTimeDiff\r\n:0\r\n\
17166 +ignoreMaxValDiff\r\n$1\r\n0\r\n"
17167 );
17168 assert_eq!(
17171 f.run(&[b"TS.CREATE", b"t"]),
17172 "-ERR TSDB: key already exists\r\n"
17173 );
17174 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
17175 assert_eq!(
17176 f.run(&[b"TS.CREATE", b"str"]),
17177 "-ERR TSDB: key already exists\r\n"
17178 );
17179 assert_eq!(
17182 f.run(&[b"TS.CREATE", b"t", b"RETENTION", b"abc"]),
17183 "-ERR TSDB: Couldn't parse RETENTION\r\n"
17184 );
17185 let wrong = "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
17191 assert_eq!(f.run(&[b"TS.INFO", b"str"]), wrong);
17192 assert_eq!(f.run(&[b"TS.GET", b"str"]), wrong);
17193 assert_eq!(f.run(&[b"TS.ALTER", b"str"]), wrong);
17194 assert_eq!(f.run(&[b"TS.DEL", b"str", b"0", b"1"]), wrong);
17195 assert_eq!(f.run(&[b"TS.INCRBY", b"str", b"1"]), wrong);
17196 assert_eq!(
17197 f.run(&[b"TS.ADD", b"str", b"1", b"1"]),
17198 "-ERR TSDB: the key is not a TSDB key\r\n"
17199 );
17200 assert_eq!(
17202 f.run(&[b"TS.INFO", b"nope"]),
17203 "-ERR TSDB: the key does not exist\r\n"
17204 );
17205 assert_eq!(
17206 f.run(&[b"TS.GET", b"nope"]),
17207 "-ERR TSDB: the key does not exist\r\n"
17208 );
17209 assert_eq!(
17210 f.run(&[b"TS.ALTER", b"nope"]),
17211 "-ERR TSDB: the key does not exist\r\n"
17212 );
17213 assert_eq!(
17214 f.run(&[b"TS.DEL", b"nope", b"1", b"2"]),
17215 "-ERR TSDB: the key does not exist\r\n"
17216 );
17217 }
17218
17219 #[test]
17222 fn the_options_are_a_keyword_scan_and_not_a_grammar() {
17223 let mut f = Fixture::new();
17224 assert_eq!(
17225 f.run(&[
17226 b"TS.CREATE",
17227 b"t",
17228 b"RETENTION",
17229 b"5000",
17230 b"ENCODING",
17231 b"UNCOMPRESSED",
17232 b"CHUNK_SIZE",
17233 b"128",
17234 b"DUPLICATE_POLICY",
17235 b"LAST",
17236 b"IGNORE",
17237 b"10",
17238 b"0.5",
17239 b"LABELS",
17240 b"room",
17241 b"kitchen"
17242 ]),
17243 "+OK\r\n"
17244 );
17245 let info = f.run(&[b"TS.INFO", b"t"]);
17246 assert!(info.contains("+retentionTime\r\n:5000\r\n"), "{info}");
17247 assert!(info.contains("+chunkSize\r\n:128\r\n"), "{info}");
17248 assert!(info.contains("+chunkType\r\n+uncompressed\r\n"), "{info}");
17249 assert!(info.contains("+duplicatePolicy\r\n+last\r\n"), "{info}");
17250 assert!(info.contains("+ignoreMaxTimeDiff\r\n:10\r\n"), "{info}");
17251 assert!(
17254 info.contains("+ignoreMaxValDiff\r\n$3\r\n0.5\r\n"),
17255 "{info}"
17256 );
17257 assert!(
17258 info.contains("+labels\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n"),
17259 "{info}"
17260 );
17261
17262 assert_eq!(f.run(&[b"TS.CREATE", b"junk", b"FOO"]), "+OK\r\n");
17264 assert_eq!(
17268 f.run(&[
17269 b"TS.CREATE",
17270 b"g",
17271 b"LABELS",
17272 b"a",
17273 b"b",
17274 b"RETENTION",
17275 b"5"
17276 ]),
17277 "+OK\r\n"
17278 );
17279 let greedy = f.run(&[b"TS.INFO", b"g"]);
17280 assert!(greedy.contains("+retentionTime\r\n:5\r\n"), "{greedy}");
17281 assert!(
17282 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"),
17283 "{greedy}"
17284 );
17285
17286 assert_eq!(
17288 f.run(&[b"TS.CREATE", b"e", b"LABELS", b"a", b"b(c"]),
17289 "-ERR TSDB: Couldn't parse LABELS\r\n"
17290 );
17291 assert_eq!(
17292 f.run(&[b"TS.CREATE", b"e", b"LABELS", b"", b"b"]),
17293 "-ERR TSDB: Couldn't parse LABELS\r\n"
17294 );
17295 assert_eq!(
17296 f.run(&[b"TS.CREATE", b"e", b"RETENTION"]),
17297 "-ERR TSDB: Couldn't parse RETENTION\r\n"
17298 );
17299 assert_eq!(
17302 f.run(&[b"TS.CREATE", b"e", b"RETENTION", b"-1"]),
17303 "-TSDB: Couldn't parse RETENTION\r\n"
17304 );
17305 assert_eq!(
17306 f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"abc"]),
17307 "-ERR TSDB: Couldn't parse CHUNK_SIZE\r\n"
17308 );
17309 assert_eq!(
17310 f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"100"]),
17311 "-ERR TSDB: CHUNK_SIZE value must be a multiple of 8 in the range [48 .. 1048576]\r\n"
17312 );
17313 assert_eq!(
17314 f.run(&[b"TS.CREATE", b"e", b"ENCODING", b"nope"]),
17315 "-ERR TSDB: unknown ENCODING parameter\r\n"
17316 );
17317 assert!(
17320 f.run(&[b"TS.CREATE", b"e", b"ENCODING"])
17321 .contains("wrong number of arguments for 'ts.create' command")
17322 );
17323 assert_eq!(
17324 f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY"]),
17325 "-ERR TSDB: Couldn't parse DUPLICATE_POLICY\r\n"
17326 );
17327 assert_eq!(
17328 f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY", b"nope"]),
17329 "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
17330 );
17331 assert_eq!(
17332 f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"10"]),
17333 "-ERR TSDB: Couldn't parse IGNORE\r\n"
17334 );
17335 assert_eq!(
17336 f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"-1", b"1"]),
17337 "-ERR TSDB: IGNORE arguments cannot be negative\r\n"
17338 );
17339 assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
17340
17341 assert_eq!(f.run(&[b"TS.ALTER", b"t", b"RETENTION", b"9"]), "+OK\r\n");
17344 let after = f.run(&[b"TS.INFO", b"t"]);
17345 assert!(after.contains("+retentionTime\r\n:9\r\n"), "{after}");
17346 assert!(after.contains("+chunkSize\r\n:128\r\n"), "{after}");
17347 assert!(after.contains("+duplicatePolicy\r\n+last\r\n"), "{after}");
17348 assert_eq!(
17349 f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"nope"]),
17350 "-ERR TSDB: unknown ENCODING parameter\r\n"
17351 );
17352 assert_eq!(
17354 f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"COMPRESSED"]),
17355 "+OK\r\n"
17356 );
17357 assert!(
17358 f.run(&[b"TS.INFO", b"t"])
17359 .contains("+chunkType\r\n+uncompressed\r\n")
17360 );
17361 }
17362
17363 #[test]
17366 fn samples_land_where_they_are_put_and_the_newest_comes_back() {
17367 let mut f = Fixture::new();
17368 assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1.5"]), ":100\r\n");
17369 assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
17371 assert_eq!(f.run(&[b"TS.ADD", b"t", b"200", b"2"]), ":200\r\n");
17372 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+2\r\n");
17375 assert_eq!(f.run(&[b"TS.ADD", b"t", b"300", b"1e300"]), ":300\r\n");
17376 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+1E300\r\n");
17377 assert_eq!(f.run(&[b"TS.CREATE", b"empty"]), "+OK\r\n");
17380 assert_eq!(f.run(&[b"TS.GET", b"empty"]), "*0\r\n");
17381
17382 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
17385 assert_eq!(
17386 f.run(&[b"TS.ADD", b"str", b"1", b".5"]),
17387 "-ERR TSDB: invalid value\r\n"
17388 );
17389 for bad in [
17393 &b".5"[..],
17394 b"1.",
17395 b"+1",
17396 b" 1",
17397 b"0x10",
17398 b"inf",
17399 b"1e400",
17400 b"--1",
17401 b"1e",
17402 ] {
17403 assert_eq!(
17404 f.run(&[b"TS.ADD", b"v", b"1", bad]),
17405 "-ERR TSDB: invalid value\r\n",
17406 "{}",
17407 String::from_utf8_lossy(bad)
17408 );
17409 }
17410 assert_eq!(f.run(&[b"TS.ADD", b"v", b"1", b"NaN"]), ":1\r\n");
17412
17413 assert_eq!(
17416 f.run(&[b"TS.ADD", b"t", b"abc", b"1"]),
17417 "-ERR TSDB: invalid timestamp\r\n"
17418 );
17419 assert_eq!(
17420 f.run(&[b"TS.ADD", b"t", b"-1", b"1"]),
17421 "-ERR TSDB: invalid timestamp, must be a nonnegative integer\r\n"
17422 );
17423
17424 assert_eq!(
17427 f.run(&[b"TS.ADD", b"t", b"300", b"7"]),
17428 "-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"
17429 );
17430 assert_eq!(
17431 f.run(&[b"TS.ADD", b"t", b"300", b"7", b"ON_DUPLICATE", b"LAST"]),
17432 ":300\r\n"
17433 );
17434 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+7\r\n");
17435 assert_eq!(
17438 f.run(&[b"TS.ADD", b"fresh", b"1", b"1", b"ON_DUPLICATE", b"nope"]),
17439 ":1\r\n"
17440 );
17441 assert_eq!(
17442 f.run(&[b"TS.ADD", b"fresh", b"2", b"1", b"ON_DUPLICATE", b"nope"]),
17443 "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
17444 );
17445
17446 assert_eq!(f.run(&[b"TS.CREATE", b"r", b"RETENTION", b"50"]), "+OK\r\n");
17449 assert_eq!(f.run(&[b"TS.ADD", b"r", b"1000", b"1"]), ":1000\r\n");
17450 assert_eq!(f.run(&[b"TS.ADD", b"r", b"960", b"1"]), ":960\r\n");
17451 assert_eq!(
17452 f.run(&[b"TS.ADD", b"r", b"940", b"1"]),
17453 "-ERR TSDB: Timestamp is older than retention\r\n"
17454 );
17455 assert_eq!(f.run(&[b"TS.ADD", b"r", b"1100", b"1"]), ":1100\r\n");
17457 assert!(
17458 f.run(&[b"TS.INFO", b"r"])
17459 .contains("+totalSamples\r\n:1\r\n")
17460 );
17461
17462 assert_eq!(
17465 f.run(&[
17466 b"TS.CREATE",
17467 b"i",
17468 b"DUPLICATE_POLICY",
17469 b"LAST",
17470 b"IGNORE",
17471 b"10",
17472 b"0.5"
17473 ]),
17474 "+OK\r\n"
17475 );
17476 assert_eq!(f.run(&[b"TS.ADD", b"i", b"1000", b"1"]), ":1000\r\n");
17477 assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"1.2"]), ":1000\r\n");
17478 assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"9"]), ":1005\r\n");
17479 }
17480
17481 #[test]
17484 fn a_madd_answers_each_triple_and_creates_nothing() {
17485 let mut f = Fixture::new();
17486 assert_eq!(f.run(&[b"TS.CREATE", b"a"]), "+OK\r\n");
17487 assert_eq!(f.run(&[b"TS.CREATE", b"b"]), "+OK\r\n");
17488 assert_eq!(
17489 f.run(&[
17490 b"TS.MADD", b"a", b"100", b"1", b"b", b"100", b"2", b"a", b"200", b"3"
17491 ]),
17492 "*3\r\n:100\r\n:100\r\n:200\r\n"
17493 );
17494 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
17497 assert_eq!(
17498 f.run(&[
17499 b"TS.MADD", b"gone", b"1", b"1", b"str", b"1", b"1", b"a", b"300", b"4"
17500 ]),
17501 "*3\r\n\
17502 -ERR TSDB: the key is not a TSDB key\r\n\
17503 -ERR TSDB: the key is not a TSDB key\r\n\
17504 :300\r\n"
17505 );
17506 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
17507 assert_eq!(
17509 f.run(&[b"TS.MADD", b"a", b"400", b"zzz", b"a", b"abc", b"1"]),
17510 "*2\r\n-ERR TSDB: invalid value\r\n-ERR TSDB: invalid timestamp\r\n"
17511 );
17512 assert!(
17514 f.run(&[b"TS.MADD", b"a", b"1", b"1", b"a"])
17515 .contains("wrong number of arguments for 'ts.madd' command")
17516 );
17517 }
17518
17519 #[test]
17521 fn an_increment_walks_the_newest_value_up_and_down() {
17522 let mut f = Fixture::new();
17523 assert_eq!(
17524 f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
17525 ":100\r\n"
17526 );
17527 assert_eq!(
17528 f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
17529 ":100\r\n"
17530 );
17531 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n+10\r\n");
17534 assert_eq!(
17535 f.run(&[b"TS.DECRBY", b"t", b"3", b"TIMESTAMP", b"200"]),
17536 ":200\r\n"
17537 );
17538 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+7\r\n");
17539 assert_eq!(
17542 f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP", b"150"]),
17543 "-TSDB: timestamp must be equal to or higher than the maximum existing timestamp\r\n"
17544 );
17545 assert_eq!(
17549 f.run(&[b"TS.INCRBY", b"p", b"+5", b"TIMESTAMP", b"1"]),
17550 ":1\r\n"
17551 );
17552 assert_eq!(
17553 f.run(&[b"TS.INCRBY", b"q", b".5", b"TIMESTAMP", b"1"]),
17554 ":1\r\n"
17555 );
17556 assert_eq!(
17557 f.run(&[b"TS.INCRBY", b"t", b"nan"]),
17558 "-ERR TSDB: invalid increase/decrease value\r\n"
17559 );
17560 assert_eq!(
17561 f.run(&[b"TS.INCRBY", b"t", b"zzz"]),
17562 "-ERR TSDB: invalid increase/decrease value\r\n"
17563 );
17564 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
17567 assert_eq!(
17568 f.run(&[b"TS.INCRBY", b"str", b"zzz"]),
17569 "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
17570 );
17571 assert_eq!(
17576 f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP"]),
17577 "-ERR TSDB: invalid timestamp\r\n"
17578 );
17579 assert_eq!(
17582 f.run(&[b"TS.INCRBY", b"lab", b"1", b"LABELS", b"TIMESTAMP", b"5"]),
17583 format!(":{}\r\n", f.server.now_ms())
17584 );
17585 assert_eq!(f.run(&[b"TS.ADD", b"n", b"1", b"nan"]), ":1\r\n");
17587 assert_eq!(
17588 f.run(&[b"TS.INCRBY", b"n", b"1", b"TIMESTAMP", b"2"]),
17589 "-ERR TSDB: cannot increment/decrement NaN value\r\n"
17590 );
17591 }
17592
17593 #[test]
17595 fn deleting_takes_out_a_span_and_answers_how_many_went() {
17596 let mut f = Fixture::new();
17597 for at in [b"100".as_slice(), b"200", b"300", b"400"] {
17598 f.run(&[b"TS.ADD", b"t", at, b"1"]);
17599 }
17600 assert_eq!(f.run(&[b"TS.DEL", b"t", b"200", b"300"]), ":2\r\n");
17601 assert!(
17602 f.run(&[b"TS.INFO", b"t"])
17603 .contains("+totalSamples\r\n:2\r\n")
17604 );
17605 assert_eq!(f.run(&[b"TS.DEL", b"t", b"400", b"100"]), ":0\r\n");
17607 assert_eq!(f.run(&[b"TS.DEL", b"t", b"-", b"+"]), ":2\r\n");
17609 let empty = f.run(&[b"TS.INFO", b"t"]);
17612 assert!(empty.contains("+totalSamples\r\n:0\r\n"), "{empty}");
17613 assert!(empty.contains("+chunkCount\r\n:1\r\n"), "{empty}");
17614 assert!(empty.contains("+firstTimestamp\r\n:0\r\n"), "{empty}");
17615 assert!(empty.contains("+lastTimestamp\r\n:0\r\n"), "{empty}");
17616 assert_eq!(f.run(&[b"TS.DEL", b"t", b"0", b"1000"]), ":0\r\n");
17617 assert_eq!(
17619 f.run(&[b"TS.DEL", b"t", b"abc", b"5"]),
17620 "-ERR TSDB: wrong fromTimestamp\r\n"
17621 );
17622 assert_eq!(
17623 f.run(&[b"TS.DEL", b"t", b"5", b"abc"]),
17624 "-ERR TSDB: wrong toTimestamp\r\n"
17625 );
17626 assert_eq!(
17627 f.run(&[b"TS.DEL", b"t", b"-5", b"5"]),
17628 "-ERR TSDB: wrong fromTimestamp\r\n"
17629 );
17630 }
17631
17632 #[test]
17635 fn resp3_writes_a_sample_as_a_double_and_the_info_as_a_map() {
17636 let mut f = Fixture::new();
17637 f.out = Out::new(Proto::Resp3);
17638 assert_eq!(
17639 f.run(&[b"TS.CREATE", b"t", b"LABELS", b"room", b"kitchen"]),
17640 "+OK\r\n"
17641 );
17642 assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1e300"]), ":100\r\n");
17643 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n,1e+300\r\n");
17645 assert_eq!(
17646 without_memory(&f.run(&[b"TS.INFO", b"t"])),
17647 "%14\r\n\
17648 +totalSamples\r\n:1\r\n\
17649 +memoryUsage\r\n:\r\n\
17650 +firstTimestamp\r\n:100\r\n\
17651 +lastTimestamp\r\n:100\r\n\
17652 +retentionTime\r\n:0\r\n\
17653 +chunkCount\r\n:1\r\n\
17654 +chunkSize\r\n:4096\r\n\
17655 +chunkType\r\n+compressed\r\n\
17656 +duplicatePolicy\r\n+block\r\n\
17657 +labels\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
17658 +sourceKey\r\n_\r\n\
17659 +rules\r\n%0\r\n\
17660 +ignoreMaxTimeDiff\r\n:0\r\n\
17661 +ignoreMaxValDiff\r\n,0\r\n"
17662 );
17663 }
17664
17665 #[test]
17668 fn a_range_walks_a_span_and_a_revrange_walks_it_backwards() {
17669 let mut f = Fixture::new();
17670 for (at, v) in [
17671 (b"100".as_slice(), b"1".as_slice()),
17672 (b"200", b"2"),
17673 (b"300", b"3"),
17674 (b"400", b"4"),
17675 ] {
17676 f.run(&[b"TS.ADD", b"t", at, v]);
17677 }
17678 assert_eq!(
17679 f.run(&[b"TS.RANGE", b"t", b"-", b"+"]),
17680 "*4\r\n*2\r\n:100\r\n+1\r\n*2\r\n:200\r\n+2\r\n\
17681 *2\r\n:300\r\n+3\r\n*2\r\n:400\r\n+4\r\n"
17682 );
17683 assert_eq!(
17685 f.run(&[b"TS.RANGE", b"t", b"150", b"350"]),
17686 "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
17687 );
17688 assert_eq!(
17691 f.run(&[b"TS.REVRANGE", b"t", b"-", b"+", b"COUNT", b"2"]),
17692 "*2\r\n*2\r\n:400\r\n+4\r\n*2\r\n:300\r\n+3\r\n"
17693 );
17694 assert_eq!(f.run(&[b"TS.RANGE", b"t", b"400", b"100"]), "*0\r\n");
17696 assert_eq!(
17698 f.run(&[
17699 b"TS.RANGE",
17700 b"t",
17701 b"-",
17702 b"+",
17703 b"FILTER_BY_VALUE",
17704 b"2",
17705 b"3"
17706 ]),
17707 "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
17708 );
17709 assert_eq!(
17710 f.run(&[
17711 b"TS.RANGE",
17712 b"t",
17713 b"-",
17714 b"+",
17715 b"FILTER_BY_TS",
17716 b"100",
17717 b"400"
17718 ]),
17719 "*2\r\n*2\r\n:100\r\n+1\r\n*2\r\n:400\r\n+4\r\n"
17720 );
17721 assert_eq!(
17723 f.run(&[
17724 b"TS.RANGE",
17725 b"t",
17726 b"-",
17727 b"+",
17728 b"ZZZ",
17729 b"FILTER_BY_TS",
17730 b"400"
17731 ]),
17732 "*1\r\n*2\r\n:400\r\n+4\r\n"
17733 );
17734 assert_eq!(
17736 f.run(&[b"TS.RANGE", b"t", b"-", b"+", b"LATEST", b"COUNT", b"1"]),
17737 "*1\r\n*2\r\n:100\r\n+1\r\n"
17738 );
17739 }
17740
17741 #[test]
17743 fn aggregation_puts_one_column_a_reduction_in_a_flat_row() {
17744 let mut f = Fixture::new();
17745 for (at, v) in [
17746 (b"100".as_slice(), b"1".as_slice()),
17747 (b"200", b"2"),
17748 (b"300", b"3"),
17749 (b"400", b"4"),
17750 ] {
17751 f.run(&[b"TS.ADD", b"t", at, v]);
17752 }
17753 assert_eq!(
17754 f.run(&[
17755 b"TS.RANGE",
17756 b"t",
17757 b"-",
17758 b"+",
17759 b"AGGREGATION",
17760 b"avg",
17761 b"200"
17762 ]),
17763 "*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"
17764 );
17765 assert_eq!(
17768 f.run(&[
17769 b"TS.RANGE",
17770 b"t",
17771 b"-",
17772 b"+",
17773 b"AGGREGATION",
17774 b"min,max,count",
17775 b"200"
17776 ]),
17777 "*3\r\n\
17778 *4\r\n:0\r\n+1\r\n+1\r\n+1\r\n\
17779 *4\r\n:200\r\n+2\r\n+3\r\n+2\r\n\
17780 *4\r\n:400\r\n+4\r\n+4\r\n+1\r\n"
17781 );
17782 assert_eq!(
17784 f.run(&[
17785 b"TS.RANGE",
17786 b"t",
17787 b"-",
17788 b"+",
17789 b"AGGREGATION",
17790 b"avg",
17791 b"200",
17792 b"BUCKETTIMESTAMP",
17793 b"+"
17794 ]),
17795 "*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"
17796 );
17797 assert_eq!(
17799 f.run(&[
17800 b"TS.RANGE",
17801 b"t",
17802 b"100",
17803 b"400",
17804 b"ALIGN",
17805 b"100",
17806 b"AGGREGATION",
17807 b"sum",
17808 b"200"
17809 ]),
17810 "*2\r\n*2\r\n:100\r\n+3\r\n*2\r\n:300\r\n+7\r\n"
17811 );
17812 assert_eq!(
17815 f.run(&[
17816 b"TS.RANGE",
17817 b"t",
17818 b"-",
17819 b"+",
17820 b"AGGREGATION",
17821 b"count",
17822 b"200"
17823 ]),
17824 "*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"
17825 );
17826 assert_eq!(
17827 f.run(&[
17828 b"TS.RANGE",
17829 b"t",
17830 b"-",
17831 b"+",
17832 b"AGGREGATION",
17833 b"count",
17834 b"200",
17835 b"COUNT",
17836 b"1"
17837 ]),
17838 "*1\r\n*2\r\n:0\r\n+1\r\n"
17839 );
17840 }
17841
17842 #[test]
17845 fn empty_fills_a_gap_and_last_carries_the_reading_before_it() {
17846 let mut f = Fixture::new();
17847 for (at, v) in [
17848 (b"0".as_slice(), b"1".as_slice()),
17849 (b"100", b"2"),
17850 (b"500", b"nan"),
17851 (b"600", b"3"),
17852 ] {
17853 f.run(&[b"TS.ADD", b"g", at, v]);
17854 }
17855 assert_eq!(
17858 f.run(&[
17859 b"TS.RANGE",
17860 b"g",
17861 b"-",
17862 b"+",
17863 b"AGGREGATION",
17864 b"avg",
17865 b"100"
17866 ]),
17867 "*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"
17868 );
17869 assert_eq!(
17871 f.run(&[
17872 b"TS.RANGE",
17873 b"g",
17874 b"-",
17875 b"+",
17876 b"AGGREGATION",
17877 b"sum",
17878 b"100",
17879 b"EMPTY"
17880 ]),
17881 "*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\
17882 *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\
17883 *2\r\n:600\r\n+3\r\n"
17884 );
17885 assert_eq!(
17890 f.run(&[
17891 b"TS.RANGE",
17892 b"g",
17893 b"-",
17894 b"+",
17895 b"AGGREGATION",
17896 b"last",
17897 b"100",
17898 b"EMPTY"
17899 ]),
17900 "*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\
17901 *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\
17902 *2\r\n:600\r\n+3\r\n"
17903 );
17904 assert_eq!(
17905 f.run(&[
17906 b"TS.REVRANGE",
17907 b"g",
17908 b"-",
17909 b"+",
17910 b"AGGREGATION",
17911 b"last",
17912 b"100",
17913 b"EMPTY"
17914 ]),
17915 "*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\
17916 *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\
17917 *2\r\n:0\r\n+1\r\n"
17918 );
17919 assert_eq!(
17922 f.run(&[
17923 b"TS.RANGE",
17924 b"g",
17925 b"500",
17926 b"600",
17927 b"AGGREGATION",
17928 b"last",
17929 b"100",
17930 b"EMPTY"
17931 ]),
17932 "*2\r\n*2\r\n:500\r\n+NaN\r\n*2\r\n:600\r\n+3\r\n"
17933 );
17934 }
17935
17936 #[test]
17939 fn a_range_says_what_the_module_says_when_the_options_do_not_add_up() {
17940 let mut f = Fixture::new();
17941 f.run(&[b"TS.ADD", b"t", b"100", b"1"]);
17942 f.run(&[b"SET", b"str", b"x"]);
17943 let cases: &[(&[&[u8]], &str)] = &[
17944 (
17945 &[b"TS.RANGE", b"t"],
17946 "-ERR wrong number of arguments for 'ts.range' command\r\n",
17947 ),
17948 (
17950 &[b"TS.RANGE", b"gone", b"-", b"+", b"COUNT", b"x"],
17951 "-ERR TSDB: the key does not exist\r\n",
17952 ),
17953 (
17954 &[b"TS.RANGE", b"str", b"-", b"+"],
17955 "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n",
17956 ),
17957 (
17958 &[b"TS.RANGE", b"t", b"abc", b"+"],
17959 "-ERR TSDB: wrong fromTimestamp\r\n",
17960 ),
17961 (
17962 &[b"TS.RANGE", b"t", b"-", b"abc"],
17963 "-ERR TSDB: wrong toTimestamp\r\n",
17964 ),
17965 (
17966 &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT"],
17967 "-ERR TSDB: COUNT argument is missing\r\n",
17968 ),
17969 (
17970 &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"x"],
17971 "-ERR TSDB: Couldn't parse COUNT\r\n",
17972 ),
17973 (
17974 &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"0"],
17975 "-ERR TSDB: Invalid COUNT value\r\n",
17976 ),
17977 (
17978 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg"],
17979 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
17980 ),
17981 (
17982 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"x"],
17983 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
17984 ),
17985 (
17986 &[
17987 b"TS.RANGE",
17988 b"t",
17989 b"-",
17990 b"+",
17991 b"AGGREGATION",
17992 b"nope",
17993 b"100",
17994 ],
17995 "-ERR TSDB: Unknown aggregation type\r\n",
17996 ),
17997 (
17998 &[
17999 b"TS.RANGE",
18000 b"t",
18001 b"-",
18002 b"+",
18003 b"AGGREGATION",
18004 b"avg,,min",
18005 b"100",
18006 ],
18007 "-ERR TSDB: Empty aggregation type in list\r\n",
18008 ),
18009 (
18011 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"nope", b"0"],
18012 "-ERR TSDB: Unknown aggregation type\r\n",
18013 ),
18014 (
18015 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"0"],
18016 "-ERR TSDB: bucketDuration must be greater than zero\r\n",
18017 ),
18018 (
18019 &[
18020 b"TS.RANGE",
18021 b"t",
18022 b"-",
18023 b"+",
18024 b"AGGREGATION",
18025 b"avg",
18026 b"100",
18027 b"X",
18028 b"EMPTY",
18029 ],
18030 "-ERR TSDB: EMPTY flag should be the 3rd or 5th flag after AGGREGATION flag\r\n",
18031 ),
18032 (
18033 &[
18034 b"TS.RANGE",
18035 b"t",
18036 b"-",
18037 b"+",
18038 b"AGGREGATION",
18039 b"avg",
18040 b"100",
18041 b"BUCKETTIMESTAMP",
18042 b"z",
18043 ],
18044 "-ERR TSDB: unknown BUCKETTIMESTAMP parameter\r\n",
18045 ),
18046 (
18047 &[
18048 b"TS.RANGE",
18049 b"t",
18050 b"-",
18051 b"+",
18052 b"AGGREGATION",
18053 b"avg",
18054 b"100",
18055 b"X",
18056 b"Y",
18057 b"BUCKETTIMESTAMP",
18058 b"-",
18059 ],
18060 "-ERR TSDB: BUCKETTIMESTAMP flag should be the 3rd or 4th flag after \
18061 AGGREGATION flag\r\n",
18062 ),
18063 (
18064 &[
18065 b"TS.RANGE",
18066 b"t",
18067 b"-",
18068 b"+",
18069 b"ALIGN",
18070 b"z",
18071 b"AGGREGATION",
18072 b"avg",
18073 b"100",
18074 ],
18075 "-ERR TSDB: unknown ALIGN parameter\r\n",
18076 ),
18077 (
18078 &[b"TS.RANGE", b"t", b"-", b"+", b"ALIGN", b"5"],
18079 "-ERR TSDB: ALIGN parameter can only be used with AGGREGATION\r\n",
18080 ),
18081 (
18082 &[
18083 b"TS.RANGE",
18084 b"t",
18085 b"-",
18086 b"+",
18087 b"ALIGN",
18088 b"-",
18089 b"AGGREGATION",
18090 b"avg",
18091 b"100",
18092 ],
18093 "-ERR TSDB: start alignment can only be used with explicit start timestamp\r\n",
18094 ),
18095 (
18096 &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_VALUE", b"1"],
18097 "-ERR TSDB: FILTER_BY_VALUE one or more arguments are missing\r\n",
18098 ),
18099 (
18100 &[
18101 b"TS.RANGE",
18102 b"t",
18103 b"-",
18104 b"+",
18105 b"FILTER_BY_VALUE",
18106 b"x",
18107 b"2",
18108 ],
18109 "-ERR TSDB: Couldn't parse MIN\r\n",
18110 ),
18111 (
18112 &[
18113 b"TS.RANGE",
18114 b"t",
18115 b"-",
18116 b"+",
18117 b"FILTER_BY_VALUE",
18118 b"1",
18119 b"y",
18120 ],
18121 "-ERR TSDB: Couldn't parse MAX\r\n",
18122 ),
18123 (
18124 &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_TS"],
18125 "-ERR TSDB: FILTER_BY_TS one or more arguments are missing\r\n",
18126 ),
18127 ];
18128 for (argv, want) in cases {
18129 let got = f.run(argv);
18130 assert_eq!(&got, want, "{:?}", argv.last());
18131 }
18132 f.run(&[b"TS.ADD", b"wide", b"0", b"1"]);
18136 f.run(&[b"TS.ADD", b"wide", b"1000000000000", b"2"]);
18137 assert_eq!(
18138 f.run(&[
18139 b"TS.RANGE",
18140 b"wide",
18141 b"-",
18142 b"+",
18143 b"AGGREGATION",
18144 b"avg",
18145 b"1",
18146 b"EMPTY"
18147 ]),
18148 "-ERR TSDB: the requested range holds too many empty buckets\r\n"
18149 );
18150 }
18151
18152 #[test]
18154 fn resp3_writes_a_read_value_as_a_double() {
18155 let mut f = Fixture::new();
18156 f.out = Out::new(Proto::Resp3);
18157 for (at, v) in [
18158 (b"0".as_slice(), b"1".as_slice()),
18159 (b"100", b"2"),
18160 (b"500", b"nan"),
18161 (b"600", b"3"),
18162 ] {
18163 f.run(&[b"TS.ADD", b"g", at, v]);
18164 }
18165 assert_eq!(
18166 f.run(&[
18167 b"TS.RANGE",
18168 b"g",
18169 b"0",
18170 b"100",
18171 b"AGGREGATION",
18172 b"avg,min",
18173 b"200"
18174 ]),
18175 "*1\r\n*3\r\n:0\r\n,1.5\r\n,1\r\n"
18176 );
18177 assert_eq!(
18178 f.run(&[
18179 b"TS.RANGE",
18180 b"g",
18181 b"500",
18182 b"600",
18183 b"AGGREGATION",
18184 b"last",
18185 b"100",
18186 b"EMPTY"
18187 ]),
18188 "*2\r\n*2\r\n:500\r\n,nan\r\n*2\r\n:600\r\n,3\r\n"
18189 );
18190 }
18191
18192 fn joined() -> Fixture {
18195 let mut f = Fixture::new();
18196 f.run(&[b"TS.CREATE", b"z"]);
18197 for (at, v) in [
18198 (b"10".as_slice(), b"1".as_slice()),
18199 (b"20", b"2"),
18200 (b"40", b"4"),
18201 (b"50", b"5"),
18202 ] {
18203 f.run(&[b"TS.ADD", b"x", at, v]);
18204 }
18205 for (at, v) in [
18206 (b"20".as_slice(), b"20".as_slice()),
18207 (b"30", b"30"),
18208 (b"50", b"50"),
18209 (b"60", b"60"),
18210 ] {
18211 f.run(&[b"TS.ADD", b"y", at, v]);
18212 }
18213 f
18214 }
18215
18216 #[test]
18220 fn an_nrange_joins_its_keys_on_the_timestamp() {
18221 let mut f = joined();
18222 assert_eq!(
18224 f.run(&[b"TS.NRANGE", b"1", b"x", b"-", b"+"]),
18225 "*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\
18226 *2\r\n:40\r\n*1\r\n+4\r\n*2\r\n:50\r\n*1\r\n+5\r\n"
18227 );
18228 assert_eq!(
18230 f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+"]),
18231 "*6\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n\
18232 *2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
18233 *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
18234 *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
18235 *2\r\n:50\r\n*2\r\n+5\r\n+50\r\n\
18236 *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
18237 );
18238 assert_eq!(
18241 f.run(&[b"TS.NRANGE", b"2", b"x", b"z", b"20", b"40"]),
18242 "*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"
18243 );
18244 assert_eq!(
18245 f.run(&[b"TS.NRANGE", b"2", b"x", b"x", b"40", b"50"]),
18246 "*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"
18247 );
18248 assert_eq!(
18251 f.run(&[
18252 b"TS.NREVRANGE",
18253 b"2",
18254 b"x",
18255 b"y",
18256 b"-",
18257 b"+",
18258 b"COUNT",
18259 b"1"
18260 ]),
18261 "*1\r\n*2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
18262 );
18263 assert_eq!(
18264 f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+", b"COUNT", b"1"]),
18265 "*1\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n"
18266 );
18267 assert_eq!(
18269 f.run(&[
18270 b"TS.NRANGE",
18271 b"2",
18272 b"x",
18273 b"y",
18274 b"-",
18275 b"+",
18276 b"FILTER_BY_VALUE",
18277 b"2",
18278 b"30"
18279 ]),
18280 "*4\r\n*2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
18281 *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
18282 *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
18283 *2\r\n:50\r\n*2\r\n+5\r\n+NaN\r\n"
18284 );
18285 }
18286
18287 #[test]
18291 fn an_nrange_aggregation_names_one_reduction_a_key() {
18292 let mut f = joined();
18293 assert_eq!(
18294 f.run(&[
18295 b"TS.NRANGE",
18296 b"2",
18297 b"x",
18298 b"y",
18299 b"-",
18300 b"+",
18301 b"AGGREGATION",
18302 b"sum",
18303 b"sum",
18304 b"20"
18305 ]),
18306 "*4\r\n*2\r\n:0\r\n*2\r\n+1\r\n+NaN\r\n\
18307 *2\r\n:20\r\n*2\r\n+2\r\n+50\r\n\
18308 *2\r\n:40\r\n*2\r\n+9\r\n+50\r\n\
18309 *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
18310 );
18311 assert_eq!(
18313 f.run(&[
18314 b"TS.NRANGE",
18315 b"2",
18316 b"x",
18317 b"y",
18318 b"-",
18319 b"+",
18320 b"AGGREGATION",
18321 b"sum,count",
18322 b"avg",
18323 b"20"
18324 ]),
18325 "*4\r\n*2\r\n:0\r\n*3\r\n+1\r\n+1\r\n+NaN\r\n\
18326 *2\r\n:20\r\n*3\r\n+2\r\n+1\r\n+25\r\n\
18327 *2\r\n:40\r\n*3\r\n+9\r\n+2\r\n+50\r\n\
18328 *2\r\n:60\r\n*3\r\n+NaN\r\n+NaN\r\n+60\r\n"
18329 );
18330 assert_eq!(
18333 f.run(&[
18334 b"TS.NRANGE",
18335 b"2",
18336 b"x",
18337 b"y",
18338 b"-",
18339 b"+",
18340 b"AGGREGATION",
18341 b"avg",
18342 b"sum",
18343 b"100",
18344 b"EMPTY",
18345 b"BUCKETTIMESTAMP",
18346 b"end"
18347 ]),
18348 "*1\r\n*2\r\n:100\r\n*2\r\n+3\r\n+160\r\n"
18349 );
18350 assert_eq!(
18353 f.run(&[
18354 b"TS.NRANGE",
18355 b"2",
18356 b"x",
18357 b"y",
18358 b"-",
18359 b"+",
18360 b"AGGREGATION",
18361 b"avg",
18362 b"COUNT",
18363 b"100"
18364 ]),
18365 "*1\r\n*2\r\n:0\r\n*2\r\n+3\r\n+4\r\n"
18366 );
18367 }
18368
18369 #[test]
18372 fn an_nrange_says_what_the_module_says_when_it_does_not_add_up() {
18373 let mut f = joined();
18374 f.run(&[b"SET", b"str", b"hi"]);
18375 let bad_keys = "-ERR TSDB: numkeys must be a positive integer\r\n";
18376 let numkeys = "-ERR TSDB: the number of AGGREGATION arguments \
18377 must be equal to numkeys\r\n";
18378 let cases: &[(&[&[u8]], &str)] = &[
18379 (&[b"TS.NRANGE", b"0", b"x", b"-", b"+"], bad_keys),
18380 (&[b"TS.NRANGE", b"-1", b"x", b"-", b"+"], bad_keys),
18381 (&[b"TS.NRANGE", b"abc", b"x", b"-", b"+"], bad_keys),
18382 (
18385 &[b"TS.NRANGE", b"2", b"x", b"-", b"+"],
18386 "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
18387 ),
18388 (
18389 &[b"TS.NRANGE", b"99", b"x", b"-", b"+"],
18390 "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
18391 ),
18392 (
18395 &[
18396 b"TS.NRANGE",
18397 b"2",
18398 b"x",
18399 b"y",
18400 b"abc",
18401 b"+",
18402 b"AGGREGATION",
18403 b"nope",
18404 b"sum",
18405 b"100",
18406 ],
18407 "-ERR TSDB: Unknown aggregation type\r\n",
18408 ),
18409 (
18410 &[b"TS.NRANGE", b"2", b"x", b"y", b"abc", b"+"],
18411 "-ERR TSDB: wrong fromTimestamp\r\n",
18412 ),
18413 (
18414 &[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"abc"],
18415 "-ERR TSDB: wrong toTimestamp\r\n",
18416 ),
18417 (
18421 &[
18422 b"TS.NRANGE",
18423 b"2",
18424 b"x",
18425 b"y",
18426 b"-",
18427 b"+",
18428 b"AGGREGATION",
18429 b"avg",
18430 ],
18431 numkeys,
18432 ),
18433 (
18434 &[
18435 b"TS.NRANGE",
18436 b"2",
18437 b"x",
18438 b"y",
18439 b"-",
18440 b"+",
18441 b"AGGREGATION",
18442 b"100",
18443 b"sum",
18444 b"100",
18445 ],
18446 numkeys,
18447 ),
18448 (
18449 &[
18450 b"TS.NRANGE",
18451 b"2",
18452 b"x",
18453 b"y",
18454 b"-",
18455 b"+",
18456 b"AGGREGATION",
18457 b"avg",
18458 b"sum",
18459 b"sum",
18460 b"100",
18461 ],
18462 numkeys,
18463 ),
18464 (
18465 &[
18466 b"TS.NRANGE",
18467 b"2",
18468 b"x",
18469 b"y",
18470 b"-",
18471 b"+",
18472 b"AGGREGATION",
18473 b"avg",
18474 b"sum",
18475 b"abc",
18476 ],
18477 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
18478 ),
18479 (
18480 &[
18481 b"TS.NRANGE",
18482 b"2",
18483 b"x",
18484 b"y",
18485 b"-",
18486 b"+",
18487 b"AGGREGATION",
18488 b"avg",
18489 b"sum",
18490 b"0",
18491 ],
18492 "-ERR TSDB: bucketDuration must be greater than zero\r\n",
18493 ),
18494 (
18497 &[b"TS.NRANGE", b"1", b"x", b"-", b"+", b"AGGREGATION", b"100"],
18498 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
18499 ),
18500 (
18501 &[
18502 b"TS.NRANGE",
18503 b"1",
18504 b"x",
18505 b"-",
18506 b"+",
18507 b"AGGREGATION",
18508 b"100",
18509 b"200",
18510 ],
18511 "-ERR TSDB: Unknown aggregation type\r\n",
18512 ),
18513 (
18515 &[b"TS.NRANGE", b"2", b"x", b"nope", b"-", b"+"],
18516 "-ERR TSDB: the key does not exist\r\n",
18517 ),
18518 (
18519 &[b"TS.NRANGE", b"2", b"str", b"nope", b"-", b"+"],
18520 "-ERR WRONGTYPE Operation against a key \
18521 holding the wrong kind of value\r\n",
18522 ),
18523 ];
18524 for (argv, want) in cases {
18525 let got = f.run(argv);
18526 assert_eq!(&got, want, "{argv:?}");
18527 }
18528 }
18529
18530 #[test]
18532 fn a_read_walks_from_a_timestamp_to_the_end_of_the_series() {
18533 let mut f = joined();
18534 assert_eq!(
18535 f.run(&[b"TS.READ", b"x", b"-"]),
18536 "*4\r\n*2\r\n:10\r\n+1\r\n*2\r\n:20\r\n+2\r\n\
18537 *2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
18538 );
18539 assert_eq!(
18542 f.run(&[b"TS.READ", b"x", b"+"]),
18543 "*1\r\n*2\r\n:50\r\n+5\r\n"
18544 );
18545 assert_eq!(
18546 f.run(&[b"TS.READ", b"x", b"25"]),
18547 "*2\r\n*2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
18548 );
18549 assert_eq!(f.run(&[b"TS.READ", b"x", b"99"]), "*0\r\n");
18552 assert_eq!(f.run(&[b"TS.READ", b"z", b"-"]), "*0\r\n");
18553 assert_eq!(f.run(&[b"TS.READ", b"z", b"+"]), "*0\r\n");
18554 assert_eq!(f.run(&[b"TS.READ", b"nope", b"-"]), "*0\r\n");
18555 assert_eq!(
18559 f.run(&[b"TS.READ", b"x", b"abc"]),
18560 "-TSDB: invalid timestamp\r\n"
18561 );
18562 assert_eq!(
18563 f.run(&[b"TS.READ", b"x", b"-1"]),
18564 "-TSDB: invalid timestamp\r\n"
18565 );
18566 f.run(&[b"SET", b"str", b"hi"]);
18567 assert_eq!(
18568 f.run(&[b"TS.READ", b"str", b"-"]),
18569 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
18570 );
18571 assert_eq!(
18574 f.run(&[b"TS.READ", b"x"]),
18575 "-ERR wrong number of arguments for 'ts.read' command\r\n"
18576 );
18577 assert_eq!(
18578 f.run(&[b"TS.READ", b"x", b"-", b"COUNT", b"1"]),
18579 "-ERR wrong number of arguments for 'ts.read' command\r\n"
18580 );
18581 }
18582
18583 #[test]
18586 fn getkeys_reads_the_count_of_a_joined_read() {
18587 let mut f = Fixture::new();
18588 assert_eq!(
18589 f.run(&[
18590 b"COMMAND",
18591 b"GETKEYS",
18592 b"TS.NRANGE",
18593 b"2",
18594 b"a",
18595 b"b",
18596 b"-",
18597 b"+"
18598 ]),
18599 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
18600 );
18601 assert_eq!(
18602 f.run(&[
18603 b"COMMAND",
18604 b"GETKEYS",
18605 b"TS.NREVRANGE",
18606 b"1",
18607 b"a",
18608 b"-",
18609 b"+"
18610 ]),
18611 "*1\r\n$1\r\na\r\n"
18612 );
18613 for n in [b"0".as_slice(), b"9", b"abc"] {
18616 assert_eq!(
18617 f.run(&[b"COMMAND", b"GETKEYS", b"TS.NRANGE", n, b"a", b"-", b"+"]),
18618 "-ERR Invalid arguments specified for command\r\n"
18619 );
18620 }
18621 }
18622
18623 fn labelled() -> Fixture {
18625 let mut f = Fixture::new();
18626 f.run(&[
18627 b"TS.CREATE",
18628 b"a",
18629 b"LABELS",
18630 b"room",
18631 b"kitchen",
18632 b"x",
18633 b"1",
18634 ]);
18635 f.run(&[
18636 b"TS.CREATE",
18637 b"b",
18638 b"LABELS",
18639 b"room",
18640 b"bedroom",
18641 b"x",
18642 b"2",
18643 ]);
18644 f.run(&[b"TS.CREATE", b"c", b"LABELS", b"room", b"kitchen"]);
18645 f.run(&[b"TS.CREATE", b"d"]);
18646 f.run(&[b"TS.CREATE", b"e", b"LABELS", b"r", b"bb", b"r", b"b"]);
18647 f.run(&[b"TS.ADD", b"a", b"100", b"1.5"]);
18648 f.run(&[b"TS.ADD", b"b", b"200", b"2"]);
18649 f
18650 }
18651
18652 #[test]
18655 fn a_filter_is_taken_apart_the_way_the_module_takes_one_apart() {
18656 let mut f = labelled();
18657 let cases: &[(&[&[u8]], &str)] = &[
18658 (
18661 &[b"TS.QUERYINDEX", b"room=kitchen"],
18662 "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
18663 ),
18664 (
18665 &[b"TS.QUERYINDEX", b"room=kitchen", b"x=1"],
18666 "*1\r\n$1\r\na\r\n",
18667 ),
18668 (
18669 &[b"TS.QUERYINDEX", b"room=(kitchen,bedroom)"],
18670 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n",
18671 ),
18672 (&[b"TS.QUERYINDEX", b"room=()"], "*0\r\n"),
18675 (
18677 &[b"TS.QUERYINDEX", b"x=", b"room=kitchen"],
18678 "*1\r\n$1\r\nc\r\n",
18679 ),
18680 (
18681 &[b"TS.QUERYINDEX", b"room=kitchen", b"x!="],
18682 "*1\r\n$1\r\na\r\n",
18683 ),
18684 (
18685 &[b"TS.QUERYINDEX", b"room!=kitchen", b"x!="],
18686 "-ERR TSDB: please provide at least one matcher\r\n",
18687 ),
18688 (
18691 &[b"TS.QUERYINDEX", b"room==kitchen"],
18692 "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
18693 ),
18694 (
18695 &[b"TS.QUERYINDEX", b"room=kitchen=zz"],
18696 "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
18697 ),
18698 (&[b"TS.QUERYINDEX", b"room!!=kitchen", b"x=1"], "*0\r\n"),
18699 (&[b"TS.QUERYINDEX", b"()=1"], "*0\r\n"),
18702 (
18703 &[b"TS.QUERYINDEX", b"=(1)"],
18704 "-ERR TSDB: failed parsing labels\r\n",
18705 ),
18706 (
18707 &[b"TS.QUERYINDEX", b"room=(kitchen,)"],
18708 "-ERR TSDB: failed parsing labels\r\n",
18709 ),
18710 (
18711 &[b"TS.QUERYINDEX", b"room=(kitchen"],
18712 "-ERR TSDB: failed parsing labels\r\n",
18713 ),
18714 (&[b"TS.QUERYINDEX", b"room=x()"], "*0\r\n"),
18715 (
18716 &[b"TS.QUERYINDEX", b"nonsense"],
18717 "-ERR TSDB: failed parsing labels\r\n",
18718 ),
18719 (
18721 &[b"TS.QUERYINDEX", b"room!=kitchen"],
18722 "-ERR TSDB: please provide at least one matcher\r\n",
18723 ),
18724 (&[b"TS.QUERYINDEX", b"ROOM=kitchen"], "*0\r\n"),
18726 (&[b"TS.QUERYINDEX", b"room=KITCHEN"], "*0\r\n"),
18727 (
18728 &[b"TS.QUERYINDEX"],
18729 "-ERR wrong number of arguments for 'ts.queryindex' command\r\n",
18730 ),
18731 ];
18732 for (argv, want) in cases {
18733 let got = f.run(argv);
18734 assert_eq!(&got, want, "{:?}", argv.last());
18735 }
18736 }
18737
18738 #[test]
18740 fn querylabels_says_which_names_are_worn_and_what_they_are_set_to() {
18741 let mut f = labelled();
18742 let cases: &[(&[&[u8]], &str)] = &[
18743 (
18744 &[b"TS.QUERYLABELS", b"LABELS"],
18745 "*3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
18746 ),
18747 (
18748 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=kitchen"],
18749 "*2\r\n$4\r\nroom\r\n$1\r\nx\r\n",
18750 ),
18751 (
18752 &[b"TS.QUERYLABELS", b"VALUES", b"room"],
18753 "*2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
18754 ),
18755 (&[b"TS.QUERYLABELS", b"VALUES", b"r"], "*1\r\n$1\r\nb\r\n"),
18758 (&[b"TS.QUERYLABELS", b"VALUES", b"nolabel"], "*0\r\n"),
18759 (
18760 &[b"TS.QUERYLABELS", b"VALUES"],
18761 "-ERR wrong number of arguments for 'ts.querylabels' command\r\n",
18762 ),
18763 (
18764 &[b"TS.QUERYLABELS", b"ZZZ"],
18765 "-ERR TSDB: unknown subtype, must be one of LABELS|VALUES\r\n",
18766 ),
18767 (
18768 &[b"TS.QUERYLABELS", b"LABELS", b"ZZZ"],
18769 "-ERR TSDB: unknown argument, expected FILTER\r\n",
18770 ),
18771 (
18772 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER"],
18773 "-ERR TSDB: FILTER given with no filter expressions\r\n",
18774 ),
18775 (
18779 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room!=kitchen"],
18780 "-ERR TSDB: please provide at least one matcher\r\n",
18781 ),
18782 (
18783 &[
18784 b"TS.QUERYLABELS",
18785 b"LABELS",
18786 b"FILTER",
18787 b"room=kitchen",
18788 b"x=",
18789 ],
18790 "*1\r\n$4\r\nroom\r\n",
18791 ),
18792 ];
18793 for (argv, want) in cases {
18794 let got = f.run(argv);
18795 assert_eq!(&got, want, "{:?}", argv.last());
18796 }
18797 }
18798
18799 #[test]
18802 fn mget_writes_the_newest_sample_and_the_labels_that_were_asked_for() {
18803 let mut f = labelled();
18804 let cases: &[(&[&[u8]], &str)] = &[
18805 (
18808 &[b"TS.MGET", b"FILTER", b"room=kitchen"],
18809 "*2\r\n*3\r\n$1\r\na\r\n*0\r\n*2\r\n:100\r\n+1.5\r\n\
18810 *3\r\n$1\r\nc\r\n*0\r\n*0\r\n",
18811 ),
18812 (
18813 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
18814 "*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\
18815 *2\r\n$1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n+1.5\r\n\
18816 *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",
18817 ),
18818 (
18820 &[
18821 b"TS.MGET",
18822 b"SELECTED_LABELS",
18823 b"x",
18824 b"FILTER",
18825 b"room=kitchen",
18826 ],
18827 "*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\
18828 *2\r\n:100\r\n+1.5\r\n\
18829 *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",
18830 ),
18831 (
18834 &[b"TS.MGET", b"SELECTED_LABELS", b"r", b"FILTER", b"r=b"],
18835 "*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",
18836 ),
18837 (
18838 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
18839 "*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\
18840 *2\r\n$1\r\nr\r\n$1\r\nb\r\n*0\r\n",
18841 ),
18842 (
18845 &[b"TS.MGET", b"ZZZ", b"FILTER", b"room=bedroom"],
18846 "*1\r\n*3\r\n$1\r\nb\r\n*0\r\n*2\r\n:200\r\n+2\r\n",
18847 ),
18848 (
18849 &[b"TS.MGET", b"a", b"b", b"c"],
18850 "-ERR wrong number of arguments for 'ts.mget' command\r\n",
18851 ),
18852 (
18853 &[b"TS.MGET", b"FILTER"],
18854 "-ERR wrong number of arguments for 'ts.mget' command\r\n",
18855 ),
18856 (
18859 &[
18860 b"TS.MGET",
18861 b"WITHLABELS",
18862 b"SELECTED_LABELS",
18863 b"x",
18864 b"FILTER",
18865 b"bad",
18866 ],
18867 "-ERR TSDB: cannot accept WITHLABELS and SELECT_LABELS together\r\n",
18868 ),
18869 (
18870 &[b"TS.MGET", b"SELECTED_LABELS", b"FILTER", b"bad"],
18871 "-ERR TSDB: SELECT_LABELS should have at least 1 parameter\r\n",
18872 ),
18873 ];
18874 for (argv, want) in cases {
18875 let got = f.run(argv);
18876 assert_eq!(&got, want, "{:?}", argv.last());
18877 }
18878 }
18879
18880 #[test]
18883 fn resp3_writes_the_label_surface_as_sets_and_maps() {
18884 let mut f = labelled();
18885 f.out = Out::new(Proto::Resp3);
18886 let cases: &[(&[&[u8]], &str)] = &[
18887 (
18888 &[b"TS.QUERYINDEX", b"room=kitchen"],
18889 "~2\r\n$1\r\na\r\n$1\r\nc\r\n",
18890 ),
18891 (
18892 &[b"TS.QUERYLABELS", b"LABELS"],
18893 "~3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
18894 ),
18895 (
18896 &[b"TS.QUERYLABELS", b"VALUES", b"room"],
18897 "~2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
18898 ),
18899 (
18902 &[b"TS.MGET", b"FILTER", b"room=kitchen"],
18903 "%2\r\n$1\r\na\r\n*2\r\n%0\r\n*2\r\n:100\r\n,1.5\r\n\
18904 $1\r\nc\r\n*2\r\n%0\r\n*0\r\n",
18905 ),
18906 (
18907 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
18908 "%2\r\n$1\r\na\r\n*2\r\n%2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
18909 $1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n,1.5\r\n\
18910 $1\r\nc\r\n*2\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n*0\r\n",
18911 ),
18912 (
18913 &[
18914 b"TS.MGET",
18915 b"SELECTED_LABELS",
18916 b"x",
18917 b"FILTER",
18918 b"room=kitchen",
18919 ],
18920 "%2\r\n$1\r\na\r\n*2\r\n%1\r\n$1\r\nx\r\n$1\r\n1\r\n\
18921 *2\r\n:100\r\n,1.5\r\n\
18922 $1\r\nc\r\n*2\r\n%1\r\n$1\r\nx\r\n_\r\n*0\r\n",
18923 ),
18924 (
18927 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
18928 "%1\r\n$1\r\ne\r\n*2\r\n%2\r\n$1\r\nr\r\n$2\r\nbb\r\n\
18929 $1\r\nr\r\n$1\r\nb\r\n*0\r\n",
18930 ),
18931 ];
18932 for (argv, want) in cases {
18933 let got = f.run(argv);
18934 assert_eq!(&got, want, "{:?}", argv.last());
18935 }
18936 }
18937
18938 fn spanned() -> Fixture {
18941 let mut f = labelled();
18942 f.run(&[b"TS.ADD", b"a", b"200", b"2.5"]);
18943 f.run(&[b"TS.ADD", b"c", b"100", b"10"]);
18944 f.run(&[b"TS.ADD", b"c", b"300", b"30"]);
18945 f
18946 }
18947
18948 #[test]
18951 fn mrange_reads_every_series_and_folds_the_groups_it_is_asked_for() {
18952 let mut f = spanned();
18953 let cases: &[(&[&[u8]], &str)] = &[
18954 (
18955 &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=kitchen"],
18956 "*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\
18957 *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",
18958 ),
18959 (
18962 &[
18963 b"TS.MREVRANGE",
18964 b"-",
18965 b"+",
18966 b"WITHLABELS",
18967 b"FILTER",
18968 b"room=kitchen",
18969 ],
18970 "*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\
18971 *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\
18972 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
18973 *2\r\n*2\r\n:300\r\n+30\r\n*2\r\n:100\r\n+10\r\n",
18974 ),
18975 (
18978 &[
18979 b"TS.MRANGE",
18980 b"-",
18981 b"+",
18982 b"SELECTED_LABELS",
18983 b"x",
18984 b"FILTER",
18985 b"room=kitchen",
18986 ],
18987 "*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\
18988 *2\r\n*2\r\n:100\r\n+1.5\r\n*2\r\n:200\r\n+2.5\r\n\
18989 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$1\r\nx\r\n$-1\r\n\
18990 *2\r\n*2\r\n:100\r\n+10\r\n*2\r\n:300\r\n+30\r\n",
18991 ),
18992 (
18995 &[
18996 b"TS.MRANGE",
18997 b"-",
18998 b"+",
18999 b"FILTER",
19000 b"room=kitchen",
19001 b"GROUPBY",
19002 b"room",
19003 b"REDUCE",
19004 b"sum",
19005 ],
19006 "*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\
19007 *2\r\n:200\r\n+2.5\r\n*2\r\n:300\r\n+30\r\n",
19008 ),
19009 (
19012 &[
19013 b"TS.MRANGE",
19014 b"-",
19015 b"+",
19016 b"WITHLABELS",
19017 b"FILTER",
19018 b"room=kitchen",
19019 b"GROUPBY",
19020 b"room",
19021 b"REDUCE",
19022 b"max",
19023 ],
19024 "*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\
19025 *2\r\n$11\r\n__reducer__\r\n$3\r\nmax\r\n\
19026 *2\r\n$10\r\n__source__\r\n$3\r\na,c\r\n\
19027 *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",
19028 ),
19029 (
19031 &[
19032 b"TS.MREVRANGE",
19033 b"-",
19034 b"+",
19035 b"COUNT",
19036 b"1",
19037 b"FILTER",
19038 b"room=kitchen",
19039 b"GROUPBY",
19040 b"room",
19041 b"REDUCE",
19042 b"count",
19043 ],
19044 "*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",
19045 ),
19046 (
19048 &[
19049 b"TS.MRANGE",
19050 b"-",
19051 b"+",
19052 b"FILTER",
19053 b"room=kitchen",
19054 b"GROUPBY",
19055 b"nope",
19056 b"REDUCE",
19057 b"sum",
19058 ],
19059 "*0\r\n",
19060 ),
19061 (
19062 &[
19063 b"TS.MRANGE",
19064 b"-",
19065 b"+",
19066 b"AGGREGATION",
19067 b"sum,avg",
19068 b"100",
19069 b"FILTER",
19070 b"room=bedroom",
19071 ],
19072 "*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",
19073 ),
19074 (
19076 &[b"TS.MRANGE", b"-", b"+", b"room=kitchen"],
19077 "-ERR TSDB: missing FILTER argument\r\n",
19078 ),
19079 (
19080 &[b"TS.MRANGE", b"-", b"+", b"FILTER"],
19081 "-ERR TSDB: missing labels for filter argument\r\n",
19082 ),
19083 (
19084 &[
19085 b"TS.MRANGE",
19086 b"-",
19087 b"+",
19088 b"GROUPBY",
19089 b"room",
19090 b"REDUCE",
19091 b"sum",
19092 b"FILTER",
19093 b"room=kitchen",
19094 ],
19095 "-ERR TSDB: GROUPBY should always come after filter\r\n",
19096 ),
19097 (
19100 &[
19101 b"TS.MRANGE",
19102 b"-",
19103 b"+",
19104 b"FILTER",
19105 b"room=kitchen",
19106 b"GROUPBY",
19107 b"room",
19108 b"REDUCE",
19109 b"sum",
19110 b"x",
19111 ],
19112 "-ERR wrong number of arguments for 'ts.mrange' command\r\n",
19113 ),
19114 (
19116 &[
19117 b"TS.MRANGE",
19118 b"-",
19119 b"+",
19120 b"FILTER",
19121 b"nope",
19122 b"GROUPBY",
19123 b"room",
19124 b"REDUCE",
19125 b"sum",
19126 b"x",
19127 ],
19128 "-ERR TSDB: failed parsing labels\r\n",
19129 ),
19130 (
19131 &[
19132 b"TS.MRANGE",
19133 b"-",
19134 b"+",
19135 b"FILTER",
19136 b"room=kitchen",
19137 b"GROUPBY",
19138 b"room",
19139 b"REDUCE",
19140 b"twa",
19141 ],
19142 "-ERR TSDB: Invalid reducer type\r\n",
19143 ),
19144 (
19145 &[
19146 b"TS.MRANGE",
19147 b"-",
19148 b"+",
19149 b"AGGREGATION",
19150 b"sum,avg",
19151 b"100",
19152 b"FILTER",
19153 b"room=kitchen",
19154 b"GROUPBY",
19155 b"room",
19156 b"REDUCE",
19157 b"sum",
19158 ],
19159 "-ERR TSDB: GROUPBY is not allowed when multiple aggregators are specified\r\n",
19160 ),
19161 (
19164 &[
19165 b"TS.MRANGE",
19166 b"-",
19167 b"+",
19168 b"SELECTED_LABELS",
19169 b"COUNT",
19170 b"FILTER",
19171 b"room=kitchen",
19172 ],
19173 "-ERR TSDB: Couldn't parse COUNT\r\n",
19174 ),
19175 ];
19176 for (argv, want) in cases {
19177 let got = f.run(argv);
19178 assert_eq!(&got, want, "{argv:?}");
19179 }
19180 }
19181
19182 #[test]
19185 fn resp3_writes_a_multi_key_read_as_a_map_of_four() {
19186 let mut f = spanned();
19187 f.out = Out::new(Proto::Resp3);
19188 let cases: &[(&[&[u8]], &str)] = &[
19189 (
19190 &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=bedroom"],
19191 "%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\
19192 *1\r\n*2\r\n:200\r\n,2\r\n",
19193 ),
19194 (
19197 &[
19198 b"TS.MRANGE",
19199 b"-",
19200 b"+",
19201 b"AGGREGATION",
19202 b"sum,avg",
19203 b"100",
19204 b"FILTER",
19205 b"room=bedroom",
19206 ],
19207 "%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\
19208 $3\r\navg\r\n*1\r\n*3\r\n:200\r\n,2\r\n,2\r\n",
19209 ),
19210 (
19211 &[
19212 b"TS.MRANGE",
19213 b"-",
19214 b"+",
19215 b"FILTER",
19216 b"room=kitchen",
19217 b"GROUPBY",
19218 b"room",
19219 b"REDUCE",
19220 b"sum",
19221 ],
19222 "%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\
19223 $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\
19224 *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",
19225 ),
19226 (
19229 &[
19230 b"TS.MRANGE",
19231 b"-",
19232 b"+",
19233 b"WITHLABELS",
19234 b"FILTER",
19235 b"room=kitchen",
19236 b"GROUPBY",
19237 b"room",
19238 b"REDUCE",
19239 b"max",
19240 ],
19241 "%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\
19242 %1\r\n$8\r\nreducers\r\n*1\r\n$3\r\nmax\r\n\
19243 %1\r\n$7\r\nsources\r\n*2\r\n$1\r\na\r\n$1\r\nc\r\n\
19244 *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",
19245 ),
19246 (
19247 &[
19248 b"TS.MRANGE",
19249 b"-",
19250 b"+",
19251 b"FILTER",
19252 b"room=kitchen",
19253 b"GROUPBY",
19254 b"nope",
19255 b"REDUCE",
19256 b"sum",
19257 ],
19258 "%0\r\n",
19259 ),
19260 ];
19261 for (argv, want) in cases {
19262 let got = f.run(argv);
19263 assert_eq!(&got, want, "{argv:?}");
19264 }
19265 }
19266
19267 #[test]
19269 fn createrule_checks_the_two_keys_last_and_the_two_links_after_that() {
19270 let mut f = Fixture::new();
19271 f.run(&[b"TS.CREATE", b"src"]);
19272 f.run(&[b"TS.CREATE", b"dst"]);
19273 f.run(&[b"SET", b"plain", b"v"]);
19274 let cases: &[(&[&[u8]], &str)] = &[
19275 (
19279 &[
19280 b"TS.CREATERULE",
19281 b"src",
19282 b"dst",
19283 b"AGGREGATION",
19284 b"nope",
19285 b"x",
19286 ],
19287 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
19288 ),
19289 (
19290 &[
19291 b"TS.CREATERULE",
19292 b"src",
19293 b"dst",
19294 b"AGGREGATION",
19295 b"nope",
19296 b"10",
19297 ],
19298 "-ERR TSDB: Unknown aggregation type\r\n",
19299 ),
19300 (
19301 &[
19302 b"TS.CREATERULE",
19303 b"src",
19304 b"dst",
19305 b"AGGREGATION",
19306 b"avg",
19307 b"0",
19308 ],
19309 "-ERR TSDB: bucketDuration must be greater than zero\r\n",
19310 ),
19311 (
19312 &[
19313 b"TS.CREATERULE",
19314 b"src",
19315 b"dst",
19316 b"AGGREGATION",
19317 b"avg",
19318 b"10",
19319 b"x",
19320 ],
19321 "-ERR TSDB: Couldn't parse alignTimestamp\r\n",
19322 ),
19323 (
19324 &[
19325 b"TS.CREATERULE",
19326 b"src",
19327 b"src",
19328 b"AGGREGATION",
19329 b"avg",
19330 b"10",
19331 ],
19332 "-ERR TSDB: the source key and destination key should be different\r\n",
19333 ),
19334 (
19338 &[
19339 b"TS.CREATERULE",
19340 b"nope",
19341 b"plain",
19342 b"AGGREGATION",
19343 b"avg",
19344 b"10",
19345 ],
19346 "-ERR TSDB: the key does not exist\r\n",
19347 ),
19348 (
19349 &[
19350 b"TS.CREATERULE",
19351 b"src",
19352 b"nope",
19353 b"AGGREGATION",
19354 b"avg",
19355 b"10",
19356 ],
19357 "-ERR TSDB: the key does not exist\r\n",
19358 ),
19359 (
19362 &[b"TS.CREATERULE", b"src", b"dst", b"NOPE", b"avg", b"10"],
19363 "-ERR wrong number of arguments for 'ts.createrule' command\r\n",
19364 ),
19365 (
19366 &[
19367 b"TS.CREATERULE",
19368 b"src",
19369 b"dst",
19370 b"AGGREGATION",
19371 b"avg",
19372 b"10",
19373 ],
19374 "+OK\r\n",
19375 ),
19376 (
19379 &[
19380 b"TS.CREATERULE",
19381 b"src",
19382 b"dst",
19383 b"AGGREGATION",
19384 b"avg",
19385 b"10",
19386 ],
19387 "-ERR TSDB: the destination key already has a src rule\r\n",
19388 ),
19389 (
19392 &[
19393 b"TS.CREATERULE",
19394 b"dst",
19395 b"src",
19396 b"AGGREGATION",
19397 b"avg",
19398 b"10",
19399 ],
19400 "-ERR TSDB: the source key already has a source rule\r\n",
19401 ),
19402 (&[b"TS.DELETERULE", b"src", b"dst"], "+OK\r\n"),
19403 (
19404 &[b"TS.DELETERULE", b"src", b"dst"],
19405 "-ERR TSDB: compaction rule does not exist\r\n",
19406 ),
19407 (
19411 &[b"TS.DELETERULE", b"src", b"nope"],
19412 "-ERR TSDB: compaction rule does not exist\r\n",
19413 ),
19414 (
19415 &[b"TS.DELETERULE", b"nope", b"dst"],
19416 "-ERR TSDB: the key does not exist\r\n",
19417 ),
19418 ];
19419 for (argv, want) in cases {
19420 let got = f.run(argv);
19421 assert_eq!(&got, want, "{argv:?}");
19422 }
19423 }
19424
19425 #[test]
19427 fn a_rule_writes_a_bucket_when_a_later_reading_closes_it() {
19428 let mut f = Fixture::new();
19429 f.run(&[b"TS.CREATE", b"src"]);
19430 f.run(&[b"TS.CREATE", b"dst"]);
19431 f.run(&[b"TS.ADD", b"src", b"10", b"1"]);
19434 f.run(&[
19435 b"TS.CREATERULE",
19436 b"src",
19437 b"dst",
19438 b"AGGREGATION",
19439 b"sum",
19440 b"100",
19441 ]);
19442 f.run(&[b"TS.ADD", b"src", b"20", b"2"]);
19443 assert_eq!(f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]), "*0\r\n");
19444 assert_eq!(f.run(&[b"TS.GET", b"dst", b"LATEST"]), "*2\r\n:0\r\n+2\r\n");
19447 f.run(&[b"TS.ADD", b"src", b"110", b"4"]);
19448 assert_eq!(
19449 f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
19450 "*1\r\n*2\r\n:0\r\n+2\r\n"
19451 );
19452 f.run(&[b"TS.ADD", b"src", b"30", b"8"]);
19455 assert_eq!(
19456 f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
19457 "*1\r\n*2\r\n:0\r\n+11\r\n"
19458 );
19459 assert_eq!(f.run(&[b"TS.DEL", b"src", b"0", b"25"]), ":2\r\n");
19462 assert_eq!(
19463 f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
19464 "*1\r\n*2\r\n:0\r\n+8\r\n"
19465 );
19466 assert_eq!(
19467 f.run(&[b"TS.GET", b"dst", b"LATEST"]),
19468 "*2\r\n:100\r\n+4\r\n"
19469 );
19470 assert!(f.run(&[b"TS.INFO", b"dst"]).contains("sourceKey"));
19472 f.run(&[b"DEL", b"dst"]);
19473 assert_eq!(
19474 f.run(&[b"TS.DELETERULE", b"src", b"dst"]),
19475 "-ERR TSDB: compaction rule does not exist\r\n"
19476 );
19477 }
19478
19479 #[test]
19482 fn xadd_ids_only_ever_go_up() {
19483 let mut f = Fixture::new();
19484 assert_eq!(f.run(&[b"XADD", b"s", b"5", b"a", b"1"]), "$3\r\n5-0\r\n");
19486 assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"2"]), "$3\r\n5-1\r\n");
19488 assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"3"]), "$3\r\n5-2\r\n");
19489 assert_eq!(f.run(&[b"XADD", b"s", b"6-9", b"a", b"4"]), "$3\r\n6-9\r\n");
19490 assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
19491
19492 assert!(
19493 f.run(&[b"XADD", b"s", b"6-9", b"a", b"5"])
19494 .contains("equal or smaller")
19495 );
19496 assert!(
19497 f.run(&[b"XADD", b"s", b"0-0", b"a", b"5"])
19498 .contains("must be greater than 0-0")
19499 );
19500 assert!(
19501 f.run(&[b"XADD", b"s", b"nonsense", b"a", b"5"])
19502 .contains("Invalid stream ID")
19503 );
19504 assert!(
19507 f.run(&[b"XADD", b"s", b"*", b"a"])
19508 .contains("wrong number of arguments")
19509 );
19510
19511 assert_eq!(
19514 f.run(&[b"XADD", b"gone", b"NOMKSTREAM", b"*", b"a", b"1"]),
19515 "$-1\r\n"
19516 );
19517 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
19518 assert_eq!(f.run(&[b"TYPE", b"s"]), "+stream\r\n");
19519 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$6\r\nstream\r\n");
19520 }
19521
19522 #[test]
19525 fn trimming_reads_its_options_the_way_redis_does() {
19526 let mut f = Fixture::new();
19527 for i in 1..=10u32 {
19528 f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
19529 }
19530 assert_eq!(f.run(&[b"XTRIM", b"s", b"MAXLEN", b"4"]), ":6\r\n");
19531 assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
19532 assert_eq!(f.run(&[b"XTRIM", b"s", b"MINID", b"9"]), ":2\r\n");
19533 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
19534
19535 assert!(
19539 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"~"])
19540 .contains("not an integer")
19541 );
19542 assert!(
19543 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"-1"])
19544 .contains("MAXLEN argument must be >= 0")
19545 );
19546 assert!(
19549 f.run(&[b"XTRIM", b"s", b"LIMIT", b"5"])
19550 .contains("without specifying a trimming strategy")
19551 );
19552 assert!(
19553 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"LIMIT", b"5"])
19554 .contains("without the special ~ option")
19555 );
19556 assert!(
19557 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"MINID", b"5"])
19558 .contains("at the same time are not compatible")
19559 );
19560 assert!(
19562 f.run(&[b"XTRIM", b"s", b"NOMKSTREAM", b"MAXLEN", b"5"])
19563 .contains("syntax error")
19564 );
19565 assert_eq!(f.run(&[b"XTRIM", b"missing", b"MAXLEN", b"5"]), ":0\r\n");
19566 }
19567
19568 #[test]
19570 fn xrange_looks_the_key_up_before_it_reads_the_count() {
19571 let mut f = Fixture::new();
19572 f.run(&[b"XADD", b"s", b"5-1", b"a", b"1"]);
19573 f.run(&[b"XADD", b"s", b"6-1", b"b", b"2"]);
19574
19575 assert_eq!(
19576 f.run(&[b"XRANGE", b"s", b"-", b"+"]),
19577 "*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\
19578 *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
19579 );
19580 assert_eq!(
19581 f.run(&[b"XREVRANGE", b"s", b"+", b"-", b"COUNT", b"1"]),
19582 "*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"
19583 );
19584 assert_eq!(
19588 f.run(&[b"XRANGE", b"s", b"-", b"(6"]),
19589 "*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\
19590 *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
19591 );
19592 assert_eq!(
19593 f.run(&[b"XRANGE", b"s", b"(5-1", b"+"]),
19594 "*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"
19595 );
19596 assert!(
19597 f.run(&[b"XRANGE", b"s", b"(-", b"+"])
19598 .contains("Invalid stream ID")
19599 );
19600
19601 assert_eq!(
19605 f.run(&[b"XRANGE", b"missing", b"-", b"+", b"COUNT", b"0"]),
19606 "*0\r\n"
19607 );
19608 assert_eq!(
19609 f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"]),
19610 "*-1\r\n"
19611 );
19612 f.run(&[b"SET", b"str", b"v"]);
19613 assert!(
19614 f.run(&[b"XRANGE", b"str", b"-", b"+", b"COUNT", b"0"])
19615 .starts_with("-WRONGTYPE")
19616 );
19617 assert_eq!(
19619 f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"2", b"COUNT", b"1"]),
19620 "*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"
19621 );
19622 }
19623
19624 #[test]
19626 fn a_bad_id_late_in_the_list_stops_the_whole_command() {
19627 let mut f = Fixture::new();
19628 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
19629 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
19630 assert!(
19631 f.run(&[b"XDEL", b"s", b"1-1", b"nonsense"])
19632 .contains("Invalid stream ID")
19633 );
19634 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
19635 assert_eq!(f.run(&[b"XDEL", b"s", b"1-1", b"9-9"]), ":1\r\n");
19636 assert_eq!(f.run(&[b"XLEN", b"s"]), ":1\r\n");
19637 assert_eq!(f.run(&[b"XDEL", b"missing", b"1-1"]), ":0\r\n");
19638 assert_eq!(f.run(&[b"XACK", b"missing", b"g", b"1-1"]), ":0\r\n");
19639 }
19640
19641 #[test]
19643 fn xgroup_has_an_arity_per_subcommand() {
19644 let mut f = Fixture::new();
19645 assert!(
19646 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
19647 .contains("requires the key")
19648 );
19649 assert_eq!(
19650 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$", b"MKSTREAM"]),
19651 "+OK\r\n"
19652 );
19653 assert!(
19656 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
19657 .starts_with("-BUSYGROUP")
19658 );
19659 assert_eq!(
19660 f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
19661 ":1\r\n"
19662 );
19663 assert_eq!(
19664 f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
19665 ":0\r\n"
19666 );
19667 assert_eq!(
19668 f.run(&[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c"]),
19669 ":0\r\n"
19670 );
19671
19672 let short = f.run(&[b"XGROUP", b"DESTROY", b"s"]);
19674 assert!(
19675 short.contains("wrong number of arguments for 'xgroup|destroy' command"),
19676 "{short}"
19677 );
19678 let odd = f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0", b"ENTRIESREAD"]);
19680 assert!(
19681 odd.contains("unknown subcommand or wrong number of arguments for 'SETID'"),
19682 "{odd}"
19683 );
19684 assert!(
19685 f.run(&[b"XGROUP", b"NOSUCH", b"s"])
19686 .contains("Try XGROUP HELP")
19687 );
19688
19689 assert_eq!(f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0"]), "+OK\r\n");
19690 assert!(
19691 f.run(&[b"XGROUP", b"SETID", b"s", b"nogroup", b"0"])
19692 .starts_with("-NOGROUP")
19693 );
19694 assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":1\r\n");
19695 assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":0\r\n");
19696 assert!(
19697 f.run(&[b"XGROUP", b"DESTROY", b"missing", b"g"])
19698 .contains("requires the key")
19699 );
19700 }
19701
19702 #[test]
19704 fn xreadgroup_hands_out_and_xack_takes_back() {
19705 let mut f = Fixture::new();
19706 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
19707 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
19708 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
19709
19710 let first = f.run(&[
19711 b"XREADGROUP",
19712 b"GROUP",
19713 b"g",
19714 b"c1",
19715 b"COUNT",
19716 b"1",
19717 b"STREAMS",
19718 b"s",
19719 b">",
19720 ]);
19721 assert_eq!(
19722 first,
19723 "*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"
19724 );
19725 assert_eq!(
19728 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b"0"]),
19729 "*1\r\n*2\r\n$1\r\ns\r\n*0\r\n"
19730 );
19731 assert_eq!(
19732 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
19733 "*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"
19734 );
19735
19736 assert_eq!(
19737 f.run(&[b"XPENDING", b"s", b"g"]),
19738 "*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"
19739 );
19740 assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":1\r\n");
19741 assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":0\r\n");
19742 assert_eq!(
19744 f.run(&[b"XPENDING", b"s", b"g"]),
19745 "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n"
19746 );
19747
19748 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
19751 f.run(&[b"XDEL", b"s", b"2-1"]);
19752 assert_eq!(
19753 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
19754 "*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"
19755 );
19756
19757 assert!(
19760 f.run(&[
19761 b"XREADGROUP",
19762 b"GROUP",
19763 b"nope",
19764 b"c",
19765 b"STREAMS",
19766 b"s",
19767 b"+"
19768 ])
19769 .starts_with("-NOGROUP")
19770 );
19771 assert!(
19772 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"])
19773 .contains("meaningless in the context of XREADGROUP")
19774 );
19775 assert!(
19776 f.run(&[b"XREAD", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"0"])
19777 .contains("only supported by XREADGROUP")
19778 );
19779 assert!(
19780 f.run(&[
19781 b"XREADGROUP",
19782 b"GROUP",
19783 b"g",
19784 b"c",
19785 b"STREAMS",
19786 b"s",
19787 b"a",
19788 b"b"
19789 ])
19790 .contains("Unbalanced 'xreadgroup' list of streams")
19791 );
19792 }
19793
19794 #[test]
19797 fn xread_with_no_block_writes_the_null_itself() {
19798 let mut f = Fixture::new();
19799 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
19800 assert_eq!(
19801 f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
19802 "*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"
19803 );
19804 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "*-1\r\n");
19807 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"missing", b"0"]), "*-1\r\n");
19808 f.run(&[b"XADD", b"other", b"1-1", b"b", b"2"]);
19809 assert_eq!(
19810 f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"1-1", b"0"]),
19811 "*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"
19812 );
19813 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"$"]), "*-1\r\n");
19815 assert_eq!(
19817 f.run(&[b"XREAD", b"COUNT", b"5", b"STREAMS", b"s", b"+"]),
19818 "*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"
19819 );
19820 assert_eq!(
19823 f.run(&[b"XREAD", b"COUNT", b"0", b"STREAMS", b"s", b"0"]),
19824 "*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"
19825 );
19826 assert!(
19828 f.run(&[b"XREAD", b"BLOCK", b"0.5", b"STREAMS", b"s", b"$"])
19829 .contains("not an integer")
19830 );
19831 assert!(
19832 f.run(&[b"XREAD", b"BLOCK", b"-1", b"STREAMS", b"s", b"$"])
19833 .contains("timeout is negative")
19834 );
19835 assert!(
19836 f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"0"])
19837 .contains("Unbalanced 'xread' list of streams")
19838 );
19839 }
19840
19841 #[test]
19843 fn a_blocked_xread_wakes_on_the_next_entry() {
19844 let mut f = Fixture::new();
19845 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
19846 let (flow, reply) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
19847 assert_eq!(flow, Flow::Block);
19848 assert!(reply.is_empty());
19849
19850 f.session = Session::new(8);
19855 let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
19856 assert_eq!(flow, Flow::Block);
19857 assert_eq!(f.server.parked(), 2);
19858
19859 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
19860 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";
19861 for client in [7, 8] {
19862 let mut out = Out::new(Proto::Resp2);
19863 assert!(f.server.serve_waiter(client, 0, &mut out));
19864 assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
19865 }
19866
19867 f.server.forget_waiters(7);
19870 f.server.forget_waiters(8);
19871 let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"50", b"STREAMS", b"s", b"$"]);
19872 assert_eq!(flow, Flow::Block);
19873 let mut out = Out::new(Proto::Resp2);
19874 assert!(!f.server.serve_waiter(8, 0, &mut out));
19875 assert!(out.as_slice().is_empty());
19876 assert!(f.server.serve_waiter(8, u64::MAX, &mut out));
19877 assert_eq!(
19878 core::str::from_utf8(out.as_slice()).expect("ascii"),
19879 "*-1\r\n"
19880 );
19881 }
19882
19883 #[test]
19885 fn losing_a_group_while_blocked_is_the_ordinary_sentence() {
19886 let mut f = Fixture::new();
19887 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
19888 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"]);
19889 let (flow, _) = f.flow(&[
19890 b"XREADGROUP",
19891 b"GROUP",
19892 b"g",
19893 b"c",
19894 b"BLOCK",
19895 b"0",
19896 b"STREAMS",
19897 b"s",
19898 b">",
19899 ]);
19900 assert_eq!(flow, Flow::Block);
19901
19902 f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]);
19903 let mut out = Out::new(Proto::Resp2);
19904 assert!(f.server.serve_waiter(7, 0, &mut out));
19905 assert_eq!(
19908 core::str::from_utf8(out.as_slice()).expect("ascii"),
19909 "-NOGROUP No such key 's' or consumer group 'g' in XREADGROUP with GROUP option\r\n"
19910 );
19911 }
19912
19913 #[test]
19915 fn xclaim_reads_ids_until_one_will_not_parse() {
19916 let mut f = Fixture::new();
19917 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
19918 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
19919 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
19920 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
19921
19922 assert!(
19925 f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"-"])
19926 .contains("Unrecognized XCLAIM option '-'")
19927 );
19928 assert_eq!(
19929 f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1", b"JUSTID"]),
19930 "*1\r\n$3\r\n1-1\r\n"
19931 );
19932 f.run(&[b"XDEL", b"s", b"2-1"]);
19935 assert_eq!(
19936 f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1"]),
19937 "*0\r\n"
19938 );
19939 assert!(
19940 f.run(&[b"XPENDING", b"s", b"g"])
19941 .starts_with("*4\r\n:1\r\n")
19942 );
19943 assert!(
19944 f.run(&[b"XCLAIM", b"s", b"nope", b"c", b"0", b"1-1"])
19945 .starts_with("-NOGROUP")
19946 );
19947 assert!(
19948 f.run(&[b"XCLAIM", b"s", b"g", b"c", b"nan", b"1-1"])
19949 .contains("Invalid min-idle-time argument for XCLAIM")
19950 );
19951 }
19952
19953 #[test]
19955 fn xautoclaim_reports_what_it_dropped() {
19956 let mut f = Fixture::new();
19957 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
19958 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
19959 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
19960 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
19961 f.run(&[b"XDEL", b"s", b"1-1"]);
19962
19963 assert_eq!(
19966 f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"JUSTID"]),
19967 "*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"
19968 );
19969 assert!(
19970 f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"COUNT", b"0"])
19971 .contains("COUNT must be > 0")
19972 );
19973 assert!(
19974 f.run(&[b"XAUTOCLAIM", b"s", b"nope", b"c", b"0", b"-"])
19975 .starts_with("-NOGROUP")
19976 );
19977 }
19978
19979 #[test]
19981 fn xdelex_answers_one_integer_an_id() {
19982 let mut f = Fixture::new();
19983 for i in 1..=4 {
19984 f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
19985 }
19986 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
19987 f.run(&[
19988 b"XREADGROUP",
19989 b"GROUP",
19990 b"g",
19991 b"c",
19992 b"COUNT",
19993 b"2",
19994 b"STREAMS",
19995 b"s",
19996 b">",
19997 ]);
19998
19999 assert_eq!(
20001 f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1", b"9-9"]),
20002 "*2\r\n:1\r\n:-1\r\n"
20003 );
20004 assert!(
20007 f.run(&[b"XPENDING", b"s", b"g"])
20008 .starts_with("*4\r\n:2\r\n")
20009 );
20010 assert_eq!(
20012 f.run(&[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"]),
20013 "*1\r\n:1\r\n"
20014 );
20015 assert_eq!(
20017 f.run(&[b"XPENDING", b"s", b"g"]),
20018 "*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"
20019 );
20020
20021 assert_eq!(
20025 f.run(&[b"XDELEX", b"s", b"ACKED", b"IDS", b"2", b"3-1", b"4-1"]),
20026 "*2\r\n:2\r\n:2\r\n"
20027 );
20028
20029 assert_eq!(
20031 f.run(&[b"XDELEX", b"nope", b"IDS", b"2", b"bad", b"worse"]),
20032 "*2\r\n:-1\r\n:-1\r\n"
20033 );
20034 assert!(
20036 f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"3-1", b"bad"])
20037 .starts_with("-ERR Invalid stream ID")
20038 );
20039 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20040
20041 assert!(
20042 f.run(&[b"XDELEX", b"s", b"IDS", b"0", b"1-1"])
20043 .contains("Number of IDs must be a positive integer")
20044 );
20045 assert!(
20046 f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1"])
20047 .contains("The `numids` parameter must match the number of arguments")
20048 );
20049 assert!(
20052 f.run(&[b"XDELEX", b"s", b"KEEPREF", b"DELREF", b"IDS", b"1", b"1-1"])
20053 .starts_with("-ERR syntax error")
20054 );
20055 assert!(
20056 f.run(&[b"XDELEX", b"s", b"IDS", b"1", b"1-1", b"2-1"])
20057 .starts_with("-ERR syntax error")
20058 );
20059 f.run(&[b"SET", b"str", b"v"]);
20061 assert!(
20062 f.run(&[b"XDELEX", b"str", b"BOGUS", b"IDS", b"0", b"1-1"])
20063 .starts_with("-WRONGTYPE")
20064 );
20065 }
20066
20067 #[test]
20069 fn xackdel_reports_what_the_group_was_holding() {
20070 let mut f = Fixture::new();
20071 for i in 1..=3 {
20072 f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
20073 }
20074 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20075 f.run(&[
20076 b"XREADGROUP",
20077 b"GROUP",
20078 b"g",
20079 b"c",
20080 b"COUNT",
20081 b"1",
20082 b"STREAMS",
20083 b"s",
20084 b">",
20085 ]);
20086
20087 assert_eq!(
20091 f.run(&[b"XACKDEL", b"s", b"g", b"IDS", b"2", b"1-1", b"2-1"]),
20092 "*2\r\n:1\r\n:-1\r\n"
20093 );
20094 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20095
20096 assert_eq!(
20098 f.run(&[b"XACKDEL", b"s", b"nope", b"IDS", b"1", b"2-1"]),
20099 "*1\r\n:-1\r\n"
20100 );
20101 assert_eq!(
20102 f.run(&[b"XACKDEL", b"nope", b"g", b"IDS", b"1", b"2-1"]),
20103 "*1\r\n:-1\r\n"
20104 );
20105
20106 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b">"]);
20109 f.run(&[b"XGROUP", b"CREATE", b"s", b"g2", b"0"]);
20110 assert_eq!(
20111 f.run(&[b"XACKDEL", b"s", b"g", b"ACKED", b"IDS", b"1", b"2-1"]),
20112 "*1\r\n:2\r\n"
20113 );
20114 assert_eq!(
20115 f.run(&[b"XPENDING", b"s", b"g"]),
20116 "*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"
20117 );
20118 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20119 }
20120
20121 #[test]
20123 fn xnack_releases_an_entry_for_the_next_claim() {
20124 let mut f = Fixture::new();
20125 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20126 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20127 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20128 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
20129 f.run(&[b"XCLAIM", b"s", b"g", b"c1", b"0", b"1-1", b"2-1"]);
20132
20133 assert_eq!(
20134 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1"]),
20135 ":1\r\n"
20136 );
20137 assert_eq!(
20141 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]),
20142 "*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"
20143 );
20144 assert_eq!(
20146 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
20147 "*1\r\n*4\r\n$3\r\n2-1\r\n$2\r\nc1\r\n:0\r\n:2\r\n"
20148 );
20149 assert_eq!(
20151 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b">"]),
20152 "*-1\r\n"
20153 );
20154 assert_eq!(
20156 f.run(&[
20157 b"XAUTOCLAIM",
20158 b"s",
20159 b"g",
20160 b"c2",
20161 b"99999999",
20162 b"-",
20163 b"JUSTID"
20164 ]),
20165 "*3\r\n$3\r\n0-0\r\n*1\r\n$3\r\n1-1\r\n*0\r\n"
20166 );
20167
20168 f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
20172 assert!(
20173 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
20174 .contains(":-1\r\n:1\r\n")
20175 );
20176 f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
20178 f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
20179 assert!(
20180 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
20181 .contains(":-1\r\n:0\r\n")
20182 );
20183 f.run(&[b"XNACK", b"s", b"g", b"FATAL", b"IDS", b"1", b"1-1"]);
20185 assert!(
20186 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
20187 .contains(":9223372036854775807\r\n")
20188 );
20189 f.run(&[
20190 b"XNACK",
20191 b"s",
20192 b"g",
20193 b"FATAL",
20194 b"IDS",
20195 b"1",
20196 b"1-1",
20197 b"RETRYCOUNT",
20198 b"3",
20199 ]);
20200 assert!(
20201 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
20202 .contains(":-1\r\n:3\r\n")
20203 );
20204
20205 f.run(&[b"XACK", b"s", b"g", b"2-1"]);
20209 assert_eq!(
20210 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"]),
20211 ":0\r\n"
20212 );
20213 assert_eq!(
20214 f.run(&[
20215 b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1", b"FORCE"
20216 ]),
20217 ":1\r\n"
20218 );
20219 assert!(
20220 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
20221 .contains(":-1\r\n:0\r\n")
20222 );
20223 assert_eq!(
20225 f.run(&[
20226 b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"9-9", b"FORCE"
20227 ]),
20228 ":0\r\n"
20229 );
20230
20231 assert_eq!(
20234 f.run(&[b"XNACK", b"s", b"nope", b"BOGUS", b"IDS", b"1", b"1-1"]),
20235 "-NOGROUP No such key 's' or consumer group 'nope'\r\n"
20236 );
20237 assert!(
20238 f.run(&[b"XNACK", b"s", b"g", b"BOGUS", b"IDS", b"1", b"1-1"])
20239 .starts_with("-ERR")
20240 );
20241 assert!(
20243 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"0", b"1-1"])
20244 .contains("numids must be a positive integer")
20245 );
20246 assert!(
20247 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"2", b"1-1"])
20248 .contains("number of IDs doesn't match numids")
20249 );
20250 assert!(
20253 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1", b"2-1"])
20254 .contains("Unrecognized XNACK option '2-1'")
20255 );
20256 }
20257
20258 #[test]
20260 fn xinfo_reports_the_stream_the_groups_and_the_consumers() {
20261 let mut f = Fixture::new();
20262 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20263 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20264 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20265 f.run(&[
20266 b"XREADGROUP",
20267 b"GROUP",
20268 b"g",
20269 b"c1",
20270 b"COUNT",
20271 b"1",
20272 b"STREAMS",
20273 b"s",
20274 b">",
20275 ]);
20276
20277 let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
20278 assert!(info.starts_with("*20\r\n"), "{info}");
20281 assert!(info.contains("$6\r\nlength\r\n:2\r\n"), "{info}");
20282 assert!(
20283 info.contains("$17\r\nlast-generated-id\r\n$3\r\n2-1\r\n"),
20284 "{info}"
20285 );
20286 assert!(info.contains("$13\r\nentries-added\r\n:2\r\n"), "{info}");
20287 assert!(info.contains("$6\r\ngroups\r\n:1\r\n"), "{info}");
20288
20289 let groups = f.run(&[b"XINFO", b"GROUPS", b"s"]);
20290 assert!(groups.starts_with("*1\r\n*12\r\n"), "{groups}");
20291 assert!(groups.contains("$9\r\nconsumers\r\n:1\r\n"), "{groups}");
20292 assert!(groups.contains("$7\r\npending\r\n:1\r\n"), "{groups}");
20293 assert!(groups.contains("$3\r\nlag\r\n:1\r\n"), "{groups}");
20294
20295 f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c2"]);
20299 let consumers = f.run(&[b"XINFO", b"CONSUMERS", b"s", b"g"]);
20300 assert!(consumers.starts_with("*2\r\n"), "{consumers}");
20301 assert!(
20302 consumers.contains("$8\r\ninactive\r\n:-1\r\n"),
20303 "{consumers}"
20304 );
20305 let c1 = consumers.find("c1").unwrap();
20307 let c2 = consumers.find("c2").unwrap();
20308 assert!(c1 < c2, "{consumers}");
20309
20310 let full = f.run(&[b"XINFO", b"STREAM", b"s", b"FULL"]);
20311 assert!(full.starts_with("*18\r\n"), "{full}");
20312 assert!(full.contains("$12\r\nnacked-count\r\n:0\r\n"), "{full}");
20313 assert!(full.contains("$11\r\nactive-time\r\n"), "{full}");
20314
20315 assert!(
20316 f.run(&[b"XINFO", b"STREAM", b"missing"])
20317 .contains("no such key")
20318 );
20319 assert!(
20320 f.run(&[b"XINFO", b"GROUPS", b"missing"])
20321 .contains("no such key")
20322 );
20323 assert!(
20324 f.run(&[b"XINFO", b"CONSUMERS", b"s", b"nope"])
20325 .starts_with("-NOGROUP")
20326 );
20327 assert!(
20328 f.run(&[b"XINFO", b"NOSUCH", b"s"])
20329 .contains("Try XINFO HELP")
20330 );
20331 assert!(f.run(&[b"XINFO", b"HELP"]).contains("XINFO <subcommand>"));
20332 assert!(f.run(&[b"XGROUP", b"HELP"]).contains("XGROUP <subcommand>"));
20333 }
20334
20335 #[test]
20337 fn xpending_takes_the_consumer_only_when_the_count_comes_out_right() {
20338 let mut f = Fixture::new();
20339 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20340 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20341 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
20342
20343 let list = f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]);
20344 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");
20345 assert_eq!(
20346 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
20347 "*1\r\n*4\r\n$3\r\n1-1\r\n$2\r\nc1\r\n:0\r\n:1\r\n"
20348 );
20349 assert_eq!(
20351 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"nope"]),
20352 "*0\r\n"
20353 );
20354 assert_eq!(
20355 f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0", b"-", b"+", b"10"]),
20356 list
20357 );
20358 assert!(
20360 f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0"])
20361 .contains("syntax error")
20362 );
20363 assert!(
20364 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+"])
20365 .contains("syntax error")
20366 );
20367 assert_eq!(
20368 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"-1"]),
20369 "*0\r\n"
20370 );
20371 assert!(
20372 f.run(&[b"XPENDING", b"missing", b"g"])
20373 .starts_with("-NOGROUP")
20374 );
20375 }
20376
20377 #[test]
20379 fn xsetid_will_not_go_below_what_is_there() {
20380 let mut f = Fixture::new();
20381 f.run(&[b"XADD", b"s", b"5-5", b"a", b"1"]);
20382 assert_eq!(f.run(&[b"XSETID", b"s", b"9-9"]), "+OK\r\n");
20383 assert_eq!(
20384 f.run(&[
20385 b"XSETID",
20386 b"s",
20387 b"10-1",
20388 b"ENTRIESADDED",
20389 b"7",
20390 b"MAXDELETEDID",
20391 b"9-1"
20392 ]),
20393 "+OK\r\n"
20394 );
20395 let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
20396 assert!(info.contains("$13\r\nentries-added\r\n:7\r\n"), "{info}");
20397 assert!(
20398 info.contains("$20\r\nmax-deleted-entry-id\r\n$3\r\n9-1\r\n"),
20399 "{info}"
20400 );
20401
20402 assert!(
20403 f.run(&[b"XSETID", b"s", b"1-1"])
20404 .contains("smaller than the target stream top item")
20405 );
20406 assert!(
20407 f.run(&[b"XSETID", b"s", b"10-1", b"ENTRIESADDED", b"-1"])
20408 .contains("entries_added must be positive")
20409 );
20410 assert!(
20411 f.run(&[b"XSETID", b"missing", b"1-1"])
20412 .contains("no such key")
20413 );
20414 }
20415
20416 #[test]
20418 fn xread_answers_a_map_on_resp3_and_the_fields_stay_flat() {
20419 let mut f = Fixture::new();
20420 f.run(&[b"HELLO", b"3"]);
20421 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20422 assert_eq!(
20425 f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
20426 "%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"
20427 );
20428 assert_eq!(
20431 f.run(&[b"XRANGE", b"s", b"-", b"+"]),
20432 "*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"
20433 );
20434 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "_\r\n");
20435 }
20436
20437 struct Mem {
20443 blobs: Vec<Vec<u8>>,
20444 }
20445
20446 impl yo_kv::cold::Blocks for Mem {
20447 fn put(&mut self, bytes: &[u8]) -> yo_common::Result<yo_common::Addr> {
20448 self.blobs.push(bytes.to_vec());
20449 Ok(yo_common::Addr::new(
20450 yo_common::Space::Log,
20451 (self.blobs.len() - 1) as u64,
20452 ))
20453 }
20454
20455 fn get(&self, at: yo_common::Addr) -> yo_common::Result<&[u8]> {
20456 self.blobs
20457 .get(at.offset() as usize)
20458 .map(Vec::as_slice)
20459 .ok_or_else(|| {
20460 yo_common::Error::new(yo_common::Code::Corrupt, "no chunk at that address")
20461 })
20462 }
20463
20464 fn bytes(&self) -> u64 {
20465 self.blobs.iter().map(|b| b.len() as u64).sum()
20466 }
20467 }
20468
20469 fn filled(attach: bool) -> (Fixture, usize) {
20482 let mut f = Fixture::new();
20483 if attach {
20484 f.server
20485 .striped(0)
20486 .hold_stripe(0)
20487 .attach(Box::new(Mem { blobs: Vec::new() }));
20488 }
20489 let val = vec![b'v'; 256];
20490 for i in 0..24000u32 {
20491 let k = format!("key:{i:08}");
20492 f.run(&[b"SET", k.as_bytes(), &val]);
20493 }
20494 let full = f.server.memory_bytes();
20495 assert!(full > 3 * 1024 * 1024, "the arena is several segments");
20496 (f, full)
20497 }
20498
20499 fn press(f: &mut Fixture, limit: usize) {
20505 let val = vec![b'v'; 256];
20506 for i in 0..3000u32 {
20507 let k = format!("new:{i:08}");
20508 assert_eq!(
20509 f.run(&[b"SET", k.as_bytes(), &val]),
20510 "+OK\r\n",
20511 "write {i} was refused"
20512 );
20513 f.server.refresh_memory();
20514 if f.server.memory_bytes() <= limit {
20515 return;
20516 }
20517 }
20518 panic!(
20519 "it never got under: {} against {limit}",
20520 f.server.memory_bytes()
20521 );
20522 }
20523
20524 #[test]
20525 fn the_storage_limit_reads_back_and_minus_one_is_no_limit() {
20526 let mut f = Fixture::new();
20527 assert_eq!(
20528 f.run(&[b"CONFIG", b"GET", b"maxstore"]),
20529 "*2\r\n$8\r\nmaxstore\r\n$2\r\n-1\r\n",
20530 "no limit is the default"
20531 );
20532 for (typed, bytes) in [
20535 (&b"0"[..], "0"),
20536 (b"1024", "1024"),
20537 (b"1k", "1000"),
20538 (b"1gb", "1073741824"),
20539 (b"-1", "-1"),
20540 ] {
20541 assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxstore", typed]), "+OK\r\n");
20542 assert_eq!(
20543 f.run(&[b"CONFIG", b"GET", b"maxstore"]),
20544 format!("*2\r\n$8\r\nmaxstore\r\n${}\r\n{bytes}\r\n", bytes.len()),
20545 "set {}",
20546 String::from_utf8_lossy(typed)
20547 );
20548 }
20549 for bad in [&b"1tb"[..], b"-2", b"", b"lots"] {
20550 assert_eq!(
20551 f.run(&[b"CONFIG", b"SET", b"maxstore", bad]),
20552 "-ERR CONFIG SET failed (possibly related to argument 'maxstore') - argument must be a memory value or -1\r\n",
20553 "refused {}",
20554 String::from_utf8_lossy(bad)
20555 );
20556 }
20557 let info = f.run(&[b"INFO", b"memory"]);
20559 assert!(info.contains("maxstore:-1"), "{info}");
20560 assert!(info.contains("yo_memory_regime:evict"), "{info}");
20561 assert!(info.contains("yo_store_bytes:0"), "{info}");
20562 }
20563
20564 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
20565 #[test]
20566 fn a_memory_limit_moves_values_to_the_file_instead_of_dropping_keys() {
20567 let (mut f, full) = filled(true);
20571 let keys = f.run(&[b"DBSIZE"]);
20572 assert!(
20573 f.run(&[b"INFO", b"memory"])
20574 .contains("yo_memory_regime:migrate"),
20575 "a database with somewhere to put values migrates"
20576 );
20577
20578 let limit = full - 2 * 1024 * 1024;
20579 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
20580 f.run(&[
20581 b"CONFIG",
20582 b"SET",
20583 b"maxmemory",
20584 limit.to_string().as_bytes(),
20585 ]);
20586 press(&mut f, limit);
20587
20588 assert!(
20589 f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
20590 "nothing was thrown away"
20591 );
20592 let after: usize = f.run(&[b"DBSIZE"])[1..]
20593 .trim_end()
20594 .parse()
20595 .expect("a count");
20596 let before: usize = keys[1..].trim_end().parse().expect("a count");
20597 assert!(after > before, "the keys that came in are all still here");
20598 assert!(
20599 f.server.store_bytes() > 0,
20600 "and what came out of memory went to the file"
20601 );
20602 let val = format!("$256\r\n{}\r\n", "v".repeat(256));
20605 assert_eq!(f.run(&[b"GET", b"key:00000000"]), val);
20606 assert_eq!(f.run(&[b"GET", b"key:00023999"]), val);
20607 }
20608
20609 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
20610 #[test]
20611 fn a_storage_limit_of_zero_restores_redis_behaviour_exactly() {
20612 let (mut f, full) = filled(true);
20616 f.run(&[b"CONFIG", b"SET", b"maxstore", b"0"]);
20617 assert!(
20618 f.run(&[b"INFO", b"memory"])
20619 .contains("yo_memory_regime:evict"),
20620 "nothing may go to the file"
20621 );
20622
20623 let limit = full - 2 * 1024 * 1024;
20624 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
20625 f.run(&[
20626 b"CONFIG",
20627 b"SET",
20628 b"maxmemory",
20629 limit.to_string().as_bytes(),
20630 ]);
20631 press(&mut f, limit);
20632
20633 assert!(
20634 !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
20635 "keys were thrown away, which is what was asked for"
20636 );
20637 assert_eq!(f.server.store_bytes(), 0, "and the file was never written");
20638 }
20639
20640 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
20641 #[test]
20642 fn a_full_file_goes_back_to_evicting() {
20643 let (mut f, full) = filled(true);
20647 f.run(&[b"CONFIG", b"SET", b"maxstore", b"64kb"]);
20648 let limit = full - 2 * 1024 * 1024;
20649 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
20650 f.run(&[
20651 b"CONFIG",
20652 b"SET",
20653 b"maxmemory",
20654 limit.to_string().as_bytes(),
20655 ]);
20656 press(&mut f, limit);
20657
20658 assert!(f.server.store_bytes() >= 64 * 1024, "the file filled up");
20659 assert!(
20660 !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
20661 "and then it started evicting"
20662 );
20663 assert!(
20664 f.run(&[b"INFO", b"memory"])
20665 .contains("yo_memory_regime:evict"),
20666 "and it says so"
20667 );
20668 }
20669 #[test]
20685 fn the_string_group_answers_the_same_however_many_stripes_there_are() {
20686 let script: &[&[&[u8]]] = &[
20687 &[b"SET", b"k1", b"v1"],
20690 &[b"SET", b"k2", b"v2"],
20691 &[b"GET", b"k1"],
20692 &[b"GET", b"nothing"],
20693 &[b"GETSET", b"k1", b"v1b"],
20694 &[b"SETNX", b"k1", b"no"],
20695 &[b"SETNX", b"k3", b"yes"],
20696 &[b"APPEND", b"k3", b"!"],
20697 &[b"STRLEN", b"k3"],
20698 &[b"SETRANGE", b"k3", b"1", b"XY"],
20699 &[b"GETRANGE", b"k3", b"0", b"-1"],
20700 &[b"INCR", b"n1"],
20701 &[b"INCRBY", b"n1", b"41"],
20702 &[b"DECRBY", b"n1", b"2"],
20703 &[b"INCRBYFLOAT", b"f1", b"1.5"],
20704 &[b"SETEX", b"e1", b"100", b"v"],
20705 &[b"PSETEX", b"e2", b"100000", b"v"],
20706 &[b"GETEX", b"e1", b"PERSIST"],
20707 &[b"GETDEL", b"k2"],
20708 &[b"GET", b"k2"],
20709 &[b"DIGEST", b"k1"],
20710 &[b"DELEX", b"k3"],
20711 &[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"],
20714 &[b"MGET", b"a", b"b", b"c", b"missing"],
20715 &[b"MSETNX", b"d", b"4", b"e", b"5"],
20716 &[b"MSETNX", b"e", b"6", b"f", b"7"],
20717 &[b"MGET", b"d", b"e", b"f"],
20718 &[b"MSETEX", b"2", b"g", b"7", b"h", b"8", b"NX"],
20719 &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"NX"],
20720 &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"XX"],
20721 &[b"MGET", b"g", b"h"],
20722 &[b"SET", b"s1", b"ohmytext"],
20723 &[b"SET", b"s2", b"mynewtext"],
20724 &[b"LCS", b"s1", b"s2"],
20725 &[b"LCS", b"s1", b"s2", b"LEN"],
20726 &[b"LCS", b"s1", b"s2", b"IDX", b"MINMATCHLEN", b"4"],
20727 &[b"LCS", b"s1", b"s2", b"IDX", b"WITHMATCHLEN"],
20728 &[b"LCS", b"s1", b"gone"],
20729 &[b"MSET", b"odd"],
20731 &[b"LCS", b"s1", b"s2", b"LEN", b"IDX"],
20732 &[b"MGET"],
20733 ];
20734
20735 let mut one = Fixture::new();
20736 let mut many = Fixture::striped(8);
20737 for parts in script {
20738 let a = one.run(parts);
20739 let b = many.run(parts);
20740 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20741 }
20742 }
20743
20744 #[test]
20749 fn a_striped_database_spreads_the_keys_it_is_given() {
20750 let mut f = Fixture::striped(8);
20751 for i in 0..256 {
20752 let key = format!("key:{i}");
20753 f.run(&[b"SET", key.as_bytes(), b"v"]);
20754 }
20755 assert_eq!(f.run(&[b"DBSIZE"]), ":256\r\n");
20756 }
20757
20758 #[test]
20761 fn a_wrong_type_in_the_middle_of_an_mget_is_still_one_nil() {
20762 let mut one = Fixture::new();
20763 let mut many = Fixture::striped(8);
20764 for f in [&mut one, &mut many] {
20765 f.run(&[b"SET", b"str", b"v"]);
20766 f.server
20772 .striped(0)
20773 .hold(b"list")
20774 .push(b"list", yo_kv::End::Right, core::iter::once(&b"v"[..]))
20775 .expect("a new list");
20776 }
20777 assert_eq!(
20778 one.run(&[b"MGET", b"str", b"list", b"gone"]),
20779 many.run(&[b"MGET", b"str", b"list", b"gone"])
20780 );
20781 }
20782
20783 #[test]
20791 fn the_keyspace_group_answers_the_same_however_many_stripes_there_are() {
20792 let script: &[&[&[u8]]] = &[
20793 &[b"SET", b"k1", b"v1"],
20794 &[b"SET", b"k2", b"v2"],
20795 &[b"EXISTS", b"k1", b"k2", b"k1", b"gone"],
20796 &[b"TYPE", b"k1"],
20797 &[b"TYPE", b"gone"],
20798 &[b"TOUCH", b"k1", b"k2", b"k1", b"gone"],
20799 &[b"EXPIRE", b"k1", b"100"],
20800 &[b"TTL", b"k1"],
20801 &[b"EXPIRE", b"k1", b"200", b"NX"],
20802 &[b"PERSIST", b"k1"],
20803 &[b"TTL", b"k1"],
20804 &[b"PEXPIREAT", b"k2", b"1900000000000"],
20805 &[b"EXPIRETIME", b"k2"],
20806 &[b"PEXPIRETIME", b"k2"],
20807 &[b"PERSIST", b"k2"],
20808 &[b"OBJECT", b"ENCODING", b"k1"],
20809 &[b"OBJECT", b"REFCOUNT", b"k1"],
20810 &[b"OBJECT", b"IDLETIME", b"k1"],
20811 &[b"OBJECT", b"FREQ", b"k1"],
20812 &[b"OBJECT", b"ENCODING", b"gone"],
20813 &[b"OBJECT", b"HELP"],
20814 &[b"RENAME", b"k1", b"k9"],
20815 &[b"GET", b"k9"],
20816 &[b"RENAME", b"gone", b"x"],
20817 &[b"RENAMENX", b"k9", b"k2"],
20818 &[b"RENAMENX", b"k9", b"k8"],
20819 &[b"GET", b"k8"],
20820 &[b"COPY", b"k8", b"c1"],
20821 &[b"COPY", b"k8", b"c1"],
20822 &[b"COPY", b"k8", b"c1", b"REPLACE"],
20823 &[b"COPY", b"k8", b"k8"],
20824 &[b"COPY", b"gone", b"c2"],
20825 &[b"COPY", b"k8", b"k8", b"DB", b"1"],
20826 &[b"COPY", b"k8", b"c9", b"DB", b"9"],
20827 &[b"MOVE", b"c1", b"1"],
20828 &[b"MOVE", b"c1", b"1"],
20829 &[b"MOVE", b"k8", b"0"],
20830 &[b"DEL", b"k2", b"gone"],
20831 &[b"UNLINK", b"k8", b"k8"],
20832 &[b"DBSIZE"],
20833 ];
20834
20835 let mut one = Fixture::new();
20836 let mut many = Fixture::striped(8);
20837 for parts in script {
20838 let a = one.run(parts);
20839 let b = many.run(parts);
20840 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
20841 }
20842
20843 for f in [&mut one, &mut many] {
20849 f.run(&[b"SET", b"d1", b"payload"]);
20850 let payload = f
20851 .server
20852 .striped(0)
20853 .hold(b"d1")
20854 .dump(b"d1")
20855 .expect("a key that is there");
20856 assert!(
20857 f.run(&[b"DUMP", b"d1"])
20858 .starts_with(&format!("${}", payload.len())),
20859 "a payload of the length the store gave"
20860 );
20861 assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
20862 assert_eq!(f.run(&[b"RESTORE", b"d2", b"0", &payload]), "+OK\r\n");
20863 assert_eq!(f.run(&[b"GET", b"d2"]), "$7\r\npayload\r\n");
20864 assert_eq!(
20865 f.run(&[b"RESTORE", b"d2", b"0", &payload]),
20866 "-BUSYKEY Target key name already exists.\r\n"
20867 );
20868 assert_eq!(
20869 f.run(&[b"RESTORE", b"d3", b"0", b"rubbish"]),
20870 "-ERR DUMP payload version or checksum are wrong\r\n"
20871 );
20872 }
20873 }
20874
20875 #[test]
20882 fn a_scan_of_a_striped_database_walks_all_of_it() {
20883 let n = if cfg!(miri) { 80 } else { 500 };
20887 let mut f = Fixture::striped(8);
20888 for i in 0..n {
20889 let key = format!("key:{i}");
20890 f.run(&[b"SET", key.as_bytes(), b"v"]);
20891 }
20892
20893 let mut seen = Vec::new();
20894 let mut cursor = "0".to_owned();
20895 let mut calls = 0;
20896 loop {
20897 let reply = f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"10"]);
20898 let (next, keys) = scan_reply(&reply);
20899 seen.extend(keys);
20900 cursor = next;
20901 calls += 1;
20902 assert!(calls < 5_000, "a scan that will not finish");
20903 if cursor == "0" {
20904 break;
20905 }
20906 }
20907 seen.sort();
20908 assert_eq!(seen.len(), n, "a quiet scan answered a key twice");
20909 assert_eq!(seen, sorted(&f.run(&[b"KEYS", b"*"])));
20910
20911 let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"MATCH", b"key:4?"]);
20915 let (_, keys) = scan_reply(&reply);
20916 assert_eq!(keys.len(), 10, "key:40 through key:49");
20917 let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"TYPE", b"list"]);
20918 let (_, keys) = scan_reply(&reply);
20919 assert!(keys.is_empty(), "nothing here is a list");
20920 }
20921
20922 #[test]
20928 fn a_random_key_can_come_from_any_stripe() {
20929 let mut f = Fixture::striped(8);
20930 assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
20931 for i in 0..200 {
20932 let key = format!("key:{i}");
20933 f.run(&[b"SET", key.as_bytes(), b"v"]);
20934 }
20935 let mut homes = std::collections::HashSet::new();
20936 for _ in 0..200 {
20937 let got = f.run(&[b"RANDOMKEY"]);
20938 let key = got.split("\r\n").nth(1).expect("a key").to_owned();
20939 assert_eq!(f.run(&[b"EXISTS", key.as_bytes()]), ":1\r\n");
20940 homes.insert(f.server.striped(0).stripe_of(key.as_bytes()));
20941 }
20942 assert_eq!(homes.len(), 8, "some stripe was never drawn from");
20943 }
20944
20945 fn apart(f: &mut Fixture, src: &str) -> String {
20949 let home = f.server.striped(0).stripe_of(src.as_bytes());
20950 for i in 0..1_000 {
20951 let dst = format!("dst:{i}");
20952 if f.server.striped(0).stripe_of(dst.as_bytes()) != home {
20953 return dst;
20954 }
20955 }
20956 panic!("eight stripes and a thousand keys all landed in one place");
20957 }
20958
20959 #[test]
20962 fn a_rename_across_stripes_takes_everything_with_it() {
20963 let mut f = Fixture::striped(8);
20964 let dst = apart(&mut f, "src");
20965 let (src, dst) = (b"src".as_slice(), dst.as_bytes());
20966
20967 f.run(&[b"SET", src, b"v"]);
20968 f.run(&[b"EXPIRE", src, b"100"]);
20969 assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
20970 assert_eq!(f.run(&[b"EXISTS", src, dst]), ":1\r\n");
20971 assert_eq!(f.run(&[b"GET", dst]), "$1\r\nv\r\n");
20972 assert_eq!(f.run(&[b"TTL", dst]), ":100\r\n", "the deadline came too");
20973
20974 f.server
20979 .striped(0)
20980 .hold(src)
20981 .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
20982 .expect("a new list");
20983 assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
20984 assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n");
20985 assert_eq!(
20986 f.server.striped(0).hold(dst).llen(dst).expect("a list"),
20987 2,
20988 "the members are on the stripe the key moved to"
20989 );
20990
20991 f.run(&[b"SET", src, b"v"]);
20994 assert_eq!(f.run(&[b"RENAMENX", src, dst]), ":0\r\n");
20995 assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n", "and left it alone");
20996 assert_eq!(f.run(&[b"GET", src]), "$1\r\nv\r\n", "and left the source");
20997 }
20998
20999 #[test]
21001 fn a_copy_across_stripes_leaves_the_source_where_it_was() {
21002 let mut f = Fixture::striped(8);
21003 let dst = apart(&mut f, "src");
21004 let (src, dst) = (b"src".as_slice(), dst.as_bytes());
21005
21006 f.run(&[b"SET", src, b"v"]);
21007 assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
21008 assert_eq!(f.run(&[b"EXISTS", src, dst]), ":2\r\n");
21009 assert_eq!(
21010 f.run(&[b"COPY", src, dst]),
21011 ":0\r\n",
21012 "the destination is taken"
21013 );
21014 f.run(&[b"SET", src, b"w"]);
21015 assert_eq!(f.run(&[b"COPY", src, dst, b"REPLACE"]), ":1\r\n");
21016 assert_eq!(f.run(&[b"GET", dst]), "$1\r\nw\r\n");
21017
21018 f.run(&[b"DEL", src, dst]);
21022 f.server
21023 .striped(0)
21024 .hold(src)
21025 .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
21026 .expect("a new list");
21027 assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
21028 f.server
21029 .striped(0)
21030 .hold(src)
21031 .push(src, yo_kv::End::Right, core::iter::once(&b"c"[..]))
21032 .expect("a list that is there");
21033 assert_eq!(f.server.striped(0).hold(src).llen(src).expect("a list"), 3);
21034 assert_eq!(f.server.striped(0).hold(dst).llen(dst).expect("a list"), 2);
21035 }
21036
21037 #[test]
21045 fn the_bitmap_group_answers_the_same_however_many_stripes_there_are() {
21046 let script: &[&[&[u8]]] = &[
21047 &[b"SET", b"k1", b"foobar"],
21048 &[b"SETBIT", b"b1", b"7", b"1"],
21049 &[b"SETBIT", b"b1", b"7", b"0"],
21050 &[b"GETBIT", b"k1", b"6"],
21051 &[b"GETBIT", b"k1", b"100"],
21052 &[b"BITCOUNT", b"k1"],
21053 &[b"BITCOUNT", b"k1", b"0", b"0"],
21054 &[b"BITCOUNT", b"k1", b"5", b"30", b"BIT"],
21055 &[b"BITPOS", b"k1", b"1"],
21056 &[b"BITPOS", b"k1", b"0", b"2"],
21057 &[b"BITPOS", b"k1", b"1", b"2", b"-1", b"BIT"],
21058 &[
21059 b"BITFIELD",
21060 b"bf",
21061 b"SET",
21062 b"u8",
21063 b"0",
21064 b"255",
21065 b"GET",
21066 b"u8",
21067 b"0",
21068 ],
21069 &[
21070 b"BITFIELD",
21071 b"bf",
21072 b"OVERFLOW",
21073 b"SAT",
21074 b"INCRBY",
21075 b"u8",
21076 b"0",
21077 b"10",
21078 ],
21079 &[b"BITFIELD_RO", b"bf", b"GET", b"u8", b"0"],
21080 &[b"SET", b"s1", b"abc"],
21083 &[b"SET", b"s2", b"abd"],
21084 &[b"SET", b"s3", b"a"],
21085 &[b"BITOP", b"AND", b"d1", b"s1", b"s2"],
21086 &[b"GET", b"d1"],
21087 &[b"BITOP", b"OR", b"d2", b"s1", b"s2", b"s3"],
21088 &[b"GET", b"d2"],
21089 &[b"BITOP", b"XOR", b"d3", b"s1", b"s2"],
21090 &[b"STRLEN", b"d3"],
21091 &[b"BITOP", b"NOT", b"d4", b"s1"],
21092 &[b"STRLEN", b"d4"],
21093 &[b"BITOP", b"DIFF", b"d5", b"s1", b"s2"],
21094 &[b"BITOP", b"DIFF1", b"d6", b"s1", b"s2"],
21095 &[b"BITOP", b"ANDOR", b"d7", b"s1", b"s2"],
21096 &[b"BITOP", b"ONE", b"d8", b"s1", b"s2"],
21097 &[b"BITOP", b"AND", b"d1", b"gone", b"also-gone"],
21100 &[b"EXISTS", b"d1"],
21101 &[b"BITOP", b"OR", b"d9", b"s1", b"gone"],
21102 &[b"GET", b"d9"],
21103 &[b"BITOP", b"AND", b"d1", b"s1", b"list"],
21107 &[b"BITOP", b"AND", b"list", b"s1", b"s2"],
21108 &[b"BITOP", b"NOT", b"d1", b"s1", b"s2"],
21109 &[b"BITOP", b"DIFF", b"d1", b"s1"],
21110 &[b"BITOP", b"NOPE", b"d1", b"s1"],
21111 &[b"BITCOUNT", b"list"],
21112 &[b"BITFIELD_RO", b"bf", b"SET", b"u8", b"0", b"1"],
21113 ];
21114
21115 let mut one = Fixture::new();
21116 let mut many = Fixture::striped(8);
21117 for f in [&mut one, &mut many] {
21118 plant_list(f, b"list");
21119 }
21120 for parts in script {
21121 let a = one.run(parts);
21122 let b = many.run(parts);
21123 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
21124 }
21125 }
21126
21127 fn plant_list(f: &mut Fixture, key: &[u8]) {
21133 f.server
21134 .striped(0)
21135 .hold(key)
21136 .push(key, yo_kv::End::Right, core::iter::once(&b"x"[..]))
21137 .expect("a new list");
21138 }
21139
21140 #[test]
21147 fn a_bitop_across_stripes_reads_every_source() {
21148 let mut f = Fixture::striped(8);
21149 let other = apart(&mut f, "src");
21150 let (src, far) = (b"src".as_slice(), other.as_bytes());
21151 assert_ne!(
21152 f.server.striped(0).stripe_of(src),
21153 f.server.striped(0).stripe_of(far),
21154 "the two keys are the point of the test"
21155 );
21156
21157 f.run(&[b"SET", src, b"abc"]);
21158 f.run(&[b"SET", far, b"abd"]);
21159 assert_eq!(f.run(&[b"BITOP", b"AND", far, src, far]), ":3\r\n");
21160 assert_eq!(
21161 f.run(&[b"GET", far]),
21162 "$3\r\nab`\r\n",
21163 "a destination that is also a source"
21164 );
21165 f.run(&[b"SET", far, b"abd"]);
21166 assert_eq!(f.run(&[b"BITOP", b"XOR", src, src, far]), ":3\r\n");
21167 assert_eq!(
21168 f.run(&[b"GET", src]),
21169 "$3\r\n\0\0\x07\r\n",
21170 "and the other way round"
21171 );
21172
21173 f.run(&[b"SET", src, b"abc"]);
21177 f.run(&[b"DEL", far]);
21178 assert_eq!(f.run(&[b"BITOP", b"AND", src, far, b"gone"]), ":0\r\n");
21179 assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n");
21180 f.run(&[b"SET", src, b"abc"]);
21181 f.run(&[b"DEL", far]);
21182 plant_list(&mut f, far);
21183 assert_eq!(
21184 f.run(&[b"BITOP", b"OR", b"out", src, far]),
21185 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
21186 );
21187 assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
21188 }
21189
21190 #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
21196 #[test]
21197 fn the_hyperloglog_group_answers_the_same_however_many_stripes_there_are() {
21198 let script: &[&[&[u8]]] = &[
21199 &[b"PFADD", b"h1", b"a", b"b", b"c"],
21200 &[b"PFADD", b"h1", b"a"],
21201 &[b"PFADD", b"h2"],
21202 &[b"PFADD", b"h2", b"c", b"d", b"e"],
21203 &[b"PFCOUNT", b"h1"],
21204 &[b"PFCOUNT", b"h2"],
21205 &[b"PFCOUNT", b"missing"],
21206 &[b"PFCOUNT", b"h1", b"h2"],
21208 &[b"PFCOUNT", b"h1", b"missing"],
21209 &[b"PFMERGE", b"m", b"h1", b"h2"],
21210 &[b"PFCOUNT", b"m"],
21211 &[b"STRLEN", b"m"],
21212 &[b"PFMERGE", b"m"],
21213 &[b"PFCOUNT", b"m"],
21214 &[b"PFMERGE", b"m2", b"missing"],
21215 &[b"PFCOUNT", b"m2"],
21216 &[b"PFDEBUG", b"ENCODING", b"h1"],
21219 &[b"PFDEBUG", b"DECODE", b"h1"],
21220 &[b"PFDEBUG", b"TODENSE", b"h1"],
21221 &[b"PFDEBUG", b"ENCODING", b"h1"],
21222 &[b"PFDEBUG", b"TODENSE", b"h1"],
21223 &[b"PFCOUNT", b"h1", b"h2"],
21224 &[b"PFSELFTEST"],
21225 &[b"SET", b"plain", b"not a sketch at all"],
21227 &[b"PFADD", b"plain", b"a"],
21228 &[b"PFCOUNT", b"plain"],
21229 &[b"PFCOUNT", b"h1", b"plain"],
21230 &[b"PFMERGE", b"plain", b"h1"],
21231 &[b"PFMERGE", b"m", b"plain"],
21232 &[b"PFDEBUG", b"ENCODING", b"gone"],
21233 &[b"PFDEBUG", b"NOPE", b"h1"],
21234 ];
21235
21236 let mut one = Fixture::new();
21237 let mut many = Fixture::striped(8);
21238 for parts in script {
21239 let a = one.run(parts);
21240 let b = many.run(parts);
21241 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
21242 }
21243 }
21244
21245 #[test]
21254 fn the_set_group_answers_the_same_however_many_stripes_there_are() {
21255 const UNORDERED: [&str; 4] = ["SMEMBERS", "SINTER", "SUNION", "SDIFF"];
21256 let script: &[&[&[u8]]] = &[
21257 &[b"SADD", b"s1", b"a", b"b", b"c"],
21258 &[b"SADD", b"s1", b"a"],
21259 &[b"SADD", b"s2", b"b", b"c", b"d"],
21260 &[b"SADD", b"ints", b"1", b"2", b"3"],
21261 &[b"SCARD", b"s1"],
21262 &[b"SISMEMBER", b"s1", b"a"],
21263 &[b"SISMEMBER", b"s1", b"z"],
21264 &[b"SMISMEMBER", b"s1", b"a", b"z", b"c"],
21265 &[b"SMEMBERS", b"s1"],
21266 &[b"SREM", b"s1", b"c"],
21267 &[b"SADD", b"s1", b"c"],
21268 &[b"SSCAN", b"s1", b"0"],
21269 &[b"SSCAN", b"s1", b"0", b"COUNT", b"100", b"MATCH", b"a*"],
21270 &[b"SADD", b"one", b"m"],
21273 &[b"SRANDMEMBER", b"one"],
21274 &[b"SRANDMEMBER", b"one", b"-3"],
21275 &[b"SRANDMEMBER", b"gone"],
21276 &[b"SPOP", b"one"],
21277 &[b"SPOP", b"one"],
21278 &[b"SPOP", b"gone", b"2"],
21279 &[b"SMOVE", b"s1", b"s2", b"a"],
21281 &[b"SMOVE", b"s1", b"s2", b"zzz"],
21282 &[b"SMOVE", b"gone", b"s2", b"a"],
21283 &[b"SMEMBERS", b"s1"],
21284 &[b"SMEMBERS", b"s2"],
21285 &[b"SINTER", b"s1", b"s2"],
21287 &[b"SUNION", b"s1", b"s2"],
21288 &[b"SDIFF", b"s2", b"s1"],
21289 &[b"SINTER", b"s1", b"gone"],
21290 &[b"SUNION", b"s1", b"gone"],
21291 &[b"SDIFF", b"gone", b"s1"],
21292 &[b"SINTER", b"ints", b"s1"],
21293 &[b"SINTERCARD", b"2", b"s1", b"s2"],
21294 &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"1"],
21295 &[b"SUNIONCARD", b"2", b"s1", b"s2"],
21296 &[b"SDIFFCARD", b"2", b"s2", b"s1"],
21297 &[b"SINTERSTORE", b"d1", b"s1", b"s2"],
21298 &[b"SMEMBERS", b"d1"],
21299 &[b"SUNIONSTORE", b"d2", b"s1", b"s2"],
21300 &[b"SCARD", b"d2"],
21301 &[b"SDIFFSTORE", b"d3", b"s2", b"s1"],
21302 &[b"SCARD", b"d3"],
21303 &[b"SINTERSTORE", b"d4", b"s1", b"gone"],
21306 &[b"EXISTS", b"d4"],
21307 &[b"SUNIONSTORE", b"s2", b"s1", b"s2"],
21309 &[b"SCARD", b"s2"],
21310 &[b"SET", b"str", b"v"],
21312 &[b"SADD", b"str", b"a"],
21313 &[b"SINTER", b"s1", b"str"],
21314 &[b"SINTERSTORE", b"d5", b"s1", b"str"],
21315 &[b"EXISTS", b"d5"],
21316 &[b"SMOVE", b"str", b"s2", b"a"],
21317 &[b"SMOVE", b"s1", b"str", b"b"],
21318 &[b"SMOVE", b"gone", b"str", b"b"],
21319 &[b"SINTERCARD", b"0", b"s1"],
21320 &[b"SINTERCARD", b"3", b"s1", b"s2"],
21321 &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"-1"],
21322 &[b"SPOP", b"s1", b"-1"],
21323 ];
21324
21325 let mut one = Fixture::new();
21326 let mut many = Fixture::striped(8);
21327 for parts in script {
21328 let a = one.run(parts);
21329 let b = many.run(parts);
21330 let name = String::from_utf8_lossy(parts[0]).to_uppercase();
21331 if UNORDERED.contains(&name.as_str()) && a.starts_with(['*', '~']) {
21332 assert_eq!(sorted(&a), sorted(&b), "{name}");
21333 } else {
21334 assert_eq!(a, b, "{name}");
21335 }
21336 }
21337 }
21338
21339 #[test]
21341 fn a_set_operation_across_stripes_reads_every_set() {
21342 let mut f = Fixture::striped(8);
21343 let second = apart(&mut f, "s1");
21344 let third = apart(&mut f, &second);
21345 let (s1, s2, s3) = (b"s1".as_slice(), second.as_bytes(), third.as_bytes());
21346
21347 f.run(&[b"SADD", s1, b"a", b"b", b"c"]);
21348 f.run(&[b"SADD", s2, b"b", b"c", b"d"]);
21349 assert_eq!(sorted(&f.run(&[b"SINTER", s1, s2])), ["b", "c"]);
21350 assert_eq!(
21351 sorted(&f.run(&[b"SUNION", s1, s2])),
21352 ["a", "b", "c", "d"],
21353 "a union of two stripes is both of them"
21354 );
21355 assert_eq!(sorted(&f.run(&[b"SDIFF", s1, s2])), ["a"]);
21356 assert_eq!(f.run(&[b"SINTERCARD", b"2", s1, s2]), ":2\r\n");
21357 assert_eq!(f.run(&[b"SUNIONCARD", b"2", s1, s2]), ":4\r\n");
21358 assert_eq!(f.run(&[b"SDIFFCARD", b"2", s1, s2]), ":1\r\n");
21359
21360 assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, s2]), ":2\r\n");
21362 assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["b", "c"]);
21363 assert_eq!(f.run(&[b"SUNIONSTORE", s2, s1, s2]), ":4\r\n");
21364 assert_eq!(sorted(&f.run(&[b"SMEMBERS", s2])), ["a", "b", "c", "d"]);
21365 assert_eq!(f.run(&[b"SDIFFSTORE", s3, s2, s1]), ":1\r\n");
21366 assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["d"]);
21367
21368 assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, b"gone"]), ":0\r\n");
21371 assert_eq!(f.run(&[b"EXISTS", s3]), ":0\r\n");
21372 f.run(&[b"SET", s3, b"v"]);
21373 assert_eq!(
21374 f.run(&[b"SINTER", s1, s3]),
21375 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
21376 );
21377 assert_eq!(f.run(&[b"GET", s3]), "$1\r\nv\r\n", "and left it alone");
21378 }
21379
21380 #[test]
21382 fn a_move_across_stripes_takes_the_member_with_it() {
21383 let mut f = Fixture::striped(8);
21384 let other = apart(&mut f, "src");
21385 let (src, dst) = (b"src".as_slice(), other.as_bytes());
21386
21387 f.run(&[b"SADD", src, b"a", b"b"]);
21388 f.run(&[b"SADD", dst, b"c"]);
21389 assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":1\r\n");
21390 assert_eq!(sorted(&f.run(&[b"SMEMBERS", src])), ["b"]);
21391 assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["a", "c"]);
21392 assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":0\r\n", "it has gone");
21393
21394 f.run(&[b"DEL", dst]);
21397 assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":1\r\n");
21398 assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n", "the source is empty");
21399 assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["b"]);
21400
21401 f.run(&[b"SET", dst, b"v"]);
21405 assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":0\r\n");
21406 f.run(&[b"SADD", src, b"b"]);
21407 assert_eq!(
21408 f.run(&[b"SMOVE", src, dst, b"b"]),
21409 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
21410 );
21411 }
21412
21413 #[test]
21415 fn a_pfcount_and_a_pfmerge_reach_across_stripes() {
21416 let mut f = Fixture::striped(8);
21417 let other = apart(&mut f, "src");
21418 let (src, far) = (b"src".as_slice(), other.as_bytes());
21419
21420 for i in 0..150 {
21421 let ele = format!("e:{i}");
21422 f.run(&[b"PFADD", src, ele.as_bytes()]);
21423 }
21424 for i in 150..200 {
21425 let ele = format!("e:{i}");
21426 f.run(&[b"PFADD", far, ele.as_bytes()]);
21427 }
21428 assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n");
21431 assert_eq!(f.run(&[b"PFCOUNT", far]), ":49\r\n");
21432 assert_eq!(f.run(&[b"PFCOUNT", src, far]), ":199\r\n");
21433
21434 let dest = apart(&mut f, &other);
21437 assert_eq!(f.run(&[b"PFMERGE", dest.as_bytes(), src, far]), "+OK\r\n");
21438 assert_eq!(f.run(&[b"PFCOUNT", dest.as_bytes()]), ":199\r\n");
21439 assert_eq!(f.run(&[b"PFMERGE", far, src]), "+OK\r\n");
21440 assert_eq!(f.run(&[b"PFCOUNT", far]), ":199\r\n", "and kept its own");
21441 assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n", "and left the source");
21442 }
21443
21444 #[test]
21451 fn the_sorted_set_group_answers_the_same_however_many_stripes_there_are() {
21452 let script: &[&[&[u8]]] = &[
21453 &[b"ZADD", b"z1", b"1", b"a", b"2", b"b", b"3", b"c"],
21454 &[b"ZADD", b"z1", b"NX", b"9", b"a"],
21455 &[b"ZADD", b"z1", b"XX", b"CH", b"5", b"a"],
21456 &[b"ZADD", b"z1", b"GT", b"CH", b"1", b"a"],
21457 &[b"ZADD", b"z1", b"INCR", b"2", b"a"],
21458 &[b"ZINCRBY", b"z1", b"1.5", b"b"],
21459 &[b"ZADD", b"z2", b"1", b"b", b"2", b"c", b"3", b"d"],
21460 &[b"ZADD", b"lex", b"0", b"a", b"0", b"b", b"0", b"c"],
21461 &[b"ZADD", b"one", b"1", b"m"],
21462 &[b"ZCARD", b"z1"],
21463 &[b"ZCARD", b"gone"],
21464 &[b"ZSCORE", b"z1", b"a"],
21465 &[b"ZSCORE", b"z1", b"zz"],
21466 &[b"ZMSCORE", b"z1", b"a", b"zz", b"c"],
21467 &[b"ZRANK", b"z1", b"c"],
21468 &[b"ZRANK", b"z1", b"c", b"WITHSCORE"],
21469 &[b"ZREVRANK", b"z1", b"c"],
21470 &[b"ZRANK", b"z1", b"gone"],
21471 &[b"ZCOUNT", b"z1", b"-inf", b"+inf"],
21472 &[b"ZCOUNT", b"z1", b"(1", b"3"],
21473 &[b"ZLEXCOUNT", b"lex", b"-", b"+"],
21474 &[b"ZRANGE", b"z1", b"0", b"-1"],
21476 &[b"ZRANGE", b"z1", b"0", b"-1", b"WITHSCORES"],
21477 &[b"ZRANGE", b"z1", b"1", b"9", b"BYSCORE"],
21478 &[b"ZRANGE", b"z1", b"9", b"1", b"BYSCORE", b"REV"],
21479 &[b"ZRANGE", b"lex", b"[a", b"(c", b"BYLEX"],
21480 &[b"ZREVRANGE", b"z1", b"0", b"-1"],
21481 &[
21482 b"ZRANGEBYSCORE",
21483 b"z1",
21484 b"-inf",
21485 b"+inf",
21486 b"LIMIT",
21487 b"1",
21488 b"1",
21489 ],
21490 &[b"ZREVRANGEBYLEX", b"lex", b"+", b"-"],
21491 &[b"ZSCAN", b"z1", b"0"],
21492 &[b"ZSCAN", b"z1", b"0", b"MATCH", b"a*", b"COUNT", b"100"],
21493 &[b"ZRANDMEMBER", b"one"],
21496 &[b"ZRANDMEMBER", b"one", b"-3", b"WITHSCORES"],
21497 &[b"ZRANDMEMBER", b"gone"],
21498 &[b"ZRANGESTORE", b"d0", b"z1", b"0", b"1"],
21500 &[b"ZRANGE", b"d0", b"0", b"-1", b"WITHSCORES"],
21501 &[b"ZRANGESTORE", b"d0", b"z1", b"5", b"1"],
21502 &[b"EXISTS", b"d0"],
21503 &[b"ZUNION", b"2", b"z1", b"z2"],
21505 &[b"ZUNION", b"2", b"z1", b"z2", b"WITHSCORES"],
21506 &[
21507 b"ZUNION",
21508 b"2",
21509 b"z1",
21510 b"z2",
21511 b"WEIGHTS",
21512 b"2",
21513 b"3",
21514 b"AGGREGATE",
21515 b"MAX",
21516 b"WITHSCORES",
21517 ],
21518 &[b"ZINTER", b"2", b"z1", b"z2", b"WITHSCORES"],
21519 &[b"ZDIFF", b"2", b"z1", b"z2", b"WITHSCORES"],
21520 &[b"ZDIFF", b"2", b"gone", b"z1"],
21521 &[b"ZINTERCARD", b"2", b"z1", b"z2"],
21522 &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"1"],
21523 &[b"ZUNIONSTORE", b"d1", b"2", b"z1", b"z2"],
21524 &[b"ZRANGE", b"d1", b"0", b"-1", b"WITHSCORES"],
21525 &[
21526 b"ZINTERSTORE",
21527 b"d2",
21528 b"2",
21529 b"z1",
21530 b"z2",
21531 b"AGGREGATE",
21532 b"MIN",
21533 ],
21534 &[b"ZRANGE", b"d2", b"0", b"-1", b"WITHSCORES"],
21535 &[b"ZDIFFSTORE", b"d3", b"2", b"z1", b"z2"],
21536 &[b"ZCARD", b"d3"],
21537 &[b"ZINTERSTORE", b"d4", b"2", b"z1", b"gone"],
21540 &[b"EXISTS", b"d4"],
21541 &[b"SADD", b"plain", b"a", b"x"],
21544 &[b"ZUNIONSTORE", b"d5", b"2", b"z1", b"plain"],
21545 &[b"ZRANGE", b"d5", b"0", b"-1", b"WITHSCORES"],
21546 &[b"ZUNIONSTORE", b"z2", b"2", b"z1", b"z2"],
21548 &[b"ZRANGE", b"z2", b"0", b"-1", b"WITHSCORES"],
21549 &[b"ZREM", b"d5", b"x", b"nothere"],
21551 &[b"ZREMRANGEBYRANK", b"d5", b"0", b"0"],
21552 &[b"ZREMRANGEBYSCORE", b"d1", b"-inf", b"1"],
21553 &[b"ZREMRANGEBYLEX", b"lex", b"[a", b"[a"],
21554 &[b"ZPOPMIN", b"z1"],
21555 &[b"ZPOPMAX", b"z1", b"2"],
21556 &[b"ZPOPMIN", b"gone"],
21557 &[b"ZMPOP", b"2", b"gone", b"z2", b"MIN"],
21558 &[b"ZMPOP", b"2", b"gone", b"nothere", b"MAX", b"COUNT", b"2"],
21559 &[b"SET", b"str", b"v"],
21561 &[b"ZADD", b"str", b"1", b"a"],
21562 &[b"ZSCORE", b"str", b"a"],
21563 &[b"ZADD", b"z1", b"nan", b"a"],
21564 &[b"ZUNION", b"2", b"z1", b"str"],
21565 &[b"ZUNIONSTORE", b"d6", b"2", b"z1", b"str"],
21566 &[b"EXISTS", b"d6"],
21567 &[b"ZINTERCARD", b"0", b"z1"],
21568 &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"-1"],
21569 &[b"ZRANGESTORE", b"d7", b"str", b"0", b"-1"],
21570 &[b"ZMPOP", b"1", b"str", b"MIN"],
21571 &[b"ZPOPMIN", b"z1", b"-1"],
21572 ];
21573
21574 let mut one = Fixture::new();
21575 let mut many = Fixture::striped(8);
21576 for parts in script {
21577 let a = one.run(parts);
21578 let b = many.run(parts);
21579 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
21580 }
21581 }
21582
21583 #[test]
21585 fn a_sorted_set_operation_across_stripes_reads_every_input() {
21586 let mut f = Fixture::striped(8);
21587 let second = apart(&mut f, "z1");
21588 let third = apart(&mut f, &second);
21589 let (z1, z2, z3) = (b"z1".as_slice(), second.as_bytes(), third.as_bytes());
21590
21591 f.run(&[b"ZADD", z1, b"1", b"a", b"2", b"b"]);
21592 f.run(&[b"ZADD", z2, b"3", b"b", b"4", b"c"]);
21593 assert_eq!(
21596 f.run(&[b"ZUNION", b"2", z1, z2]),
21597 "*3\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n"
21598 );
21599 assert_eq!(f.run(&[b"ZINTER", b"2", z1, z2]), "*1\r\n$1\r\nb\r\n");
21600 assert_eq!(f.run(&[b"ZDIFF", b"2", z1, z2]), "*1\r\n$1\r\na\r\n");
21601 assert_eq!(f.run(&[b"ZINTERCARD", b"2", z1, z2]), ":1\r\n");
21602 assert_eq!(
21603 f.run(&[b"ZINTERCARD", b"2", z1, z2, b"LIMIT", b"1"]),
21604 ":1\r\n"
21605 );
21606
21607 assert_eq!(f.run(&[b"ZUNIONSTORE", z3, b"2", z1, z2]), ":3\r\n");
21610 assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n5\r\n");
21611 assert_eq!(
21612 f.run(&[
21613 b"ZUNIONSTORE",
21614 z3,
21615 b"2",
21616 z1,
21617 z2,
21618 b"WEIGHTS",
21619 b"2",
21620 b"3",
21621 b"AGGREGATE",
21622 b"MAX"
21623 ]),
21624 ":3\r\n"
21625 );
21626 assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n9\r\n");
21627 assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, z2]), ":1\r\n");
21628 assert_eq!(f.run(&[b"ZCARD", z3]), ":1\r\n");
21629 assert_eq!(f.run(&[b"ZDIFFSTORE", z3, b"2", z2, z1]), ":1\r\n");
21630 assert_eq!(f.run(&[b"ZSCORE", z3, b"c"]), "$1\r\n4\r\n");
21631
21632 let popped = format!(
21635 "*2\r\n${}\r\n{second}\r\n*1\r\n*2\r\n$1\r\nb\r\n$1\r\n3\r\n",
21636 second.len()
21637 );
21638 assert_eq!(f.run(&[b"ZMPOP", b"3", b"gone", z2, z1, b"MIN"]), popped);
21639 f.run(&[b"ZADD", z2, b"3", b"b"]);
21640
21641 assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, b"gone"]), ":0\r\n");
21644 assert_eq!(f.run(&[b"EXISTS", z3]), ":0\r\n");
21645 f.run(&[b"SET", z3, b"v"]);
21646 assert_eq!(
21647 f.run(&[b"ZUNION", b"2", z1, z3]),
21648 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
21649 );
21650 assert_eq!(f.run(&[b"GET", z3]), "$1\r\nv\r\n", "and left it alone");
21651
21652 assert_eq!(f.run(&[b"ZUNIONSTORE", z2, b"2", z1, z2]), ":3\r\n");
21656 assert_eq!(f.run(&[b"ZSCORE", z2, b"b"]), "$1\r\n5\r\n");
21657 assert_eq!(f.run(&[b"ZCARD", z2]), ":3\r\n");
21658 }
21659
21660 #[test]
21662 fn a_range_store_across_stripes_copies_the_window() {
21663 let mut f = Fixture::striped(8);
21664 let other = apart(&mut f, "src");
21665 let third = apart(&mut f, &other);
21666 let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
21667
21668 f.run(&[b"ZADD", src, b"1", b"a", b"2", b"b", b"3", b"c"]);
21669 assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"0", b"1"]), ":2\r\n");
21670 assert_eq!(
21671 f.run(&[b"ZRANGE", dst, b"0", b"-1", b"WITHSCORES"]),
21672 "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
21673 );
21674 assert_eq!(f.run(&[b"ZCARD", src]), ":3\r\n", "the source kept its own");
21675
21676 assert_eq!(
21679 f.run(&[
21680 b"ZRANGESTORE",
21681 dst,
21682 src,
21683 b"+inf",
21684 b"-inf",
21685 b"BYSCORE",
21686 b"REV",
21687 b"LIMIT",
21688 b"0",
21689 b"2"
21690 ]),
21691 ":2\r\n"
21692 );
21693 assert_eq!(
21694 f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
21695 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
21696 );
21697
21698 assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"5", b"1"]), ":0\r\n");
21701 assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
21702 f.run(&[b"ZRANGESTORE", dst, src, b"0", b"-1"]);
21703 f.run(&[b"SET", plain, b"v"]);
21704 assert_eq!(
21705 f.run(&[b"ZRANGESTORE", dst, plain, b"0", b"-1"]),
21706 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
21707 );
21708 assert_eq!(
21709 f.run(&[b"ZCARD", dst]),
21710 ":3\r\n",
21711 "and left the destination"
21712 );
21713 }
21714
21715 #[test]
21722 fn the_list_group_answers_the_same_however_many_stripes_there_are() {
21723 let script: &[&[&[u8]]] = &[
21724 &[b"RPUSH", b"l1", b"a", b"b", b"c"],
21725 &[b"LPUSH", b"l1", b"z"],
21726 &[b"RPUSHX", b"l1", b"d"],
21727 &[b"LPUSHX", b"gone", b"x"],
21728 &[b"RPUSHX", b"gone", b"x"],
21729 &[b"LLEN", b"l1"],
21730 &[b"LLEN", b"gone"],
21731 &[b"LRANGE", b"l1", b"0", b"-1"],
21732 &[b"LRANGE", b"l1", b"1", b"2"],
21733 &[b"LRANGE", b"l1", b"5", b"9"],
21734 &[b"LINDEX", b"l1", b"0"],
21735 &[b"LINDEX", b"l1", b"-1"],
21736 &[b"LINDEX", b"l1", b"99"],
21737 &[b"LSET", b"l1", b"0", b"y"],
21738 &[b"LINSERT", b"l1", b"BEFORE", b"b", b"aa"],
21739 &[b"LINSERT", b"l1", b"AFTER", b"nothere", b"x"],
21740 &[b"LPOS", b"l1", b"b"],
21741 &[b"LPOS", b"l1", b"b", b"COUNT", b"0"],
21742 &[b"LPOS", b"l1", b"nothere"],
21743 &[b"LPOS", b"l1", b"b", b"RANK", b"-1", b"MAXLEN", b"2"],
21744 &[b"LREM", b"l1", b"1", b"aa"],
21745 &[b"LTRIM", b"l1", b"0", b"3"],
21746 &[b"LRANGE", b"l1", b"0", b"-1"],
21747 &[b"LPOP", b"l1"],
21748 &[b"RPOP", b"l1"],
21749 &[b"LPOP", b"l1", b"2"],
21750 &[b"LPOP", b"gone"],
21751 &[b"LPOP", b"gone", b"2"],
21752 &[b"EXISTS", b"l1"],
21753 &[b"RPUSH", b"src", b"a", b"b", b"c", b"d"],
21756 &[b"LMOVE", b"src", b"dst", b"LEFT", b"RIGHT"],
21757 &[b"RPOPLPUSH", b"src", b"dst"],
21758 &[b"LRANGE", b"dst", b"0", b"-1"],
21759 &[b"LMOVE", b"gone", b"dst", b"LEFT", b"RIGHT"],
21760 &[b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT"],
21761 &[
21762 b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK",
21763 ],
21764 &[
21765 b"LMOVEM", b"dst", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"OBO",
21766 ],
21767 &[b"LRANGE", b"dst", b"0", b"-1"],
21768 &[
21769 b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"EXACTLY", b"9", b"BULK",
21770 ],
21771 &[b"LMPOP", b"2", b"gone", b"dst", b"LEFT"],
21772 &[b"LMPOP", b"2", b"gone", b"dst", b"RIGHT", b"COUNT", b"2"],
21773 &[b"LMPOP", b"1", b"gone", b"LEFT"],
21774 &[b"RPUSH", b"q", b"a", b"b", b"c"],
21777 &[b"BLPOP", b"gone", b"q", b"0"],
21778 &[b"BRPOP", b"q", b"0"],
21779 &[b"BLMPOP", b"0", b"2", b"gone", b"q", b"LEFT"],
21780 &[b"RPUSH", b"q", b"x", b"y", b"z"],
21781 &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
21782 &[b"BRPOPLPUSH", b"q", b"dst", b"0"],
21783 &[b"BLMOVEM", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
21784 &[b"BLPOP", b"q", b"0"],
21785 &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
21786 &[b"SET", b"plain", b"v"],
21788 &[b"LPUSH", b"plain", b"a"],
21789 &[b"LLEN", b"plain"],
21790 &[b"LMOVE", b"dst", b"plain", b"LEFT", b"RIGHT"],
21791 &[b"LRANGE", b"dst", b"0", b"-1"],
21792 &[b"LMOVEM", b"dst", b"plain", b"LEFT", b"RIGHT"],
21793 &[b"LSET", b"gone", b"0", b"v"],
21794 &[b"LSET", b"dst", b"99", b"v"],
21795 &[b"LPOP", b"dst", b"-1"],
21796 &[b"LMPOP", b"0", b"dst", b"LEFT"],
21797 &[b"LPOS", b"dst", b"a", b"RANK", b"0"],
21798 ];
21799
21800 let mut one = Fixture::new();
21801 let mut many = Fixture::striped(8);
21802 for parts in script {
21803 let a = one.run(parts);
21804 let b = many.run(parts);
21805 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
21806 }
21807 }
21808
21809 #[test]
21811 fn a_list_move_across_stripes_takes_the_elements_with_it() {
21812 let mut f = Fixture::striped(8);
21813 let other = apart(&mut f, "src");
21814 let third = apart(&mut f, &other);
21815 let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
21816
21817 f.run(&[b"RPUSH", src, b"a", b"b", b"c", b"d"]);
21818 assert_eq!(
21819 f.run(&[b"LMOVE", src, dst, b"LEFT", b"RIGHT"]),
21820 "$1\r\na\r\n"
21821 );
21822 assert_eq!(f.run(&[b"RPOPLPUSH", src, dst]), "$1\r\nd\r\n");
21823 assert_eq!(
21824 f.run(&[b"LRANGE", dst, b"0", b"-1"]),
21825 "*2\r\n$1\r\nd\r\n$1\r\na\r\n",
21826 "one went on each end of the destination"
21827 );
21828 assert_eq!(
21829 f.run(&[b"LRANGE", src, b"0", b"-1"]),
21830 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
21831 );
21832
21833 assert_eq!(
21835 f.run(&[
21836 b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
21837 ]),
21838 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
21839 );
21840 assert_eq!(
21841 f.run(&[b"LRANGE", dst, b"0", b"-1"]),
21842 "*4\r\n$1\r\nd\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
21843 );
21844 assert_eq!(
21845 f.run(&[b"EXISTS", src]),
21846 ":0\r\n",
21847 "and the source is gone with its last element"
21848 );
21849
21850 f.run(&[b"RPUSH", src, b"e", b"f"]);
21853 assert_eq!(
21854 f.run(&[
21855 b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"EXACTLY", b"3", b"BULK"
21856 ]),
21857 "*-1\r\n"
21858 );
21859 assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n", "and took none of them");
21860 assert_eq!(
21861 f.run(&[b"LMOVE", b"gone", dst, b"LEFT", b"RIGHT"]),
21862 "$-1\r\n"
21863 );
21864 assert_eq!(
21865 f.run(&[b"LMOVEM", b"gone", dst, b"LEFT", b"RIGHT"]),
21866 "*-1\r\n"
21867 );
21868
21869 f.run(&[b"SET", plain, b"v"]);
21873 assert_eq!(
21874 f.run(&[b"LMOVE", src, plain, b"LEFT", b"RIGHT"]),
21875 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
21876 );
21877 assert_eq!(
21878 f.run(&[b"LLEN", src]),
21879 ":2\r\n",
21880 "and left the source alone"
21881 );
21882 assert_eq!(
21883 f.run(&[b"LMOVEM", src, plain, b"LEFT", b"RIGHT"]),
21884 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
21885 );
21886 assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n");
21887 }
21888
21889 #[test]
21895 fn a_parked_client_is_served_from_the_stripe_its_key_is_on() {
21896 let mut f = Fixture::striped(8);
21897 let other = apart(&mut f, "q");
21898 let (q, far) = (b"q".as_slice(), other.as_bytes());
21899
21900 assert_eq!(f.flow(&[b"BLPOP", q, far, b"0"]).0, Flow::Block);
21901 assert_eq!(f.server.parked(), 1);
21902 f.run(&[b"RPUSH", far, b"v"]);
21903 let mut out = Out::new(Proto::Resp2);
21904 assert!(f.server.serve_waiter(7, 0, &mut out));
21905 let want = format!("*2\r\n${}\r\n{other}\r\n$1\r\nv\r\n", other.len());
21906 assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
21907 assert_eq!(
21908 f.run(&[b"EXISTS", far]),
21909 ":0\r\n",
21910 "and it took the element with it"
21911 );
21912
21913 f.server.forget_waiters(7);
21916 assert_eq!(
21917 f.flow(&[b"BLMOVE", q, far, b"LEFT", b"RIGHT", b"0"]).0,
21918 Flow::Block
21919 );
21920 f.run(&[b"RPUSH", q, b"w"]);
21921 let mut out = Out::new(Proto::Resp2);
21922 assert!(f.server.serve_waiter(7, 0, &mut out));
21923 assert_eq!(
21924 core::str::from_utf8(out.as_slice()).expect("ascii"),
21925 "$1\r\nw\r\n"
21926 );
21927 assert_eq!(f.run(&[b"LRANGE", far, b"0", b"-1"]), "*1\r\n$1\r\nw\r\n");
21928 }
21929
21930 #[test]
21936 fn the_stream_group_answers_the_same_however_many_stripes_there_are() {
21937 let script: &[&[&[u8]]] = &[
21938 &[b"XADD", b"s", b"1-1", b"a", b"1"],
21939 &[b"XADD", b"s", b"2-1", b"b", b"2", b"c", b"3"],
21940 &[b"XADD", b"s", b"3-1", b"d", b"4"],
21941 &[b"XADD", b"s", b"1-1", b"e", b"5"],
21942 &[b"XADD", b"nomk", b"NOMKSTREAM", b"1-1", b"a", b"1"],
21943 &[b"XLEN", b"s"],
21944 &[b"XLEN", b"gone"],
21945 &[b"XRANGE", b"s", b"-", b"+"],
21946 &[b"XRANGE", b"s", b"2", b"+", b"COUNT", b"1"],
21947 &[b"XRANGE", b"gone", b"-", b"+", b"COUNT", b"0"],
21948 &[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"],
21949 &[b"XREVRANGE", b"s", b"+", b"-"],
21950 &[b"XREAD", b"COUNT", b"2", b"STREAMS", b"s", b"0"],
21951 &[b"XREAD", b"STREAMS", b"s", b"gone", b"0", b"0"],
21952 &[b"XREAD", b"STREAMS", b"s", b"$"],
21953 &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
21955 &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
21956 &[b"XGROUP", b"CREATE", b"gone", b"g", b"0"],
21957 &[b"XGROUP", b"CREATE", b"made", b"g", b"$", b"MKSTREAM"],
21958 &[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"idle"],
21959 &[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"],
21960 &[
21961 b"XREADGROUP",
21962 b"GROUP",
21963 b"g",
21964 b"c1",
21965 b"COUNT",
21966 b"1",
21967 b"STREAMS",
21968 b"s",
21969 b"0",
21970 ],
21971 &[
21972 b"XREADGROUP",
21973 b"GROUP",
21974 b"nope",
21975 b"c1",
21976 b"STREAMS",
21977 b"s",
21978 b">",
21979 ],
21980 &[b"XPENDING", b"s", b"g"],
21981 &[b"XPENDING", b"s", b"g", b"-", b"+", b"10"],
21982 &[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"],
21983 &[b"XPENDING", b"s", b"nope"],
21984 &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1"],
21985 &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1", b"JUSTID"],
21986 &[b"XAUTOCLAIM", b"s", b"g", b"c3", b"0", b"0"],
21987 &[b"XACK", b"s", b"g", b"1-1"],
21988 &[b"XACK", b"s", b"g", b"1-1"],
21989 &[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"],
21990 &[b"XPENDING", b"s", b"g"],
21991 &[b"XINFO", b"STREAM", b"s"],
21992 &[b"XINFO", b"GROUPS", b"s"],
21993 &[b"XINFO", b"CONSUMERS", b"s", b"g"],
21994 &[b"XINFO", b"STREAM", b"gone"],
21995 &[b"XDEL", b"s", b"3-1"],
21997 &[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"],
21998 &[b"XACKDEL", b"s", b"g", b"KEEPREF", b"IDS", b"1", b"1-1"],
21999 &[b"XADD", b"s", b"9-1", b"z", b"9"],
22000 &[b"XTRIM", b"s", b"MAXLEN", b"1"],
22001 &[b"XTRIM", b"s", b"MINID", b"9"],
22002 &[b"XSETID", b"s", b"99-1"],
22003 &[b"XSETID", b"s", b"1-1"],
22004 &[b"XLEN", b"s"],
22005 &[b"XGROUP", b"SETID", b"s", b"g", b"0"],
22006 &[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c1"],
22007 &[b"XGROUP", b"DESTROY", b"s", b"g"],
22008 &[b"XGROUP", b"DESTROY", b"s", b"g"],
22009 &[b"SET", b"plain", b"v"],
22011 &[b"XADD", b"plain", b"1-1", b"a", b"1"],
22012 &[b"XLEN", b"plain"],
22013 &[b"XREAD", b"STREAMS", b"plain", b"0"],
22014 &[b"XRANGE", b"s", b"bogus", b"+"],
22015 &[b"XADD", b"s", b"1-1", b"a"],
22016 &[b"XREAD", b"STREAMS", b"s", b"gone", b"0"],
22017 &[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"],
22018 ];
22019
22020 let mut one = Fixture::new();
22021 let mut many = Fixture::striped(8);
22022 for parts in script {
22023 let a = one.run(parts);
22024 let b = many.run(parts);
22025 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22026 }
22027 }
22028
22029 #[test]
22035 fn a_stream_read_across_stripes_reads_every_key() {
22036 let mut f = Fixture::striped(8);
22037 let other = apart(&mut f, "s1");
22038 let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
22039
22040 f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
22041 f.run(&[b"XADD", s2, b"2-1", b"b", b"2"]);
22042 let got = f.run(&[b"XREAD", b"STREAMS", s1, s2, b"0", b"0"]);
22043 assert!(got.starts_with("*2\r\n"), "both streams answered: {got}");
22044 assert!(got.contains("1-1"), "the first one is in there: {got}");
22045 assert!(got.contains("2-1"), "and so is the second: {got}");
22046
22047 f.run(&[b"XGROUP", b"CREATE", s1, b"g", b"0"]);
22050 let got = f.run(&[
22051 b"XREADGROUP",
22052 b"GROUP",
22053 b"g",
22054 b"c",
22055 b"STREAMS",
22056 s1,
22057 s2,
22058 b">",
22059 b">",
22060 ]);
22061 assert!(got.starts_with("-NOGROUP"), "{got}");
22062 assert_eq!(
22063 f.run(&[b"XPENDING", s1, b"g"]),
22064 "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n",
22065 "and read nothing from the key that did have the group"
22066 );
22067
22068 f.run(&[b"XGROUP", b"CREATE", s2, b"g", b"0"]);
22069 let got = f.run(&[
22070 b"XREADGROUP",
22071 b"GROUP",
22072 b"g",
22073 b"c",
22074 b"STREAMS",
22075 s1,
22076 s2,
22077 b">",
22078 b">",
22079 ]);
22080 assert!(got.starts_with("*2\r\n"), "now both are read: {got}");
22081 }
22082
22083 #[test]
22085 fn a_parked_stream_reader_is_served_from_the_stripe_its_key_is_on() {
22086 let mut f = Fixture::striped(8);
22087 let other = apart(&mut f, "s1");
22088 let (s1, far) = (b"s1".as_slice(), other.as_bytes());
22089 f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
22090 f.run(&[b"XADD", far, b"1-1", b"a", b"1"]);
22091
22092 assert_eq!(
22093 f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", s1, far, b"$", b"$"])
22094 .0,
22095 Flow::Block
22096 );
22097 f.run(&[b"XADD", far, b"2-1", b"b", b"2"]);
22098 let mut out = Out::new(Proto::Resp2);
22099 assert!(f.server.serve_waiter(7, 0, &mut out));
22100 let want = format!(
22101 "*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",
22102 other.len()
22103 );
22104 assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
22105 }
22106
22107 #[test]
22109 fn the_json_group_answers_the_same_however_many_stripes_there_are() {
22110 let script: &[&[&[u8]]] = &[
22111 &[
22112 b"JSON.SET",
22113 b"d",
22114 b"$",
22115 br#"{"a":1,"b":[1,2,3],"s":"hi","t":true}"#,
22116 ],
22117 &[b"JSON.SET", b"d", b"$.a", b"2"],
22118 &[b"JSON.SET", b"d", b"$.new", b"9", b"NX"],
22119 &[b"JSON.SET", b"d", b"$.new", b"8", b"NX"],
22120 &[b"JSON.SET", b"d", b"$.nope", b"7", b"XX"],
22121 &[b"JSON.GET", b"d"],
22122 &[b"JSON.GET", b"d", b"$.b"],
22123 &[b"JSON.GET", b"gone", b"$"],
22124 &[b"JSON.TYPE", b"d", b"$.b"],
22125 &[b"JSON.TYPE", b"d", b"$.s"],
22126 &[b"JSON.TOGGLE", b"d", b"$.t"],
22127 &[b"JSON.ARRLEN", b"d", b"$.b"],
22128 &[b"JSON.OBJLEN", b"d", b"$"],
22129 &[b"JSON.OBJKEYS", b"d", b"$"],
22130 &[b"JSON.STRLEN", b"d", b"$.s"],
22131 &[b"JSON.STRAPPEND", b"d", b"$.s", br#""there""#],
22132 &[b"JSON.ARRAPPEND", b"d", b"$.b", b"4"],
22133 &[b"JSON.ARRINSERT", b"d", b"$.b", b"0", b"0"],
22134 &[b"JSON.ARRINDEX", b"d", b"$.b", b"3"],
22135 &[b"JSON.ARRTRIM", b"d", b"$.b", b"1", b"3"],
22136 &[b"JSON.ARRPOP", b"d", b"$.b"],
22137 &[b"JSON.NUMINCRBY", b"d", b"$.a", b"5"],
22138 &[b"JSON.NUMMULTBY", b"d", b"$.a", b"2"],
22139 &[b"JSON.NUMPOWBY", b"d", b"$.a", b"2"],
22140 &[b"JSON.MERGE", b"d", b"$", br#"{"a":null,"m":1}"#],
22141 &[b"JSON.RESP", b"d", b"$.b"],
22142 &[b"JSON.DEBUG", b"MEMORY", b"d"],
22143 &[b"JSON.CLEAR", b"d", b"$.b"],
22144 &[b"JSON.DEL", b"d", b"$.m"],
22145 &[b"JSON.FORGET", b"d", b"$.nothere"],
22146 &[
22148 b"JSON.MSET",
22149 b"m1",
22150 b"$",
22151 b"1",
22152 b"m2",
22153 b"$",
22154 b"2",
22155 b"m3",
22156 b"$",
22157 b"3",
22158 ],
22159 &[b"JSON.MGET", b"m1", b"m2", b"m3", b"gone", b"$"],
22160 &[b"JSON.MSET", b"m1", b"$", b"9", b"m2", b"$.deep", b"9"],
22161 &[b"JSON.GET", b"m1", b"$"],
22162 &[b"JSON.MSET", b"m1", b"$", b"nonsense", b"m2", b"$", b"5"],
22163 &[b"JSON.GET", b"m2", b"$"],
22164 &[b"SET", b"plain", b"v"],
22166 &[b"JSON.GET", b"plain", b"$"],
22167 &[b"JSON.SET", b"plain", b"$", b"1"],
22168 &[b"JSON.MGET", b"m1", b"plain", b"$"],
22169 &[b"JSON.SET", b"d", b"$.b", b"["],
22170 &[b"JSON.DEL", b"plain"],
22171 ];
22172
22173 let mut one = Fixture::new();
22174 let mut many = Fixture::striped(8);
22175 for parts in script {
22176 let a = one.run(parts);
22177 let b = many.run(parts);
22178 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22179 }
22180 }
22181
22182 #[test]
22189 fn a_json_multi_write_across_stripes_reaches_every_key() {
22190 let mut f = Fixture::striped(8);
22191 let second = apart(&mut f, "m1");
22192 let third = apart(&mut f, &second);
22193 let (m1, m2, m3) = (b"m1".as_slice(), second.as_bytes(), third.as_bytes());
22194
22195 assert_eq!(
22196 f.run(&[b"JSON.MSET", m1, b"$", b"1", m2, b"$", b"2", m3, b"$", b"3"]),
22197 "+OK\r\n"
22198 );
22199 assert_eq!(
22200 f.run(&[b"JSON.MGET", m1, m2, m3, b"gone", b"$"]),
22201 "*4\r\n$3\r\n[1]\r\n$3\r\n[2]\r\n$3\r\n[3]\r\n$-1\r\n"
22202 );
22203
22204 assert_eq!(
22207 f.run(&[b"JSON.MSET", m1, b"$", b"9", m2, b"$", b"nonsense"]),
22208 "-this is not the start of a value, at byte 0 of the JSON text\r\n"
22209 );
22210 assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[1]\r\n");
22211
22212 assert_eq!(
22216 f.run(&[
22217 b"JSON.MSET",
22218 m1,
22219 b"$",
22220 b"9",
22221 m2,
22222 b"$.deep",
22223 b"9",
22224 m3,
22225 b"$",
22226 b"7"
22227 ]),
22228 "$-1\r\n"
22229 );
22230 assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[9]\r\n");
22231 assert_eq!(f.run(&[b"JSON.GET", m2, b"$"]), "$3\r\n[2]\r\n");
22232 assert_eq!(f.run(&[b"JSON.GET", m3, b"$"]), "$3\r\n[7]\r\n");
22233 }
22234
22235 #[test]
22237 fn the_geo_group_answers_the_same_however_many_stripes_there_are() {
22238 let script: &[&[&[u8]]] = &[
22239 &[
22240 b"GEOADD",
22241 b"g",
22242 b"13.361389",
22243 b"38.115556",
22244 b"palermo",
22245 b"15.087269",
22246 b"37.502669",
22247 b"catania",
22248 ],
22249 &[
22250 b"GEOADD",
22251 b"g",
22252 b"NX",
22253 b"13.361389",
22254 b"38.115556",
22255 b"palermo",
22256 ],
22257 &[b"GEOADD", b"g", b"XX", b"CH", b"13.4", b"38.1", b"palermo"],
22258 &[b"GEOPOS", b"g", b"palermo", b"nothere"],
22259 &[b"GEOHASH", b"g", b"palermo", b"catania"],
22260 &[b"GEODIST", b"g", b"palermo", b"catania"],
22261 &[b"GEODIST", b"g", b"palermo", b"catania", b"KM"],
22262 &[b"GEODIST", b"g", b"palermo", b"nothere"],
22263 &[
22264 b"GEOSEARCH",
22265 b"g",
22266 b"FROMLONLAT",
22267 b"15",
22268 b"37",
22269 b"BYRADIUS",
22270 b"200",
22271 b"KM",
22272 b"ASC",
22273 b"WITHCOORD",
22274 b"WITHDIST",
22275 b"WITHHASH",
22276 ],
22277 &[
22278 b"GEOSEARCH",
22279 b"g",
22280 b"FROMMEMBER",
22281 b"palermo",
22282 b"BYBOX",
22283 b"400",
22284 b"400",
22285 b"KM",
22286 b"DESC",
22287 ],
22288 &[
22289 b"GEORADIUS",
22290 b"g",
22291 b"15",
22292 b"37",
22293 b"200",
22294 b"KM",
22295 b"COUNT",
22296 b"1",
22297 ],
22298 &[b"GEORADIUSBYMEMBER", b"g", b"palermo", b"200", b"KM"],
22299 &[b"GEORADIUSBYMEMBER_RO", b"g", b"nothere", b"200", b"KM"],
22300 &[
22301 b"GEOSEARCHSTORE",
22302 b"dst",
22303 b"g",
22304 b"FROMLONLAT",
22305 b"15",
22306 b"37",
22307 b"BYRADIUS",
22308 b"200",
22309 b"KM",
22310 ],
22311 &[b"ZRANGE", b"dst", b"0", b"-1"],
22312 &[
22313 b"GEOSEARCHSTORE",
22314 b"dst",
22315 b"g",
22316 b"FROMLONLAT",
22317 b"15",
22318 b"37",
22319 b"BYRADIUS",
22320 b"1",
22321 b"M",
22322 b"STOREDIST",
22323 ],
22324 &[b"EXISTS", b"dst"],
22325 &[
22326 b"GEORADIUS",
22327 b"g",
22328 b"15",
22329 b"37",
22330 b"200",
22331 b"KM",
22332 b"STORE",
22333 b"dst",
22334 ],
22335 &[b"ZCARD", b"dst"],
22336 &[b"GEOADD", b"g", b"181", b"38", b"nowhere"],
22338 &[b"SET", b"plain", b"v"],
22339 &[b"GEOPOS", b"plain", b"a"],
22340 &[b"GEOSEARCH", b"g", b"FROMLONLAT", b"15", b"37"],
22341 &[
22342 b"GEOSEARCHSTORE",
22343 b"dst",
22344 b"g",
22345 b"FROMLONLAT",
22346 b"15",
22347 b"37",
22348 b"BYRADIUS",
22349 b"200",
22350 b"KM",
22351 b"WITHCOORD",
22352 ],
22353 ];
22354
22355 let mut one = Fixture::new();
22356 let mut many = Fixture::striped(8);
22357 for parts in script {
22358 let a = one.run(parts);
22359 let b = many.run(parts);
22360 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22361 }
22362 }
22363
22364 #[test]
22366 fn a_geo_search_store_across_stripes_writes_what_it_found() {
22367 let mut f = Fixture::striped(8);
22368 let other = apart(&mut f, "g");
22369 let third = apart(&mut f, &other);
22370 let (g, dst, plain) = (b"g".as_slice(), other.as_bytes(), third.as_bytes());
22371
22372 f.run(&[
22373 b"GEOADD",
22374 g,
22375 b"13.361389",
22376 b"38.115556",
22377 b"palermo",
22378 b"15.087269",
22379 b"37.502669",
22380 b"catania",
22381 ]);
22382 assert_eq!(
22383 f.run(&[
22384 b"GEOSEARCHSTORE",
22385 dst,
22386 g,
22387 b"FROMLONLAT",
22388 b"15",
22389 b"37",
22390 b"BYRADIUS",
22391 b"200",
22392 b"KM",
22393 b"ASC",
22394 ]),
22395 ":2\r\n"
22396 );
22397 assert_eq!(
22398 f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
22399 "*2\r\n$7\r\npalermo\r\n$7\r\ncatania\r\n",
22400 "the geohash is the score, so the order is not the search order"
22401 );
22402 assert_eq!(f.run(&[b"ZCARD", g]), ":2\r\n", "the source is untouched");
22403
22404 assert_eq!(
22407 f.run(&[
22408 b"GEOSEARCHSTORE",
22409 dst,
22410 g,
22411 b"FROMMEMBER",
22412 b"palermo",
22413 b"BYRADIUS",
22414 b"200",
22415 b"KM",
22416 b"STOREDIST",
22417 ]),
22418 ":2\r\n"
22419 );
22420 assert_eq!(
22421 f.run(&[b"ZSCORE", dst, b"palermo"]),
22422 "$1\r\n0\r\n",
22423 "the centre is nought away from itself"
22424 );
22425
22426 assert_eq!(
22430 f.run(&[
22431 b"GEOSEARCHSTORE",
22432 dst,
22433 g,
22434 b"FROMLONLAT",
22435 b"0",
22436 b"0",
22437 b"BYRADIUS",
22438 b"1",
22439 b"M",
22440 ]),
22441 ":0\r\n"
22442 );
22443 assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
22444 f.run(&[
22445 b"GEOSEARCHSTORE",
22446 dst,
22447 g,
22448 b"FROMLONLAT",
22449 b"15",
22450 b"37",
22451 b"BYRADIUS",
22452 b"200",
22453 b"KM",
22454 ]);
22455 f.run(&[b"SET", plain, b"v"]);
22456 assert_eq!(
22457 f.run(&[
22458 b"GEOSEARCHSTORE",
22459 dst,
22460 plain,
22461 b"FROMLONLAT",
22462 b"15",
22463 b"37",
22464 b"BYRADIUS",
22465 b"200",
22466 b"KM",
22467 ]),
22468 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22469 );
22470 assert_eq!(
22471 f.run(&[b"ZCARD", dst]),
22472 ":2\r\n",
22473 "and left the destination"
22474 );
22475 }
22476
22477 #[test]
22483 fn the_time_series_group_answers_the_same_however_many_stripes_there_are() {
22484 let script: &[&[&[u8]]] = &[
22485 &[
22486 b"TS.CREATE",
22487 b"ts:a",
22488 b"LABELS",
22489 b"sensor",
22490 b"a",
22491 b"room",
22492 b"1",
22493 ],
22494 &[b"TS.CREATE", b"ts:a"],
22495 &[b"TS.ALTER", b"ts:a", b"RETENTION", b"0"],
22496 &[b"TS.ADD", b"ts:a", b"1000", b"1.5"],
22497 &[
22498 b"TS.ADD", b"ts:b", b"1000", b"2", b"LABELS", b"sensor", b"b", b"room", b"1",
22499 ],
22500 &[
22501 b"TS.MADD", b"ts:a", b"2000", b"2.5", b"ts:b", b"2000", b"3", b"gone", b"1", b"1",
22502 ],
22503 &[b"TS.INCRBY", b"ts:a", b"1", b"TIMESTAMP", b"3000"],
22504 &[b"TS.DECRBY", b"ts:a", b"0.5", b"TIMESTAMP", b"4000"],
22505 &[b"TS.GET", b"ts:a"],
22506 &[b"TS.GET", b"gone"],
22507 &[b"TS.RANGE", b"ts:a", b"-", b"+"],
22508 &[b"TS.RANGE", b"ts:a", b"1000", b"3000", b"COUNT", b"2"],
22509 &[
22510 b"TS.RANGE",
22511 b"ts:a",
22512 b"-",
22513 b"+",
22514 b"AGGREGATION",
22515 b"avg",
22516 b"2000",
22517 ],
22518 &[b"TS.REVRANGE", b"ts:a", b"-", b"+"],
22519 &[b"TS.NRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
22520 &[b"TS.NREVRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
22521 &[b"TS.NRANGE", b"2", b"ts:a", b"gone", b"-", b"+"],
22522 &[b"TS.READ", b"ts:a", b"0"],
22523 &[b"TS.READ", b"ts:a", b"+"],
22524 &[b"TS.QUERYINDEX", b"sensor=a"],
22526 &[b"TS.QUERYINDEX", b"room=1"],
22527 &[b"TS.QUERYINDEX", b"room=9"],
22528 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"],
22529 &[
22530 b"TS.QUERYLABELS",
22531 b"VALUES",
22532 b"sensor",
22533 b"FILTER",
22534 b"room=1",
22535 ],
22536 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=1"],
22537 &[
22538 b"TS.MGET",
22539 b"SELECTED_LABELS",
22540 b"sensor",
22541 b"FILTER",
22542 b"sensor=a",
22543 ],
22544 &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"],
22545 &[
22546 b"TS.MREVRANGE",
22547 b"-",
22548 b"+",
22549 b"WITHLABELS",
22550 b"FILTER",
22551 b"sensor=a",
22552 ],
22553 &[
22554 b"TS.MRANGE",
22555 b"-",
22556 b"+",
22557 b"FILTER",
22558 b"room=1",
22559 b"GROUPBY",
22560 b"room",
22561 b"REDUCE",
22562 b"max",
22563 ],
22564 &[b"TS.INFO", b"ts:a"],
22565 &[
22567 b"TS.CREATERULE",
22568 b"ts:a",
22569 b"ts:down",
22570 b"AGGREGATION",
22571 b"avg",
22572 b"1000",
22573 ],
22574 &[b"TS.CREATE", b"ts:down"],
22575 &[
22576 b"TS.CREATERULE",
22577 b"ts:a",
22578 b"ts:down",
22579 b"AGGREGATION",
22580 b"avg",
22581 b"1000",
22582 ],
22583 &[b"TS.ADD", b"ts:a", b"5000", b"4"],
22584 &[b"TS.ADD", b"ts:a", b"6000", b"5"],
22585 &[b"TS.RANGE", b"ts:down", b"-", b"+"],
22586 &[b"TS.GET", b"ts:down", b"LATEST"],
22587 &[b"TS.INFO", b"ts:down"],
22588 &[b"TS.DEL", b"ts:a", b"5000", b"6000"],
22589 &[b"TS.RANGE", b"ts:down", b"-", b"+"],
22590 &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
22591 &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
22592 &[b"TS.DEL", b"ts:a", b"0", b"1000"],
22593 &[b"SET", b"plain", b"v"],
22595 &[b"TS.ADD", b"plain", b"1", b"1"],
22596 &[b"TS.GET", b"plain"],
22597 &[b"TS.READ", b"plain", b"0"],
22598 &[b"TS.ALTER", b"gone", b"RETENTION", b"0"],
22599 &[b"TS.RANGE", b"gone", b"-", b"+"],
22600 &[b"TS.INFO", b"gone"],
22601 ];
22602
22603 let mut one = Fixture::new();
22604 let mut many = Fixture::striped(8);
22605 for parts in script {
22606 let a = one.run(parts);
22607 let b = many.run(parts);
22608 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22609 }
22610 }
22611
22612 #[test]
22622 fn a_compaction_rule_across_stripes_reaches_both_ends() {
22623 let mut many = Fixture::striped(8);
22624 let other = apart(&mut many, "src");
22625 let (src, dst) = (b"src".as_slice(), other.as_bytes());
22626 let mut one = Fixture::new();
22627 let mut both = |parts: &[&[u8]]| {
22628 let a = one.run(parts);
22629 let b = many.run(parts);
22630 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22631 a
22632 };
22633
22634 both(&[b"TS.CREATE", src]);
22635 both(&[b"TS.CREATE", dst]);
22636 assert_eq!(
22637 both(&[b"TS.CREATERULE", src, dst, b"AGGREGATION", b"avg", b"1000"]),
22638 "+OK\r\n"
22639 );
22640 both(&[b"TS.ADD", src, b"1000", b"1"]);
22641 both(&[b"TS.ADD", src, b"1500", b"3"]);
22642 assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
22645 let open = both(&[b"TS.GET", dst, b"LATEST"]);
22646 assert!(open.contains(":1000"), "the open bucket is folded: {open}");
22647
22648 both(&[b"TS.ADD", src, b"2000", b"5"]);
22651 let got = both(&[b"TS.RANGE", dst, b"-", b"+"]);
22652 assert!(got.starts_with("*1\r\n"), "the bucket was written: {got}");
22653 assert!(got.contains(":1000"), "{got}");
22654
22655 both(&[b"TS.DEL", src, b"1000", b"1999"]);
22657 assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
22658
22659 assert!(
22662 both(&[b"TS.INFO", dst]).contains("src"),
22663 "the source is named"
22664 );
22665 assert_eq!(both(&[b"TS.DELETERULE", src, dst]), "+OK\r\n");
22666 assert_eq!(
22667 both(&[b"TS.DELETERULE", src, dst]),
22668 "-ERR TSDB: compaction rule does not exist\r\n"
22669 );
22670 }
22671
22672 #[test]
22674 fn a_label_query_across_stripes_finds_every_series() {
22675 let names: [&[u8]; 6] = [b"q:1", b"q:2", b"q:3", b"q:4", b"q:5", b"q:6"];
22676 let mut many = Fixture::striped(8);
22677 let mut homes: Vec<usize> = names
22678 .iter()
22679 .map(|name| many.server.striped(0).stripe_of(name))
22680 .collect();
22681 homes.sort_unstable();
22682 homes.dedup();
22683 assert!(homes.len() > 1, "the six keys are not all on one stripe");
22684
22685 let mut one = Fixture::new();
22686 let mut both = |parts: &[&[u8]]| {
22687 let a = one.run(parts);
22688 let b = many.run(parts);
22689 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22690 a
22691 };
22692 for name in &names {
22693 both(&[b"TS.CREATE", name, b"LABELS", b"room", b"1"]);
22694 both(&[b"TS.ADD", name, b"1000", b"1"]);
22695 }
22696
22697 let got = both(&[b"TS.QUERYINDEX", b"room=1"]);
22698 assert!(got.starts_with("*6\r\n"), "every series answered: {got}");
22699 assert!(both(&[b"TS.MGET", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
22700 assert!(both(&[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
22701 assert_eq!(
22702 both(&[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"]),
22703 "*1\r\n$4\r\nroom\r\n"
22704 );
22705 }
22706
22707 #[test]
22714 fn the_hash_group_answers_the_same_however_many_stripes_there_are() {
22715 let script: &[&[&[u8]]] = &[
22716 &[b"HSET", b"h", b"a", b"1", b"b", b"2"],
22717 &[b"HMSET", b"h", b"c", b"3"],
22718 &[b"HSETNX", b"h", b"a", b"9"],
22719 &[b"HSETNX", b"h", b"d", b"4"],
22720 &[b"HGET", b"h", b"a"],
22721 &[b"HGET", b"h", b"nope"],
22722 &[b"HMGET", b"h", b"a", b"nope"],
22723 &[b"HLEN", b"h"],
22724 &[b"HEXISTS", b"h", b"a"],
22725 &[b"HSTRLEN", b"h", b"a"],
22726 &[b"HGETALL", b"h"],
22727 &[b"HKEYS", b"h"],
22728 &[b"HVALS", b"h"],
22729 &[b"HINCRBY", b"h", b"a", b"5"],
22730 &[b"HINCRBYFLOAT", b"h", b"a", b"1.5"],
22731 &[b"HSCAN", b"h", b"0"],
22732 &[b"HSCAN", b"h", b"0", b"MATCH", b"a", b"COUNT", b"10"],
22733 &[b"HSCAN", b"h", b"0", b"NOVALUES"],
22734 &[b"HDEL", b"h", b"d"],
22735 &[b"HSET", b"one", b"f", b"v"],
22736 &[b"HRANDFIELD", b"one"],
22737 &[b"HRANDFIELD", b"one", b"1", b"WITHVALUES"],
22738 &[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"],
22740 &[b"HTTL", b"h", b"FIELDS", b"1", b"a"],
22741 &[b"HPTTL", b"h", b"FIELDS", b"1", b"a"],
22742 &[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
22743 &[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
22744 &[b"HPERSIST", b"h", b"FIELDS", b"1", b"a"],
22745 &[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"],
22746 &[b"HGET", b"h", b"b"],
22747 &[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"e", b"5"],
22749 &[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"e"],
22750 &[b"HGETDEL", b"h", b"FIELDS", b"1", b"e"],
22751 &[b"HGET", b"h", b"e"],
22752 &[b"HIMPORT", b"PREPARE", b"fs", b"x", b"y"],
22754 &[b"HIMPORT", b"SET", b"imp", b"fs", b"1", b"2"],
22755 &[b"HGETALL", b"imp"],
22756 &[b"HIMPORT", b"SET", b"imp", b"nofs", b"1", b"2"],
22757 &[b"HIMPORT", b"DISCARD", b"fs"],
22758 &[b"SET", b"plain", b"v"],
22760 &[b"HSET", b"plain", b"a", b"1"],
22761 &[b"HGETALL", b"plain"],
22762 &[b"HGET", b"gone", b"a"],
22763 &[b"HINCRBY", b"h", b"a", b"nan"],
22764 ];
22765
22766 let mut one = Fixture::new();
22767 let mut many = Fixture::striped(8);
22768 one.server.set_clock_ms(1_700_000_000_000);
22772 many.server.set_clock_ms(1_700_000_000_000);
22773 for parts in script {
22774 let a = one.run(parts);
22775 let b = many.run(parts);
22776 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22777 }
22778 }
22779
22780 #[test]
22782 fn the_array_group_answers_the_same_however_many_stripes_there_are() {
22783 let script: &[&[&[u8]]] = &[
22784 &[b"ARSET", b"a", b"0", b"x", b"y", b"z"],
22785 &[b"ARMSET", b"a", b"5", b"p", b"7", b"q"],
22786 &[b"ARGET", b"a", b"1"],
22787 &[b"ARGET", b"a", b"99"],
22788 &[b"ARMGET", b"a", b"0", b"5", b"99"],
22789 &[b"ARGETRANGE", b"a", b"0", b"7"],
22790 &[b"ARLEN", b"a"],
22791 &[b"ARCOUNT", b"a"],
22792 &[b"ARINSERT", b"a", b"m", b"n"],
22793 &[b"ARSCAN", b"a", b"0", b"20"],
22794 &[b"ARSCAN", b"a", b"0", b"20", b"LIMIT", b"2"],
22795 &[b"ARGREP", b"a", b"0", b"20", b"EXACT", b"x"],
22796 &[b"ARGREP", b"a", b"0", b"20", b"GLOB", b"*", b"WITHVALUES"],
22797 &[b"ARLASTITEMS", b"a", b"2"],
22798 &[b"ARLASTITEMS", b"a", b"2", b"REV"],
22799 &[b"ARNEXT", b"a"],
22800 &[b"ARSEEK", b"a", b"3"],
22801 &[b"AROP", b"a", b"0", b"20", b"USED"],
22802 &[b"AROP", b"a", b"0", b"20", b"MATCH", b"x"],
22803 &[b"ARINFO", b"a"],
22804 &[b"ARINFO", b"a", b"FULL"],
22805 &[b"ARDEL", b"a", b"0"],
22806 &[b"ARDELRANGE", b"a", b"1", b"2"],
22807 &[b"ARCOUNT", b"a"],
22808 &[b"ARRING", b"r", b"3", b"1", b"2", b"3", b"4"],
22809 &[b"ARGETRANGE", b"r", b"0", b"9"],
22810 &[b"SET", b"plain", b"v"],
22812 &[b"ARGET", b"plain", b"0"],
22813 &[b"ARSET", b"plain", b"0", b"v"],
22814 &[b"ARGET", b"gone", b"0"],
22815 &[b"ARSET", b"a", b"bad", b"v"],
22816 ];
22817
22818 let mut one = Fixture::new();
22819 let mut many = Fixture::striped(8);
22820 for parts in script {
22821 let a = one.run(parts);
22822 let b = many.run(parts);
22823 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22824 }
22825 }
22826
22827 #[test]
22833 fn the_graph_and_vector_groups_answer_the_same_however_many_stripes_there_are() {
22834 let script: &[&[&[u8]]] = &[
22835 &[b"G.NADD", b"g", b"n1", b"name", b"one"],
22836 &[b"G.NADD", b"g", b"n2", b"name", b"two"],
22837 &[b"G.NADD", b"g", b"n3"],
22838 &[b"G.NGET", b"g", b"n1"],
22839 &[b"G.NGET", b"g", b"gone"],
22840 &[b"G.EADD", b"g", b"n1", b"n2", b"knows"],
22841 &[b"G.EADD", b"g", b"n2", b"n3", b"knows"],
22842 &[b"G.OUT", b"g", b"n1", b"knows"],
22843 &[b"G.IN", b"g", b"n2", b"knows"],
22844 &[b"G.DEG", b"g", b"n1", b"knows"],
22845 &[b"G.DEG", b"g", b"n2", b"knows", b"BOTH"],
22846 &[b"G.NEIGH", b"g", b"n1", b"knows", b"DEPTH", b"2"],
22847 &[b"G.PATH", b"g", b"n1", b"n3"],
22848 &[b"G.EDEL", b"g", b"n1", b"n2", b"knows"],
22849 &[b"G.NDEL", b"g", b"n3"],
22850 &[b"G.NGET", b"g", b"n3"],
22851 &[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"e1"],
22853 &[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"e2"],
22854 &[b"VCARD", b"v"],
22855 &[b"VDIM", b"v"],
22856 &[b"VEMB", b"v", b"e1"],
22857 &[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0"],
22858 &[b"VSIM", b"v", b"ELE", b"e1"],
22859 &[b"VISMEMBER", b"v", b"e1"],
22860 &[b"VISMEMBER", b"v", b"gone"],
22861 &[b"VSETATTR", b"v", b"e1", b"{\"k\":1}"],
22862 &[b"VGETATTR", b"v", b"e1"],
22863 &[b"VRANGE", b"v", b"-", b"+"],
22864 &[b"VLINKS", b"v", b"e1"],
22865 &[b"VINFO", b"v"],
22866 &[b"VREM", b"v", b"e2"],
22867 &[b"VCARD", b"v"],
22868 &[b"SET", b"plain", b"v"],
22870 &[b"G.NGET", b"plain", b"n1"],
22871 &[b"VCARD", b"plain"],
22872 &[b"G.NADD", b"gone2", b"n"],
22873 &[b"VEMB", b"gone3", b"e"],
22874 ];
22875
22876 let mut one = Fixture::new();
22877 let mut many = Fixture::striped(8);
22878 for parts in script {
22879 let a = one.run(parts);
22880 let b = many.run(parts);
22881 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22882 }
22883 }
22884
22885 #[test]
22888 fn the_probabilistic_groups_answer_the_same_however_many_stripes_there_are() {
22889 let script: &[&[&[u8]]] = &[
22890 &[b"BF.RESERVE", b"bf", b"0.01", b"100"],
22892 &[b"BF.ADD", b"bf", b"a"],
22893 &[b"BF.ADD", b"bf", b"a"],
22894 &[b"BF.MADD", b"bf", b"b", b"c"],
22895 &[b"BF.EXISTS", b"bf", b"a"],
22896 &[b"BF.MEXISTS", b"bf", b"a", b"zz"],
22897 &[b"BF.CARD", b"bf"],
22898 &[b"BF.INFO", b"bf"],
22899 &[b"BF.INFO", b"bf", b"CAPACITY"],
22900 &[b"BF.DEBUG", b"bf"],
22901 &[b"BF.INSERT", b"made", b"CAPACITY", b"50", b"ITEMS", b"x"],
22902 &[b"BF.EXISTS", b"made", b"x"],
22903 &[b"BF.SCANDUMP", b"bf", b"0"],
22904 &[b"CF.RESERVE", b"cf", b"100"],
22906 &[b"CF.ADD", b"cf", b"a"],
22907 &[b"CF.ADDNX", b"cf", b"a"],
22908 &[b"CF.COUNT", b"cf", b"a"],
22909 &[b"CF.EXISTS", b"cf", b"a"],
22910 &[b"CF.MEXISTS", b"cf", b"a", b"zz"],
22911 &[b"CF.INSERT", b"cf", b"ITEMS", b"b", b"c"],
22912 &[b"CF.DEL", b"cf", b"a"],
22913 &[b"CF.COMPACT", b"cf"],
22914 &[b"CF.INFO", b"cf"],
22915 &[b"CF.DEBUG", b"cf"],
22916 &[b"CF.SCANDUMP", b"cf", b"0"],
22917 &[b"CMS.INITBYDIM", b"cms", b"100", b"5"],
22919 &[b"CMS.INITBYPROB", b"cms2", b"0.01", b"0.01"],
22920 &[b"CMS.INCRBY", b"cms", b"a", b"5", b"b", b"3"],
22921 &[b"CMS.QUERY", b"cms", b"a", b"b", b"gone"],
22922 &[b"CMS.INFO", b"cms"],
22923 &[b"TOPK.RESERVE", b"tk", b"3"],
22925 &[b"TOPK.ADD", b"tk", b"a", b"b", b"a"],
22926 &[b"TOPK.INCRBY", b"tk", b"c", b"4"],
22927 &[b"TOPK.QUERY", b"tk", b"a", b"zz"],
22928 &[b"TOPK.COUNT", b"tk", b"a", b"c"],
22929 &[b"TOPK.LIST", b"tk"],
22930 &[b"TOPK.LIST", b"tk", b"WITHCOUNT"],
22931 &[b"TOPK.INFO", b"tk"],
22932 &[b"TDIGEST.CREATE", b"td"],
22934 &[b"TDIGEST.ADD", b"td", b"1", b"2", b"3", b"4", b"5"],
22935 &[b"TDIGEST.MIN", b"td"],
22936 &[b"TDIGEST.MAX", b"td"],
22937 &[b"TDIGEST.QUANTILE", b"td", b"0.5"],
22938 &[b"TDIGEST.CDF", b"td", b"3"],
22939 &[b"TDIGEST.RANK", b"td", b"3"],
22940 &[b"TDIGEST.REVRANK", b"td", b"3"],
22941 &[b"TDIGEST.BYRANK", b"td", b"0"],
22942 &[b"TDIGEST.BYREVRANK", b"td", b"0"],
22943 &[b"TDIGEST.TRIMMED_MEAN", b"td", b"0.1", b"0.9"],
22944 &[b"TDIGEST.INFO", b"td"],
22945 &[b"TDIGEST.RESET", b"td"],
22946 &[b"TDIGEST.MIN", b"td"],
22947 &[b"SET", b"plain", b"v"],
22949 &[b"BF.ADD", b"plain", b"a"],
22950 &[b"CF.ADD", b"plain", b"a"],
22951 &[b"CMS.QUERY", b"plain", b"a"],
22952 &[b"TOPK.ADD", b"plain", b"a"],
22953 &[b"TDIGEST.ADD", b"plain", b"1"],
22954 &[b"CMS.INFO", b"gone"],
22955 &[b"TOPK.INFO", b"gone"],
22956 &[b"TDIGEST.INFO", b"gone"],
22957 ];
22958
22959 let mut one = Fixture::new();
22960 let mut many = Fixture::striped(8);
22961 for parts in script {
22962 let a = one.run(parts);
22963 let b = many.run(parts);
22964 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22965 }
22966 }
22967
22968 #[test]
22975 fn a_sketch_merge_across_stripes_reads_every_source() {
22976 let mut many = Fixture::striped(8);
22977 let other = apart(&mut many, "s1");
22978 let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
22979 let mut one = Fixture::new();
22980 let mut both = |parts: &[&[u8]]| {
22981 let a = one.run(parts);
22982 let b = many.run(parts);
22983 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22984 a
22985 };
22986
22987 for key in [b"cd".as_slice(), s1, s2] {
22991 both(&[b"CMS.INITBYDIM", key, b"100", b"5"]);
22992 }
22993 both(&[b"CMS.INCRBY", s1, b"x", b"5"]);
22994 both(&[b"CMS.INCRBY", s2, b"x", b"3"]);
22995 assert_eq!(
22996 both(&[b"CMS.MERGE", b"cd", b"2", s1, s2]),
22997 "+OK\r\n",
22998 "the merge took both sources"
22999 );
23000 assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:8\r\n");
23001 both(&[b"CMS.MERGE", b"cd", b"2", s1, s2, b"WEIGHTS", b"2", b"1"]);
23003 assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
23004 both(&[b"SET", b"plain", b"v"]);
23006 assert!(both(&[b"CMS.MERGE", b"cd", b"2", s1, b"plain"]).starts_with('-'));
23007 assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
23008
23009 both(&[b"FLUSHALL"]);
23012 both(&[b"TDIGEST.CREATE", b"td"]);
23013 both(&[b"TDIGEST.CREATE", s1]);
23014 both(&[b"TDIGEST.CREATE", s2]);
23015 both(&[b"TDIGEST.ADD", s1, b"1", b"2"]);
23016 both(&[b"TDIGEST.ADD", s2, b"9", b"10"]);
23017 assert_eq!(both(&[b"TDIGEST.MERGE", b"td", b"2", s1, s2]), "+OK\r\n");
23018 assert_eq!(both(&[b"TDIGEST.MIN", b"td"]), "$1\r\n1\r\n");
23019 assert_eq!(both(&[b"TDIGEST.MAX", b"td"]), "$2\r\n10\r\n");
23020 }
23021
23022 #[test]
23029 fn the_sort_command_answers_the_same_however_many_stripes_there_are() {
23030 let script: &[&[&[u8]]] = &[
23031 &[b"RPUSH", b"l", b"3", b"1", b"2", b"10"],
23032 &[b"SORT", b"l"],
23033 &[b"SORT", b"l", b"DESC"],
23034 &[b"SORT", b"l", b"ALPHA"],
23035 &[b"SORT", b"l", b"LIMIT", b"1", b"2"],
23036 &[b"SORT_RO", b"l"],
23037 &[
23040 b"MSET", b"w_1", b"4", b"w_2", b"3", b"w_3", b"2", b"w_10", b"1",
23041 ],
23042 &[b"SORT", b"l", b"BY", b"w_*"],
23043 &[b"SORT", b"l", b"BY", b"w_*", b"DESC"],
23044 &[b"DEL", b"w_2"],
23045 &[b"SORT", b"l", b"BY", b"w_*"],
23046 &[b"MSET", b"d_1", b"one", b"d_3", b"three"],
23049 &[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"],
23050 &[b"HSET", b"h_1", b"f", b"9"],
23052 &[b"HSET", b"h_2", b"f", b"8"],
23053 &[b"HSET", b"h_3", b"f", b"7"],
23054 &[b"HSET", b"h_10", b"f", b"6"],
23055 &[b"SORT", b"l", b"BY", b"h_*->f"],
23056 &[b"SORT", b"l", b"BY", b"nosort", b"GET", b"h_*->f"],
23057 &[b"SORT", b"l", b"BY", b"w_*", b"STORE", b"out"],
23059 &[b"LRANGE", b"out", b"0", b"-1"],
23060 &[b"SORT", b"l", b"STORE", b"l"],
23061 &[b"LRANGE", b"l", b"0", b"-1"],
23062 &[b"SORT", b"missing", b"STORE", b"out"],
23065 &[b"EXISTS", b"out"],
23066 &[b"SADD", b"s", b"c", b"a", b"b"],
23069 &[b"SORT", b"s", b"ALPHA"],
23070 &[b"SORT", b"s", b"BY", b"nosort", b"STORE", b"out"],
23071 &[b"LRANGE", b"out", b"0", b"-1"],
23072 &[b"ZADD", b"z", b"3", b"c", b"1", b"a", b"2", b"b"],
23073 &[b"SORT", b"z", b"BY", b"nosort"],
23074 &[b"SORT", b"z", b"ALPHA", b"DESC"],
23075 &[b"SET", b"str", b"v"],
23078 &[b"SORT", b"str"],
23079 &[b"RPUSH", b"words", b"one", b"two"],
23080 &[b"SORT", b"words"],
23081 &[b"SORT_RO", b"l", b"STORE", b"out"],
23082 ];
23083
23084 let mut one = Fixture::new();
23085 let mut many = Fixture::striped(8);
23086 for parts in script {
23087 let a = one.run(parts);
23088 let b = many.run(parts);
23089 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23090 }
23091 }
23092
23093 #[test]
23100 fn a_sort_across_stripes_reads_every_pattern_key() {
23101 let mut f = Fixture::striped(8);
23102 let out = apart(&mut f, "l");
23103 let (list, dest) = (b"l".as_slice(), out.as_bytes());
23104
23105 f.run(&[b"RPUSH", list, b"a", b"b", b"c", b"d"]);
23106 f.run(&[
23107 b"MSET", b"w_a", b"4", b"w_b", b"3", b"w_c", b"2", b"w_d", b"1",
23108 ]);
23109 f.run(&[
23110 b"MSET", b"d_a", b"A", b"d_b", b"B", b"d_c", b"C", b"d_d", b"D",
23111 ]);
23112
23113 let db = f.server.striped(0);
23116 let weights: Vec<usize> = [b"w_a", b"w_b", b"w_c", b"w_d"]
23117 .iter()
23118 .map(|k| db.stripe_of(k.as_slice()))
23119 .collect();
23120 assert!(
23121 weights.iter().any(|s| *s != weights[0]),
23122 "the four weight keys all landed on one stripe, so this proves nothing"
23123 );
23124
23125 assert_eq!(
23126 f.run(&[b"SORT", list, b"BY", b"w_*", b"GET", b"d_*"]),
23127 "*4\r\n$1\r\nD\r\n$1\r\nC\r\n$1\r\nB\r\n$1\r\nA\r\n",
23128 "the order came off the weights and the answer off the data keys"
23129 );
23130 assert_eq!(
23131 f.run(&[b"SORT", list, b"BY", b"w_*", b"STORE", dest]),
23132 ":4\r\n"
23133 );
23134 assert_eq!(
23135 f.run(&[b"LRANGE", dest, b"0", b"-1"]),
23136 "*4\r\n$1\r\nd\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n",
23137 "the destination is on a stripe of its own and got the whole answer"
23138 );
23139 }
23140
23141 #[test]
23149 fn a_setting_reaches_every_stripe_and_reads_back_from_any_of_them() {
23150 let mut f = Fixture::striped(8);
23151 let other = apart(&mut f, "h");
23152 let (first, second) = (b"h".as_slice(), other.as_bytes());
23153
23154 assert_eq!(
23155 f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"2"]),
23156 "+OK\r\n"
23157 );
23158 assert_eq!(
23159 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
23160 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n",
23161 "the read comes off one stripe and has to answer for all of them"
23162 );
23163 for key in [first, second] {
23164 f.run(&[b"HSET", key, b"a", b"1", b"b", b"2"]);
23165 assert_eq!(
23166 f.run(&[b"OBJECT", b"ENCODING", key]),
23167 "$8\r\nlistpack\r\n",
23168 "two fields is still under the ladder"
23169 );
23170 f.run(&[b"HSET", key, b"c", b"3"]);
23171 assert_eq!(
23172 f.run(&[b"OBJECT", b"ENCODING", key]),
23173 "$9\r\nhashtable\r\n",
23174 "three fields is over it, on whichever stripe the key is on"
23175 );
23176 }
23177
23178 assert_eq!(
23181 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]),
23182 "+OK\r\n"
23183 );
23184 let db = f.server.striped(0);
23185 assert!(
23186 (0..db.width()).all(|i| db.hold_stripe(i).policy().name() == "allkeys-lru"),
23187 "a stripe kept the old policy"
23188 );
23189 }
23190
23191 fn held(f: &Fixture, name: &[u8]) -> (usize, u32) {
23197 let search = f.server.search.lock();
23198 let index = search.named(name).expect("the index is there");
23199 (index.held.docs.len(), index.held.docs.last())
23200 }
23201
23202 #[test]
23205 fn a_hash_that_is_written_reaches_the_index_that_follows_it() {
23206 let mut f = Fixture::new();
23207 f.run(&[
23208 b"FT.CREATE",
23209 b"ix",
23210 b"PREFIX",
23211 b"1",
23212 b"p:",
23213 b"SCHEMA",
23214 b"t",
23215 b"TEXT",
23216 ]);
23217 f.run(&[b"HSET", b"p:1", b"t", b"running dogs"]);
23218 assert_eq!(held(&f, b"ix"), (1, 1));
23219 f.run(&[b"HSET", b"other:1", b"t", b"running dogs"]);
23220 assert_eq!(held(&f, b"ix"), (1, 1));
23221
23222 f.run(&[b"HSET", b"p:1", b"u", b"beta"]);
23225 f.run(&[b"HDEL", b"p:1", b"u"]);
23226 assert_eq!(held(&f, b"ix"), (1, 3));
23227 let search = f.server.search.lock();
23228 let index = search.named(b"ix").expect("there");
23229 assert_eq!(index.held.docs.id(b"p:1"), Some(3));
23230 }
23231
23232 #[test]
23235 fn a_fresh_index_reads_the_keys_that_were_already_there() {
23236 let mut f = Fixture::new();
23237 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
23238 f.run(&[b"SET", b"p:str", b"not a hash"]);
23239 f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
23240 f.run(&[
23241 b"FT.CREATE",
23242 b"ix",
23243 b"PREFIX",
23244 b"1",
23245 b"p:",
23246 b"SCHEMA",
23247 b"t",
23248 b"TEXT",
23249 ]);
23250
23251 assert_eq!(held(&f, b"ix"), (1, 1));
23252 let search = f.server.search.lock();
23253 let index = search.named(b"ix").expect("there");
23254 assert_eq!(index.trouble.whole().failures(), 0);
23255 }
23256
23257 #[test]
23260 fn an_index_that_skipped_the_scan_fills_up_on_the_next_write() {
23261 let mut f = Fixture::new();
23262 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
23263 f.run(&[
23264 b"FT.CREATE",
23265 b"ix",
23266 b"PREFIX",
23267 b"1",
23268 b"p:",
23269 b"SKIPINITIALSCAN",
23270 b"SCHEMA",
23271 b"t",
23272 b"TEXT",
23273 ]);
23274 assert_eq!(held(&f, b"ix"), (0, 0));
23275 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
23276 assert_eq!(held(&f, b"ix"), (1, 1));
23277 }
23278
23279 #[test]
23287 fn only_a_real_change_gives_the_document_a_new_number() {
23288 let mut f = Fixture::new();
23289 f.run(&[
23290 b"FT.CREATE",
23291 b"ix",
23292 b"PREFIX",
23293 b"1",
23294 b"p:",
23295 b"SCHEMA",
23296 b"t",
23297 b"TEXT",
23298 ]);
23299 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
23300 assert_eq!(held(&f, b"ix"), (1, 1));
23301
23302 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
23303 assert_eq!(held(&f, b"ix"), (1, 2), "the same value still rewrites");
23304
23305 for quiet in [
23306 vec![b"HSETNX".as_slice(), b"p:1", b"t", b"other"],
23307 vec![b"HDEL".as_slice(), b"p:1", b"nosuch"],
23308 vec![b"HGET".as_slice(), b"p:1", b"t"],
23309 vec![b"HGETALL".as_slice(), b"p:1"],
23310 vec![b"HEXPIRE".as_slice(), b"p:1", b"100", b"FIELDS", b"1", b"t"],
23311 vec![b"HPERSIST".as_slice(), b"p:1", b"FIELDS", b"1", b"t"],
23312 vec![
23313 b"HGETEX".as_slice(),
23314 b"p:1",
23315 b"EX",
23316 b"100",
23317 b"FIELDS",
23318 b"1",
23319 b"t",
23320 ],
23321 vec![b"HGETDEL".as_slice(), b"p:1", b"FIELDS", b"1", b"nosuch"],
23322 ] {
23323 f.run(&quiet);
23324 assert_eq!(held(&f, b"ix"), (1, 2), "{:?} moved the document", quiet[0]);
23325 }
23326
23327 f.run(&[b"HSET", b"p:2", b"n", b"1"]);
23329 f.run(&[b"HINCRBY", b"p:2", b"n", b"1"]);
23330 assert_eq!(held(&f, b"ix"), (2, 4));
23331 f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"n"]);
23337 assert_eq!(held(&f, b"ix"), (1, 5));
23338 }
23339
23340 #[test]
23346 fn a_key_emptied_by_a_deadline_spends_a_number_and_one_emptied_by_hdel_does_not() {
23347 fn refused(f: &Fixture, name: &[u8]) -> u64 {
23349 let search = f.server.search.lock();
23350 let index = search.named(name).expect("the index is there");
23351 index.trouble.whole().failures()
23352 }
23353
23354 let mut f = Fixture::new();
23355 f.run(&[
23356 b"FT.CREATE",
23357 b"ix",
23358 b"PREFIX",
23359 b"1",
23360 b"p:",
23361 b"SCHEMA",
23362 b"t",
23363 b"TEXT",
23364 ]);
23365 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
23366 assert_eq!(held(&f, b"ix"), (1, 1));
23367 f.run(&[b"HDEL", b"p:1", b"t"]);
23368 assert_eq!(
23369 held(&f, b"ix"),
23370 (0, 1),
23371 "HDEL of the last field spends none"
23372 );
23373 assert_eq!(refused(&f, b"ix"), 1, "and is counted as a refusal");
23374
23375 f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
23376 assert_eq!(held(&f, b"ix"), (1, 2));
23377 f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"t"]);
23378 assert_eq!(held(&f, b"ix"), (0, 3), "a deadline spends one");
23379 assert_eq!(refused(&f, b"ix"), 1, "and is counted as nothing");
23380
23381 f.run(&[b"HSET", b"p:3", b"t", b"alpha"]);
23382 assert_eq!(held(&f, b"ix"), (1, 4));
23383 f.run(&[b"HGETDEL", b"p:3", b"FIELDS", b"1", b"t"]);
23384 assert_eq!(held(&f, b"ix"), (0, 5), "and so does HGETDEL");
23385
23386 f.run(&[b"HSET", b"p:4", b"t", b"alpha", b"u", b"beta"]);
23389 assert_eq!(held(&f, b"ix"), (1, 6));
23390 f.run(&[b"HEXPIRE", b"p:4", b"0", b"FIELDS", b"2", b"t", b"u"]);
23391 assert_eq!(held(&f, b"ix"), (0, 7));
23392 assert_eq!(refused(&f, b"ix"), 1);
23393 }
23394
23395 #[test]
23399 fn a_field_written_already_past_its_deadline_moves_the_number_twice() {
23400 let mut f = Fixture::new();
23401 f.run(&[
23402 b"FT.CREATE",
23403 b"ix",
23404 b"PREFIX",
23405 b"1",
23406 b"p:",
23407 b"SCHEMA",
23408 b"t",
23409 b"TEXT",
23410 b"u",
23411 b"TEXT",
23412 ]);
23413 f.run(&[b"HSET", b"p:1", b"u", b"keepme"]);
23414 assert_eq!(held(&f, b"ix"), (1, 1));
23415 f.run(&[
23416 b"HSETEX", b"p:1", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
23417 ]);
23418 assert_eq!(
23419 held(&f, b"ix"),
23420 (1, 3),
23421 "the key lived and the field did not"
23422 );
23423
23424 f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
23426 assert_eq!(held(&f, b"ix"), (2, 4));
23427 f.run(&[
23428 b"HSETEX", b"p:2", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
23429 ]);
23430 assert_eq!(held(&f, b"ix"), (1, 6));
23431 }
23432
23433 fn number(f: &Fixture, name: &[u8], key: &[u8]) -> Option<u32> {
23436 let search = f.server.search.lock();
23437 let index = search.named(name).expect("the index is there");
23438 index.held.docs.id(key)
23439 }
23440
23441 fn indexed() -> Fixture {
23444 let mut f = Fixture::new();
23445 f.run(&[
23446 b"FT.CREATE",
23447 b"ix",
23448 b"PREFIX",
23449 b"1",
23450 b"p:",
23451 b"SCHEMA",
23452 b"t",
23453 b"TEXT",
23454 ]);
23455 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
23456 f
23457 }
23458
23459 #[test]
23462 fn a_key_a_keyspace_command_takes_away_loses_its_document() {
23463 for take in [
23464 vec![b"DEL".as_slice(), b"p:1"],
23465 vec![b"UNLINK".as_slice(), b"p:1"],
23466 vec![b"PEXPIREAT".as_slice(), b"p:1", b"1"],
23467 vec![b"EXPIRE".as_slice(), b"p:1", b"-1"],
23468 ] {
23469 let mut f = indexed();
23470 assert_eq!(held(&f, b"ix"), (1, 1));
23471 f.run(&take);
23472 assert_eq!(held(&f, b"ix"), (0, 1), "{:?} left something", take[0]);
23473 let search = f.server.search.lock();
23474 let index = search.named(b"ix").expect("the index is there");
23475 assert_eq!(index.trouble.whole().failures(), 0, "{:?}", take[0]);
23476 }
23477
23478 let mut f = indexed();
23480 f.run(&[b"EXPIRE", b"p:1", b"1000"]);
23481 assert_eq!(held(&f, b"ix"), (1, 1));
23482 f.run(&[b"PERSIST", b"p:1"]);
23483 assert_eq!(held(&f, b"ix"), (1, 1));
23484 }
23485
23486 #[test]
23490 fn a_rename_inside_the_prefix_keeps_the_number_the_document_had() {
23491 let mut f = indexed();
23492 f.run(&[b"RENAME", b"p:1", b"p:2"]);
23493 assert_eq!(held(&f, b"ix"), (1, 1), "nothing was read again");
23494 assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
23495 assert_eq!(number(&f, b"ix", b"p:1"), None);
23496
23497 f.run(&[b"RENAME", b"p:2", b"q:1"]);
23498 assert_eq!(held(&f, b"ix"), (0, 1), "out of the prefix is an erase");
23499
23500 f.run(&[b"RENAME", b"q:1", b"p:3"]);
23501 assert_eq!(held(&f, b"ix"), (1, 2), "and into it is a reading");
23502 assert_eq!(number(&f, b"ix", b"p:3"), Some(2));
23503
23504 f.run(&[b"HSET", b"p:4", b"t", b"beta"]);
23507 assert_eq!(f.run(&[b"RENAMENX", b"p:3", b"p:4"]), ":0\r\n");
23508 assert_eq!(held(&f, b"ix"), (2, 3));
23509 f.run(&[b"RENAMENX", b"p:3", b"p:5"]);
23510 assert_eq!(number(&f, b"ix", b"p:5"), Some(2));
23511 }
23512
23513 #[test]
23516 fn a_rename_over_a_document_leaves_one_of_them() {
23517 let mut f = indexed();
23518 f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
23519 assert_eq!(held(&f, b"ix"), (2, 2));
23520 f.run(&[b"RENAME", b"p:1", b"p:2"]);
23521 assert_eq!(held(&f, b"ix"), (1, 2));
23522 assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
23523 }
23524
23525 #[test]
23529 fn a_key_that_arrives_under_the_prefix_is_read_and_one_overwritten_is_erased() {
23530 let mut f = indexed();
23531 f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
23532 f.run(&[b"COPY", b"q:1", b"p:2"]);
23533 assert_eq!(held(&f, b"ix"), (2, 2));
23534 assert_eq!(number(&f, b"ix", b"p:2"), Some(2));
23535
23536 f.run(&[b"COPY", b"p:1", b"q:2"]);
23538 assert_eq!(held(&f, b"ix"), (2, 2));
23539
23540 f.run(&[b"COPY", b"q:1", b"p:1", b"REPLACE"]);
23542 assert_eq!(held(&f, b"ix"), (2, 3));
23543 assert_eq!(number(&f, b"ix", b"p:1"), Some(3));
23544
23545 f.run(&[b"SET", b"s:1", b"plain"]);
23548 f.run(&[b"COPY", b"s:1", b"p:1", b"REPLACE"]);
23549 assert_eq!(held(&f, b"ix"), (1, 3));
23550 let dump = f.run(&[b"DUMP", b"q:1"]);
23551 assert!(dump.starts_with('$'), "{dump}");
23552 }
23553
23554 #[test]
23559 fn the_keyspace_group_reads_database_zero_whatever_database_it_ran_on() {
23560 let mut f = indexed();
23561 f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
23562 assert_eq!(held(&f, b"ix"), (2, 2));
23563 f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
23566 assert_eq!(held(&f, b"ix"), (2, 3));
23567 f.run(&[b"DEL", b"p:2"]);
23570 assert_eq!(held(&f, b"ix"), (1, 3));
23571 f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
23572 assert_eq!(held(&f, b"ix"), (1, 3), "the copy landed out of sight");
23573
23574 let dump = f.run(&[b"DUMP", b"p:1"]);
23576 assert!(dump.starts_with('$'), "{dump}");
23577 f.run(&[b"SELECT", b"1"]);
23578 f.run(&[b"HSET", b"q:1", b"t", b"gamma"]);
23579 f.run(&[b"RENAME", b"q:1", b"p:3"]);
23580 assert_eq!(held(&f, b"ix"), (1, 3), "and so is a rename");
23581 }
23582
23583 #[test]
23586 fn a_move_leaves_the_document_where_it_is() {
23587 let mut f = indexed();
23588 f.run(&[b"MOVE", b"p:1", b"1"]);
23589 assert_eq!(held(&f, b"ix"), (1, 1), "the key moved and nothing else");
23590 assert_eq!(number(&f, b"ix", b"p:1"), Some(1));
23591
23592 f.run(&[b"SELECT", b"1"]);
23593 f.run(&[b"HSET", b"p:1", b"t", b"beta"]);
23594 assert_eq!(held(&f, b"ix"), (1, 2), "and a write there still lands");
23595 f.run(&[b"DEL", b"p:1"]);
23596 assert_eq!(held(&f, b"ix"), (0, 2));
23597 }
23598
23599 #[test]
23601 fn a_flush_drops_the_indexes() {
23602 for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
23603 let mut f = indexed();
23604 f.run(&[flush]);
23605 assert!(f.server.search.lock().is_empty(), "{flush:?} kept an index");
23606 assert_eq!(f.run(&[b"FT._LIST"]), "*0\r\n");
23607 }
23608
23609 let mut f = indexed();
23612 f.run(&[b"SELECT", b"9"]);
23613 f.run(&[b"FLUSHDB"]);
23614 assert!(f.server.search.lock().is_empty());
23615 }
23616
23617 fn tagged() -> Fixture {
23620 let mut f = Fixture::new();
23621 f.run(&[
23622 b"FT.CREATE",
23623 b"tv",
23624 b"PREFIX",
23625 b"1",
23626 b"tv:",
23627 b"SCHEMA",
23628 b"g",
23629 b"AS",
23630 b"gg",
23631 b"TAG",
23632 b"h",
23633 b"TAG",
23634 b"SEPARATOR",
23635 b"|",
23636 b"CASESENSITIVE",
23637 b"n",
23638 b"NUMERIC",
23639 ]);
23640 f.run(&[
23641 b"HSET",
23642 b"tv:1",
23643 b"g",
23644 b"Red, BLUE ",
23645 b"h",
23646 b"Aa|bB",
23647 b"n",
23648 b"1",
23649 ]);
23650 f.run(&[b"HSET", b"tv:2", b"g", b"red", b"h", b"aa", b"n", b"2"]);
23651 f
23652 }
23653
23654 #[test]
23658 fn tag_values_come_back_as_they_are_stored_and_sorted_by_their_bytes() {
23659 let mut f = tagged();
23660 assert_eq!(
23661 f.run(&[b"FT.TAGVALS", b"tv", b"gg"]),
23662 "*2\r\n$4\r\nblue\r\n$3\r\nred\r\n"
23663 );
23664 assert_eq!(
23665 f.run(&[b"FT.TAGVALS", b"tv", b"h"]),
23666 "*3\r\n$2\r\nAa\r\n$2\r\naa\r\n$2\r\nbB\r\n"
23667 );
23668 }
23669
23670 #[test]
23673 fn tag_values_are_asked_for_by_the_attribute_and_not_the_identifier() {
23674 let mut f = tagged();
23675 for (name, want) in [
23676 (b"g".as_slice(), "-SEARCH_ATTR_BAD No such field\r\n"),
23677 (b"zz", "-SEARCH_ATTR_BAD No such field\r\n"),
23678 (b"n", "-SEARCH_ATTR_BAD Not a tag field\r\n"),
23679 ] {
23680 assert_eq!(f.run(&[b"FT.TAGVALS", b"tv", name]), want);
23681 }
23682 assert_eq!(
23683 f.run(&[b"FT.TAGVALS", b"nope", b"g"]),
23684 "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
23685 );
23686 }
23687
23688 #[test]
23691 fn asking_for_tag_values_counts_a_use_of_the_index() {
23692 let mut f = tagged();
23693 let uses = |f: &mut Fixture| {
23694 let reply = f.run(&[b"FT.INFO", b"tv"]);
23695 let at = reply.find("number_of_uses").expect("the field is reported");
23696 let value = reply[at..].split("\r\n").nth(1).unwrap();
23697 value.trim_start_matches(':').parse::<i64>().unwrap()
23698 };
23699 let before = uses(&mut f);
23700 f.run(&[b"FT.TAGVALS", b"tv", b"gg"]);
23701 f.run(&[b"FT.TAGVALS", b"tv", b"zz"]);
23702 assert_eq!(uses(&mut f), before + 3);
23704 }
23705
23706 #[test]
23709 fn a_tag_field_with_nothing_in_it_answers_empty() {
23710 let mut f = Fixture::new();
23711 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"g", b"TAG"]);
23712 assert_eq!(f.run(&[b"FT.TAGVALS", b"e", b"g"]), "*0\r\n");
23713 }
23714
23715 #[test]
23718 fn a_dictionary_is_not_a_key() {
23719 let mut f = Fixture::new();
23720 assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"a", b"b"]), ":2\r\n");
23721 assert_eq!(f.run(&[b"TYPE", b"d"]), "+none\r\n");
23722 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
23723 assert_eq!(f.run(&[b"KEYS", b"d"]), "*0\r\n");
23724 }
23725
23726 #[test]
23729 fn a_dictionary_counts_the_terms_it_had_not_seen() {
23730 let mut f = Fixture::new();
23731 assert_eq!(
23732 f.run(&[b"FT.DICTADD", b"d", b"zeta", b"alpha", b"Beta", b"alpha"]),
23733 ":3\r\n"
23734 );
23735 assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"alpha"]), ":0\r\n");
23736 assert_eq!(f.run(&[b"FT.DICTADD", b"d", b""]), ":0\r\n");
23737 assert_eq!(
23738 f.run(&[b"FT.DICTDUMP", b"d"]),
23739 "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nzeta\r\n"
23740 );
23741 assert_eq!(f.run(&[b"FT.DICTDEL", b"d", b"alpha", b"nope"]), ":1\r\n");
23742 }
23743
23744 #[test]
23748 fn a_dictionary_nobody_made_dumps_empty_rather_than_failing() {
23749 let mut f = Fixture::new();
23750 assert_eq!(f.run(&[b"FT.DICTDUMP", b"nope"]), "*0\r\n");
23751 assert_eq!(f.run(&[b"FT.DICTDEL", b"nope", b"a"]), ":0\r\n");
23752 }
23753
23754 #[test]
23756 fn a_flush_drops_the_dictionaries() {
23757 for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
23758 let mut f = Fixture::new();
23759 f.run(&[b"FT.DICTADD", b"d", b"a"]);
23760 f.run(&[flush]);
23761 assert_eq!(f.run(&[b"FT.DICTDUMP", b"d"]), "*0\r\n", "{flush:?}");
23762 }
23763 }
23764
23765 fn profiling() -> Fixture {
23770 let mut f = Fixture::new();
23771 f.run(&[
23772 b"FT.CREATE",
23773 b"ix",
23774 b"PREFIX",
23775 b"1",
23776 b"p:",
23777 b"SCHEMA",
23778 b"t",
23779 b"TEXT",
23780 b"n",
23781 b"NUMERIC",
23782 ]);
23783 f.run(&[b"HSET", b"p:1", b"t", b"alpha", b"n", b"1"]);
23784 f.run(&[b"HSET", b"p:2", b"t", b"alpha beta", b"n", b"2"]);
23785 f.run(&[b"HSET", b"p:3", b"t", b"beta", b"n", b"3"]);
23786 f
23787 }
23788
23789 fn timeless(reply: &str) -> String {
23792 const KEYS: &[&str] = &[
23793 "+Total profile time",
23794 "+Parsing time",
23795 "+Workers queue time",
23796 "+Pipeline creation time",
23797 "+Time",
23798 ];
23799 let mut out = String::new();
23800 let mut parts = reply.split("\r\n").peekable();
23801 while let Some(part) = parts.next() {
23802 out.push_str(part);
23803 out.push_str("\r\n");
23804 if !KEYS.contains(&part) {
23805 continue;
23806 }
23807 match parts.next() {
23810 Some(head) if head.starts_with('$') => {
23811 parts.next();
23812 }
23813 _ => {}
23814 }
23815 out.push_str("<t>\r\n");
23816 }
23817 out.truncate(out.len() - 2);
23819 out
23820 }
23821
23822 #[test]
23825 fn a_profile_wraps_the_reply_it_would_have_answered_anyway() {
23826 let mut f = profiling();
23827 assert_eq!(
23828 timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"])),
23829 "*2\r\n\
23830 *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\
23831 $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\
23832 *4\r\n+Shards\r\n*1\r\n*14\r\n\
23833 +Total profile time\r\n<t>\r\n+Parsing time\r\n<t>\r\n\
23834 +Workers queue time\r\n<t>\r\n+Pipeline creation time\r\n<t>\r\n\
23835 +Warning\r\n*1\r\n+None\r\n\
23836 +Iterators profile\r\n*10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
23837 +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
23838 +Estimated number of matches\r\n:2\r\n\
23839 +Result processors profile\r\n*4\r\n\
23840 *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
23841 *6\r\n+Type\r\n+Scorer\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
23842 *6\r\n+Type\r\n+Sorter\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
23843 *6\r\n+Type\r\n+Loader\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
23844 +Coordinator\r\n*0\r\n"
23845 );
23846 let mut g = profiling();
23847 g.run(&[b"HELLO", b"3"]);
23848 let three = timeless(&g.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"]));
23849 assert!(three.starts_with("%2\r\n+Results\r\n"), "{three}");
23850 assert!(
23851 three.contains("+Profile\r\n%2\r\n+Shards\r\n*1\r\n%7\r\n"),
23852 "{three}"
23853 );
23854 assert!(three.ends_with("+Coordinator\r\n%0\r\n"), "{three}");
23855 assert!(
23856 three.contains(
23857 "+Iterators profile\r\n%5\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
23858 +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
23859 +Estimated number of matches\r\n:2\r\n"
23860 ),
23861 "{three}"
23862 );
23863 }
23864
23865 #[test]
23868 fn each_kind_of_step_writes_the_keys_that_belong_to_it() {
23869 let mut f = profiling();
23870 let tree = |f: &mut Fixture, query: &[u8]| {
23871 let reply = timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", query]));
23872 let at = reply.find("+Iterators profile").expect("a tree");
23873 let end = reply.find("+Result processors").expect("a list of steps");
23874 reply[at..end].to_string()
23875 };
23876 assert_eq!(
23877 tree(&mut f, b"alpha beta"),
23878 "+Iterators profile\r\n*8\r\n+Type\r\n+INTERSECT\r\n+Time\r\n<t>\r\n\
23879 +Number of reading operations\r\n:1\r\n+Child iterators\r\n*2\r\n\
23880 *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n+Time\r\n<t>\r\n\
23881 +Number of reading operations\r\n:2\r\n+Estimated number of matches\r\n:2\r\n\
23882 *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$4\r\nbeta\r\n+Time\r\n<t>\r\n\
23883 +Number of reading operations\r\n:1\r\n+Estimated number of matches\r\n:2\r\n"
23884 );
23885 assert!(tree(&mut f, b"alpha|beta").starts_with(
23886 "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n+Query type\r\n+UNION\r\n\
23887 +Time\r\n<t>\r\n+Number of reading operations\r\n:3\r\n+Child iterators\r\n*2\r\n"
23888 ));
23889 assert!(tree(&mut f, b"-alpha").starts_with(
23892 "+Iterators profile\r\n*8\r\n+Type\r\n+NOT\r\n+Time\r\n<t>\r\n\
23893 +Number of reading operations\r\n:1\r\n+Child iterator\r\n*10\r\n"
23894 ));
23895 assert!(tree(&mut f, b"~alpha").starts_with(
23896 "+Iterators profile\r\n*8\r\n+Type\r\n+OPTIONAL\r\n+Time\r\n<t>\r\n\
23897 +Number of reading operations\r\n:3\r\n+Child iterator\r\n*10\r\n"
23898 ));
23899 assert_eq!(
23901 tree(&mut f, b"*"),
23902 "+Iterators profile\r\n*6\r\n+Type\r\n+WILDCARD\r\n+Time\r\n<t>\r\n\
23903 +Number of reading operations\r\n:3\r\n"
23904 );
23905 assert!(tree(&mut f, b"@n:[1 2]").starts_with(
23906 "+Iterators profile\r\n*10\r\n+Type\r\n+NUMERIC\r\n+Term\r\n\
23907 $19\r\n1.000000 - 2.000000\r\n"
23908 ));
23909 }
23910
23911 #[test]
23914 fn limited_folds_the_branches_an_expansion_made_and_leaves_a_bar_alone() {
23915 let mut f = profiling();
23916 f.run(&[b"HSET", b"p:4", b"t", b"alps"]);
23917 let tree = |f: &mut Fixture, words: &[&[u8]]| {
23918 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH"];
23919 argv.extend_from_slice(words);
23920 let reply = timeless(&f.run(&argv));
23921 let at = reply.find("+Iterators profile").expect("a tree");
23922 let end = reply.find("+Result processors").expect("a list of steps");
23923 reply[at..end].to_string()
23924 };
23925 assert_eq!(
23926 tree(&mut f, &[b"LIMITED", b"QUERY", b"al*"]),
23927 "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n\
23928 +Query type\r\n$11\r\nPREFIX - al\r\n+Time\r\n<t>\r\n\
23929 +Number of reading operations\r\n:3\r\n+Child iterators\r\n\
23930 +The number of iterators in the union is 2\r\n"
23931 );
23932 assert!(tree(&mut f, &[b"QUERY", b"al*"]).contains("+Child iterators\r\n*2\r\n"));
23933 assert!(
23934 tree(&mut f, &[b"LIMITED", b"QUERY", b"alpha|beta"])
23935 .contains("+Child iterators\r\n*2\r\n")
23936 );
23937 assert!(tree(&mut f, &[b"QUERY", b"alpha|beta"]).contains("+Query type\r\n+UNION\r\n"));
23941 assert!(
23942 tree(&mut f, &[b"QUERY", b"al*"]).contains("+Query type\r\n$11\r\nPREFIX - al\r\n")
23943 );
23944 }
23945
23946 #[test]
23949 fn the_steps_a_search_runs_depend_on_what_was_asked_for() {
23950 let mut f = profiling();
23951 let steps = |f: &mut Fixture, words: &[&[u8]]| {
23952 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"];
23953 argv.extend_from_slice(words);
23954 let reply = timeless(&f.run(&argv));
23955 let at = reply.find("+Result processors").expect("a list of steps");
23956 let end = reply.find("+Coordinator").expect("an end");
23957 let mut out = Vec::new();
23958 let mut parts = reply[at..end].split("\r\n").peekable();
23959 while let Some(part) = parts.next() {
23960 if part == "+Type" {
23961 out.push(parts.next().unwrap_or_default().to_string());
23962 }
23963 }
23964 out
23965 };
23966 assert_eq!(
23967 steps(&mut f, &[]),
23968 ["+Index", "+Scorer", "+Sorter", "+Loader"]
23969 );
23970 assert_eq!(
23971 steps(&mut f, &[b"NOCONTENT"]),
23972 ["+Index", "+Scorer", "+Sorter"]
23973 );
23974 assert_eq!(
23977 steps(&mut f, &[b"LIMIT", b"0", b"0"]),
23978 ["+Index", "+Counter"]
23979 );
23980 assert_eq!(
23983 steps(&mut f, &[b"SORTBY", b"n"]),
23984 ["+Index", "+Sorter", "+Loader"]
23985 );
23986 assert_eq!(
23987 steps(&mut f, &[b"SORTBY", b"n", b"WITHSCORES"]),
23988 ["+Index", "+Scorer", "+Sorter", "+Loader"]
23989 );
23990 assert_eq!(
23991 steps(&mut f, &[b"HIGHLIGHT"]),
23992 ["+Index", "+Scorer", "+Sorter", "+Loader", "+Highlighter"]
23993 );
23994 assert_eq!(
23995 steps(&mut f, &[b"SUMMARIZE", b"NOCONTENT"]),
23996 ["+Index", "+Scorer", "+Sorter"]
23997 );
23998 }
23999
24000 #[test]
24003 fn a_pipeline_names_every_step_after_what_it_runs() {
24004 let mut f = profiling();
24005 let steps = |f: &mut Fixture, words: &[&[u8]]| {
24006 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"AGGREGATE", b"QUERY", b"*"];
24007 argv.extend_from_slice(words);
24008 let reply = timeless(&f.run(&argv));
24009 let at = reply.find("+Result processors").expect("a list of steps");
24010 let end = reply.find("+Coordinator").expect("an end");
24011 let mut out = Vec::new();
24012 let mut parts = reply[at..end].split("\r\n").peekable();
24013 while let Some(part) = parts.next() {
24014 if part == "+Type" {
24015 out.push(parts.next().unwrap_or_default().to_string());
24016 }
24017 }
24018 out
24019 };
24020 assert_eq!(steps(&mut f, &[]), ["+Index"]);
24021 assert_eq!(
24022 steps(&mut f, &[b"APPLY", b"1", b"AS", b"one"]),
24023 ["+Index", "+Projector - Literal 1"]
24024 );
24025 assert_eq!(
24026 steps(
24027 &mut f,
24028 &[b"LOAD", b"1", b"@n", b"APPLY", b"@n * 2", b"AS", b"d"]
24029 ),
24030 ["+Index", "+Loader", "+Projector - Operator *"]
24031 );
24032 assert_eq!(
24033 steps(&mut f, &[b"LOAD", b"1", b"@n", b"FILTER", b"@n > 1"]),
24034 ["+Index", "+Loader", "+Filter - Predicate >"]
24035 );
24036 assert_eq!(
24037 steps(
24038 &mut f,
24039 &[b"GROUPBY", b"1", b"@n", b"REDUCE", b"COUNT", b"0"]
24040 ),
24041 ["+Index", "+Loader", "+Grouper"]
24042 );
24043 assert_eq!(
24044 steps(&mut f, &[b"SORTBY", b"1", b"@n"]),
24045 ["+Index", "+Loader", "+Sorter"]
24046 );
24047 assert_eq!(
24048 steps(&mut f, &[b"LIMIT", b"0", b"2"]),
24049 ["+Index", "+Pager/Limiter"]
24050 );
24051 assert_eq!(
24054 steps(
24055 &mut f,
24056 &[
24057 b"ADDSCORES",
24058 b"LOAD",
24059 b"1",
24060 b"@n",
24061 b"APPLY",
24062 b"@__score",
24063 b"AS",
24064 b"s"
24065 ]
24066 ),
24067 [
24068 "+Index",
24069 "+Scorer",
24070 "+Loader",
24071 "+Projector - Property __score"
24072 ]
24073 );
24074 }
24075
24076 #[test]
24084 fn a_sortable_field_is_read_without_the_key_being_opened() {
24085 let mut f = Fixture::new();
24086 f.run(&[
24087 b"FT.CREATE",
24088 b"sx",
24089 b"PREFIX",
24090 b"1",
24091 b"s:",
24092 b"SCHEMA",
24093 b"n",
24094 b"NUMERIC",
24095 b"SORTABLE",
24096 b"g",
24097 b"TAG",
24098 ]);
24099 f.run(&[b"HSET", b"s:1", b"n", b"1", b"g", b"one"]);
24100 f.run(&[b"HSET", b"s:2", b"n", b"2", b"g", b"two"]);
24101 let loads = |f: &mut Fixture, words: &[&[u8]]| {
24102 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"sx", b"AGGREGATE", b"QUERY", b"*"];
24103 argv.extend_from_slice(words);
24104 f.run(&argv).contains("+Loader")
24105 };
24106 assert!(!loads(&mut f, &[b"LOAD", b"1", b"@n"]));
24107 assert!(!loads(&mut f, &[b"SORTBY", b"1", b"@n"]));
24108 assert!(!loads(&mut f, &[b"APPLY", b"@n * 2", b"AS", b"d"]));
24109 assert!(loads(&mut f, &[b"LOAD", b"1", b"@g"]));
24110 assert!(loads(&mut f, &[b"LOAD", b"2", b"@n", b"@g"]));
24111 assert!(loads(
24112 &mut f,
24113 &[b"GROUPBY", b"1", b"@g", b"REDUCE", b"COUNT", b"0"]
24114 ));
24115 assert!(loads(&mut f, &[b"LOAD", b"*"]));
24116 }
24117
24118 #[test]
24121 fn a_profile_checks_its_own_words_before_it_runs_anything() {
24122 let mut f = profiling();
24123 assert_eq!(
24124 f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY"]),
24125 "-ERR wrong number of arguments for 'FT.PROFILE' command\r\n"
24126 );
24127 assert_eq!(
24128 f.run(&[b"FT.PROFILE", b"ix", b"BOGUS", b"QUERY", b"alpha"]),
24129 "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
24130 );
24131 assert_eq!(
24134 f.run(&[
24135 b"FT.PROFILE",
24136 b"ix",
24137 b"LIMITED",
24138 b"SEARCH",
24139 b"QUERY",
24140 b"alpha"
24141 ]),
24142 "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
24143 );
24144 assert_eq!(
24145 f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"BOGUS", b"alpha"]),
24146 "-The QUERY keyword is expected\r\n"
24147 );
24148 assert_eq!(
24149 f.run(&[
24150 b"FT.PROFILE",
24151 b"ix",
24152 b"AGGREGATE",
24153 b"QUERY",
24154 b"alpha",
24155 b"WITHCURSOR"
24156 ]),
24157 "-FT.PROFILE does not support cursor\r\n"
24158 );
24159 assert_eq!(
24162 f.run(&[b"FT.PROFILE", b"nope", b"SEARCH", b"QUERY", b"alpha"]),
24163 "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
24164 );
24165 assert_eq!(
24166 f.run(&[
24167 b"FT.PROFILE",
24168 b"ix",
24169 b"SEARCH",
24170 b"QUERY",
24171 b"alpha",
24172 b"extra"
24173 ]),
24174 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `extra` at position 1 for <main>\r\n"
24175 );
24176 }
24177
24178 #[test]
24180 fn the_words_of_a_profile_are_read_the_way_every_other_word_is() {
24181 let mut f = profiling();
24182 let one = f.run(&[
24183 b"FT.PROFILE",
24184 b"ix",
24185 b"search",
24186 b"limited",
24187 b"query",
24188 b"alpha",
24189 ]);
24190 let two = f.run(&[
24191 b"FT.PROFILE",
24192 b"ix",
24193 b"SEARCH",
24194 b"LIMITED",
24195 b"QUERY",
24196 b"alpha",
24197 ]);
24198 assert_eq!(timeless(&one), timeless(&two));
24199 }
24200
24201 #[test]
24206 fn the_two_ways_of_dropping_an_index_disagree_about_the_documents() {
24207 let mut f = profiling();
24208 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix"]), "+OK\r\n");
24209 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
24210
24211 let mut f = profiling();
24212 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
24213 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
24214
24215 let mut f = profiling();
24216 assert_eq!(f.run(&[b"FT.DROP", b"ix"]), "+OK\r\n");
24217 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
24218
24219 let mut f = profiling();
24220 assert_eq!(f.run(&[b"FT.DROP", b"ix", b"KEEPDOCS"]), "+OK\r\n");
24221 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
24222 }
24223
24224 #[test]
24227 fn neither_way_of_dropping_an_index_takes_the_other_ones_word() {
24228 let mut f = profiling();
24229 let line = "-SEARCH_ARG_UNRECOGNIZED Unknown argument\r\n";
24230 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"KEEPDOCS"]), line);
24231 assert_eq!(f.run(&[b"FT.DROP", b"ix", b"DD"]), line);
24232 assert_eq!(f.run(&[b"FT._LIST"]), "*1\r\n+ix\r\n");
24234 }
24235
24236 #[test]
24239 fn dropping_the_documents_leaves_a_key_the_index_never_read() {
24240 let mut f = profiling();
24241 f.run(&[b"SET", b"p:4", b"alpha"]);
24242 f.run(&[b"HSET", b"q:1", b"t", b"alpha"]);
24243 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
24244 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
24245 assert_eq!(f.run(&[b"EXISTS", b"p:4", b"q:1"]), ":2\r\n");
24246 }
24247
24248 #[test]
24251 fn another_index_over_the_same_keys_loses_the_documents_too() {
24252 let mut f = profiling();
24253 f.run(&[
24254 b"FT.CREATE",
24255 b"other",
24256 b"PREFIX",
24257 b"1",
24258 b"p:",
24259 b"SCHEMA",
24260 b"t",
24261 b"TEXT",
24262 ]);
24263 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
24264 assert_eq!(
24265 f.run(&[b"FT.SEARCH", b"other", b"alpha", b"NOCONTENT"]),
24266 "*1\r\n:0\r\n"
24267 );
24268 }
24269
24270 #[test]
24273 fn a_drop_of_an_index_that_is_not_there_touches_no_keys() {
24274 let mut f = profiling();
24275 assert_eq!(f.run(&[b"FT._DROPINDEXIFX", b"nope", b"DD"]), "+OK\r\n");
24276 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
24277 assert_eq!(f.run(&[b"FT._DROPIFX", b"nope"]), "+OK\r\n");
24278 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
24279 }
24280
24281 #[test]
24286 fn a_setting_reads_back_as_a_pair_on_one_protocol_and_a_map_on_the_other() {
24287 let mut f = Fixture::new();
24288 assert_eq!(
24289 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
24290 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
24291 );
24292 assert_eq!(
24293 f.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
24294 "*1\r\n*2\r\n+EXTLOAD\r\n$-1\r\n"
24295 );
24296 let mut g = Fixture::new();
24297 g.run(&[b"HELLO", b"3"]);
24298 assert_eq!(
24299 g.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
24300 "%1\r\n+TIMEOUT\r\n$3\r\n500\r\n"
24301 );
24302 assert_eq!(
24303 g.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
24304 "%1\r\n+EXTLOAD\r\n_\r\n"
24305 );
24306 }
24307
24308 #[test]
24311 fn a_help_row_carries_the_description_and_the_value_together() {
24312 let mut f = Fixture::new();
24313 assert_eq!(
24314 f.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
24315 "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
24316 +Value\r\n$3\r\n500\r\n"
24317 );
24318 let mut g = Fixture::new();
24319 g.run(&[b"HELLO", b"3"]);
24320 assert_eq!(
24321 g.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
24322 "%1\r\n+TIMEOUT\r\n%2\r\n+Description\r\n+Query (search) timeout\r\n\
24323 +Value\r\n$3\r\n500\r\n"
24324 );
24325 }
24326
24327 #[test]
24330 fn only_a_bare_star_asks_for_every_setting_and_nothing_else_globs() {
24331 let mut f = Fixture::new();
24332 assert_eq!(
24333 f.run(&[b"FT.CONFIG", b"GET", b"timeout"]),
24334 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
24335 );
24336 for name in [
24337 b"TIMEOUT*".as_slice(),
24338 b"?IMEOUT",
24339 b"*TIMEOUT*",
24340 b"TIME",
24341 b"NOSUCH",
24342 b"",
24343 ] {
24344 assert_eq!(f.run(&[b"FT.CONFIG", b"GET", name]), "*0\r\n", "{name:?}");
24345 }
24346 assert!(f.run(&[b"FT.CONFIG", b"GET", b"*"]).starts_with("*69\r\n"));
24347 assert!(f.run(&[b"FT.CONFIG", b"HELP", b"*"]).starts_with("*69\r\n"));
24348 }
24349
24350 #[test]
24353 fn a_read_ignores_whatever_follows_the_name() {
24354 let mut f = Fixture::new();
24355 assert_eq!(
24356 f.run(&[b"FT.CONFIG", b"GET", b"timeout", b"extra", b"more"]),
24357 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
24358 );
24359 assert_eq!(
24360 f.run(&[b"FT.CONFIG", b"HELP", b"timeout", b"extra"]),
24361 "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
24362 +Value\r\n$3\r\n500\r\n"
24363 );
24364 }
24365
24366 #[test]
24369 fn a_missing_subcommand_and_a_missing_name_are_told_apart() {
24370 let mut f = Fixture::new();
24371 assert_eq!(
24372 f.run(&[b"FT.CONFIG"]),
24373 "-ERR wrong number of arguments for 'FT.CONFIG' command\r\n"
24374 );
24375 for sub in [b"GET".as_slice(), b"SET", b"HELP"] {
24376 let want = format!(
24377 "-ERR wrong number of arguments for 'FT.CONFIG|{}' command\r\n",
24378 String::from_utf8_lossy(sub)
24379 );
24380 assert_eq!(f.run(&[b"FT.CONFIG", sub]), want);
24381 }
24382 assert_eq!(
24383 f.run(&[b"ft.config", b"get"]),
24384 "-ERR wrong number of arguments for 'FT.CONFIG|GET' command\r\n"
24385 );
24386 assert_eq!(
24387 f.run(&[b"FT.CONFIG", b"bogus"]),
24388 "-ERR unknown subcommand 'bogus'. Try FT.CONFIG HELP.\r\n"
24389 );
24390 }
24391
24392 #[test]
24395 fn a_write_checks_the_name_then_the_setting_then_the_value() {
24396 let mut f = Fixture::new();
24397 for tail in [vec![b"1".as_slice()], vec![], vec![b"1", b"2", b"3"]] {
24398 let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"NOSUCH"];
24399 cmd.extend(tail);
24400 assert_eq!(f.run(&cmd), "-SEARCH_OPTION_INVALID Invalid option\r\n");
24401 }
24402 for tail in [vec![b"1000".as_slice()], vec![], vec![b"x", b"y"]] {
24403 let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"MAXDOCTABLESIZE"];
24404 cmd.extend(tail);
24405 assert_eq!(
24406 f.run(&cmd),
24407 "-SEARCH_OPTION_BAD Not modifiable at runtime\r\n"
24408 );
24409 }
24410 assert_eq!(
24411 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"x", b"y", b"z"]),
24412 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n"
24413 );
24414 }
24415
24416 #[test]
24419 fn an_excess_of_words_is_noticed_after_the_value_is_kept() {
24420 let mut f = Fixture::new();
24421 assert_eq!(
24422 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"500"]),
24423 "+OK\r\n"
24424 );
24425 assert_eq!(
24426 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"600", b"junk"]),
24427 "+EXCESSARGS\r\n"
24428 );
24429 assert_eq!(
24430 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
24431 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n600\r\n"
24432 );
24433 }
24434
24435 #[test]
24438 fn a_number_is_read_the_strict_way_and_then_the_loose_one() {
24439 let mut f = Fixture::new();
24440 for (given, want) in [
24441 (b"0x10".as_slice(), "16"),
24442 (b"0X1f", "31"),
24443 (b"+0x10", "16"),
24444 (b"+5", "5"),
24445 (b"010", "10"),
24446 (b"08", "8"),
24447 (b"0777", "777"),
24448 (b"1e3", "1000"),
24449 (b"0.0", "0"),
24450 (b"-0.0", "0"),
24451 ] {
24452 assert_eq!(
24453 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
24454 "+OK\r\n",
24455 "{given:?}"
24456 );
24457 let want = format!("*1\r\n*2\r\n+TIMEOUT\r\n${}\r\n{want}\r\n", want.len());
24458 assert_eq!(
24459 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
24460 want,
24461 "{given:?}"
24462 );
24463 }
24464 for given in [
24465 b" 5".as_slice(),
24466 b"5 ",
24467 b"1.5",
24468 b"1e-3",
24469 b"x",
24470 b"",
24471 b"0b11",
24472 b"0xg",
24473 b"nan",
24474 b"inf",
24475 b"1e100",
24476 b"99999999999999999999",
24477 ] {
24478 assert_eq!(
24479 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
24480 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
24481 "{given:?}"
24482 );
24483 }
24484 }
24485
24486 #[test]
24489 fn a_negative_is_answered_by_whichever_reader_found_it() {
24490 let mut f = Fixture::new();
24491 for given in [b"-1".as_slice(), b"-16"] {
24492 assert_eq!(
24493 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
24494 "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n",
24495 "{given:?}"
24496 );
24497 }
24498 for given in [b"-0x10".as_slice(), b"-1e3", b"-010", b"-2.0"] {
24499 assert_eq!(
24500 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
24501 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
24502 "{given:?}"
24503 );
24504 }
24505 let unlimited = "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n$9\r\nunlimited\r\n";
24506 for given in [b"-1".as_slice(), b"-0x10", b"-1e3", b"-010"] {
24507 assert_eq!(
24508 f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
24509 "+OK\r\n",
24510 "{given:?}"
24511 );
24512 assert_eq!(
24513 f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
24514 unlimited,
24515 "{given:?}"
24516 );
24517 }
24518 }
24519
24520 #[test]
24523 fn a_wide_setting_wraps_into_its_slot_before_it_is_read_back() {
24524 let mut f = Fixture::new();
24525 for (given, want) in [
24526 (b"2147483647".as_slice(), "2147483647"),
24527 (b"2147483648", "unlimited"),
24528 (b"4294967295", "unlimited"),
24529 (b"9223372036854775806", "unlimited"),
24530 (b"0", "0"),
24531 ] {
24532 assert_eq!(
24533 f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
24534 "+OK\r\n",
24535 "{given:?}"
24536 );
24537 let want = format!(
24538 "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n${}\r\n{want}\r\n",
24539 want.len()
24540 );
24541 assert_eq!(
24542 f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
24543 want,
24544 "{given:?}"
24545 );
24546 }
24547 }
24548
24549 #[test]
24552 fn a_number_out_of_range_names_the_limit_it_crossed() {
24553 let mut f = Fixture::new();
24554 let bounds = "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n";
24555 for (name, given) in [
24556 (b"MINPREFIX".as_slice(), b"0".as_slice()),
24557 (b"MAX_AGGREGATE_GROUPS", b"0"),
24558 (b"BM25STD_TANH_FACTOR", b"0"),
24559 (b"DEFAULT_DIALECT", b"0"),
24560 (b"MINSTEMLEN", b"4294967296"),
24561 (b"_BG_INDEX_OOM_PAUSE_TIME", b"4294967296"),
24562 (b"INDEXER_YIELD_EVERY_OPS", b"4294967296"),
24563 (b"CONNECT_TIMEOUT", b"2147483648"),
24564 ] {
24565 assert_eq!(
24566 f.run(&[b"FT.CONFIG", b"SET", name, given]),
24567 bounds,
24568 "{name:?}"
24569 );
24570 }
24571 for (name, given, want) in [
24572 (
24573 b"MINSTEMLEN".as_slice(),
24574 b"1".as_slice(),
24575 "-SEARCH_SYNTAX Minimum stem length cannot be lower than 2\r\n",
24576 ),
24577 (
24578 b"MAX_AGGREGATE_GROUPS",
24579 b"67108865",
24580 "-SEARCH_LIMIT_OVER Value exceeds maximum possible aggregate groups\r\n",
24581 ),
24582 (
24583 b"WORKERS",
24584 b"17",
24585 "-SEARCH_LIMIT_OVER Number of worker threads cannot exceed 16\r\n",
24586 ),
24587 (
24588 b"_NUMERIC_RANGES_PARENTS",
24589 b"3",
24590 "-SEARCH_PARSE_ARGS Max depth for range cannot be higher than max \
24591 depth for balance\r\n",
24592 ),
24593 (
24594 b"DEFAULT_DIALECT",
24595 b"5",
24596 "-SEARCH_VALUE_BAD Default dialect version cannot be higher than 4\r\n",
24597 ),
24598 (
24599 b"_BG_INDEX_MEM_PCT_THR",
24600 b"101",
24601 "-SEARCH_LIMIT_OVER Memory limit for indexing cannot be greater then \
24602 100%\r\n",
24603 ),
24604 (
24605 b"BM25STD_TANH_FACTOR",
24606 b"10001",
24607 "-SEARCH_LIMIT_OVER BM25STD_TANH_FACTOR must be between 1 and 10000 \
24608 inclusive\r\n",
24609 ),
24610 (
24611 b"BG_INDEX_SLEEP_DURATION_US",
24612 b"1000000",
24613 "-SEARCH_LIMIT_OVER BG_INDEX_SLEEP_DURATION_US must be between 1 and \
24614 999999 (usleep POSIX limit)\r\n",
24615 ),
24616 ] {
24617 assert_eq!(
24618 f.run(&[b"FT.CONFIG", b"SET", name, given]),
24619 want,
24620 "{name:?}"
24621 );
24622 }
24623 }
24624
24625 #[test]
24628 fn the_trimming_delays_are_checked_against_one_another() {
24629 let mut f = Fixture::new();
24630 assert_eq!(
24631 f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"5000"]),
24632 "-SEARCH_PARSE_ARGS _MIN_TRIM_DELAY_MS (5000) must be less than \
24633 _MAX_TRIM_DELAY_MS (5000)\r\n"
24634 );
24635 assert_eq!(
24636 f.run(&[b"FT.CONFIG", b"SET", b"_MAX_TRIM_DELAY_MS", b"1999"]),
24637 "-SEARCH_PARSE_ARGS _MAX_TRIM_DELAY_MS (1999) must be greater than \
24638 _MIN_TRIM_DELAY_MS (2000)\r\n"
24639 );
24640 assert_eq!(
24641 f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"4999"]),
24642 "+OK\r\n"
24643 );
24644 }
24645
24646 #[test]
24649 fn a_word_setting_folds_where_a_real_server_folds_and_not_otherwise() {
24650 let mut f = Fixture::new();
24651 assert_eq!(
24652 f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"RETURN"]),
24653 "+OK\r\n"
24654 );
24655 assert_eq!(
24656 f.run(&[b"FT.CONFIG", b"GET", b"ON_TIMEOUT"]),
24657 "*1\r\n*2\r\n+ON_TIMEOUT\r\n$6\r\nreturn\r\n"
24658 );
24659 assert_eq!(
24660 f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"nope"]),
24661 "-SEARCH_VALUE_BAD Invalid ON_TIMEOUT value\r\n"
24662 );
24663 assert_eq!(
24664 f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"IGNORE"]),
24665 "+OK\r\n"
24666 );
24667 assert_eq!(
24668 f.run(&[b"FT.CONFIG", b"GET", b"ON_OOM"]),
24669 "*1\r\n*2\r\n+ON_OOM\r\n$6\r\nignore\r\n"
24670 );
24671 assert_eq!(
24672 f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"nope"]),
24673 "-SEARCH_VALUE_BAD Invalid ON_OOM value\r\n"
24674 );
24675 let bad = "-SEARCH_VALUE_BAD Invalid default scorer value\r\n";
24676 for given in [b"bm25std".as_slice(), b"Bm25", b"TFIDF.docnorm", b""] {
24677 assert_eq!(
24678 f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", given]),
24679 bad,
24680 "{given:?}"
24681 );
24682 }
24683 assert_eq!(
24684 f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", b"TFIDF.DOCNORM"]),
24685 "+OK\r\n"
24686 );
24687 }
24688
24689 #[test]
24692 fn a_yes_or_no_setting_takes_those_two_words_only() {
24693 let mut f = Fixture::new();
24694 assert_eq!(
24695 f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", b"TRUE"]),
24696 "+OK\r\n"
24697 );
24698 assert_eq!(
24699 f.run(&[b"FT.CONFIG", b"GET", b"_NUMERIC_COMPRESS"]),
24700 "*1\r\n*2\r\n+_NUMERIC_COMPRESS\r\n$4\r\ntrue\r\n"
24701 );
24702 for given in [b"yes".as_slice(), b"no", b"1", b"0", b"enabled", b""] {
24703 assert_eq!(
24704 f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", given]),
24705 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
24706 "{given:?}"
24707 );
24708 }
24709 }
24710
24711 #[test]
24714 fn two_names_for_one_setting_move_together() {
24715 let mut f = Fixture::new();
24716 f.run(&[b"FT.CONFIG", b"SET", b"MAXEXPANSIONS", b"300"]);
24717 assert_eq!(
24718 f.run(&[b"FT.CONFIG", b"GET", b"MAXPREFIXEXPANSIONS"]),
24719 "*1\r\n*2\r\n+MAXPREFIXEXPANSIONS\r\n$3\r\n300\r\n"
24720 );
24721 f.run(&[b"FT.CONFIG", b"SET", b"MAXPREFIXEXPANSIONS", b"200"]);
24722 assert_eq!(
24723 f.run(&[b"FT.CONFIG", b"GET", b"MAXEXPANSIONS"]),
24724 "*1\r\n*2\r\n+MAXEXPANSIONS\r\n$3\r\n200\r\n"
24725 );
24726 let long = b"_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
24727 let short = b"FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
24728 f.run(&[b"FT.CONFIG", b"SET", long, b"false"]);
24729 assert_eq!(
24730 f.run(&[b"FT.CONFIG", b"GET", short]),
24731 "*1\r\n*2\r\n+FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$5\r\nfalse\r\n"
24732 );
24733 assert_eq!(f.run(&[b"FT.CONFIG", b"SET", short]), "+OK\r\n");
24734 assert_eq!(
24735 f.run(&[b"FT.CONFIG", b"GET", long]),
24736 "*1\r\n*2\r\n+_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$4\r\ntrue\r\n"
24737 );
24738 }
24739
24740 #[test]
24742 fn a_password_reads_back_as_stars_whatever_was_written() {
24743 let mut f = Fixture::new();
24744 assert_eq!(
24745 f.run(&[b"FT.CONFIG", b"SET", b"OSS_GLOBAL_PASSWORD", b"hunter2"]),
24746 "+OK\r\n"
24747 );
24748 assert_eq!(
24749 f.run(&[b"FT.CONFIG", b"GET", b"OSS_GLOBAL_PASSWORD"]),
24750 "*1\r\n*2\r\n+OSS_GLOBAL_PASSWORD\r\n$17\r\nPassword: *******\r\n"
24751 );
24752 }
24753
24754 #[test]
24757 fn a_flush_leaves_the_settings_alone() {
24758 for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
24759 let mut f = Fixture::new();
24760 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"777"]);
24761 f.run(&[flush]);
24762 assert_eq!(
24763 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
24764 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n777\r\n",
24765 "{flush:?}"
24766 );
24767 }
24768 }
24769
24770 fn debugging() -> Fixture {
24775 let mut f = Fixture::new();
24776 f.run(&[
24777 b"FT.CREATE",
24778 b"dx",
24779 b"PREFIX",
24780 b"1",
24781 b"d:",
24782 b"SCHEMA",
24783 b"t",
24784 b"TEXT",
24785 b"g",
24786 b"TAG",
24787 b"n",
24788 b"NUMERIC",
24789 b"s",
24790 b"TEXT",
24791 b"SORTABLE",
24792 ]);
24793 f.run(&[
24794 b"HSET",
24795 b"d:1",
24796 b"t",
24797 b"running dogs",
24798 b"g",
24799 b"red,blue",
24800 b"n",
24801 b"1",
24802 b"s",
24803 b"Alpha",
24804 ]);
24805 f.run(&[
24806 b"HSET", b"d:2", b"t", b"running", b"g", b"red", b"n", b"2", b"s", b"beta",
24807 ]);
24808 f.run(&[
24809 b"HSET",
24810 b"d:3",
24811 b"t",
24812 b"dogs alpha",
24813 b"g",
24814 b"green",
24815 b"n",
24816 b"3",
24817 ]);
24818 f
24819 }
24820
24821 #[test]
24824 fn a_term_dump_lists_the_stems_beside_the_words() {
24825 let mut f = debugging();
24826 assert_eq!(
24827 f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"dx"]),
24828 "*6\r\n$4\r\n+dog\r\n$4\r\n+run\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n\
24829 $4\r\ndogs\r\n$7\r\nrunning\r\n"
24830 );
24831 }
24832
24833 #[test]
24836 fn a_posting_list_is_read_by_the_bytes_and_not_by_the_word() {
24837 let mut f = debugging();
24838 assert_eq!(
24839 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
24840 "*2\r\n:1\r\n:2\r\n"
24841 );
24842 assert_eq!(
24843 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"+run"]),
24844 "*2\r\n:1\r\n:2\r\n"
24845 );
24846 for term in [b"RUNNING".as_slice(), b"nosuchterm", b""] {
24847 assert_eq!(
24848 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", term]),
24849 "-Can not find the inverted index\r\n",
24850 "{term:?}"
24851 );
24852 }
24853 }
24854
24855 #[test]
24858 fn a_tag_dump_pairs_every_value_with_its_documents() {
24859 let mut f = debugging();
24860 assert_eq!(
24861 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"dx", b"g"]),
24862 "*3\r\n*2\r\n$4\r\nblue\r\n*1\r\n:1\r\n*2\r\n$5\r\ngreen\r\n*1\r\n:3\r\n\
24863 *2\r\n$3\r\nred\r\n*2\r\n:1\r\n:2\r\n"
24864 );
24865 }
24866
24867 #[test]
24870 fn a_number_dump_answers_a_single_range() {
24871 let mut f = debugging();
24872 assert_eq!(
24873 f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"dx", b"n"]),
24874 "*1\r\n*3\r\n:1\r\n:2\r\n:3\r\n"
24875 );
24876 }
24877
24878 #[test]
24881 fn a_geo_field_is_dumped_as_a_numeric_one() {
24882 let mut f = Fixture::new();
24883 f.run(&[
24884 b"FT.CREATE",
24885 b"gx",
24886 b"PREFIX",
24887 b"1",
24888 b"q:",
24889 b"SCHEMA",
24890 b"loc",
24891 b"GEO",
24892 b"gg",
24893 b"AS",
24894 b"tag",
24895 b"TAG",
24896 ]);
24897 f.run(&[b"HSET", b"q:1", b"loc", b"1,2", b"gg", b"red"]);
24898 f.run(&[b"HSET", b"q:2", b"loc", b"3,4", b"gg", b"BLUE"]);
24899 assert_eq!(
24900 f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"gx", b"loc"]),
24901 "*1\r\n*2\r\n:1\r\n:2\r\n"
24902 );
24903 assert_eq!(
24904 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"gx", b"loc"]),
24905 "-Could not find given field in index spec\r\n"
24906 );
24907 }
24908
24909 #[test]
24912 fn a_dump_takes_the_attribute_and_not_the_identifier() {
24913 let mut f = Fixture::new();
24914 f.run(&[
24915 b"FT.CREATE",
24916 b"zx",
24917 b"PREFIX",
24918 b"1",
24919 b"z:",
24920 b"SCHEMA",
24921 b"gg",
24922 b"AS",
24923 b"tag",
24924 b"TAG",
24925 ]);
24926 f.run(&[b"HSET", b"z:1", b"gg", b"red"]);
24927 assert_eq!(
24928 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"zx", b"tag"]),
24929 "*1\r\n*2\r\n$3\r\nred\r\n*1\r\n:1\r\n"
24930 );
24931 assert_eq!(
24932 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"zx", b"gg"]),
24933 "-Could not find given field in index spec\r\n"
24934 );
24935 }
24936
24937 #[test]
24940 fn a_document_row_is_flat_on_one_protocol_and_a_map_on_the_other() {
24941 let mut f = debugging();
24942 assert_eq!(
24943 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]),
24944 "*14\r\n+internal_id\r\n:1\r\n$5\r\nflags\r\n\
24945 $36\r\n(0xc):HasSortVector,HasOffsetVector,\r\n+score\r\n$1\r\n1\r\n\
24946 +num_tokens\r\n:3\r\n+max_freq\r\n:1\r\n+refcount\r\n:1\r\n\
24947 +sortables\r\n*1\r\n*6\r\n+index\r\n:0\r\n$5\r\nfield\r\n$6\r\ns AS s\r\n\
24948 $5\r\nvalue\r\n$5\r\nalpha\r\n"
24949 );
24950 let mut g = debugging();
24951 g.run(&[b"HELLO", b"3"]);
24952 assert_eq!(
24953 g.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]),
24954 "%7\r\n+internal_id\r\n:1\r\n$5\r\nflags\r\n\
24955 $36\r\n(0xc):HasSortVector,HasOffsetVector,\r\n+score\r\n,1\r\n\
24956 +num_tokens\r\n:3\r\n+max_freq\r\n:1\r\n+refcount\r\n:1\r\n\
24957 +sortables\r\n*1\r\n*6\r\n+index\r\n:0\r\n$5\r\nfield\r\n$6\r\ns AS s\r\n\
24958 $5\r\nvalue\r\n$5\r\nalpha\r\n"
24959 );
24960 }
24961
24962 #[test]
24965 fn a_document_with_no_sortable_value_drops_the_key() {
24966 let mut f = debugging();
24967 assert_eq!(
24968 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:3", b"REVEAL"]),
24969 "*12\r\n+internal_id\r\n:3\r\n$5\r\nflags\r\n$22\r\n(0x8):HasOffsetVector,\r\n\
24970 +score\r\n$1\r\n1\r\n+num_tokens\r\n:2\r\n+max_freq\r\n:1\r\n+refcount\r\n:1\r\n"
24971 );
24972 }
24973
24974 #[test]
24977 fn the_flag_word_spells_out_the_bits_it_carries() {
24978 let mut f = Fixture::new();
24979 f.run(&[
24980 b"FT.CREATE",
24981 b"nx",
24982 b"NOOFFSETS",
24983 b"PREFIX",
24984 b"1",
24985 b"o:",
24986 b"SCHEMA",
24987 b"t",
24988 b"TEXT",
24989 ]);
24990 f.run(&[b"HSET", b"o:1", b"t", b"alpha"]);
24991 assert!(
24992 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"nx", b"o:1", b"REVEAL"])
24993 .contains("$6\r\n(0x0):\r\n")
24994 );
24995 }
24996
24997 #[test]
25000 fn obfuscation_numbers_a_field_by_its_place_in_the_schema() {
25001 let mut f = debugging();
25002 assert!(
25003 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"OBFUSCATE"])
25004 .contains("$22\r\nFieldPath@3 AS Field@3\r\n")
25005 );
25006 }
25007
25008 #[test]
25011 fn a_document_row_reads_its_keyword_at_a_fixed_place() {
25012 let mut f = debugging();
25013 let want = f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]);
25014 assert_eq!(
25015 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL", b"more"]),
25016 want
25017 );
25018 assert_eq!(
25019 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"more", b"REVEAL"]),
25020 "-Invalid argument. Expected REVEAL or OBFUSCATE as the last argument\r\n"
25021 );
25022 assert_eq!(
25023 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1"]),
25024 "-ERR wrong number of arguments for '_FT.DEBUG|DOCINFO' command\r\n"
25025 );
25026 }
25027
25028 #[test]
25031 fn a_document_row_looks_the_key_up_before_it_reads_the_keyword() {
25032 let mut f = debugging();
25033 assert_eq!(
25034 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"nope", b"zz"]),
25035 "-Document not found in index\r\n"
25036 );
25037 assert_eq!(
25038 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"zz"]),
25039 "-Invalid argument. Expected REVEAL or OBFUSCATE as the last argument\r\n"
25040 );
25041 }
25042
25043 #[test]
25046 fn a_document_number_goes_both_ways() {
25047 let mut f = debugging();
25048 assert_eq!(
25049 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"2"]),
25050 "$3\r\nd:2\r\n"
25051 );
25052 assert_eq!(
25053 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:2"]),
25054 ":2\r\n"
25055 );
25056 assert_eq!(
25057 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"nope"]),
25058 ":0\r\n"
25059 );
25060 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"dx"]), ":3\r\n");
25061 for id in [b"9".as_slice(), b"0", b"-1", b"9223372036854775807"] {
25062 assert_eq!(
25063 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", id]),
25064 "-document was removed\r\n",
25065 "{id:?}"
25066 );
25067 }
25068 }
25069
25070 #[test]
25073 fn a_document_number_is_read_the_strict_way() {
25074 let mut f = debugging();
25075 for id in [
25076 b"x".as_slice(),
25077 b"1.5",
25078 b" 1",
25079 b"+1",
25080 b"01",
25081 b"0x1",
25082 b"",
25083 b"9223372036854775808",
25084 b"18446744073709551615",
25085 ] {
25086 assert_eq!(
25087 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", id]),
25088 "-bad id given\r\n",
25089 "{id:?}"
25090 );
25091 }
25092 }
25093
25094 #[test]
25097 fn a_dump_keeps_a_number_the_table_has_given_up() {
25098 let mut f = debugging();
25099 f.run(&[b"DEL", b"d:2"]);
25100 assert_eq!(
25101 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
25102 "*2\r\n:1\r\n:2\r\n"
25103 );
25104 assert_eq!(
25105 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"2"]),
25106 "-document was removed\r\n"
25107 );
25108 assert_eq!(
25109 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:2"]),
25110 ":0\r\n"
25111 );
25112 }
25113
25114 #[test]
25117 fn a_rewrite_takes_a_number_of_its_own() {
25118 let mut f = debugging();
25119 f.run(&[b"HSET", b"d:1", b"t", b"cats"]);
25120 assert_eq!(
25121 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:1"]),
25122 ":4\r\n"
25123 );
25124 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"dx"]), ":4\r\n");
25125 assert_eq!(
25126 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"1"]),
25127 "-document was removed\r\n"
25128 );
25129 assert_eq!(
25130 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
25131 "*2\r\n:1\r\n:2\r\n"
25132 );
25133 }
25134
25135 #[test]
25137 fn a_dump_follows_an_alias() {
25138 let mut f = debugging();
25139 f.run(&[b"FT.ALIASADD", b"da", b"dx"]);
25140 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"da"]), ":3\r\n");
25141 assert_eq!(
25142 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"da", b"1"]),
25143 "$3\r\nd:1\r\n"
25144 );
25145 }
25146
25147 #[test]
25150 fn an_index_name_is_case_sensitive_and_a_subcommand_name_is_not() {
25151 let mut f = debugging();
25152 assert_eq!(f.run(&[b"_FT.DEBUG", b"get_max_doc_id", b"dx"]), ":3\r\n");
25153 assert_eq!(
25154 f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"DX"]),
25155 "-Can not create a search ctx\r\n"
25156 );
25157 assert_eq!(
25158 f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"nope"]),
25159 "-Can not create a search ctx\r\n"
25160 );
25161 }
25162
25163 #[test]
25166 fn an_empty_field_dumps_as_nothing_at_all() {
25167 let mut f = Fixture::new();
25168 f.run(&[
25169 b"FT.CREATE",
25170 b"ex",
25171 b"PREFIX",
25172 b"1",
25173 b"e:",
25174 b"SCHEMA",
25175 b"t",
25176 b"TEXT",
25177 b"g",
25178 b"TAG",
25179 b"n",
25180 b"NUMERIC",
25181 ]);
25182 assert_eq!(f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"ex"]), "*0\r\n");
25183 assert_eq!(
25184 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"ex", b"g"]),
25185 "*0\r\n"
25186 );
25187 assert_eq!(
25188 f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"ex", b"n"]),
25189 "*0\r\n"
25190 );
25191 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"ex"]), ":0\r\n");
25192 }
25193
25194 #[test]
25197 fn the_two_lines_with_a_code_word_are_the_arity_and_the_unknown_one() {
25198 let mut f = debugging();
25199 for (sub, extra) in [
25200 (b"DUMP_TERMS".as_slice(), 1),
25201 (b"GET_MAX_DOC_ID", 1),
25202 (b"DUMP_INVIDX", 2),
25203 (b"DUMP_TAGIDX", 2),
25204 (b"DUMP_NUMIDX", 2),
25205 (b"IDTODOCID", 2),
25206 (b"DOCIDTOID", 2),
25207 ] {
25208 let want = format!(
25209 "-ERR wrong number of arguments for '_FT.DEBUG|{}' command\r\n",
25210 str::from_utf8(sub).unwrap()
25211 );
25212 for given in [extra - 1, extra + 1] {
25213 let mut cmd: Vec<&[u8]> = vec![b"_FT.DEBUG", sub];
25214 cmd.extend(std::iter::repeat_n(b"dx".as_slice(), given));
25215 assert_eq!(f.run(&cmd), want, "{sub:?} {given}");
25216 }
25217 let mut right: Vec<&[u8]> = vec![b"_FT.DEBUG", sub, b"dx"];
25218 right.extend(std::iter::repeat_n(b"g".as_slice(), extra - 1));
25219 assert_ne!(f.run(&right), want, "{sub:?}");
25220 }
25221 assert_eq!(
25222 f.run(&[b"_FT.DEBUG", b"bogus", b"dx"]),
25223 "-ERR unknown subcommand 'bogus'. Try _FT.DEBUG HELP.\r\n"
25224 );
25225 }
25226
25227 #[test]
25230 fn the_help_names_the_subcommands_that_answer() {
25231 let mut f = Fixture::new();
25232 let want = "*8\r\n$11\r\nDUMP_INVIDX\r\n$11\r\nDUMP_NUMIDX\r\n$11\r\nDUMP_TAGIDX\r\n\
25233 $9\r\nIDTODOCID\r\n$9\r\nDOCIDTOID\r\n$7\r\nDOCINFO\r\n$10\r\nDUMP_TERMS\r\n\
25234 $14\r\nGET_MAX_DOC_ID\r\n";
25235 assert_eq!(f.run(&[b"_FT.DEBUG", b"HELP"]), want);
25236 assert_eq!(f.run(&[b"_FT.DEBUG", b"HELP", b"extra"]), want);
25237 }
25238
25239 #[test]
25244 fn a_synonym_dump_folds_the_terms_and_keeps_the_ids_as_given() {
25245 let mut f = Fixture::new();
25246 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
25247 assert_eq!(
25248 f.run(&[b"FT.SYNUPDATE", b"e", b"G1", b"BOY", b"kid"]),
25249 "+OK\r\n"
25250 );
25251 assert_eq!(f.run(&[b"FT.SYNUPDATE", b"e", b"g2", b"boy"]), "+OK\r\n");
25252 assert_eq!(
25253 f.run(&[b"FT.SYNDUMP", b"e"]),
25254 "*4\r\n$3\r\nboy\r\n*2\r\n$2\r\nG1\r\n$2\r\ng2\r\n\
25255 $3\r\nkid\r\n*1\r\n$2\r\nG1\r\n"
25256 );
25257 }
25258
25259 #[test]
25263 fn a_word_in_a_group_reads_as_a_union_with_the_group_term() {
25264 let mut f = Fixture::new();
25265 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
25266 f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"jogging"]);
25267 assert_eq!(
25268 f.run(&[b"FT.EXPLAIN", b"e", b"jogging"]),
25269 "$69\r\nUNION {\n jogging\n ~gr(expanded)\n +jog(expanded)\n jog(expanded)\n}\n\r\n"
25270 );
25271 }
25272
25273 #[test]
25282 fn a_group_matches_the_word_it_holds_and_not_a_stem_of_it() {
25283 let mut f = Fixture::new();
25284 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
25285 f.run(&[b"HSET", b"d1", b"t", b"boy"]);
25286 f.run(&[b"HSET", b"d2", b"t", b"runs"]);
25287 f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"boy", b"child", b"running"]);
25288 assert_eq!(
25289 f.run(&[b"FT.SEARCH", b"e", b"child", b"NOCONTENT"]),
25290 "*2\r\n:1\r\n$2\r\nd1\r\n"
25291 );
25292 assert_eq!(
25293 f.run(&[b"FT.SEARCH", b"e", b"running", b"NOCONTENT"]),
25294 "*3\r\n:2\r\n$2\r\nd1\r\n$2\r\nd2\r\n"
25295 );
25296 }
25297
25298 #[test]
25301 fn a_synonym_command_on_a_name_that_is_not_there_fails() {
25302 let mut f = Fixture::new();
25303 let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
25304 assert_eq!(f.run(&[b"FT.SYNDUMP", b"nope"]), missing);
25305 assert_eq!(f.run(&[b"FT.SYNUPDATE", b"nope", b"g", b"a"]), missing);
25306 }
25307
25308 #[test]
25312 fn params_counts_the_words_before_it_pairs_them_up() {
25313 let mut f = Fixture::new();
25314 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
25315 let none = "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: \
25316 Expected an argument, but none provided\r\n";
25317 let odd = "-SEARCH_ADD_ARGS Parameters must be specified in PARAM VALUE pairs\r\n";
25318 assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1"]), none);
25319 assert_eq!(
25320 f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"3", b"a", b"b"]),
25321 none
25322 );
25323 assert_eq!(
25324 f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1", b"a"]),
25325 odd
25326 );
25327 assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"0"]), odd);
25328 assert_eq!(
25329 f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"-1"]),
25330 "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: Value is outside acceptable bounds\r\n"
25331 );
25332 }
25333
25334 fn vectored(f: &mut Fixture) {
25344 f.run(&[
25345 b"FT.CREATE",
25346 b"h",
25347 b"SCHEMA",
25348 b"t",
25349 b"TEXT",
25350 b"v",
25351 b"VECTOR",
25352 b"FLAT",
25353 b"6",
25354 b"TYPE",
25355 b"FLOAT32",
25356 b"DIM",
25357 b"2",
25358 b"DISTANCE_METRIC",
25359 b"L2",
25360 ]);
25361 let at: [&[u8]; 5] = [
25362 b"\x00\x00\x80\x40\x00\x00\x00\x00",
25363 b"\x00\x00\x40\x40\x00\x00\x00\x00",
25364 b"\x00\x00\x00\x40\x00\x00\x00\x00",
25365 b"\x00\x00\x80\x3f\x00\x00\x00\x00",
25366 b"\x00\x00\x00\x00\x00\x00\x00\x00",
25367 ];
25368 for (n, point) in at.iter().enumerate() {
25369 let key = format!("d{}", n + 1);
25370 let word: &[u8] = match n {
25371 0 | 1 | 3 => b"alpha",
25372 _ => b"beta",
25373 };
25374 f.run(&[b"HSET", key.as_bytes(), b"t", word, b"v", point]);
25375 }
25376 }
25377
25378 const ORIGIN: &[u8] = b"\x00\x00\x00\x00\x00\x00\x00\x00";
25380
25381 #[test]
25385 fn a_knn_picks_the_nearest_and_answers_them_in_document_order() {
25386 let mut f = Fixture::new();
25387 vectored(&mut f);
25388 assert_eq!(
25389 f.run(&[
25390 b"FT.SEARCH",
25391 b"h",
25392 b"*=>[KNN 5 @v $vec]",
25393 b"PARAMS",
25394 b"2",
25395 b"vec",
25396 ORIGIN,
25397 b"DIALECT",
25398 b"2",
25399 b"NOCONTENT",
25400 ]),
25401 "*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"
25402 );
25403 assert_eq!(
25404 f.run(&[
25405 b"FT.SEARCH",
25406 b"h",
25407 b"*=>[KNN 3 @v $vec]",
25408 b"PARAMS",
25409 b"2",
25410 b"vec",
25411 ORIGIN,
25412 b"DIALECT",
25413 b"2",
25414 b"NOCONTENT",
25415 ]),
25416 "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
25417 );
25418 }
25419
25420 #[test]
25423 fn a_range_takes_what_is_inside_it_and_the_distance_is_squared() {
25424 let mut f = Fixture::new();
25425 vectored(&mut f);
25426 for (radius, want) in [
25427 ("0", "*2\r\n:1\r\n$2\r\nd5\r\n"),
25428 ("2", "*3\r\n:2\r\n$2\r\nd4\r\n$2\r\nd5\r\n"),
25429 (
25430 "9",
25431 "*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",
25432 ),
25433 ] {
25434 let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
25435 assert_eq!(
25436 f.run(&[
25437 b"FT.SEARCH",
25438 b"h",
25439 query.as_bytes(),
25440 b"PARAMS",
25441 b"2",
25442 b"vec",
25443 ORIGIN,
25444 b"DIALECT",
25445 b"2",
25446 b"NOCONTENT",
25447 ]),
25448 want,
25449 "radius {radius}"
25450 );
25451 }
25452 }
25453
25454 #[test]
25458 fn a_knn_measures_what_the_query_in_front_of_it_matched() {
25459 let mut f = Fixture::new();
25460 vectored(&mut f);
25461 assert_eq!(
25462 f.run(&[
25463 b"FT.SEARCH",
25464 b"h",
25465 b"alpha=>[KNN 2 @v $vec]",
25466 b"PARAMS",
25467 b"2",
25468 b"vec",
25469 ORIGIN,
25470 b"DIALECT",
25471 b"2",
25472 b"NOCONTENT",
25473 ]),
25474 "*3\r\n:2\r\n$2\r\nd2\r\n$2\r\nd4\r\n"
25475 );
25476 }
25477
25478 #[test]
25487 fn a_count_and_a_radius_are_refused_in_their_own_words() {
25488 let mut f = Fixture::new();
25489 vectored(&mut f);
25490 let ask = |f: &mut Fixture, query: &str| {
25491 f.run(&[
25492 b"FT.SEARCH",
25493 b"h",
25494 query.as_bytes(),
25495 b"PARAMS",
25496 b"2",
25497 b"vec",
25498 ORIGIN,
25499 b"DIALECT",
25500 b"2",
25501 b"NOCONTENT",
25502 ])
25503 };
25504 for (query, at, near) in [
25505 ("*=>[KNN -1 @v $vec]", 8, "-1"),
25506 ("*=>[KNN 1.5 @v $vec]", 8, "1.5"),
25507 ("*=>[KNN +3 @v $vec]", 8, "+3"),
25508 ("*=>[KNN 0x10 @v $vec]", 8, "0x10"),
25509 ("*=>[KNN abc @v $vec]", 8, "abc"),
25510 ("*=>[KNN 3 $vec]", 10, "vec"),
25511 ("*=>[KNN 3 @v vec]", 13, "vec"),
25512 ("@v:[VECTOR_RANGE 2 -1]", 19, "-1"),
25513 ] {
25514 assert_eq!(
25515 ask(&mut f, query),
25516 format!("-SEARCH_SYNTAX Syntax error at offset {at} near {near}\r\n"),
25517 "{query}"
25518 );
25519 }
25520
25521 let large = "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
25524 query KNN K parameter is too large, must not exceed 288230376151711744\r\n";
25525 assert_eq!(
25526 ask(&mut f, "*=>[KNN 288230376151711776 @v $vec]"),
25527 "*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"
25528 );
25529 assert_eq!(ask(&mut f, "*=>[KNN 288230376151711777 @v $vec]"), large);
25530 assert_eq!(ask(&mut f, "*=>[KNN 99999999999999999999 @v $vec]"), large);
25531
25532 for (radius, printed) in [("-1", "-1"), ("-0.5", "-0.5"), ("-1e2", "-100")] {
25533 let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
25534 assert_eq!(
25535 ask(&mut f, &query),
25536 format!(
25537 "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
25538 negative radius ({printed}) given in a range query\r\n"
25539 ),
25540 "{query}"
25541 );
25542 }
25543 assert_eq!(
25545 ask(&mut f, "@v:[VECTOR_RANGE -0 $vec]"),
25546 "*2\r\n:1\r\n$2\r\nd5\r\n"
25547 );
25548 }
25549
25550 #[test]
25553 fn a_count_that_came_from_params_is_read_by_its_own_rules() {
25554 let mut f = Fixture::new();
25555 vectored(&mut f);
25556 let ask = |f: &mut Fixture, count: &[u8]| {
25557 f.run(&[
25558 b"FT.SEARCH",
25559 b"h",
25560 b"*=>[KNN $k @v $vec]",
25561 b"PARAMS",
25562 b"4",
25563 b"vec",
25564 ORIGIN,
25565 b"k",
25566 count,
25567 b"DIALECT",
25568 b"2",
25569 b"NOCONTENT",
25570 ])
25571 };
25572 let three = "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n";
25573 assert_eq!(ask(&mut f, b"3"), three);
25574 assert_eq!(ask(&mut f, b" 3"), three);
25575 assert_eq!(ask(&mut f, b"+3"), three);
25576 for bad in [
25577 &b"3.0"[..],
25578 b"0x3",
25579 b"-1",
25580 b"abc",
25581 b"",
25582 b"99999999999999999999",
25583 ] {
25584 let value = String::from_utf8_lossy(bad).into_owned();
25585 assert_eq!(
25586 ask(&mut f, bad),
25587 format!(
25588 "-SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value ({value}) \
25589 for parameter `k`\r\n"
25590 ),
25591 "{value}"
25592 );
25593 }
25594 assert_eq!(
25595 ask(&mut f, b"288230376151711777"),
25596 "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
25597 query KNN K parameter is too large, must not exceed 288230376151711744\r\n"
25598 );
25599 }
25600
25601 #[test]
25604 fn a_vector_the_wrong_size_is_refused_by_the_field_it_reached() {
25605 let mut f = Fixture::new();
25606 vectored(&mut f);
25607 assert_eq!(
25608 f.run(&[
25609 b"FT.SEARCH",
25610 b"h",
25611 b"*=>[KNN 5 @v $vec]",
25612 b"PARAMS",
25613 b"2",
25614 b"vec",
25615 b"abc",
25616 b"DIALECT",
25617 b"2",
25618 b"NOCONTENT",
25619 ]),
25620 "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
25621 query vector blob size (3) does not match index's expected size (8).\r\n"
25622 );
25623 }
25624
25625 #[test]
25630 fn a_vector_clause_yields_its_distance_under_the_name_it_was_given() {
25631 let mut f = Fixture::new();
25632 vectored(&mut f);
25633 let ask = |f: &mut Fixture, query: &str| {
25634 f.run(&[
25635 b"FT.SEARCH",
25636 b"h",
25637 query.as_bytes(),
25638 b"PARAMS",
25639 b"2",
25640 b"vec",
25641 ORIGIN,
25642 b"DIALECT",
25643 b"2",
25644 b"LIMIT",
25645 b"0",
25646 b"1",
25647 ])
25648 };
25649 assert_eq!(
25650 ask(&mut f, "*=>[KNN 3 @v $vec]"),
25651 "*3\r\n:3\r\n$2\r\nd3\r\n*6\r\n$9\r\n__v_score\r\n$1\r\n4\r\n$1\r\nt\r\n$4\r\nbeta\r\n$1\r\nv\r\n$8\r\n\0\0\0@\0\0\0\0\r\n"
25652 );
25653 assert_eq!(
25654 ask(&mut f, "*=>[KNN 3 @v $vec AS d]"),
25655 "*3\r\n:3\r\n$2\r\nd3\r\n*6\r\n$1\r\nd\r\n$1\r\n4\r\n$1\r\nt\r\n$4\r\nbeta\r\n$1\r\nv\r\n$8\r\n\0\0\0@\0\0\0\0\r\n"
25656 );
25657 assert_eq!(
25658 ask(&mut f, "@v:[VECTOR_RANGE 4 $vec]"),
25659 "*3\r\n:3\r\n$2\r\nd3\r\n*4\r\n$1\r\nt\r\n$4\r\nbeta\r\n$1\r\nv\r\n$8\r\n\0\0\0@\0\0\0\0\r\n"
25660 );
25661 assert_eq!(
25662 ask(&mut f, "@v:[VECTOR_RANGE 4 $vec]=>{$YIELD_DISTANCE_AS: d}"),
25663 "*3\r\n:3\r\n$2\r\nd3\r\n*6\r\n$1\r\nd\r\n$1\r\n4\r\n$1\r\nt\r\n$4\r\nbeta\r\n$1\r\nv\r\n$8\r\n\0\0\0@\0\0\0\0\r\n"
25664 );
25665 }
25666
25667 #[test]
25674 fn a_return_answers_the_distance_by_the_name_the_row_carries_it_under() {
25675 let mut f = Fixture::new();
25676 vectored(&mut f);
25677 let ask = |f: &mut Fixture, ret: &[&[u8]]| {
25678 let mut args: Vec<&[u8]> = vec![b"FT.SEARCH", b"h", b"*=>[KNN 1 @v $vec]"];
25679 args.extend_from_slice(ret);
25680 args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
25681 f.run(&args)
25682 };
25683 assert_eq!(
25684 ask(&mut f, &[b"RETURN", b"1", b"__v_score"]),
25685 "*3\r\n:1\r\n$2\r\nd5\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n0\r\n"
25686 );
25687 assert_eq!(
25688 ask(&mut f, &[b"RETURN", b"3", b"__v_score", b"AS", b"x"]),
25689 "*3\r\n:1\r\n$2\r\nd5\r\n*0\r\n"
25690 );
25691 assert_eq!(
25692 ask(&mut f, &[b"RETURN", b"3", b"t", b"AS", b"__v_score"]),
25693 "*3\r\n:1\r\n$2\r\nd5\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n0\r\n"
25694 );
25695 assert_eq!(
25696 ask(&mut f, &[b"RETURN", b"1", b"t"]),
25697 "*3\r\n:1\r\n$2\r\nd5\r\n*2\r\n$1\r\nt\r\n$4\r\nbeta\r\n"
25698 );
25699 assert_eq!(
25702 ask(&mut f, &[b"RETURN", b"2", b"t", b"__v_score"]),
25703 "*3\r\n:1\r\n$2\r\nd5\r\n*4\r\n$9\r\n__v_score\r\n$1\r\n0\r\n$1\r\nt\r\n$4\r\nbeta\r\n"
25704 );
25705 assert_eq!(ask(&mut f, &[b"NOCONTENT"]), "*2\r\n:1\r\n$2\r\nd5\r\n");
25706 }
25707
25708 #[test]
25712 fn a_sortby_can_name_a_distance_the_query_yielded() {
25713 let mut f = Fixture::new();
25714 vectored(&mut f);
25715 let ask = |f: &mut Fixture, query: &str, by: &[u8], desc: bool| {
25716 let mut args: Vec<&[u8]> = vec![b"FT.SEARCH", b"h", query.as_bytes(), b"SORTBY", by];
25717 if desc {
25718 args.push(b"DESC");
25719 }
25720 args.extend_from_slice(&[
25721 b"PARAMS",
25722 b"2",
25723 b"vec",
25724 ORIGIN,
25725 b"DIALECT",
25726 b"2",
25727 b"NOCONTENT",
25728 ]);
25729 f.run(&args)
25730 };
25731 assert_eq!(
25732 ask(&mut f, "*=>[KNN 3 @v $vec]", b"__v_score", false),
25733 "*4\r\n:3\r\n$2\r\nd5\r\n$2\r\nd4\r\n$2\r\nd3\r\n"
25734 );
25735 assert_eq!(
25736 ask(&mut f, "*=>[KNN 3 @v $vec]", b"__v_score", true),
25737 "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
25738 );
25739 assert_eq!(
25740 ask(&mut f, "*=>[KNN 3 @v $vec AS d]", b"d", false),
25741 "*4\r\n:3\r\n$2\r\nd5\r\n$2\r\nd4\r\n$2\r\nd3\r\n"
25742 );
25743 let missing = "-SEARCH_PROP_NOT_FOUND Property `__v_score` \
25746 not loaded nor in schema\r\n";
25747 assert_eq!(
25748 ask(&mut f, "*=>[KNN 3 @v $vec AS d]", b"__v_score", false),
25749 missing
25750 );
25751 assert_eq!(ask(&mut f, "alpha", b"__v_score", false), missing);
25752 assert_eq!(
25755 ask(&mut f, "foo(", b"zz", false),
25756 "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
25757 );
25758 }
25759
25760 #[test]
25762 fn two_vector_clauses_answer_two_distances() {
25763 let mut f = Fixture::new();
25764 vectored(&mut f);
25765 assert_eq!(
25766 f.run(&[
25767 b"FT.SEARCH",
25768 b"h",
25769 b"@v:[VECTOR_RANGE 9 $vec]=>{$YIELD_DISTANCE_AS: rr}=>[KNN 2 @v $vec]",
25770 b"RETURN",
25771 b"2",
25772 b"rr",
25773 b"__v_score",
25774 b"PARAMS",
25775 b"2",
25776 b"vec",
25777 ORIGIN,
25778 b"DIALECT",
25779 b"2",
25780 ]),
25781 "*5\r\n:2\r\n$2\r\nd4\r\n*4\r\n$9\r\n__v_score\r\n$1\r\n1\r\n$2\r\nrr\r\n$1\r\n1\r\n$2\r\nd5\r\n*4\r\n$9\r\n__v_score\r\n$1\r\n0\r\n$2\r\nrr\r\n$1\r\n0\r\n"
25782 );
25783 }
25784
25785 #[test]
25789 fn an_aggregation_answers_a_distance_nothing_asked_for() {
25790 let mut f = Fixture::new();
25791 vectored(&mut f);
25792 let ask = |f: &mut Fixture, query: &str, rest: &[&[u8]]| {
25793 let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h", query.as_bytes()];
25794 args.extend_from_slice(rest);
25795 args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
25796 f.run(&args)
25797 };
25798 assert_eq!(
25799 ask(&mut f, "*=>[KNN 2 @v $vec]", &[]),
25800 "*3\r\n:1\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n0\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n1\r\n"
25801 );
25802 assert_eq!(
25803 ask(&mut f, "*=>[KNN 2 @v $vec]", &[b"LOAD", b"1", b"@t"]),
25804 "*3\r\n:1\r\n*4\r\n$9\r\n__v_score\r\n$1\r\n0\r\n$1\r\nt\r\n$4\r\nbeta\r\n*4\r\n$9\r\n__v_score\r\n$1\r\n1\r\n$1\r\nt\r\n$5\r\nalpha\r\n"
25805 );
25806 assert_eq!(
25807 ask(&mut f, "*=>[KNN 2 @v $vec AS d]", &[]),
25808 "*3\r\n:1\r\n*2\r\n$1\r\nd\r\n$1\r\n0\r\n*2\r\n$1\r\nd\r\n$1\r\n1\r\n"
25809 );
25810 assert_eq!(
25812 ask(&mut f, "@v:[VECTOR_RANGE 1 $vec]", &[]),
25813 "*3\r\n:1\r\n*0\r\n*0\r\n"
25814 );
25815 assert_eq!(
25816 ask(
25817 &mut f,
25818 "@v:[VECTOR_RANGE 1 $vec]=>{$YIELD_DISTANCE_AS: rr}",
25819 &[]
25820 ),
25821 "*3\r\n:1\r\n*2\r\n$2\r\nrr\r\n$1\r\n1\r\n*2\r\n$2\r\nrr\r\n$1\r\n0\r\n"
25822 );
25823 }
25824
25825 #[test]
25829 fn an_aggregation_keeps_the_order_a_nearest_neighbour_clause_made() {
25830 let mut f = Fixture::new();
25831 vectored(&mut f);
25832 const MIDDLE: &[u8] = b"\x00\x00\x00\x40\x00\x00\x00\x00";
25834 let ask = |f: &mut Fixture, query: &str, vec: &[u8]| {
25835 f.run(&[
25836 b"FT.AGGREGATE",
25837 b"h",
25838 query.as_bytes(),
25839 b"LOAD",
25840 b"1",
25841 b"@t",
25842 b"PARAMS",
25843 b"2",
25844 b"vec",
25845 vec,
25846 b"DIALECT",
25847 b"2",
25848 ])
25849 };
25850 assert_eq!(
25851 ask(&mut f, "*=>[KNN 3 @v $vec]", MIDDLE),
25852 "*4\r\n:1\r\n*4\r\n$9\r\n__v_score\r\n$1\r\n0\r\n$1\r\nt\r\n$4\r\nbeta\r\n*4\r\n$9\r\n__v_score\r\n$1\r\n1\r\n$1\r\nt\r\n$5\r\nalpha\r\n*4\r\n$9\r\n__v_score\r\n$1\r\n1\r\n$1\r\nt\r\n$5\r\nalpha\r\n"
25853 );
25854 assert_eq!(
25856 ask(
25857 &mut f,
25858 "@v:[VECTOR_RANGE 1 $vec]=>{$YIELD_DISTANCE_AS: rr}",
25859 MIDDLE
25860 ),
25861 "*4\r\n:1\r\n*4\r\n$2\r\nrr\r\n$1\r\n1\r\n$1\r\nt\r\n$5\r\nalpha\r\n*4\r\n$2\r\nrr\r\n$1\r\n0\r\n$1\r\nt\r\n$4\r\nbeta\r\n*4\r\n$2\r\nrr\r\n$1\r\n1\r\n$1\r\nt\r\n$5\r\nalpha\r\n"
25862 );
25863 }
25864
25865 #[test]
25869 fn a_pipeline_step_can_name_a_distance_the_query_yielded() {
25870 let mut f = Fixture::new();
25871 vectored(&mut f);
25872 let ask = |f: &mut Fixture, query: &str, rest: &[&[u8]]| {
25873 let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h", query.as_bytes()];
25874 args.extend_from_slice(rest);
25875 args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
25876 f.run(&args)
25877 };
25878 let knn = "*=>[KNN 2 @v $vec]";
25879 assert_eq!(
25880 ask(&mut f, knn, &[b"APPLY", b"@__v_score * 2", b"AS", b"x"]),
25881 "*3\r\n:1\r\n*4\r\n$9\r\n__v_score\r\n$1\r\n0\r\n$1\r\nx\r\n$1\r\n0\r\n*4\r\n$9\r\n__v_score\r\n$1\r\n1\r\n$1\r\nx\r\n$1\r\n2\r\n"
25882 );
25883 assert_eq!(
25884 ask(&mut f, knn, &[b"FILTER", b"@__v_score > 0"]),
25885 "*2\r\n:1\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n1\r\n"
25886 );
25887 assert_eq!(
25888 ask(&mut f, knn, &[b"SORTBY", b"2", b"@__v_score", b"DESC"]),
25889 "*3\r\n:2\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n1\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n0\r\n"
25890 );
25891 assert_eq!(
25892 ask(
25893 &mut f,
25894 knn,
25895 &[
25896 b"GROUPBY",
25897 b"1",
25898 b"@t",
25899 b"REDUCE",
25900 b"MAX",
25901 b"1",
25902 b"@__v_score",
25903 b"AS",
25904 b"m"
25905 ]
25906 ),
25907 "*3\r\n:2\r\n*4\r\n$1\r\nt\r\n$4\r\nbeta\r\n$1\r\nm\r\n$1\r\n0\r\n*4\r\n$1\r\nt\r\n$5\r\nalpha\r\n$1\r\nm\r\n$1\r\n1\r\n"
25908 );
25909 assert_eq!(
25910 ask(&mut f, "*", &[b"APPLY", b"@__v_score", b"AS", b"x"]),
25911 "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: \
25912 `__v_score`\r\n"
25913 );
25914 assert_eq!(
25915 ask(&mut f, "*", &[b"GROUPBY", b"1", b"@__v_score"]),
25916 "-SEARCH_PROP_NOT_FOUND No such property `__v_score`\r\n"
25917 );
25918 assert_eq!(
25919 ask(&mut f, "*", &[b"SORTBY", b"2", b"@__v_score", b"ASC"]),
25920 "-SEARCH_PROP_NOT_FOUND Property `__v_score` not loaded nor in \
25921 schema\r\n"
25922 );
25923 }
25924
25925 #[test]
25933 fn the_words_come_before_the_query_and_the_query_before_the_pipeline() {
25934 let mut f = Fixture::new();
25935 vectored(&mut f);
25936 let ask = |f: &mut Fixture, rest: &[&[u8]]| {
25937 let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h"];
25938 args.extend_from_slice(rest);
25939 f.run(&args)
25940 };
25941 assert_eq!(
25942 ask(
25943 &mut f,
25944 &[b"foo(", b"APPLY", b"@zz", b"AS", b"x", b"LIMIT", b"x", b"1"]
25945 ),
25946 "-SEARCH_PARSE_ARGS LIMIT needs two numeric arguments\r\n"
25947 );
25948 assert_eq!(
25949 ask(&mut f, &[b"foo(", b"APPLY", b"@zz", b"AS", b"x"]),
25950 "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
25951 );
25952 assert_eq!(
25953 ask(&mut f, &[b"*", b"APPLY", b"@zz", b"AS", b"x"]),
25954 "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: `zz`\r\n"
25955 );
25956 assert_eq!(
25959 ask(&mut f, &[b"foo(", b"APPLY", b"@@@", b"AS", b"x"]),
25960 "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
25961 );
25962 assert_eq!(
25963 ask(
25964 &mut f,
25965 &[
25966 b"*", b"APPLY", b"@zz", b"AS", b"x", b"APPLY", b"@@@", b"AS", b"y"
25967 ]
25968 ),
25969 "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: `zz`\r\n"
25970 );
25971 assert_eq!(
25972 ask(&mut f, &[b"*", b"APPLY", b"@@@", b"AS", b"x"]),
25973 "-SEARCH_EXPR Syntax error at offset 0 near ''\r\n"
25974 );
25975 }
25976
25977 #[test]
25980 fn a_vector_step_says_which_way_it_was_answered() {
25981 let mut f = Fixture::new();
25982 vectored(&mut f);
25983 let tree = |f: &mut Fixture, query: &[u8]| {
25984 let reply = timeless(&f.run(&[
25985 b"FT.PROFILE",
25986 b"h",
25987 b"AGGREGATE",
25988 b"QUERY",
25989 query,
25990 b"PARAMS",
25991 b"2",
25992 b"vec",
25993 ORIGIN,
25994 b"DIALECT",
25995 b"2",
25996 ]));
25997 let at = reply.find("+Iterators profile").expect("a tree");
25998 let end = reply.find("+Result processors").expect("a list of steps");
25999 reply[at..end].to_string()
26000 };
26001 assert_eq!(
26002 tree(&mut f, b"*=>[KNN 3 @v $vec]"),
26003 "+Iterators profile\r\n*8\r\n+Type\r\n+VECTOR\r\n+Time\r\n<t>\r\n\
26004 +Number of reading operations\r\n:3\r\n\
26005 +Vector search mode\r\n+STANDARD_KNN\r\n"
26006 );
26007 assert_eq!(
26009 tree(&mut f, b"*=>[KNN 3 @v $vec AS d]"),
26010 tree(&mut f, b"*=>[KNN 3 @v $vec]")
26011 );
26012 assert_eq!(
26015 tree(&mut f, b"@v:[VECTOR_RANGE 9 $vec]"),
26016 "+Iterators profile\r\n*6\r\n+Type\r\n+ID-LIST-SORTED\r\n+Time\r\n<t>\r\n\
26017 +Number of reading operations\r\n:4\r\n"
26018 );
26019 assert_eq!(
26020 tree(
26021 &mut f,
26022 b"@v:[VECTOR_RANGE 9 $vec]=>{$YIELD_DISTANCE_AS: rr}"
26023 ),
26024 "+Iterators profile\r\n*8\r\n\
26025 +Type\r\n+METRIC SORTED BY ID - VECTOR DISTANCE\r\n+Time\r\n<t>\r\n\
26026 +Number of reading operations\r\n:4\r\n\
26027 +Vector search mode\r\n+RANGE_QUERY\r\n"
26028 );
26029 }
26030
26031 #[test]
26035 fn a_clause_in_front_of_a_vector_hangs_under_it_as_one_child() {
26036 let mut f = Fixture::new();
26037 vectored(&mut f);
26038 let ask = |f: &mut Fixture, query: &[u8]| {
26039 timeless(&f.run(&[
26040 b"FT.PROFILE",
26041 b"h",
26042 b"AGGREGATE",
26043 b"QUERY",
26044 query,
26045 b"PARAMS",
26046 b"2",
26047 b"vec",
26048 ORIGIN,
26049 b"DIALECT",
26050 b"2",
26051 ]))
26052 };
26053 let cut = |reply: &str| {
26054 let at = reply.find("+Iterators profile").expect("a tree");
26055 reply[at..].to_string()
26056 };
26057 assert_eq!(
26058 cut(&ask(&mut f, b"@t:alpha=>[KNN 3 @v $vec]")),
26059 "+Iterators profile\r\n*10\r\n+Type\r\n+VECTOR\r\n+Time\r\n<t>\r\n\
26060 +Number of reading operations\r\n:3\r\n\
26061 +Vector search mode\r\n+HYBRID_ADHOC_BF\r\n+Child iterator\r\n\
26062 *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n+Time\r\n<t>\r\n\
26063 +Number of reading operations\r\n:3\r\n\
26064 +Estimated number of matches\r\n:3\r\n\
26065 +Result processors profile\r\n*2\r\n\
26066 *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:3\r\n\
26067 *6\r\n+Type\r\n+Metrics Applier\r\n+Time\r\n<t>\r\n\
26068 +Results processed\r\n:3\r\n+Coordinator\r\n*0\r\n"
26069 );
26070 assert!(ask(&mut f, b"@v:[VECTOR_RANGE 9 $vec]").ends_with(
26073 "+Result processors profile\r\n*1\r\n*6\r\n+Type\r\n+Index\r\n\
26074 +Time\r\n<t>\r\n+Results processed\r\n:4\r\n+Coordinator\r\n*0\r\n"
26075 ));
26076 assert!(ask(&mut f, b"*=>[KNN 3 @v $vec]").contains("+Type\r\n+Metrics Applier\r\n"));
26079 }
26080
26081 #[test]
26085 fn a_window_of_nothing_on_an_aggregation_counts_rather_than_pages() {
26086 let mut f = profiling();
26087 let steps = |f: &mut Fixture, words: &[&[u8]]| {
26088 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"AGGREGATE", b"QUERY", b"*"];
26089 argv.extend_from_slice(words);
26090 let reply = timeless(&f.run(&argv));
26091 let at = reply.find("+Result processors").expect("a list of steps");
26092 reply[at..].to_string()
26093 };
26094 assert_eq!(
26095 steps(&mut f, &[b"LIMIT", b"0", b"0"]),
26096 "+Result processors profile\r\n*2\r\n\
26097 *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:3\r\n\
26098 *6\r\n+Type\r\n+Counter\r\n+Time\r\n<t>\r\n+Results processed\r\n:1\r\n\
26099 +Coordinator\r\n*0\r\n"
26100 );
26101 assert!(
26102 steps(
26103 &mut f,
26104 &[b"SORTBY", b"2", b"@n", b"ASC", b"LIMIT", b"0", b"0"]
26105 )
26106 .contains("+Type\r\n+Counter\r\n")
26107 );
26108 assert!(steps(&mut f, &[b"LIMIT", b"0", b"2"]).contains(
26110 "+Type\r\n+Pager/Limiter\r\n+Time\r\n<t>\r\n\
26111 +Results processed\r\n:2\r\n"
26112 ));
26113 }
26114
26115 #[test]
26121 fn a_spellcheck_scores_a_suggestion_by_how_common_it_is() {
26122 let mut f = Fixture::new();
26123 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
26124 f.run(&[b"HSET", b"d1", b"t", b"hello"]);
26125 f.run(&[b"HSET", b"d2", b"t", b"hallo hello"]);
26126 assert_eq!(
26127 f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"2"]),
26128 "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
26129 *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"
26130 );
26131 }
26132
26133 #[test]
26137 fn a_spellcheck_answers_a_map_of_maps_on_resp3() {
26138 let mut f = Fixture::new();
26139 f.run(&[b"HELLO", b"3"]);
26140 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
26141 f.run(&[b"HSET", b"d1", b"t", b"hello"]);
26142 assert_eq!(
26143 f.run(&[b"FT.SPELLCHECK", b"e", b"hellp"]),
26144 "%1\r\n$7\r\nresults\r\n%1\r\n$5\r\nhellp\r\n\
26145 *1\r\n%1\r\n$5\r\nhello\r\n,1\r\n"
26146 );
26147 }
26148
26149 #[test]
26153 fn a_word_the_index_holds_is_never_asked_about_whatever_field_it_names() {
26154 let mut f = Fixture::new();
26155 f.run(&[
26156 b"FT.CREATE",
26157 b"e",
26158 b"SCHEMA",
26159 b"a",
26160 b"TEXT",
26161 b"NOSTEM",
26162 b"b",
26163 b"TEXT",
26164 b"NOSTEM",
26165 ]);
26166 f.run(&[b"HSET", b"d1", b"b", b"world"]);
26167 assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"@a:world"]), "*0\r\n");
26168 assert_eq!(
26169 f.run(&[b"FT.SPELLCHECK", b"e", b"@a:worlt"]),
26170 "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nworlt\r\n*0\r\n"
26171 );
26172 }
26173
26174 #[test]
26178 fn a_spellcheck_reads_the_dictionaries_it_is_pointed_at() {
26179 let mut f = Fixture::new();
26180 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
26181 f.run(&[b"FT.DICTADD", b"d", b"Hellp", b"hellq"]);
26182 assert_eq!(
26183 f.run(&[b"FT.SPELLCHECK", b"e", b"hellz", b"TERMS", b"INCLUDE", b"d"]),
26184 "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellz\r\n\
26185 *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"
26186 );
26187 assert_eq!(
26188 f.run(&[b"FT.SPELLCHECK", b"e", b"hellq", b"TERMS", b"EXCLUDE", b"d"]),
26189 "*0\r\n"
26190 );
26191 assert_eq!(
26192 f.run(&[b"FT.SPELLCHECK", b"e", b"x", b"TERMS", b"INCLUDE", b"nope"]),
26193 "-Dict does not exist: nope\r\n"
26194 );
26195 }
26196
26197 #[test]
26201 fn a_spellcheck_reads_its_arguments_leniently() {
26202 let mut f = Fixture::new();
26203 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
26204 f.run(&[b"HSET", b"d1", b"t", b"hello"]);
26205 let one = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
26206 *1\r\n*2\r\n$1\r\n1\r\n$5\r\nhello\r\n";
26207 assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"BOGUS"]), one);
26208 let none = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhelqp\r\n*0\r\n";
26209 let args: &[&[u8]] = &[
26210 b"FT.SPELLCHECK",
26211 b"e",
26212 b"helqp",
26213 b"DISTANCE",
26214 b"1",
26215 b"DISTANCE",
26216 b"4",
26217 ];
26218 assert_eq!(f.run(args), none);
26219 assert_eq!(
26220 f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"5"]),
26221 "-bad distance given, distance must be a natural number between 1 to 4\r\n"
26222 );
26223 assert_eq!(
26224 f.run(&[b"FT.SPELLCHECK", b"nope", b"hellp"]),
26225 "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
26226 );
26227 }
26228
26229 #[test]
26234 fn an_add_answers_how_many_suggestions_are_in_there_now() {
26235 let mut f = Fixture::new();
26236 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]), ":1\r\n");
26237 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"9"]), ":1\r\n");
26238 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]), ":2\r\n");
26239 assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":2\r\n");
26240 assert_eq!(f.run(&[b"FT.SUGLEN", b"nokey"]), ":0\r\n");
26241 }
26242
26243 #[test]
26246 fn a_suggestion_dictionary_is_a_key_with_a_type_of_its_own() {
26247 let mut f = Fixture::new();
26248 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
26249 assert_eq!(f.run(&[b"TYPE", b"s"]), "+trietype0\r\n");
26250 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$3\r\nraw\r\n");
26251 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
26252 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\ns\r\n");
26253 assert_eq!(f.run(&[b"EXPIRE", b"s", b"100"]), ":1\r\n");
26254 assert_eq!(f.run(&[b"TTL", b"s"]), ":100\r\n");
26255 assert_eq!(f.run(&[b"DEL", b"s"]), ":1\r\n");
26256 assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":0\r\n");
26257 }
26258
26259 #[test]
26262 fn deleting_the_last_suggestion_deletes_the_key() {
26263 let mut f = Fixture::new();
26264 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
26265 assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"nope"]), ":0\r\n");
26266 assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"one"]), ":1\r\n");
26267 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
26268 assert_eq!(f.run(&[b"FT.SUGDEL", b"nokey", b"a"]), ":0\r\n");
26269 }
26270
26271 #[test]
26274 fn a_suggestion_command_on_another_kind_of_key_is_wrongtype() {
26275 let mut f = Fixture::new();
26276 f.run(&[b"SET", b"s", b"x"]);
26277 for cmd in [
26278 vec![&b"FT.SUGADD"[..], b"s", b"t", b"1"],
26279 vec![&b"FT.SUGGET"[..], b"s", b"t"],
26280 vec![&b"FT.SUGDEL"[..], b"s", b"t"],
26281 vec![&b"FT.SUGLEN"[..], b"s"],
26282 ] {
26283 assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{cmd:?}");
26284 }
26285 assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nx\r\n");
26286 }
26287
26288 #[test]
26291 fn a_lookup_answers_a_score_it_works_out_rather_than_the_one_stored() {
26292 let mut f = Fixture::new();
26293 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
26294 f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
26295 f.run(&[b"FT.SUGADD", b"s", b"ontario", b"3"]);
26296 assert_eq!(
26297 f.run(&[b"FT.SUGGET", b"s", b"on", b"WITHSCORES"]),
26298 "*6\r\n$7\r\nontario\r\n$18\r\n1.2247449159622192\r\n\
26299 $4\r\nonly\r\n$17\r\n1.154700517654419\r\n\
26300 $3\r\none\r\n$18\r\n0.7071067690849304\r\n"
26301 );
26302 assert_eq!(
26303 f.run(&[b"FT.SUGGET", b"s", b"one", b"WITHSCORES"]),
26304 "*2\r\n$3\r\none\r\n$10\r\n2147483648\r\n"
26305 );
26306 assert_eq!(f.run(&[b"FT.SUGGET", b"nokey", b"a"]), "*0\r\n");
26307 }
26308
26309 #[test]
26311 fn fuzzy_allows_one_edit_and_nothing_allows_two() {
26312 let mut f = Fixture::new();
26313 f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
26314 assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"one"]), "*0\r\n");
26315 assert_eq!(
26316 f.run(&[b"FT.SUGGET", b"s", b"one", b"FUZZY", b"WITHSCORES"]),
26317 "*2\r\n$4\r\nonly\r\n$19\r\n0.19139298796653748\r\n"
26318 );
26319 assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"xyz", b"FUZZY"]), "*0\r\n");
26320 }
26321
26322 #[test]
26324 fn a_lookup_answers_five_unless_it_is_told_otherwise() {
26325 let mut f = Fixture::new();
26326 for (term, score) in [
26327 (&b"a1"[..], &b"1"[..]),
26328 (b"a2", b"2"),
26329 (b"a3", b"3"),
26330 (b"a4", b"4"),
26331 (b"a5", b"5"),
26332 (b"a6", b"6"),
26333 ] {
26334 f.run(&[b"FT.SUGADD", b"s", term, score]);
26335 }
26336 assert_eq!(
26337 f.run(&[b"FT.SUGGET", b"s", b"a"]),
26338 "*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"
26339 );
26340 assert_eq!(
26341 f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"2"]),
26342 "*2\r\n$2\r\na6\r\n$2\r\na5\r\n"
26343 );
26344 assert!(
26346 f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"100"])
26347 .starts_with("*6\r\n")
26348 );
26349 }
26350
26351 #[test]
26354 fn a_payload_comes_back_beside_the_term_or_a_null_does() {
26355 let mut f = Fixture::new();
26356 f.run(&[b"FT.SUGADD", b"s", b"one", b"1", b"PAYLOAD", b"p"]);
26357 assert_eq!(
26358 f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
26359 "*2\r\n$3\r\none\r\n$1\r\np\r\n"
26360 );
26361 f.run(&[b"FT.SUGADD", b"s", b"one", b"2"]);
26362 assert_eq!(
26363 f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
26364 "*2\r\n$3\r\none\r\n$1\r\np\r\n"
26365 );
26366 f.run(&[b"FT.SUGADD", b"s", b"one", b"2", b"PAYLOAD", b""]);
26369 assert_eq!(
26370 f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
26371 "*2\r\n$3\r\none\r\n$1\r\np\r\n"
26372 );
26373 f.run(&[b"FT.SUGADD", b"s", b"other", b"1", b"PAYLOAD", b""]);
26375 assert_eq!(
26376 f.run(&[b"FT.SUGGET", b"s", b"ot", b"WITHPAYLOADS"]),
26377 "*2\r\n$5\r\nother\r\n$-1\r\n"
26378 );
26379 }
26380
26381 #[test]
26385 fn incr_adds_to_the_score_that_is_already_there() {
26386 let mut f = Fixture::new();
26387 for _ in 0..3 {
26388 f.run(&[b"FT.SUGADD", b"s", b"xxx", b"0.1", b"INCR"]);
26389 }
26390 assert_eq!(
26391 f.run(&[b"FT.SUGGET", b"s", b"xx", b"WITHSCORES"]),
26392 "*2\r\n$3\r\nxxx\r\n$18\r\n0.2121320366859436\r\n"
26393 );
26394 }
26395
26396 #[test]
26398 fn the_suggestion_errors_are_the_lines_the_module_sends() {
26399 let mut f = Fixture::new();
26400 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
26401 assert_eq!(
26402 f.run(&[b"FT.SUGADD", b"s", b"t", b"abc"]),
26403 "-ERR invalid score\r\n"
26404 );
26405 assert_eq!(
26407 f.run(&[b"FT.SUGADD", b"s", b"t", b"abc", b"NOPE"]),
26408 "-Unknown argument `NOPE`\r\n"
26409 );
26410 assert_eq!(
26411 f.run(&[b"FT.SUGADD", b"s", b"t", b"1", b"PAYLOAD"]),
26412 "-Invalid payload: Expected an argument, but none provided\r\n"
26413 );
26414 assert!(
26416 f.run(&[
26417 b"FT.SUGADD",
26418 b"s",
26419 b"t",
26420 b"1",
26421 b"PAYLOAD",
26422 b"a",
26423 b"PAYLOAD",
26424 b"b"
26425 ])
26426 .contains("wrong number of arguments")
26427 );
26428 assert_eq!(
26429 f.run(&[b"FT.SUGGET", b"s", b"o", b"NOPE"]),
26430 "-SEARCH_PARSE_ARGS Unrecognized argument: NOPE\r\n"
26431 );
26432 for max in [&b"0"[..], b"-1", b"4294967296", b"1e10", b"inf"] {
26436 assert_eq!(
26437 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
26438 "-SEARCH_PARSE_ARGS MAX: Value is outside acceptable bounds\r\n",
26439 "{}",
26440 String::from_utf8_lossy(max)
26441 );
26442 }
26443 for max in [
26444 &b"abc"[..],
26445 b"0.0",
26446 b"00",
26447 b"-0",
26448 b"+0",
26449 b"0.5",
26450 b"-1.5",
26451 b"1e400",
26452 ] {
26453 assert_eq!(
26454 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
26455 "-SEARCH_PARSE_ARGS MAX: Could not convert argument to expected type\r\n",
26456 "{}",
26457 String::from_utf8_lossy(max)
26458 );
26459 }
26460 for max in [&b"01"[..], b"+1", b"1.5", b"0x10", b"1e2"] {
26461 assert_eq!(
26462 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
26463 "*1\r\n$3\r\none\r\n",
26464 "{}",
26465 String::from_utf8_lossy(max)
26466 );
26467 }
26468 assert_eq!(
26469 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX"]),
26470 "-SEARCH_PARSE_ARGS MAX: Expected an argument, but none provided\r\n"
26471 );
26472 assert_eq!(
26475 f.run(&[b"FT.SUGADD", b"s", b"t", b"1e400"]),
26476 "-ERR invalid score\r\n"
26477 );
26478 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"t", b"inf"]), ":2\r\n");
26479 }
26480
26481 #[test]
26485 fn an_empty_suggestion_is_taken_and_dropped_but_still_makes_the_key() {
26486 let mut f = Fixture::new();
26487 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
26488 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"", b"1"]), ":1\r\n");
26489 assert_eq!(f.run(&[b"FT.SUGGET", b"s", b""]), "*1\r\n$3\r\none\r\n");
26490 assert_eq!(f.run(&[b"FT.SUGADD", b"e", b"", b"1"]), ":0\r\n");
26491 assert_eq!(f.run(&[b"EXISTS", b"e"]), ":1\r\n");
26492 assert_eq!(f.run(&[b"TYPE", b"e"]), "+trietype0\r\n");
26493 assert_eq!(f.run(&[b"FT.SUGDEL", b"e", b"nothing"]), ":0\r\n");
26494 assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
26495 }
26496
26497 #[test]
26500 fn a_hash_that_will_not_read_is_counted_where_ft_info_reports_it() {
26501 let mut f = Fixture::new();
26502 f.run(&[
26503 b"FT.CREATE",
26504 b"ix",
26505 b"PREFIX",
26506 b"1",
26507 b"p:",
26508 b"SCHEMA",
26509 b"n",
26510 b"NUMERIC",
26511 ]);
26512 f.run(&[b"HSET", b"p:1", b"n", b"notanumber"]);
26513 assert_eq!(held(&f, b"ix"), (0, 0));
26514
26515 let reply = f.run(&[b"FT.INFO", b"ix"]);
26516 assert!(
26517 reply.contains("SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value: 'notanumber'"),
26518 "{reply}"
26519 );
26520 assert!(reply.contains("hash_indexing_failures"), "{reply}");
26521 }
26522
26523 #[test]
26526 fn an_index_can_only_be_made_on_database_zero() {
26527 let mut f = Fixture::new();
26528 f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]);
26529 f.run(&[b"SELECT", b"1"]);
26530 let refused = "-Cannot create index on db != 0\r\n";
26531 assert_eq!(
26532 f.run(&[b"FT.CREATE", b"jx", b"SCHEMA", b"t", b"TEXT"]),
26533 refused
26534 );
26535 assert_eq!(
26537 f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]),
26538 refused
26539 );
26540 assert_eq!(
26542 f.run(&[b"FT.CREATE", b"zz", b"BOGUS", b"SCHEMA", b"t", b"TEXT"]),
26543 refused
26544 );
26545 assert_eq!(
26547 f.run(&[b"FT._CREATEIFNX", b"ix", b"SCHEMA", b"t", b"TEXT"]),
26548 "+OK\r\n"
26549 );
26550 assert_eq!(f.server.search.lock().len(), 1);
26551 }
26552
26553 #[test]
26559 fn the_scan_is_one_database_and_the_following_is_all_of_them() {
26560 let mut f = Fixture::new();
26561 f.run(&[b"SELECT", b"1"]);
26562 f.run(&[b"HSET", b"p:9", b"t", b"on one"]);
26563 f.run(&[b"SELECT", b"0"]);
26564 f.run(&[b"HSET", b"p:0", b"t", b"on zero"]);
26565 f.run(&[
26566 b"FT.CREATE",
26567 b"ix",
26568 b"PREFIX",
26569 b"1",
26570 b"p:",
26571 b"SCHEMA",
26572 b"t",
26573 b"TEXT",
26574 ]);
26575 assert_eq!(held(&f, b"ix"), (1, 1), "the scan read database zero only");
26576
26577 f.run(&[b"SELECT", b"1"]);
26578 f.run(&[b"HSET", b"p:8", b"t", b"later"]);
26579 assert_eq!(
26580 held(&f, b"ix"),
26581 (2, 2),
26582 "and then it follows every database"
26583 );
26584 }
26585
26586 fn corpus(f: &mut Fixture) {
26589 f.run(&[
26590 b"FT.CREATE",
26591 b"sx",
26592 b"PREFIX",
26593 b"1",
26594 b"d:",
26595 b"SCHEMA",
26596 b"t",
26597 b"TEXT",
26598 b"g",
26599 b"TAG",
26600 b"n",
26601 b"NUMERIC",
26602 ]);
26603 for (key, text, tag, number) in [
26604 (b"d:1".as_slice(), "alpha beta", "aa,bb", "1"),
26605 (b"d:2", "alpha gamma", "bb", "2"),
26606 (b"d:3", "delta", "cc", "3"),
26607 (b"d:4", "alpha beta gamma", "aa,cc", "4"),
26608 ] {
26609 f.run(&[
26610 b"HSET",
26611 key,
26612 b"t",
26613 text.as_bytes(),
26614 b"g",
26615 tag.as_bytes(),
26616 b"n",
26617 number.as_bytes(),
26618 ]);
26619 }
26620 }
26621
26622 fn sortable(f: &mut Fixture) {
26626 f.run(&[
26627 b"FT.CREATE",
26628 b"sy",
26629 b"PREFIX",
26630 b"1",
26631 b"s:",
26632 b"SCHEMA",
26633 b"t",
26634 b"TEXT",
26635 b"SORTABLE",
26636 b"n",
26637 b"NUMERIC",
26638 b"SORTABLE",
26639 b"body",
26640 b"AS",
26641 b"b",
26642 b"TEXT",
26643 b"SORTABLE",
26644 b"p",
26645 b"TEXT",
26646 ]);
26647 for (key, text, number) in [
26648 (b"s:1".as_slice(), "Banana Split", "2"),
26649 (b"s:2", "apple", "10"),
26650 ] {
26651 f.run(&[
26652 b"HSET",
26653 key,
26654 b"t",
26655 text.as_bytes(),
26656 b"n",
26657 number.as_bytes(),
26658 b"body",
26659 text.as_bytes(),
26660 b"p",
26661 b"alpha",
26662 ]);
26663 }
26664 f.run(&[b"HSET", b"s:3", b"p", b"alpha"]);
26667 }
26668
26669 #[test]
26672 fn a_search_sorts_by_a_field_the_index_keeps_a_copy_of() {
26673 let mut f = Fixture::new();
26674 sortable(&mut f);
26675 assert_eq!(
26676 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"NOCONTENT"]),
26677 "*4\r\n:3\r\n$3\r\ns:1\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
26678 );
26679 assert_eq!(
26680 f.run(&[
26681 b"FT.SEARCH",
26682 b"sy",
26683 b"alpha",
26684 b"SORTBY",
26685 b"n",
26686 b"DESC",
26687 b"NOCONTENT"
26688 ]),
26689 "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
26690 );
26691 assert_eq!(
26694 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"t", b"NOCONTENT"]),
26695 "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
26696 );
26697 }
26698
26699 #[test]
26702 fn a_search_sorts_by_a_field_it_has_to_read_the_key_for() {
26703 let mut f = Fixture::new();
26704 sortable(&mut f);
26705 f.run(&[b"HSET", b"s:1", b"p", b"alpha zulu"]);
26706 assert_eq!(
26707 f.run(&[
26708 b"FT.SEARCH",
26709 b"sy",
26710 b"alpha",
26711 b"SORTBY",
26712 b"p",
26713 b"NOCONTENT",
26714 b"LIMIT",
26715 b"0",
26716 b"2"
26717 ]),
26718 "*3\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
26719 );
26720 assert_eq!(
26723 f.run(&[
26724 b"FT.SEARCH",
26725 b"sy",
26726 b"alpha",
26727 b"SORTBY",
26728 b"p",
26729 b"WITHSORTKEYS",
26730 b"NOCONTENT",
26731 b"LIMIT",
26732 b"2",
26733 b"1"
26734 ]),
26735 "*3\r\n:3\r\n$3\r\ns:1\r\n$11\r\n$alpha zulu\r\n"
26736 );
26737 }
26738
26739 #[test]
26742 fn a_search_can_send_the_value_it_sorted_by_back() {
26743 let mut f = Fixture::new();
26744 sortable(&mut f);
26745 assert_eq!(
26746 f.run(&[
26747 b"FT.SEARCH",
26748 b"sy",
26749 b"alpha",
26750 b"SORTBY",
26751 b"n",
26752 b"WITHSORTKEYS",
26753 b"NOCONTENT"
26754 ]),
26755 concat!(
26756 "*7\r\n:3\r\n",
26757 "$3\r\ns:1\r\n$2\r\n#2\r\n",
26758 "$3\r\ns:2\r\n$3\r\n#10\r\n",
26759 "$3\r\ns:3\r\n$-1\r\n"
26760 )
26761 );
26762 assert_eq!(
26763 f.run(&[
26764 b"FT.SEARCH",
26765 b"sy",
26766 b"alpha",
26767 b"SORTBY",
26768 b"t",
26769 b"WITHSORTKEYS",
26770 b"NOCONTENT"
26771 ]),
26772 concat!(
26773 "*7\r\n:3\r\n",
26774 "$3\r\ns:2\r\n$6\r\n$apple\r\n",
26775 "$3\r\ns:1\r\n$13\r\n$banana split\r\n",
26776 "$3\r\ns:3\r\n$-1\r\n"
26777 )
26778 );
26779 assert_eq!(
26782 f.run(&[
26783 b"FT.SEARCH",
26784 b"sy",
26785 b"banana",
26786 b"WITHSORTKEYS",
26787 b"NOCONTENT"
26788 ]),
26789 "*3\r\n:1\r\n$3\r\ns:1\r\n$-1\r\n"
26790 );
26791 }
26792
26793 #[test]
26796 fn a_sort_puts_the_field_it_sorted_by_in_front_of_the_row() {
26797 let mut f = Fixture::new();
26798 sortable(&mut f);
26799 assert_eq!(
26803 f.run(&[
26804 b"FT.SEARCH",
26805 b"sy",
26806 b"alpha",
26807 b"SORTBY",
26808 b"b",
26809 b"LIMIT",
26810 b"0",
26811 b"1"
26812 ]),
26813 concat!(
26814 "*3\r\n:3\r\n$3\r\ns:2\r\n*10\r\n",
26815 "$1\r\nb\r\n$5\r\napple\r\n",
26816 "$1\r\nt\r\n$5\r\napple\r\n",
26817 "$1\r\nn\r\n$2\r\n10\r\n",
26818 "$4\r\nbody\r\n$5\r\napple\r\n",
26819 "$1\r\np\r\n$5\r\nalpha\r\n"
26820 )
26821 );
26822 assert_eq!(
26825 f.run(&[
26826 b"FT.SEARCH",
26827 b"sy",
26828 b"alpha",
26829 b"SORTBY",
26830 b"b",
26831 b"RETURN",
26832 b"2",
26833 b"p",
26834 b"b",
26835 b"LIMIT",
26836 b"0",
26837 b"1"
26838 ]),
26839 concat!(
26840 "*3\r\n:3\r\n$3\r\ns:2\r\n*4\r\n",
26841 "$1\r\nb\r\n$5\r\napple\r\n",
26842 "$1\r\np\r\n$5\r\nalpha\r\n"
26843 )
26844 );
26845 }
26846
26847 #[test]
26849 fn a_search_refuses_the_sorts_it_cannot_run() {
26850 let mut f = Fixture::new();
26851 sortable(&mut f);
26852 assert_eq!(
26853 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY"]),
26854 "-SEARCH_PARSE_ARGS Bad SORTBY arguments\r\n"
26855 );
26856 assert_eq!(
26857 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"SORTBY"]),
26858 "-SEARCH_PARSE_ARGS Multiple SORTBY steps are not allowed\r\n"
26859 );
26860 assert_eq!(
26861 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"MAX", b"2"]),
26862 "-SEARCH_PARSE_ARGS SORTBY MAX is not supported by FT.SEARCH\r\n"
26863 );
26864 assert_eq!(
26865 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz"]),
26866 "-SEARCH_PROP_NOT_FOUND Property `zz` not loaded nor in schema\r\n"
26867 );
26868 assert_eq!(
26871 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz", b"NOPE"]),
26872 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `NOPE` at position 3 for <main>\r\n"
26873 );
26874 }
26875
26876 fn marking(f: &mut Fixture) {
26880 f.run(&[
26881 b"FT.CREATE",
26882 b"mk",
26883 b"ON",
26884 b"HASH",
26885 b"PREFIX",
26886 b"1",
26887 b"m:",
26888 b"SCHEMA",
26889 b"a",
26890 b"TEXT",
26891 b"b",
26892 b"TEXT",
26893 b"n",
26894 b"NUMERIC",
26895 b"g",
26896 b"TAG",
26897 ]);
26898 f.run(&[
26899 b"HSET",
26900 b"m:1",
26901 b"a",
26902 b"c1 c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2 e3",
26903 b"b",
26904 b"t1 t2 t3 t4 t5 t6 t7 t8",
26905 b"n",
26906 b"1",
26907 b"g",
26908 b"red",
26909 ]);
26910 }
26911
26912 #[test]
26915 fn a_summarize_cuts_a_field_down_to_what_matched() {
26916 let mut f = Fixture::new();
26917 marking(&mut f);
26918 let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"SUMMARIZE", b"LEN", b"2"]);
26919 assert!(got.contains("c3 fox d1 d2... d9 fox e1 e2... "), "{got}");
26920 assert!(got.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{got}");
26922 assert!(got.contains("$1\r\nr\r\n"), "{got}");
26924 }
26925
26926 #[test]
26930 fn a_dropped_fragment_stops_bounding_the_one_that_was_kept() {
26931 let mut f = Fixture::new();
26932 marking(&mut f);
26933 let got = f.run(&[
26934 b"FT.SEARCH",
26935 b"mk",
26936 b"fox",
26937 b"SUMMARIZE",
26938 b"FRAGS",
26939 b"1",
26940 b"LEN",
26941 b"20",
26942 ]);
26943 assert!(
26944 got.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2... "),
26945 "{got}"
26946 );
26947 let two = f.run(&[
26950 b"FT.SEARCH",
26951 b"mk",
26952 b"fox",
26953 b"SUMMARIZE",
26954 b"FRAGS",
26955 b"2",
26956 b"LEN",
26957 b"20",
26958 ]);
26959 assert!(
26960 two.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9... d1"),
26961 "{two}"
26962 );
26963 }
26964
26965 #[test]
26968 fn a_highlight_marks_the_matches_and_leaves_the_rest_of_the_field_alone() {
26969 let mut f = Fixture::new();
26970 marking(&mut f);
26971 let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"HIGHLIGHT"]);
26972 assert!(got.contains("<b>fox</b> d1 d2"), "{got}");
26973 let both = f.run(&[
26974 b"FT.SEARCH",
26975 b"mk",
26976 b"fox",
26977 b"SUMMARIZE",
26978 b"LEN",
26979 b"2",
26980 b"HIGHLIGHT",
26981 ]);
26982 assert!(both.contains("c3 <b>fox</b> d1 d2... "), "{both}");
26983 assert!(both.contains("t1 t2 t3 t4 t5 t6 t7 t8\r\n"), "{both}");
26985 assert!(both.contains("$3\r\nred\r\n"), "{both}");
26986 let split = f.run(&[
26988 b"FT.SEARCH",
26989 b"mk",
26990 b"fox",
26991 b"SUMMARIZE",
26992 b"FIELDS",
26993 b"1",
26994 b"b",
26995 b"LEN",
26996 b"2",
26997 b"HIGHLIGHT",
26998 b"FIELDS",
26999 b"1",
27000 b"a",
27001 ]);
27002 assert!(split.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{split}");
27003 }
27004
27005 #[test]
27007 fn a_highlight_does_not_mark_a_tag() {
27008 let mut f = Fixture::new();
27009 marking(&mut f);
27010 f.run(&[b"HSET", b"m:1", b"b", b"red and blue"]);
27011 let got = f.run(&[b"FT.SEARCH", b"mk", b"@g:{red}", b"HIGHLIGHT"]);
27012 assert!(!got.contains("<b>"), "{got}");
27013 assert!(got.contains("red and blue"), "{got}");
27014 }
27015
27016 #[test]
27019 fn a_search_answers_a_total_and_then_the_rows() {
27020 let mut f = Fixture::new();
27021 corpus(&mut f);
27022 assert_eq!(
27023 f.run(&[b"FT.SEARCH", b"sx", b"delta"]),
27024 "*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"
27025 );
27026 f.run(&[b"HSET", b"d:3", b"extra", b"more"]);
27029 assert!(f.run(&[b"FT.SEARCH", b"sx", b"delta"]).contains("extra"));
27030 assert_eq!(
27033 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
27034 "*2\r\n:1\r\n$3\r\nd:3\r\n"
27035 );
27036 assert_eq!(
27037 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
27038 "*1\r\n:3\r\n"
27039 );
27040 }
27041
27042 #[test]
27045 fn the_window_is_ten_rows_and_a_million_wide_at_most() {
27046 let mut f = Fixture::new();
27047 corpus(&mut f);
27048 assert_eq!(
27049 f.run(&[
27050 b"FT.SEARCH",
27051 b"sx",
27052 b"alpha",
27053 b"NOCONTENT",
27054 b"LIMIT",
27055 b"1",
27056 b"1"
27057 ]),
27058 "*2\r\n:3\r\n$3\r\nd:2\r\n"
27059 );
27060 assert_eq!(
27061 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0"]),
27062 "-SEARCH_PARSE_ARGS LIMIT requires two arguments\r\n"
27063 );
27064 assert_eq!(
27065 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"-1"]),
27066 "-SEARCH_PARSE_ARGS LIMIT needs two numeric arguments\r\n"
27067 );
27068 assert_eq!(
27069 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"1000001"]),
27070 "-SEARCH_LIMIT_OVER LIMIT exceeds maximum of 1000000\r\n"
27071 );
27072 assert_eq!(
27073 f.run(&[
27074 b"FT.SEARCH",
27075 b"sx",
27076 b"alpha",
27077 b"NOCONTENT",
27078 b"LIMIT",
27079 b"999999",
27080 b"1000000"
27081 ]),
27082 "*1\r\n:3\r\n"
27083 );
27084 }
27085
27086 #[test]
27090 fn a_return_of_nothing_is_not_the_same_as_nocontent() {
27091 let mut f = Fixture::new();
27092 corpus(&mut f);
27093 let bare = "*2\r\n:1\r\n$3\r\nd:3\r\n";
27094 assert_eq!(
27095 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"0"]),
27096 bare
27097 );
27098 assert_eq!(
27099 f.run(&[
27100 b"FT.SEARCH",
27101 b"sx",
27102 b"delta",
27103 b"NOCONTENT",
27104 b"RETURN",
27105 b"1",
27106 b"t"
27107 ]),
27108 bare
27109 );
27110 assert_eq!(
27111 f.run(&[
27112 b"FT.SEARCH",
27113 b"sx",
27114 b"delta",
27115 b"RETURN",
27116 b"0",
27117 b"RETURN",
27118 b"1",
27119 b"t"
27120 ]),
27121 "*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"
27122 );
27123 }
27124
27125 #[test]
27128 fn the_count_after_return_counts_words() {
27129 let mut f = Fixture::new();
27130 corpus(&mut f);
27131 assert_eq!(
27134 f.run(&[
27135 b"FT.SEARCH",
27136 b"sx",
27137 b"delta",
27138 b"RETURN",
27139 b"3",
27140 b"t",
27141 b"AS",
27142 b"x"
27143 ]),
27144 "*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"
27145 );
27146 assert_eq!(
27149 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"2", b"t", b"AS"]),
27150 "-SEARCH_PARSE_ARGS RETURN path AS name - must be accompanied with NAME\r\n"
27151 );
27152 assert_eq!(
27153 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"3", b"t", b"AS"]),
27154 "-SEARCH_PARSE_ARGS Bad arguments for RETURN: Expected an argument, but none provided\r\n"
27155 );
27156 assert_eq!(
27160 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"AS"]),
27161 "*3\r\n:1\r\n$3\r\nd:3\r\n*0\r\n"
27162 );
27163 }
27164
27165 #[test]
27168 fn a_filter_is_a_range_written_outside_the_query() {
27169 let mut f = Fixture::new();
27170 corpus(&mut f);
27171 assert_eq!(
27172 f.run(&[
27173 b"FT.SEARCH",
27174 b"sx",
27175 b"alpha",
27176 b"NOCONTENT",
27177 b"FILTER",
27178 b"n",
27179 b"2",
27180 b"4"
27181 ]),
27182 "*3\r\n:2\r\n$3\r\nd:2\r\n$3\r\nd:4\r\n"
27183 );
27184 assert_eq!(
27185 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2"]),
27186 "-SEARCH_PARSE_ARGS FILTER requires 3 arguments\r\n"
27187 );
27188 assert_eq!(
27189 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"x", b"1"]),
27190 "-SEARCH_PARSE_ARGS Bad lower range: x\r\n"
27191 );
27192 assert_eq!(
27193 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2", b"1"]),
27194 "-SEARCH_SYNTAX Invalid numeric range (min > max): @n:[2.000000 1.000000]\r\n"
27195 );
27196 for field in [b"g".as_slice(), b"nope"] {
27199 assert_eq!(
27200 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", field, b"2", b"1"]),
27201 "*1\r\n:0\r\n"
27202 );
27203 }
27204 }
27205
27206 #[test]
27209 fn the_index_is_found_before_the_arguments_are_read() {
27210 let mut f = Fixture::new();
27211 corpus(&mut f);
27212 let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
27213 assert_eq!(f.run(&[b"FT.SEARCH", b"nope", b"alpha", b"BOGUS"]), missing);
27214 assert_eq!(
27215 f.run(&[b"FT.EXPLAIN", b"nope", b"alpha", b"BOGUS"]),
27216 missing
27217 );
27218 assert_eq!(
27221 f.run(&[b"FT.SEARCH", b"sx", b"@@@", b"BOGUS"]),
27222 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `BOGUS` at position 1 for <main>\r\n"
27223 );
27224 }
27225
27226 #[test]
27229 fn inkeys_comes_off_the_total() {
27230 let mut f = Fixture::new();
27231 corpus(&mut f);
27232 assert_eq!(
27233 f.run(&[
27234 b"FT.SEARCH",
27235 b"sx",
27236 b"alpha",
27237 b"NOCONTENT",
27238 b"INKEYS",
27239 b"1",
27240 b"d:1"
27241 ]),
27242 "*2\r\n:1\r\n$3\r\nd:1\r\n"
27243 );
27244 assert_eq!(
27245 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"NOCONTENT", b"INKEYS", b"0"]),
27246 "*1\r\n:0\r\n"
27247 );
27248 }
27249
27250 #[test]
27257 fn the_fields_are_read_from_the_session_database() {
27258 let mut f = Fixture::new();
27259 corpus(&mut f);
27260 f.run(&[b"SELECT", b"1"]);
27261 f.run(&[b"HSET", b"d:9", b"t", b"delta", b"n", b"9"]);
27262 assert_eq!(
27265 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
27266 "*3\r\n:2\r\n$3\r\nd:3\r\n$3\r\nd:9\r\n"
27267 );
27268 assert_eq!(
27269 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
27270 "*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"
27271 );
27272 }
27273
27274 #[test]
27277 fn the_third_protocol_answers_a_map_of_five() {
27278 let mut f = Fixture::new();
27279 corpus(&mut f);
27280 f.out = Out::new(Proto::Resp3);
27281 assert_eq!(
27282 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
27283 concat!(
27284 "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
27285 "%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",
27286 "+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
27287 )
27288 );
27289 }
27290
27291 #[test]
27295 fn a_window_of_nothing_has_to_start_at_the_top() {
27296 let mut f = Fixture::new();
27297 corpus(&mut f);
27298 let refused = "-SEARCH_LIMIT_OVER The `offset` of the LIMIT must be 0 when `num` is 0\r\n";
27299 assert_eq!(
27300 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
27301 refused
27302 );
27303 assert_eq!(
27304 f.run(&[b"FT.EXPLAIN", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
27305 refused
27306 );
27307 assert_eq!(
27308 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
27309 refused
27310 );
27311 assert_eq!(
27312 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
27313 "*1\r\n:3\r\n"
27314 );
27315 }
27316
27317 #[test]
27320 fn an_aggregation_answers_a_count_and_then_the_properties() {
27321 let mut f = Fixture::new();
27322 corpus(&mut f);
27323 assert_eq!(
27324 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha"]),
27325 "*4\r\n:1\r\n*0\r\n*0\r\n*0\r\n"
27326 );
27327 assert_eq!(
27331 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"1", b"@t"]),
27332 concat!(
27333 "*4\r\n:1\r\n*2\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
27334 "*2\r\n$1\r\nt\r\n$11\r\nalpha gamma\r\n",
27335 "*2\r\n$1\r\nt\r\n$16\r\nalpha beta gamma\r\n"
27336 )
27337 );
27338 assert_eq!(
27342 f.run(&[
27343 b"FT.AGGREGATE",
27344 b"sx",
27345 b"alpha",
27346 b"LOAD",
27347 b"1",
27348 b"@n",
27349 b"LIMIT",
27350 b"1",
27351 b"2"
27352 ]),
27353 "*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"
27354 );
27355 assert_eq!(
27357 f.run(&[b"FT.AGGREGATE", b"sx", b"nope", b"LOAD", b"1", b"@t"]),
27358 "*1\r\n:0\r\n"
27359 );
27360 }
27361
27362 #[test]
27366 fn a_load_counts_words_and_can_rename_what_it_reads() {
27367 let mut f = Fixture::new();
27368 corpus(&mut f);
27369 assert_eq!(
27371 f.run(&[
27372 b"FT.AGGREGATE",
27373 b"sx",
27374 b"alpha",
27375 b"LOAD",
27376 b"3",
27377 b"@t",
27378 b"AS",
27379 b"text"
27380 ]),
27381 concat!(
27382 "*4\r\n:1\r\n*2\r\n$4\r\ntext\r\n$10\r\nalpha beta\r\n",
27383 "*2\r\n$4\r\ntext\r\n$11\r\nalpha gamma\r\n",
27384 "*2\r\n$4\r\ntext\r\n$16\r\nalpha beta gamma\r\n"
27385 )
27386 );
27387 assert_eq!(
27388 f.run(&[
27389 b"FT.AGGREGATE",
27390 b"sx",
27391 b"alpha",
27392 b"LOAD",
27393 b"*",
27394 b"LIMIT",
27395 b"0",
27396 b"1"
27397 ]),
27398 concat!(
27399 "*2\r\n:1\r\n*6\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
27400 "$1\r\ng\r\n$5\r\naa,bb\r\n$1\r\nn\r\n$1\r\n1\r\n"
27401 )
27402 );
27403 assert_eq!(
27405 f.run(&[
27406 b"FT.AGGREGATE",
27407 b"sx",
27408 b"alpha",
27409 b"LOAD",
27410 b"2",
27411 b"@n",
27412 b"@nope",
27413 b"LIMIT",
27414 b"0",
27415 b"2"
27416 ]),
27417 "*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"
27418 );
27419 }
27420
27421 #[test]
27424 fn a_load_refuses_a_count_it_cannot_use() {
27425 let mut f = Fixture::new();
27426 corpus(&mut f);
27427 let head = "-SEARCH_PARSE_ARGS Bad arguments for LOAD: ";
27428 assert_eq!(
27429 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"x"]),
27430 format!("{head}Expected number of fields or `*`\r\n")
27431 );
27432 assert_eq!(
27433 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"-1", b"@t"]),
27434 format!("{head}Value is outside acceptable bounds\r\n")
27435 );
27436 assert_eq!(
27437 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"5", b"@t"]),
27438 format!("{head}Expected an argument, but none provided\r\n")
27439 );
27440 assert_eq!(
27441 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD"]),
27442 format!("{head}Expected an argument, but none provided\r\n")
27443 );
27444 assert_eq!(
27448 f.run(&[
27449 b"FT.AGGREGATE",
27450 b"sx",
27451 b"alpha",
27452 b"LOAD",
27453 b"2",
27454 b"@t",
27455 b"AS"
27456 ]),
27457 "-SEARCH_PARSE_ARGS LOAD path AS name - must be accompanied with NAME\r\n"
27458 );
27459 assert_eq!(
27462 f.run(&[
27463 b"FT.AGGREGATE",
27464 b"sx",
27465 b"alpha",
27466 b"LOAD",
27467 b"2",
27468 b"@t",
27469 b"AS",
27470 b"VERBATIM"
27471 ]),
27472 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 5 for <main>\r\n"
27473 );
27474 assert_eq!(
27478 f.run(&[
27479 b"FT.AGGREGATE",
27480 b"sx",
27481 b"alpha",
27482 b"LOAD",
27483 b"0",
27484 b"LIMIT",
27485 b"0",
27486 b"1"
27487 ]),
27488 "*2\r\n:1\r\n*0\r\n"
27489 );
27490 }
27491
27492 #[test]
27495 fn a_pipeline_step_closes_the_door_on_the_search_words() {
27496 let mut f = Fixture::new();
27497 corpus(&mut f);
27498 assert_eq!(
27499 f.run(&[
27500 b"FT.AGGREGATE",
27501 b"sx",
27502 b"alpha",
27503 b"LOAD",
27504 b"1",
27505 b"@t",
27506 b"VERBATIM"
27507 ]),
27508 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 4 for <main>\r\n"
27509 );
27510 assert_eq!(
27511 f.run(&[
27512 b"FT.AGGREGATE",
27513 b"sx",
27514 b"alpha",
27515 b"LIMIT",
27516 b"0",
27517 b"1",
27518 b"VERBATIM"
27519 ]),
27520 "*2\r\n:1\r\n*0\r\n"
27521 );
27522 for word in [b"RETURN".as_slice(), b"SUMMARIZE", b"HIGHLIGHT"] {
27525 let name = core::str::from_utf8(word).expect("the three words are text");
27526 assert_eq!(
27527 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", word]),
27528 format!("-SEARCH_PARSE_ARGS {name} is not supported on FT.AGGREGATE\r\n")
27529 );
27530 }
27531 }
27532
27533 #[test]
27536 fn addscores_writes_a_shorter_score_than_withscores() {
27537 let mut f = Fixture::new();
27538 corpus(&mut f);
27539 assert_eq!(
27540 f.run(&[
27541 b"FT.AGGREGATE",
27542 b"sx",
27543 b"alpha",
27544 b"ADDSCORES",
27545 b"LOAD",
27546 b"1",
27547 b"@n",
27548 b"LIMIT",
27549 b"0",
27550 b"2"
27551 ]),
27552 concat!(
27553 "*3\r\n:1\r\n",
27554 "*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",
27555 "*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"
27556 )
27557 );
27558 assert_eq!(
27562 f.run(&[
27563 b"FT.AGGREGATE",
27564 b"sx",
27565 b"alpha",
27566 b"NOCONTENT",
27567 b"WITHSCORES",
27568 b"LIMIT",
27569 b"0",
27570 b"2"
27571 ]),
27572 "*3\r\n:1\r\n$18\r\n0.3566749439387324\r\n$18\r\n0.3566749439387324\r\n"
27573 );
27574 assert_eq!(
27575 f.run(&[
27576 b"FT.AGGREGATE",
27577 b"sx",
27578 b"alpha",
27579 b"WITHSORTKEYS",
27580 b"LOAD",
27581 b"1",
27582 b"@n",
27583 b"LIMIT",
27584 b"0",
27585 b"1"
27586 ]),
27587 "*3\r\n:1\r\n$-1\r\n*2\r\n$1\r\nn\r\n$1\r\n1\r\n"
27588 );
27589 }
27590
27591 #[test]
27594 fn a_normalising_scorer_answers_the_rows_backwards() {
27595 let mut f = Fixture::new();
27596 corpus(&mut f);
27597 assert_eq!(
27598 f.run(&[
27599 b"FT.AGGREGATE",
27600 b"sx",
27601 b"alpha",
27602 b"SCORER",
27603 b"BM25STD.NORM",
27604 b"ADDSCORES",
27605 b"LOAD",
27606 b"1",
27607 b"@n",
27608 b"LIMIT",
27609 b"1",
27610 b"2"
27611 ]),
27612 concat!(
27613 "*3\r\n:3\r\n",
27614 "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n2\r\n",
27615 "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n1\r\n"
27616 )
27617 );
27618 assert_eq!(
27621 f.run(&[
27622 b"FT.AGGREGATE",
27623 b"sx",
27624 b"alpha",
27625 b"SCORER",
27626 b"BM25STD.NORM",
27627 b"LOAD",
27628 b"1",
27629 b"@n",
27630 b"LIMIT",
27631 b"1",
27632 b"2"
27633 ]),
27634 "*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"
27635 );
27636 }
27637
27638 #[test]
27641 fn an_aggregation_answers_a_map_of_five_as_well() {
27642 let mut f = Fixture::new();
27643 corpus(&mut f);
27644 f.out = Out::new(Proto::Resp3);
27645 assert_eq!(
27646 f.run(&[
27647 b"FT.AGGREGATE",
27648 b"sx",
27649 b"alpha",
27650 b"ADDSCORES",
27651 b"WITHSCORES",
27652 b"WITHSORTKEYS",
27653 b"LOAD",
27654 b"1",
27655 b"@n",
27656 b"LIMIT",
27657 b"0",
27658 b"1"
27659 ]),
27660 concat!(
27661 "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
27662 "%4\r\n+score\r\n,0.3566749439387324\r\n+sortkey\r\n_\r\n",
27663 "+extra_attributes\r\n%2\r\n$7\r\n__score\r\n$14\r\n0.356674943939\r\n",
27664 "$1\r\nn\r\n$1\r\n1\r\n+values\r\n*0\r\n",
27665 "+total_results\r\n:1\r\n+warning\r\n*0\r\n"
27666 )
27667 );
27668 assert_eq!(
27671 f.run(&[
27672 b"FT.AGGREGATE",
27673 b"sx",
27674 b"alpha",
27675 b"NOCONTENT",
27676 b"LIMIT",
27677 b"0",
27678 b"1"
27679 ]),
27680 concat!(
27681 "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
27682 "%1\r\n+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
27683 )
27684 );
27685 }
27686}