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 multi;
75mod notify;
76mod pubsub;
77mod scan;
78mod scripting;
79mod search;
80mod server;
81mod sets;
82mod streams;
83mod strings;
84mod suggest;
85pub mod table;
86mod tdigest;
87mod topk;
88mod ts;
89mod vectors;
90mod vfilter;
91mod zsets;
92
93pub use args::Args;
94pub use blocking::{Parked, Waiters};
95pub(crate) use pubsub::Envelope;
96pub use server::parse_memory;
97pub use table::{COMMANDS, Spec, arity_ok, lookup};
98
99use crate::reply::Out;
100use std::cell::Cell;
101use std::path::{Path, PathBuf};
102use std::sync::atomic::Ordering::{Acquire, Relaxed, Release};
103use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, AtomicUsize};
104use yo_common::lock::{Held, Lock};
105use yo_common::{Code, Error};
106use yo_kv::cold::Store;
107use yo_kv::{Clock, Db, Keyspace};
108use yo_search::Registry;
109
110use multi::Watches;
111use search::cursor::Cursors;
112
113pub const DATABASES: usize = 16;
120
121const ALL_DATABASES: u64 = if DATABASES == 64 {
128 u64::MAX
129} else {
130 (1u64 << DATABASES) - 1
131};
132const _: () = assert!(DATABASES <= 64);
133
134const EVICT_BUDGET: usize = 64;
146
147const NO_MAXSTORE: u64 = u64::MAX;
155
156const OOM: &[u8] = b"command not allowed when used memory > 'maxmemory'.";
161
162#[derive(Debug, Clone, Copy, PartialEq, Eq)]
164pub enum Flow {
165 Continue,
167 Close,
169 Block,
176}
177
178#[derive(Debug, Default)]
188pub struct Counter(AtomicU64);
189
190impl Counter {
191 fn bump(&self) {
193 self.0.store(self.get().wrapping_add(1), Relaxed);
194 }
195
196 fn drop_one(&self) {
203 self.0.store(self.get().saturating_sub(1), Relaxed);
204 }
205
206 fn get(&self) -> u64 {
208 self.0.load(Relaxed)
209 }
210
211 fn zero(&self) {
213 self.0.store(0, Relaxed);
214 }
215}
216
217#[derive(Debug, Default)]
223pub struct Stats {
224 clients: Counter,
226 connections: Counter,
228 commands: Counter,
230}
231
232impl Stats {
233 pub fn opened(&self) {
235 self.clients.bump();
236 self.connections.bump();
237 }
238
239 pub fn closed(&self) {
241 self.clients.drop_one();
242 }
243}
244
245#[derive(Debug, Clone, Copy, Default)]
247pub struct Totals {
248 pub clients: u64,
250 pub connections: u64,
252 pub commands: u64,
254}
255
256thread_local! {
257 static SLOT: Cell<usize> = const { Cell::new(usize::MAX) };
265}
266
267#[derive(Debug)]
278#[repr(align(64))]
279struct Local {
280 stats: Stats,
282 cmdstats: CommandStats,
284 dirty: AtomicU64,
293 turn: AtomicU64,
306 parked: AtomicUsize,
318}
319
320impl Default for Local {
321 fn default() -> Local {
322 Local {
323 stats: Stats::default(),
324 cmdstats: CommandStats::default(),
325 dirty: AtomicU64::new(0),
326 turn: AtomicU64::new(ALL_DATABASES),
327 parked: AtomicUsize::new(0),
328 }
329 }
330}
331
332impl Local {
333 fn mark(&self, dbs: u64) {
335 self.dirty.store(self.dirty.load(Relaxed) | dbs, Relaxed);
336 }
337
338 fn note(&self, dbs: u64) {
340 self.turn.store(self.turn.load(Relaxed) | dbs, Relaxed);
341 }
342
343 fn done(&self, at: usize) {
345 self.turn
346 .store(self.turn.load(Relaxed) & !(1u64 << at), Relaxed);
347 }
348
349 fn wanted(&self, at: usize) -> bool {
351 self.turn.load(Relaxed) & (1u64 << at) != 0
352 }
353
354 fn blocked(&self, n: usize) {
356 self.parked
357 .store(self.parked.load(Relaxed).saturating_add(n), Relaxed);
358 }
359
360 fn woke(&self, n: usize) {
362 self.parked
363 .store(self.parked.load(Relaxed).saturating_sub(n), Relaxed);
364 }
365}
366
367fn one_thread() -> Box<[Local]> {
369 slots(1)
370}
371
372fn slots(threads: usize) -> Box<[Local]> {
374 (0..threads.max(1)).map(|_| Local::default()).collect()
375}
376
377fn working_dir() -> PathBuf {
384 std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
385}
386
387#[derive(Debug, Clone, Copy, Default)]
396pub struct CommandStat {
397 pub calls: u64,
399 pub rejected: u64,
402 pub failed: u64,
404}
405
406impl CommandStat {
407 const fn seen(&self) -> bool {
413 self.calls != 0 || self.rejected != 0 || self.failed != 0
414 }
415}
416
417#[derive(Debug, Default)]
422struct Row {
423 calls: Counter,
425 rejected: Counter,
427 failed: Counter,
429}
430
431#[derive(Debug)]
438struct CommandStats(Box<[Row]>);
439
440impl Default for CommandStats {
441 fn default() -> CommandStats {
442 CommandStats((0..table::count()).map(|_| Row::default()).collect())
443 }
444}
445
446impl CommandStats {
447 fn at(&self, spec: &'static Spec) -> &Row {
449 &self.0[table::index_of(spec)]
450 }
451}
452
453pub type StoreSource = dyn FnMut(usize) -> Option<Store> + Send;
459
460const _: () = {
465 const fn shareable<T: Send + Sync>() {}
466 shareable::<Server>();
467};
468
469pub struct Server {
477 dbs: Vec<Db>,
478 width: usize,
484 clock: Clock,
485 started_ms: u64,
486 next_db: AtomicUsize,
495 conn_bytes: AtomicUsize,
512 maxmemory: AtomicU64,
520 store: Lock<Option<Box<StoreSource>>>,
537 maxstore: AtomicU64,
559 used: AtomicUsize,
576 evict_db: AtomicUsize,
586 expire_db: AtomicUsize,
593 expire_ms: AtomicU64,
603 waiters: Lock<Waiters>,
611 parked: AtomicUsize,
624 peers: Lock<migrate::Peers>,
636 locals: Box<[Local]>,
642 claimed: AtomicUsize,
644 next_client: AtomicU64,
652 dir: PathBuf,
660 backup: Lock<backup::State>,
670 sealed: AtomicBool,
680 search: Lock<Registry>,
695 cursors: Lock<Cursors>,
704 scripts: Lock<lua::Scripts>,
716 libraries: Lock<lua::library::Libraries>,
723 stopping: AtomicBool,
732 watches: Lock<Watches>,
739 watched: AtomicUsize,
746 pubsub: Lock<pubsub::Registry>,
753 subs: AtomicUsize,
759 mail: Box<[pubsub::Mailbox]>,
765 notify: AtomicU32,
772}
773
774impl Server {
775 #[must_use]
777 pub fn new() -> Server {
778 let clock = Clock::system();
779 Server {
780 dbs: (0..DATABASES)
781 .map(|_| Db::with_clock(clock.clone(), 1))
782 .collect(),
783 width: 1,
784 started_ms: clock.now_ms(),
785 clock,
786 next_db: AtomicUsize::new(0),
787 conn_bytes: AtomicUsize::new(0),
788 maxmemory: AtomicU64::new(0),
789 store: Lock::new(None),
790 maxstore: AtomicU64::new(NO_MAXSTORE),
791 used: AtomicUsize::new(0),
792 evict_db: AtomicUsize::new(0),
793 expire_db: AtomicUsize::new(0),
794 expire_ms: AtomicU64::new(0),
795 waiters: Lock::default(),
796 parked: AtomicUsize::new(0),
797 peers: Lock::default(),
798 locals: one_thread(),
799 claimed: AtomicUsize::new(0),
800 next_client: AtomicU64::new(1),
801 dir: working_dir(),
802 backup: Lock::default(),
803 sealed: AtomicBool::new(false),
804 search: Lock::new(Registry::new()),
805 cursors: Lock::default(),
806 scripts: Lock::default(),
807 libraries: Lock::default(),
808 stopping: AtomicBool::new(false),
809 watches: Lock::default(),
810 watched: AtomicUsize::new(0),
811 pubsub: Lock::default(),
812 subs: AtomicUsize::new(0),
813 notify: AtomicU32::new(0),
814 mail: pubsub::boxes(1),
815 }
816 }
817
818 #[must_use]
829 pub fn with_width(width: usize) -> Server {
830 let mut server = Server::new();
831 let clock = server.clock.clone();
835 server.dbs = (0..DATABASES)
836 .map(|_| Db::with_clock(clock.clone(), width))
837 .collect();
838 server.width = server.dbs[0].width();
839 server
840 }
841
842 #[must_use]
844 pub fn with_clock(clock: Clock) -> Server {
845 Server {
846 dbs: (0..DATABASES)
847 .map(|_| Db::with_clock(clock.clone(), 1))
848 .collect(),
849 width: 1,
850 started_ms: clock.now_ms(),
851 clock,
852 next_db: AtomicUsize::new(0),
853 conn_bytes: AtomicUsize::new(0),
854 maxmemory: AtomicU64::new(0),
855 store: Lock::new(None),
856 maxstore: AtomicU64::new(NO_MAXSTORE),
857 used: AtomicUsize::new(0),
858 evict_db: AtomicUsize::new(0),
859 expire_db: AtomicUsize::new(0),
860 expire_ms: AtomicU64::new(0),
861 waiters: Lock::default(),
862 parked: AtomicUsize::new(0),
863 peers: Lock::default(),
864 locals: one_thread(),
865 claimed: AtomicUsize::new(0),
866 next_client: AtomicU64::new(1),
867 dir: working_dir(),
868 backup: Lock::default(),
869 sealed: AtomicBool::new(false),
870 search: Lock::new(Registry::new()),
871 cursors: Lock::default(),
872 scripts: Lock::default(),
873 libraries: Lock::default(),
874 stopping: AtomicBool::new(false),
875 watches: Lock::default(),
876 watched: AtomicUsize::new(0),
877 pubsub: Lock::default(),
878 subs: AtomicUsize::new(0),
879 notify: AtomicU32::new(0),
880 mail: pubsub::boxes(1),
881 }
882 }
883
884 pub fn striped(&self, i: usize) -> &Db {
908 self.mine().mark(1u64 << i);
909 &self.dbs[i]
910 }
911
912 fn keyspaces(&self) -> impl Iterator<Item = Held<'_, Keyspace>> {
919 self.dbs
920 .iter()
921 .flat_map(|db| (0..db.width()).map(|i| db.hold_stripe(i)))
922 }
923
924 const fn slots(&self) -> usize {
930 DATABASES * self.width
931 }
932
933 const fn slot_db(&self, i: usize) -> usize {
935 i / self.width
936 }
937
938 fn slot(&self, i: usize) -> Held<'_, Keyspace> {
940 let (db, stripe) = (i / self.width, i % self.width);
941 self.dbs[db].hold_stripe(stripe)
942 }
943
944 #[must_use]
946 pub fn dir(&self) -> &Path {
947 &self.dir
948 }
949
950 pub fn set_dir(&mut self, dir: PathBuf) {
957 self.dir = dir;
958 }
959
960 pub fn backup_expire(&self) {
966 backup::expire(self);
967 }
968
969 pub fn stop(&self) {
975 self.stopping.store(true, Release);
976 }
977
978 #[must_use]
984 pub fn stopping(&self) -> bool {
985 self.stopping.load(Acquire)
986 }
987
988 #[must_use]
995 pub fn striped_ref(&self, i: usize) -> &Db {
996 &self.dbs[i]
997 }
998
999 fn settings(&self) -> Held<'_, Keyspace> {
1007 self.dbs[0].hold_stripe(0)
1008 }
1009
1010 pub fn refresh_clock(&self) {
1021 self.clock.refresh();
1022 }
1023
1024 pub fn advance_clock_ms(&self, ms: u64) {
1030 let now = self.clock.now_ms() + ms;
1031 self.set_clock_ms(now);
1032 }
1033
1034 pub fn set_clock_ms(&self, ms: u64) {
1042 self.clock.set(ms);
1043 }
1044
1045 #[must_use]
1047 pub fn uptime_secs(&self) -> u64 {
1048 self.clock.now_ms().saturating_sub(self.started_ms) / 1000
1049 }
1050
1051 #[must_use]
1059 pub fn memory_bytes(&self) -> usize {
1060 self.keyspaces().map(|db| db.memory_bytes()).sum::<usize>() + self.conn_bytes()
1061 }
1062
1063 #[must_use]
1069 pub fn dataset_bytes(&self) -> usize {
1070 self.keyspaces()
1071 .map(|db| db.map().arena().live_bytes() as usize)
1072 .sum()
1073 }
1074
1075 #[must_use]
1077 pub fn arena_bytes(&self) -> usize {
1078 self.keyspaces()
1079 .map(|db| db.map().arena().reserved_bytes() as usize)
1080 .sum()
1081 }
1082
1083 #[must_use]
1085 pub fn index_bytes(&self) -> usize {
1086 self.keyspaces()
1087 .map(|db| db.map().index().memory_bytes())
1088 .sum()
1089 }
1090
1091 #[must_use]
1098 pub fn compaction(&self) -> yo_kv::Compaction {
1099 self.keyspaces().map(|db| db.map().compaction()).fold(
1100 yo_kv::Compaction::default(),
1101 |a, b| yo_kv::Compaction {
1102 walked: a.walked + b.walked,
1103 moved: a.moved + b.moved,
1104 bytes: a.bytes + b.bytes,
1105 },
1106 )
1107 }
1108
1109 #[must_use]
1111 pub fn segment_count(&self) -> usize {
1112 self.keyspaces()
1113 .map(|db| db.map().arena().resident_segments())
1114 .sum()
1115 }
1116
1117 #[must_use]
1119 pub fn conn_bytes(&self) -> usize {
1120 self.conn_bytes.load(Relaxed)
1121 }
1122
1123 pub fn note_conn_bytes(&self, delta: isize) {
1131 self.conn_bytes
1137 .store(self.conn_bytes().saturating_add_signed(delta), Relaxed);
1138 }
1139
1140 #[must_use]
1142 pub fn expired_keys(&self) -> u64 {
1143 self.keyspaces().map(|db| db.expired_keys()).sum()
1144 }
1145
1146 #[must_use]
1148 pub fn evicted_keys(&self) -> u64 {
1149 self.keyspaces().map(|db| db.evicted_keys()).sum()
1150 }
1151
1152 pub fn command_stats(&self) -> impl Iterator<Item = (&'static str, CommandStat)> {
1158 (0..table::count())
1159 .map(|at| (table::name_at(at), self.command_stat(at)))
1160 .filter(|(_, row)| row.seen())
1161 }
1162
1163 fn command_stat(&self, at: usize) -> CommandStat {
1165 let mut sum = CommandStat::default();
1166 for thread in &self.locals {
1167 let row = &thread.cmdstats.0[at];
1168 sum.calls += row.calls.get();
1169 sum.rejected += row.rejected.get();
1170 sum.failed += row.failed.get();
1171 }
1172 sum
1173 }
1174
1175 pub fn counted(&self) -> &Stats {
1184 &self.mine().stats
1185 }
1186
1187 pub fn next_client(&self) -> u64 {
1192 self.next_client.fetch_add(1, Relaxed)
1193 }
1194
1195 pub fn my_slot(&self) -> usize {
1202 self.mine_at()
1203 }
1204
1205 fn mine(&self) -> &Local {
1207 &self.locals[self.mine_at()]
1208 }
1209
1210 fn mine_at(&self) -> usize {
1217 let mut slot = SLOT.get();
1218 if slot == usize::MAX {
1219 slot = self.claimed.fetch_add(1, Relaxed);
1220 SLOT.set(slot);
1221 }
1222 slot % self.locals.len()
1223 }
1224
1225 #[must_use]
1227 pub fn totals(&self) -> Totals {
1228 let mut sum = Totals::default();
1229 for thread in &self.locals {
1230 sum.clients += thread.stats.clients.get();
1231 sum.connections += thread.stats.connections.get();
1232 sum.commands += thread.stats.commands.get();
1233 }
1234 sum
1235 }
1236
1237 pub fn reset_stats(&self) {
1244 for thread in &self.locals {
1245 thread.stats.connections.zero();
1246 thread.stats.commands.zero();
1247 }
1248 }
1249
1250 pub fn set_threads(&mut self, threads: usize) {
1257 self.locals = slots(threads);
1258 self.mail = pubsub::boxes(threads);
1259 self.claimed = AtomicUsize::new(0);
1260 }
1261
1262 #[must_use]
1264 pub fn maxmemory(&self) -> u64 {
1265 self.maxmemory.load(Relaxed)
1266 }
1267
1268 pub fn set_maxmemory(&self, bytes: u64) {
1282 self.maxmemory.store(bytes, Relaxed);
1283 for db in &self.dbs {
1284 db.track_memory(bytes != 0);
1285 }
1286 self.used.store(self.settled_memory(), Relaxed);
1287 }
1288
1289 pub fn set_store_source(
1299 &mut self,
1300 source: impl FnMut(usize) -> Option<Store> + Send + 'static,
1301 ) {
1302 *self.store.lock() = Some(Box::new(source));
1303 }
1304
1305 #[must_use]
1307 pub fn has_store_source(&self) -> bool {
1308 self.store.lock().is_some()
1309 }
1310
1311 fn attach_store(&self, at: usize) {
1318 if self.slot(at).store_bytes().is_some() {
1319 return;
1320 }
1321 let mut source = self.store.lock();
1325 let Some(source) = source.as_mut() else {
1326 return;
1327 };
1328 if let Some(blocks) = source(at) {
1329 self.slot(at).attach(blocks);
1330 }
1331 }
1332
1333 #[must_use]
1335 pub fn maxstore(&self) -> Option<u64> {
1336 match self.maxstore.load(Relaxed) {
1337 NO_MAXSTORE => None,
1338 bytes => Some(bytes),
1339 }
1340 }
1341
1342 pub fn set_maxstore(&self, bytes: Option<u64>) {
1348 self.maxstore.store(bytes.unwrap_or(NO_MAXSTORE), Relaxed);
1349 }
1350
1351 #[must_use]
1357 pub fn store_bytes(&self) -> u64 {
1358 self.keyspaces().filter_map(|db| db.store_bytes()).sum()
1359 }
1360
1361 #[must_use]
1376 pub fn cold_stats(&self) -> yo_kv::tier::Stats {
1377 let mut total = yo_kv::tier::Stats::default();
1378 for db in self.keyspaces() {
1379 let Some(tier) = db.tier() else { continue };
1380 let s = tier.stats();
1381 total.demoted += s.demoted;
1382 total.promoted += s.promoted;
1383 total.faults += s.faults;
1384 total.served += s.served;
1385 total.bytes_out += s.bytes_out;
1386 total.bytes_in += s.bytes_in;
1387 }
1388 total
1389 }
1390
1391 #[must_use]
1398 pub fn regime(&self) -> &'static str {
1399 if (0..self.slots()).any(|at| self.migrates(at)) {
1400 "migrate"
1401 } else {
1402 "evict"
1403 }
1404 }
1405
1406 fn migrates(&self, at: usize) -> bool {
1418 let cap = self.maxstore();
1419 if cap == Some(0) {
1420 return false;
1421 }
1422 let bytes = self.slot(at).store_bytes();
1426 match bytes {
1427 Some(held) => cap.is_none_or(|cap| held < cap),
1428 None => self.store.lock().is_some(),
1432 }
1433 }
1434
1435 pub fn refresh_memory(&self) {
1440 if self.maxmemory() != 0 {
1441 self.used.store(self.settled_memory(), Relaxed);
1442 }
1443 }
1444
1445 fn settled_memory(&self) -> usize {
1452 self.keyspaces()
1453 .map(|mut db| db.settled_memory_bytes())
1454 .sum::<usize>()
1455 + self.conn_bytes()
1456 }
1457
1458 pub fn make_room(&self) -> bool {
1492 let limit = self.maxmemory();
1493 if limit == 0 || self.used.load(Relaxed) as u64 <= limit {
1494 return true;
1495 }
1496 let mut used = self.settled_memory();
1501 self.used.store(used, Relaxed);
1502 let mut budget = EVICT_BUDGET;
1503 while used as u64 > limit {
1504 let over = used - limit as usize;
1505 if !self.relieve_step(over) {
1506 return false;
1507 }
1508 self.compact_hard_step();
1509 used = self.settled_memory();
1510 self.used.store(used, Relaxed);
1511 budget -= 1;
1512 if budget == 0 {
1513 break;
1514 }
1515 }
1516 true
1517 }
1518
1519 fn relieve_step(&self, over: usize) -> bool {
1536 let from = self.evict_db.load(Relaxed);
1537 for turn in 0..self.slots() {
1538 let i = (from + turn) % self.slots();
1539 let used = !self.slot(i).is_empty();
1542 let gave = if used && self.migrates(i) {
1543 self.attach_store(i);
1544 self.slot(i)
1549 .relieve(over)
1550 .is_ok_and(yo_kv::tier::Relief::made_room)
1551 } else {
1552 self.slot(i).evict_one()
1553 };
1554 if gave {
1555 self.evict_db.store((i + 1) % self.slots(), Relaxed);
1556 self.mine().mark(1u64 << self.slot_db(i));
1557 return true;
1558 }
1559 }
1560 false
1561 }
1562
1563 pub fn expire_slice(&self, budget: usize) -> usize {
1578 let now = self.clock.now_ms();
1579 if now == self.expire_ms.load(Relaxed) {
1580 return 0;
1581 }
1582 self.expire_ms.store(now, Relaxed);
1583 self.expire_step(budget)
1584 }
1585
1586 pub fn expire_step(&self, budget: usize) -> usize {
1602 let mut spent = 0;
1603 let from = self.expire_db.load(Relaxed);
1604 for turn in 0..self.slots() {
1605 if spent >= budget {
1606 break;
1607 }
1608 let i = (from + turn) % self.slots();
1609 let c = self.slot(i).expire_cycle(budget - spent);
1610 spent += c.examined;
1611 if c.expired > 0 {
1612 self.expire_db.store((i + 1) % self.slots(), Relaxed);
1613 self.mine().note(1u64 << self.slot_db(i));
1614 }
1615 }
1616 spent
1617 }
1618
1619 fn compact_hard_step(&self) -> Option<usize> {
1625 let from = self.next_db.load(Relaxed);
1626 for turn in 0..self.slots() {
1627 let i = (from + turn) % self.slots();
1628 if let Some(moved) = self.slot(i).compact_hard() {
1629 self.next_db.store((i + 1) % self.slots(), Relaxed);
1630 return Some(moved);
1631 }
1632 }
1633 None
1634 }
1635
1636 fn collect_marks(&self) {
1645 let mut marked = 0;
1646 for thread in &self.locals {
1647 marked |= thread.dirty.swap(0, Relaxed);
1648 }
1649 self.mine().note(marked);
1650 }
1651
1652 pub fn compact_step(&self) -> Option<usize> {
1665 self.collect_marks();
1666 let mine = self.mine();
1667 let from = self.next_db.load(Relaxed);
1668 for turn in 0..self.slots() {
1669 let i = (from + turn) % self.slots();
1670 let at = self.slot_db(i);
1674 if !mine.wanted(at) {
1675 continue;
1676 }
1677 if let Some(moved) = self.slot(i).compact_step() {
1678 self.next_db.store((i + 1) % self.slots(), Relaxed);
1679 return Some(moved);
1680 }
1681 if i % self.width == self.width - 1 {
1685 mine.done(at);
1686 }
1687 }
1688 None
1689 }
1690}
1691
1692impl Server {
1693 fn watching(&self) -> bool {
1702 self.watched.load(Relaxed) != 0
1703 }
1704
1705 pub(crate) fn notify_flags(&self) -> u32 {
1712 self.notify.load(Relaxed)
1713 }
1714
1715 pub(crate) fn set_notify_flags(&self, flags: u32) {
1717 self.notify.store(flags, Relaxed);
1718 }
1719
1720 fn recount(&self, watches: &Watches) {
1725 self.watched.store(watches.len(), Relaxed);
1726 }
1727}
1728
1729impl Default for Server {
1730 fn default() -> Server {
1731 Server::new()
1732 }
1733}
1734
1735pub struct Session {
1737 db: usize,
1738 id: u64,
1739 conn: u32,
1747 name: Vec<u8>,
1748 sets: himport::Fieldsets,
1754 scripted: bool,
1764 multi: Option<multi::Queue>,
1770 watching: Vec<multi::Watched>,
1777 running: bool,
1784 subs: Option<Box<pubsub::Subs>>,
1792}
1793
1794impl Session {
1795 #[must_use]
1797 pub fn new(id: u64) -> Session {
1798 Session {
1799 db: 0,
1800 id,
1801 conn: u32::MAX,
1802 name: Vec::new(),
1803 sets: himport::Fieldsets::default(),
1804 scripted: false,
1805 multi: None,
1806 watching: Vec::new(),
1807 running: false,
1808 subs: None,
1809 }
1810 }
1811
1812 pub(crate) const fn scripted(&self) -> bool {
1814 self.scripted
1815 }
1816
1817 pub(crate) const fn running(&self) -> bool {
1819 self.running
1820 }
1821
1822 pub(crate) const fn set_conn(&mut self, conn: u32) {
1828 self.conn = conn;
1829 }
1830
1831 #[must_use]
1833 pub const fn id(&self) -> u64 {
1834 self.id
1835 }
1836
1837 #[must_use]
1839 pub const fn db(&self) -> usize {
1840 self.db
1841 }
1842
1843 #[must_use]
1845 pub fn name(&self) -> &[u8] {
1846 &self.name
1847 }
1848
1849 pub fn reset(&mut self) {
1854 self.db = 0;
1855 self.name.clear();
1856 self.sets.clear();
1860 }
1861
1862 fn set_name(&mut self, name: &[u8]) {
1864 yo_alloc::allow(|| {
1865 self.name.clear();
1866 self.name.extend_from_slice(name);
1867 });
1868 }
1869}
1870
1871pub fn forget_session(server: &Server, session: &mut Session) {
1880 multi::release(server, session);
1881 pubsub::release(server, session);
1882}
1883
1884pub fn execute(server: &Server, session: &mut Session, args: Args<'_>, out: &mut Out) -> Flow {
1889 if args.is_empty() {
1892 return Flow::Continue;
1893 }
1894 resolved(server, session, lookup(args.name()), args, out)
1895}
1896
1897pub fn resolved(
1909 server: &Server,
1910 session: &mut Session,
1911 spec: Option<&'static Spec>,
1912 args: Args<'_>,
1913 out: &mut Out,
1914) -> Flow {
1915 if args.is_empty() {
1916 return Flow::Continue;
1917 }
1918 server.mine().stats.commands.bump();
1919
1920 let Some(spec) = spec else {
1927 multi::refuse(server, session, None, &args::unknown_command(args), out);
1928 return Flow::Continue;
1929 };
1930 if !arity_ok(spec, args.len()) {
1931 server.mine().cmdstats.at(spec).rejected.bump();
1932 multi::refuse(
1933 server,
1934 session,
1935 Some(spec),
1936 &args::wrong_arity(spec.name),
1937 out,
1938 );
1939 return Flow::Continue;
1940 }
1941 if session.in_multi()
1942 && let Some(e) = multi::refused_in_multi(spec)
1943 {
1944 server.mine().cmdstats.at(spec).rejected.bump();
1945 multi::refuse(server, session, Some(spec), &e, out);
1946 return Flow::Continue;
1947 }
1948
1949 if server.maxmemory() != 0 && !server.make_room() && spec.flags.contains(&"denyoom") {
1959 server.mine().cmdstats.at(spec).rejected.bump();
1960 session.dirty_multi();
1961 out.error_line(b"OOM ", OOM);
1962 return Flow::Continue;
1963 }
1964
1965 if let Some(e) = pubsub::refused(session, spec, out) {
1973 server.mine().cmdstats.at(spec).rejected.bump();
1974 multi::refuse(server, session, Some(spec), &e, out);
1975 return Flow::Continue;
1976 }
1977
1978 if session.queues(spec.name) {
1983 return multi::queue(session, args, out);
1984 }
1985
1986 server.mine().mark(match spec.group {
1993 "string" | "bitmap" | "hyperloglog" | "geo" | "set" | "hash" | "list" | "zset"
1994 | "array" | "stream" | "bloom" | "cuckoo" | "cms" | "topk" | "tdigest" | "ts" => {
1995 1u64 << session.db
1996 }
1997 _ => ALL_DATABASES,
1998 });
1999
2000 let mark = out.len();
2001 let armed = notify::arm(server);
2012 let done = if spec.flags.contains(&"blocking") {
2013 blocking::execute(server, session, spec, args, out)
2014 } else {
2015 match spec.group {
2016 "string" => {
2017 let db = session.db;
2018 strings::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2019 }
2020 "bitmap" => {
2024 let db = session.db;
2025 bits::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2026 }
2027 "hyperloglog" => {
2030 let db = session.db;
2031 hll::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2032 }
2033 "set" => {
2034 let db = session.db;
2035 sets::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2036 }
2037 "hash" if spec.name == "himport" => {
2042 let db = session.db;
2043 himport::execute(&server.dbs[db], &mut session.sets, args, out)
2044 .map(|()| Flow::Continue)
2045 }
2046 "hash" => {
2051 let db = session.db;
2052 let changed = hashes::execute(&server.dbs[db], spec, args, out);
2053 changed.map(|changed| {
2054 indexing::changed(server, db, args.get(1), changed);
2055 Flow::Continue
2056 })
2057 }
2058 "list" => {
2059 let db = session.db;
2060 lists::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2061 }
2062 "zset" => {
2063 let db = session.db;
2064 zsets::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2065 }
2066 "geo" => {
2070 let db = session.db;
2071 geo::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2072 }
2073 "array" => {
2074 let db = session.db;
2075 arrays::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2076 }
2077 "graph" => {
2078 let db = session.db;
2079 graph::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2080 }
2081 "json" => {
2085 let db = session.db;
2086 json::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2087 }
2088 "vector" => {
2089 let db = session.db;
2090 vectors::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2091 }
2092 "bloom" => {
2093 let db = session.db;
2094 bloom::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2095 }
2096 "cuckoo" => {
2097 let db = session.db;
2098 cuckoo::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2099 }
2100 "cms" => {
2101 let db = session.db;
2102 cms::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2103 }
2104 "topk" => {
2105 let db = session.db;
2106 topk::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2107 }
2108 "tdigest" => {
2109 let db = session.db;
2110 tdigest::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2111 }
2112 "ts" => {
2113 let db = session.db;
2114 ts::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2115 }
2116 "stream" => {
2121 let db = session.db;
2122 let now = server.now_ms();
2123 streams::execute(&server.dbs[db], spec, args, now, out).map(|()| Flow::Continue)
2124 }
2125 "keyspace" if spec.name == "migrate" => {
2129 migrate::execute(server, session.db, args, out).map(|()| Flow::Continue)
2130 }
2131 "keyspace" => {
2137 let mut touched = indexing::Touched::new(server);
2138 let done =
2139 keyspace::execute(&server.dbs, session.db, spec, args, out, &mut touched);
2140 done.map(|()| {
2141 indexing::touched(server, &touched);
2142 Flow::Continue
2143 })
2144 }
2145 "search" if spec.name == "FT.SEARCH" => {
2152 search::find(server, session.db, args, out).map(|()| Flow::Continue)
2157 }
2158 "search" if spec.name == "FT.AGGREGATE" => {
2159 search::roll(server, session.db, args, out).map(|()| Flow::Continue)
2160 }
2161 "search" if spec.name == "FT.HYBRID" => {
2162 search::hybrid(server, session.db, args, out).map(|()| Flow::Continue)
2163 }
2164 "search" if spec.name == "FT.PROFILE" => {
2165 search::profiled(server, session.db, args, out).map(|()| Flow::Continue)
2168 }
2169 "search" if spec.name.starts_with("FT.SUG") => {
2173 let db = session.db;
2174 suggest::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2175 }
2176 "search"
2180 if matches!(
2181 spec.name,
2182 "FT.ADD" | "FT.SAFEADD" | "FT.GET" | "FT.MGET" | "FT.DEL"
2183 ) =>
2184 {
2185 let db = session.db;
2186 search::docs::execute(server, db, spec, args, out).map(|()| Flow::Continue)
2187 }
2188 "search" if spec.name == "FT.CURSOR" => {
2189 search::cursor::execute(server, args, out).map(|()| Flow::Continue)
2193 }
2194 "search" => {
2195 let db = session.db;
2196 let made = search::execute(server, &mut server.search.lock(), db, spec, args, out);
2197 made.map(|made| {
2198 match made {
2199 Some(search::After::Scan(fill)) => indexing::scan(server, db, &fill),
2200 Some(search::After::Sweep(keys)) => indexing::sweep(server, db, &keys),
2201 None => {}
2202 }
2203 Flow::Continue
2204 })
2205 }
2206 "scripting" => {
2207 scripting::execute(server, session, spec, args, out).map(|()| Flow::Continue)
2208 }
2209 "transactions" => multi::execute(server, session, spec, args, out),
2210 "pubsub" => pubsub::execute(server, session, spec, args, out),
2214 _ => server::execute(server, session, spec, args, out),
2215 }
2216 };
2217 notify::drain(server, armed);
2222
2223 let flow = match done {
2224 Ok(flow) => flow,
2225 Err(e) => {
2226 out.truncate(mark);
2227 write_error(out, &e);
2228 Flow::Continue
2229 }
2230 };
2231
2232 if server.watching() && spec.flags.contains(&"write") {
2237 multi::touched(server, session, spec, args);
2238 }
2239
2240 let row = server.mine().cmdstats.at(spec);
2251 row.calls.bump();
2252 if matches!(out.as_slice().get(mark), Some(b'-' | b'!')) {
2253 row.failed.bump();
2254 }
2255 flow
2256}
2257
2258fn write_error(out: &mut Out, e: &Error) {
2268 let prefix: &[u8] = match e.code() {
2269 Code::WrongType => b"WRONGTYPE ",
2270 Code::Corrupt => b"INVALIDOBJ ",
2274 _ => b"ERR ",
2275 };
2276 out.error_line(prefix, e.message().as_bytes());
2277}
2278
2279#[cfg(test)]
2280mod tests {
2281 use super::*;
2282 use crate::proto::{Limits, Proto};
2283 use crate::request::Argv;
2284
2285 pub(crate) fn encode(parts: &[&[u8]]) -> Vec<u8> {
2290 let mut wire = format!("*{}\r\n", parts.len()).into_bytes();
2291 for p in parts {
2292 wire.extend_from_slice(format!("${}\r\n", p.len()).as_bytes());
2293 wire.extend_from_slice(p);
2294 wire.extend_from_slice(b"\r\n");
2295 }
2296 wire
2297 }
2298
2299 struct Fixture {
2301 server: Server,
2302 session: Session,
2303 argv: Argv,
2304 out: Out,
2305 }
2306
2307 impl Fixture {
2308 fn new() -> Fixture {
2309 Fixture::on(Server::new())
2310 }
2311
2312 fn striped(width: usize) -> Fixture {
2314 Fixture::on(Server::with_width(width))
2315 }
2316
2317 fn on(server: Server) -> Fixture {
2318 Fixture {
2319 server,
2320 session: Session::new(7),
2321 argv: Argv::new(),
2322 out: Out::new(Proto::Resp2),
2323 }
2324 }
2325
2326 fn run(&mut self, parts: &[&[u8]]) -> String {
2328 self.flow(parts).1
2329 }
2330
2331 fn raw(&mut self, parts: &[&[u8]]) -> Vec<u8> {
2337 let wire = encode(parts);
2338 self.argv.decode(&wire, &Limits::default()).unwrap();
2339 self.out.clear();
2340 execute(
2341 &self.server,
2342 &mut self.session,
2343 Args::new(&self.argv, &wire),
2344 &mut self.out,
2345 );
2346 self.out.as_slice().to_vec()
2347 }
2348
2349 fn advance(&mut self, ms: u64) {
2351 self.server.advance_clock_ms(ms);
2352 }
2353
2354 fn other(&mut self, parts: &[&[u8]]) -> String {
2359 self.other_in(self.session.db(), parts)
2360 }
2361
2362 fn other_in(&mut self, db: usize, parts: &[&[u8]]) -> String {
2364 let mut session = Session::new(8);
2365 session.db = db;
2366 let reply = self.by(&mut session, parts);
2367 forget_session(&self.server, &mut session);
2368 reply
2369 }
2370
2371 fn by(&mut self, session: &mut Session, parts: &[&[u8]]) -> String {
2373 let wire = encode(parts);
2374 let mut argv = Argv::new();
2375 argv.decode(&wire, &Limits::default()).unwrap();
2376 let mut out = Out::new(Proto::Resp2);
2377 execute(&self.server, session, Args::new(&argv, &wire), &mut out);
2378 String::from_utf8_lossy(out.as_slice()).into_owned()
2379 }
2380
2381 fn flow(&mut self, parts: &[&[u8]]) -> (Flow, String) {
2383 let wire = encode(parts);
2384 self.argv.decode(&wire, &Limits::default()).unwrap();
2385 self.out.clear();
2386 let flow = execute(
2387 &self.server,
2388 &mut self.session,
2389 Args::new(&self.argv, &wire),
2390 &mut self.out,
2391 );
2392 (
2393 flow,
2394 String::from_utf8_lossy(self.out.as_slice()).into_owned(),
2395 )
2396 }
2397 }
2398
2399 #[test]
2400 fn multi_holds_commands_and_exec_runs_them() {
2401 let mut f = Fixture::new();
2402 assert_eq!(f.run(&[b"MULTI"]), "+OK\r\n");
2403 assert_eq!(f.run(&[b"SET", b"k", b"1"]), "+QUEUED\r\n");
2404 assert_eq!(f.run(&[b"INCR", b"k"]), "+QUEUED\r\n");
2405 assert_eq!(f.other(&[b"GET", b"k"]), "$-1\r\n");
2407 assert_eq!(f.run(&[b"EXEC"]), "*2\r\n+OK\r\n:2\r\n");
2408 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n2\r\n");
2409 }
2410
2411 #[test]
2412 fn an_empty_transaction_answers_an_empty_array() {
2413 let mut f = Fixture::new();
2414 f.run(&[b"MULTI"]);
2415 assert_eq!(f.run(&[b"EXEC"]), "*0\r\n");
2416 }
2417
2418 #[test]
2419 fn exec_and_discard_want_a_transaction_to_be_open() {
2420 let mut f = Fixture::new();
2421 assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2422 assert_eq!(f.run(&[b"DISCARD"]), "-ERR DISCARD without MULTI\r\n");
2423 assert_eq!(f.run(&[b"UNWATCH"]), "+OK\r\n");
2426 }
2427
2428 #[test]
2429 fn an_error_a_command_body_raises_leaves_the_transaction_alive() {
2430 let mut f = Fixture::new();
2431 f.run(&[b"MULTI"]);
2432 assert_eq!(
2433 f.run(&[b"MULTI"]),
2434 "-ERR MULTI calls can not be nested\r\n",
2435 "nested MULTI is raised by the command and not by the funnel"
2436 );
2437 assert_eq!(
2438 f.run(&[b"WATCH", b"k"]),
2439 "-ERR WATCH inside MULTI is not allowed\r\n"
2440 );
2441 f.run(&[b"SET", b"k", b"1"]);
2442 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+OK\r\n");
2443 }
2444
2445 #[test]
2446 fn an_error_the_funnel_raises_kills_the_transaction() {
2447 for bad in [
2448 &[b"NOSUCHCOMMAND".as_slice()] as &[&[u8]],
2449 &[b"GET".as_slice()],
2450 ] {
2451 let mut f = Fixture::new();
2452 f.run(&[b"MULTI"]);
2453 assert!(f.run(bad).starts_with("-ERR "));
2454 assert_eq!(
2455 f.run(&[b"SET", b"k", b"1"]),
2456 "+QUEUED\r\n",
2457 "a dead transaction still answers QUEUED, which is Redis"
2458 );
2459 assert_eq!(
2460 f.run(&[b"EXEC"]),
2461 "-EXECABORT Transaction discarded because of previous errors.\r\n"
2462 );
2463 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2464 }
2465 }
2466
2467 #[test]
2468 fn exec_with_an_argument_is_an_abort_and_not_an_arity_error() {
2469 let mut f = Fixture::new();
2470 f.run(&[b"MULTI"]);
2471 f.run(&[b"SET", b"k", b"1"]);
2472 assert_eq!(
2473 f.run(&[b"EXEC", b"x"]),
2474 "-EXECABORT Transaction discarded because of: wrong number of arguments for 'exec' command\r\n"
2475 );
2476 assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2477 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2478 }
2479
2480 #[test]
2481 fn a_command_a_transaction_may_not_hold_kills_it() {
2482 let mut f = Fixture::new();
2483 f.run(&[b"MULTI"]);
2484 assert_eq!(
2485 f.run(&[b"SHUTDOWN", b"NOSAVE"]),
2486 "-ERR Command not allowed inside a transaction\r\n"
2487 );
2488 assert_eq!(
2489 f.run(&[b"EXEC"]),
2490 "-EXECABORT Transaction discarded because of previous errors.\r\n"
2491 );
2492 }
2493
2494 #[test]
2495 fn a_failing_command_inside_exec_is_an_element_and_the_rest_still_runs() {
2496 let mut f = Fixture::new();
2497 f.run(&[b"RPUSH", b"l", b"v"]);
2498 f.run(&[b"MULTI"]);
2499 f.run(&[b"INCR", b"l"]);
2500 f.run(&[b"SET", b"y", b"2"]);
2501 assert_eq!(
2502 f.run(&[b"EXEC"]),
2503 "*2\r\n-WRONGTYPE Operation against a key holding the wrong kind of value\r\n+OK\r\n"
2504 );
2505 assert_eq!(f.run(&[b"GET", b"y"]), "$1\r\n2\r\n");
2506 }
2507
2508 #[test]
2509 fn discard_and_reset_both_throw_the_queue_away() {
2510 let mut f = Fixture::new();
2511 f.run(&[b"MULTI"]);
2512 f.run(&[b"SET", b"k", b"1"]);
2513 assert_eq!(f.run(&[b"DISCARD"]), "+OK\r\n");
2514 assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2515 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2516
2517 f.run(&[b"MULTI"]);
2518 f.run(&[b"SET", b"k", b"1"]);
2519 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
2520 assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2521 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2522 }
2523
2524 #[test]
2525 fn select_is_queued_and_applied_when_exec_runs_it() {
2526 let mut f = Fixture::new();
2527 f.run(&[b"MULTI"]);
2528 assert_eq!(f.run(&[b"SELECT", b"3"]), "+QUEUED\r\n");
2529 f.run(&[b"SET", b"k", b"1"]);
2530 assert_eq!(f.run(&[b"EXEC"]), "*2\r\n+OK\r\n+OK\r\n");
2531 assert_eq!(f.session.db(), 3, "the SELECT applied and stayed applied");
2532 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n1\r\n");
2533 }
2534
2535 #[test]
2536 fn a_write_by_another_connection_fails_the_transaction() {
2537 let mut f = Fixture::new();
2538 f.run(&[b"SET", b"k", b"1"]);
2539 assert_eq!(f.run(&[b"WATCH", b"k"]), "+OK\r\n");
2540 f.other(&[b"SET", b"k", b"2"]);
2541 f.run(&[b"MULTI"]);
2542 f.run(&[b"GET", b"k"]);
2543 assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
2544 }
2545
2546 #[test]
2547 fn a_write_that_puts_the_same_value_back_still_fails_it() {
2548 let mut f = Fixture::new();
2549 f.run(&[b"SET", b"k", b"1"]);
2550 f.run(&[b"WATCH", b"k"]);
2551 f.other(&[b"SET", b"k", b"1"]);
2552 f.run(&[b"MULTI"]);
2553 assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
2554 }
2555
2556 #[test]
2557 fn a_read_by_another_connection_does_not() {
2558 let mut f = Fixture::new();
2559 f.run(&[b"SET", b"k", b"1"]);
2560 f.run(&[b"WATCH", b"k"]);
2561 f.other(&[b"GET", b"k"]);
2562 f.other(&[b"STRLEN", b"k"]);
2563 f.run(&[b"MULTI"]);
2564 f.run(&[b"GET", b"k"]);
2565 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n1\r\n");
2566 }
2567
2568 #[test]
2569 fn deleting_a_key_that_was_never_there_does_not_fail_a_watch_on_it() {
2570 let mut f = Fixture::new();
2571 f.run(&[b"WATCH", b"k"]);
2572 f.other(&[b"DEL", b"k"]);
2573 f.run(&[b"MULTI"]);
2574 f.run(&[b"PING"]);
2575 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+PONG\r\n");
2576 f.run(&[b"WATCH", b"k"]);
2578 f.other(&[b"SET", b"k", b"1"]);
2579 f.run(&[b"MULTI"]);
2580 assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
2581 }
2582
2583 #[test]
2584 fn a_watched_key_that_expires_fails_the_transaction() {
2585 let mut f = Fixture::new();
2586 f.run(&[b"SET", b"k", b"1", b"PX", b"50"]);
2587 f.run(&[b"WATCH", b"k"]);
2588 f.run(&[b"MULTI"]);
2589 f.advance(100);
2590 assert_eq!(
2591 f.run(&[b"EXEC"]),
2592 "*-1\r\n",
2593 "nothing wrote to the key, so only the liveness check can catch this"
2594 );
2595 }
2596
2597 #[test]
2598 fn every_way_a_transaction_ends_lets_go_of_the_watches() {
2599 for end in [
2600 &[b"EXEC".as_slice()] as &[&[u8]],
2601 &[b"DISCARD".as_slice()],
2602 &[b"UNWATCH".as_slice()],
2603 &[b"RESET".as_slice()],
2604 ] {
2605 let mut f = Fixture::new();
2606 f.run(&[b"SET", b"k", b"1"]);
2607 f.run(&[b"WATCH", b"k"]);
2608 if end[0] != b"UNWATCH" && end[0] != b"RESET" {
2609 f.run(&[b"MULTI"]);
2610 }
2611 f.run(end);
2612 assert!(!f.server.watching(), "{end:?} left a row behind");
2613 f.other(&[b"SET", b"k", b"2"]);
2615 f.run(&[b"MULTI"]);
2616 f.run(&[b"GET", b"k"]);
2617 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n2\r\n");
2618 }
2619 }
2620
2621 #[test]
2622 fn a_connection_going_away_lets_go_of_its_watches() {
2623 let mut f = Fixture::new();
2624 f.run(&[b"SET", b"k", b"1"]);
2625 f.run(&[b"WATCH", b"k"]);
2626 assert!(f.server.watching());
2627 forget_session(&f.server, &mut f.session);
2628 assert!(!f.server.watching());
2629 }
2630
2631 #[test]
2632 fn watching_the_same_key_twice_is_one_watch() {
2633 let mut f = Fixture::new();
2634 f.run(&[b"SET", b"k", b"1"]);
2635 f.run(&[b"WATCH", b"k", b"k"]);
2636 f.run(&[b"UNWATCH"]);
2637 assert!(
2638 !f.server.watching(),
2639 "the row counts watchers, so a doubled watch would leave one behind"
2640 );
2641 }
2642
2643 #[test]
2644 fn two_connections_can_watch_the_same_key() {
2645 let mut f = Fixture::new();
2646 f.run(&[b"SET", b"k", b"1"]);
2647 f.run(&[b"WATCH", b"k"]);
2648 let mut second = Session::new(9);
2649 second.db = f.session.db();
2650 assert_eq!(f.by(&mut second, &[b"WATCH", b"k"]), "+OK\r\n");
2651 forget_session(&f.server, &mut second);
2653 assert!(f.server.watching());
2654 f.other(&[b"SET", b"k", b"2"]);
2655 f.run(&[b"MULTI"]);
2656 assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
2657 }
2658
2659 #[test]
2660 fn flushdb_fails_a_watch_on_a_key_that_was_there() {
2661 let mut f = Fixture::new();
2662 f.run(&[b"SET", b"k", b"1"]);
2663 f.run(&[b"WATCH", b"k"]);
2664 f.other(&[b"FLUSHDB"]);
2665 f.run(&[b"MULTI"]);
2666 assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
2667 }
2668
2669 #[test]
2670 fn flushdb_does_not_fail_a_watch_on_a_key_that_was_not() {
2671 let mut f = Fixture::new();
2672 f.run(&[b"WATCH", b"k"]);
2673 f.other(&[b"FLUSHDB"]);
2674 f.run(&[b"MULTI"]);
2675 f.run(&[b"PING"]);
2676 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+PONG\r\n");
2677 }
2678
2679 #[test]
2680 fn a_watch_is_on_a_database_and_a_key_and_not_on_a_key() {
2681 let mut f = Fixture::new();
2682 f.run(&[b"SET", b"k", b"1"]);
2683 f.run(&[b"WATCH", b"k"]);
2684 let elsewhere = f.session.db() + 1;
2686 f.other_in(elsewhere, &[b"SET", b"k", b"9"]);
2687 f.run(&[b"MULTI"]);
2688 f.run(&[b"GET", b"k"]);
2689 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n1\r\n");
2690 }
2691
2692 #[test]
2693 fn a_write_that_reaches_a_key_it_did_not_name_still_fails_a_watch() {
2694 let mut f = Fixture::new();
2695 f.run(&[b"RPUSH", b"src", b"1"]);
2696 f.run(&[b"WATCH", b"dst"]);
2697 f.other(&[b"SORT", b"src", b"STORE", b"dst"]);
2698 f.run(&[b"MULTI"]);
2699 assert_eq!(
2700 f.run(&[b"EXEC"]),
2701 "*-1\r\n",
2702 "SORT is movablekeys, so every watched key in the database is asked"
2703 );
2704 }
2705
2706 #[test]
2707 fn a_server_nobody_is_watching_says_so() {
2708 let mut f = Fixture::new();
2709 assert!(!f.server.watching());
2710 f.run(&[b"SET", b"k", b"1"]);
2711 assert!(!f.server.watching());
2712 }
2713
2714 #[test]
2720 fn the_count_a_subscribe_answers_covers_channels_and_patterns() {
2721 let mut f = Fixture::new();
2722 assert_eq!(
2723 f.run(&[b"SUBSCRIBE", b"a", b"b"]),
2724 "*3\r\n$9\r\nsubscribe\r\n$1\r\na\r\n:1\r\n*3\r\n$9\r\nsubscribe\r\n$1\r\nb\r\n:2\r\n"
2725 );
2726 assert_eq!(
2727 f.run(&[b"PSUBSCRIBE", b"c*"]),
2728 "*3\r\n$10\r\npsubscribe\r\n$2\r\nc*\r\n:3\r\n"
2729 );
2730 assert_eq!(
2731 f.run(&[b"SSUBSCRIBE", b"s"]),
2732 "*3\r\n$10\r\nssubscribe\r\n$1\r\ns\r\n:1\r\n"
2733 );
2734 assert_eq!(
2737 f.run(&[b"SUBSCRIBE", b"a"]),
2738 "*3\r\n$9\r\nsubscribe\r\n$1\r\na\r\n:3\r\n"
2739 );
2740 }
2741
2742 #[test]
2745 fn unsubscribe_answers_for_names_it_was_not_holding_too() {
2746 let mut f = Fixture::new();
2747 f.run(&[b"SUBSCRIBE", b"a"]);
2748
2749 assert_eq!(
2752 f.run(&[b"UNSUBSCRIBE", b"zz"]),
2753 "*3\r\n$11\r\nunsubscribe\r\n$2\r\nzz\r\n:1\r\n"
2754 );
2755 f.run(&[b"SUBSCRIBE", b"b"]);
2757 f.run(&[b"PSUBSCRIBE", b"p*"]);
2758 assert_eq!(
2759 f.run(&[b"UNSUBSCRIBE"]),
2760 "*3\r\n$11\r\nunsubscribe\r\n$1\r\na\r\n:2\r\n*3\r\n$11\r\nunsubscribe\r\n$1\r\nb\r\n:1\r\n"
2761 );
2762 assert_eq!(
2765 f.run(&[b"UNSUBSCRIBE"]),
2766 "*3\r\n$11\r\nunsubscribe\r\n$-1\r\n:1\r\n",
2767 "the pattern is still held, so the count is one"
2768 );
2769 assert_eq!(
2770 f.run(&[b"SUNSUBSCRIBE"]),
2771 "*3\r\n$12\r\nsunsubscribe\r\n$-1\r\n:0\r\n",
2772 "shard channels are counted on their own"
2773 );
2774 }
2775
2776 #[test]
2781 fn the_subscribe_gate_does_not_reach_inside_exec() {
2782 let mut f = Fixture::new();
2783 f.run(&[b"SET", b"k", b"1"]);
2784 f.run(&[b"MULTI"]);
2785 assert_eq!(f.run(&[b"SUBSCRIBE", b"z"]), "+QUEUED\r\n");
2786 assert_eq!(f.run(&[b"GET", b"k"]), "+QUEUED\r\n");
2787 assert_eq!(
2788 f.run(&[b"EXEC"]),
2789 "*2\r\n*3\r\n$9\r\nsubscribe\r\n$1\r\nz\r\n:1\r\n$1\r\n1\r\n"
2790 );
2791 assert_eq!(
2794 f.run(&[b"GET", b"k"]),
2795 "-ERR Can't execute 'get': only (P|S)SUBSCRIBE / (P|S)UNSUBSCRIBE / PING / QUIT / RESET are allowed in this context\r\n"
2796 );
2797 }
2798
2799 #[test]
2802 fn exec_sent_by_a_subscriber_aborts_the_transaction() {
2803 let mut f = Fixture::new();
2804 f.run(&[b"MULTI"]);
2805 f.run(&[b"SET", b"k", b"1"]);
2806 f.run(&[b"SUBSCRIBE", b"z"]);
2807 f.run(&[b"EXEC"]);
2808 f.run(&[b"MULTI"]);
2809 assert_eq!(
2810 f.run(&[b"EXEC"]),
2811 "-EXECABORT Transaction discarded because of: Can't execute 'exec': only (P|S)SUBSCRIBE / (P|S)UNSUBSCRIBE / PING / QUIT / RESET are allowed in this context\r\n"
2812 );
2813 }
2814
2815 #[test]
2818 fn reset_lets_go_of_every_subscription() {
2819 let mut f = Fixture::new();
2820 f.run(&[b"SUBSCRIBE", b"a"]);
2821 f.run(&[b"PSUBSCRIBE", b"p*"]);
2822 f.run(&[b"SSUBSCRIBE", b"s"]);
2823 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
2824 assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":0\r\n");
2825 assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*0\r\n");
2826 assert_eq!(f.run(&[b"PUBSUB", b"SHARDCHANNELS"]), "*0\r\n");
2827 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2829 }
2830
2831 #[test]
2834 fn pubsub_reports_channels_patterns_and_shard_channels_apart() {
2835 let mut f = Fixture::new();
2836 let mut sub = Session::new(9);
2837 f.by(&mut sub, &[b"SUBSCRIBE", b"a"]);
2838 f.by(&mut sub, &[b"PSUBSCRIBE", b"a*"]);
2839 f.by(&mut sub, &[b"SSUBSCRIBE", b"a"]);
2840
2841 assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*1\r\n$1\r\na\r\n");
2842 assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS", b"b*"]), "*0\r\n");
2843 assert_eq!(f.run(&[b"PUBSUB", b"SHARDCHANNELS"]), "*1\r\n$1\r\na\r\n");
2844 assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":1\r\n");
2845 assert_eq!(
2846 f.run(&[b"PUBSUB", b"NUMSUB", b"a", b"zz"]),
2847 "*4\r\n$1\r\na\r\n:1\r\n$2\r\nzz\r\n:0\r\n"
2848 );
2849 assert_eq!(
2850 f.run(&[b"PUBSUB", b"SHARDNUMSUB", b"a"]),
2851 "*2\r\n$1\r\na\r\n:1\r\n",
2852 "the shard channel and the channel share a name and not a count"
2853 );
2854 assert_eq!(f.run(&[b"PUBSUB", b"NUMSUB"]), "*0\r\n");
2855
2856 forget_session(&f.server, &mut sub);
2857 assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":0\r\n");
2858 assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*0\r\n");
2859 }
2860
2861 #[test]
2864 fn the_notification_setting_reads_back_in_the_servers_own_spelling() {
2865 let mut f = Fixture::new();
2866 assert_eq!(
2867 f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
2868 "*2\r\n$22\r\nnotify-keyspace-events\r\n$0\r\n\r\n"
2869 );
2870 assert_eq!(
2871 f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"KEA"]),
2872 "+OK\r\n"
2873 );
2874 assert_eq!(
2877 f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
2878 "*2\r\n$22\r\nnotify-keyspace-events\r\n$3\r\nAKE\r\n"
2879 );
2880 assert_eq!(
2881 f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"Kg"]),
2882 "+OK\r\n"
2883 );
2884 assert_eq!(
2885 f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
2886 "*2\r\n$22\r\nnotify-keyspace-events\r\n$2\r\ngK\r\n"
2887 );
2888 }
2889
2890 #[test]
2891 fn a_letter_the_notification_setting_does_not_know_is_refused() {
2892 let mut f = Fixture::new();
2893 assert_eq!(
2894 f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"KEQ"]),
2895 "-ERR CONFIG SET failed (possibly related to argument 'notify-keyspace-events') \
2896 - Invalid event class character. Use 'Ag$lshzxeKEtmdnocaSTIV'.\r\n"
2897 );
2898 assert_eq!(
2901 f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
2902 "*2\r\n$22\r\nnotify-keyspace-events\r\n$0\r\n\r\n"
2903 );
2904 }
2905
2906 #[test]
2912 fn pubsub_says_no_two_different_ways() {
2913 let mut f = Fixture::new();
2914 assert_eq!(
2915 f.run(&[b"PUBSUB"]),
2916 "-ERR wrong number of arguments for 'pubsub' command\r\n"
2917 );
2918 assert_eq!(
2919 f.run(&[b"PUBSUB", b"NOPE"]),
2920 "-ERR unknown subcommand 'NOPE'. Try PUBSUB HELP.\r\n"
2921 );
2922 assert_eq!(
2923 f.run(&[b"PUBSUB", b"CHANNELS", b"a*", b"b"]),
2924 "-ERR unknown subcommand or wrong number of arguments for 'CHANNELS'. Try PUBSUB HELP.\r\n"
2925 );
2926 assert_eq!(
2927 f.run(&[b"PUBSUB", b"NUMPAT", b"x"]),
2928 "-ERR wrong number of arguments for 'pubsub|numpat' command\r\n"
2929 );
2930 assert_eq!(
2931 f.run(&[b"PUBSUB", b"HELP", b"x"]),
2932 "-ERR wrong number of arguments for 'pubsub|help' command\r\n"
2933 );
2934 }
2935
2936 #[test]
2939 fn publishing_to_nobody_answers_zero() {
2940 let mut f = Fixture::new();
2941 assert_eq!(f.run(&[b"PUBLISH", b"a", b"hi"]), ":0\r\n");
2942 assert_eq!(f.run(&[b"SPUBLISH", b"a", b"hi"]), ":0\r\n");
2943 assert_eq!(f.run(&[b"PUBLISH", b"", b"hi"]), ":0\r\n");
2945 }
2946
2947 #[test]
2951 fn a_publish_counts_the_deliveries_and_not_the_clients() {
2952 let mut f = Fixture::new();
2953 let mut sub = Session::new(9);
2954 f.by(&mut sub, &[b"SUBSCRIBE", b"news"]);
2955 f.by(&mut sub, &[b"PSUBSCRIBE", b"ne*"]);
2956 f.by(&mut sub, &[b"PSUBSCRIBE", b"n*s"]);
2957 assert_eq!(f.run(&[b"PUBLISH", b"news", b"hi"]), ":3\r\n");
2958 forget_session(&f.server, &mut sub);
2959 }
2960
2961 #[cfg_attr(miri, ignore = "megabytes through the arena")]
2974 #[test]
2975 fn rewriting_the_same_keys_does_not_grow_the_server() {
2976 let mut f = Fixture::new();
2977 let val = vec![b'v'; 1024];
2978 let keys: Vec<Vec<u8>> = (0..64).map(|i| format!("key:{i}").into_bytes()).collect();
2979
2980 for k in &keys {
2981 f.run(&[b"SET", k, &val]);
2982 }
2983 f.server.compact_step();
2984 let after_first = f.server.memory_bytes();
2985
2986 for _ in 0..500 {
2991 for k in &keys {
2992 f.run(&[b"SET", k, &val]);
2993 }
2994 f.server.compact_step();
2995 }
2996
2997 assert!(
2998 f.server.memory_bytes() <= after_first * 2,
2999 "held {} after five hundred passes against {after_first} after one",
3000 f.server.memory_bytes()
3001 );
3002 assert_eq!(f.run(&[b"DBSIZE"]), format!(":{}\r\n", keys.len()));
3003 assert_eq!(f.run(&[b"STRLEN", b"key:7"]), ":1024\r\n");
3004 }
3005
3006 #[cfg_attr(miri, ignore = "megabytes through the arena")]
3023 #[test]
3024 fn a_database_nobody_started_on_is_still_collected() {
3025 let mut f = Fixture::new();
3026 assert_eq!(f.run(&[b"SELECT", b"9"]), "+OK\r\n");
3027 let val = vec![b'v'; 1024];
3028 let keys: Vec<Vec<u8>> = (0..64).map(|i| format!("key:{i}").into_bytes()).collect();
3029
3030 for k in &keys {
3031 f.run(&[b"SET", k, &val]);
3032 }
3033 while f.server.compact_step().is_some() {}
3034 assert!(
3035 !f.server.mine().wanted(9),
3036 "database nine was drained and should not be asked again until it is written to"
3037 );
3038 let after_first = f.server.memory_bytes();
3039
3040 for _ in 0..500 {
3041 for k in &keys {
3042 f.run(&[b"SET", k, &val]);
3043 }
3044 f.server.compact_step();
3045 }
3046
3047 assert!(
3048 f.server.memory_bytes() <= after_first * 2,
3049 "held {} after five hundred passes against {after_first} after one",
3050 f.server.memory_bytes()
3051 );
3052 assert_eq!(f.run(&[b"DBSIZE"]), format!(":{}\r\n", keys.len()));
3053 assert_eq!(f.run(&[b"STRLEN", b"key:7"]), ":1024\r\n");
3054 f.run(&[b"SELECT", b"0"]);
3056 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3057 }
3058
3059 #[test]
3060 fn a_command_goes_from_bytes_to_bytes() {
3061 let mut f = Fixture::new();
3062 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
3063 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
3064 assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
3065 assert_eq!(f.run(&[b"STRLEN", b"k"]), ":1\r\n");
3066 assert_eq!(f.run(&[b"set", b"k", b"v2", b"xx"]), "+OK\r\n");
3068 assert_eq!(f.run(&[b"GET", b"k"]), "$2\r\nv2\r\n");
3069 }
3070
3071 #[test]
3072 fn deleting_counts_keys_removed_and_existing_counts_arguments_matched() {
3073 let mut f = Fixture::new();
3074 f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
3075 assert_eq!(f.run(&[b"EXISTS", b"a", b"a", b"nosuch"]), ":2\r\n");
3078 assert_eq!(f.run(&[b"DEL", b"a", b"a", b"nosuch"]), ":1\r\n");
3079 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
3080 assert_eq!(f.run(&[b"UNLINK", b"b", b"c"]), ":2\r\n");
3082 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3083 }
3084
3085 #[test]
3086 fn type_is_a_simple_string_and_says_none_for_a_key_that_is_not_there() {
3087 let mut f = Fixture::new();
3088 f.run(&[b"SET", b"k", b"v"]);
3089 assert_eq!(f.run(&[b"TYPE", b"k"]), "+string\r\n");
3092 assert_eq!(f.run(&[b"TYPE", b"nosuch"]), "+none\r\n");
3093 }
3094
3095 #[test]
3096 fn touch_counts_the_way_exists_counts() {
3097 let mut f = Fixture::new();
3098 f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
3099 assert_eq!(f.run(&[b"TOUCH", b"a", b"b"]), ":2\r\n");
3100 assert_eq!(
3101 f.run(&[b"TOUCH", b"a", b"a"]),
3102 ":2\r\n",
3103 "twice counts twice"
3104 );
3105 assert_eq!(f.run(&[b"TOUCH", b"a", b"nosuch"]), ":1\r\n");
3106 assert_eq!(f.run(&[b"TOUCH", b"nosuch"]), ":0\r\n");
3107 }
3108
3109 #[test]
3110 fn a_rename_moves_the_deadline_with_the_value_and_drops_the_one_it_lands_on() {
3111 let mut f = Fixture::new();
3112 f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
3113 f.run(&[b"SET", b"b", b"v2", b"EX", b"500"]);
3114
3115 assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "+OK\r\n");
3116 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
3117 assert_eq!(
3118 f.run(&[b"TTL", b"b"]),
3119 ":100\r\n",
3120 "the source's and not b's"
3121 );
3122 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
3123 }
3124
3125 #[test]
3126 fn a_rename_with_no_source_is_an_error_and_not_a_zero() {
3127 let mut f = Fixture::new();
3128 assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "-ERR no such key\r\n");
3129 assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), "-ERR no such key\r\n");
3132 }
3133
3134 #[test]
3135 fn renamenx_refuses_a_taken_name_including_the_one_it_already_has() {
3136 let mut f = Fixture::new();
3137 f.run(&[b"MSET", b"a", b"v1", b"b", b"v2"]);
3138
3139 assert_eq!(f.run(&[b"RENAMENX", b"a", b"b"]), ":0\r\n");
3140 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
3141 assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), ":0\r\n");
3144 assert_eq!(f.run(&[b"RENAME", b"a", b"a"]), "+OK\r\n");
3145 assert_eq!(f.run(&[b"RENAMENX", b"a", b"c"]), ":1\r\n");
3146 assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\nv1\r\n");
3147 }
3148
3149 #[test]
3150 fn renaming_a_set_does_not_touch_a_member() {
3151 let mut f = Fixture::new();
3152 for i in 0..300 {
3153 f.run(&[b"SADD", b"s", format!("m{i}").as_bytes()]);
3154 }
3155 let before = f.server.memory_bytes();
3156
3157 assert_eq!(f.run(&[b"RENAME", b"s", b"t"]), "+OK\r\n");
3158 assert_eq!(f.run(&[b"SCARD", b"t"]), ":300\r\n");
3159 assert_eq!(f.run(&[b"TYPE", b"t"]), "+set\r\n");
3160 assert!(
3161 f.server.memory_bytes().abs_diff(before) < 256,
3162 "the members were copied: {} against {before}",
3163 f.server.memory_bytes()
3164 );
3165 }
3166
3167 #[test]
3168 fn a_copy_is_a_second_value_and_not_a_second_name() {
3169 let mut f = Fixture::new();
3170 f.run(&[b"SADD", b"s", b"m1", b"m2"]);
3171
3172 assert_eq!(f.run(&[b"COPY", b"s", b"t"]), ":1\r\n");
3173 f.run(&[b"SADD", b"t", b"m3"]);
3174 assert_eq!(f.run(&[b"SCARD", b"s"]), ":2\r\n", "the original is intact");
3175 assert_eq!(f.run(&[b"SCARD", b"t"]), ":3\r\n");
3176 }
3177
3178 #[test]
3188 fn every_type_can_be_copied() {
3189 let mut f = Fixture::new();
3190 f.run(&[b"SET", b"str", b"v1"]);
3191 f.run(&[b"SADD", b"set", b"m1"]);
3192 f.run(&[b"HSET", b"hash", b"f", b"v"]);
3193 f.run(&[b"RPUSH", b"list", b"a", b"b"]);
3194 f.run(&[b"ZADD", b"zset", b"1", b"m1"]);
3195
3196 for name in [
3197 &b"str"[..],
3198 &b"set"[..],
3199 &b"hash"[..],
3200 &b"list"[..],
3201 &b"zset"[..],
3202 ] {
3203 let dst = [name, b":copy"].concat();
3204 assert_eq!(
3205 f.run(&[b"COPY", name, &dst]),
3206 ":1\r\n",
3207 "copying {}",
3208 String::from_utf8_lossy(name)
3209 );
3210 assert_eq!(f.run(&[b"TYPE", name]), f.run(&[b"TYPE", &dst]));
3211 }
3212
3213 assert_eq!(f.run(&[b"LRANGE", b"list:copy", b"0", b"-1"]), {
3214 let mut want = String::from("*2\r\n");
3215 want.push_str("$1\r\na\r\n$1\r\nb\r\n");
3216 want
3217 });
3218 assert_eq!(f.run(&[b"ZSCORE", b"zset:copy", b"m1"]), "$1\r\n1\r\n");
3219
3220 f.run(&[b"RPUSH", b"list:copy", b"c"]);
3222 assert_eq!(f.run(&[b"LLEN", b"list"]), ":2\r\n");
3223 assert_eq!(f.run(&[b"LLEN", b"list:copy"]), ":3\r\n");
3224 }
3225
3226 #[test]
3227 fn a_copy_refuses_a_taken_destination_until_it_is_told_it_can_have_it() {
3228 let mut f = Fixture::new();
3229 f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
3230 f.run(&[b"SET", b"b", b"v2"]);
3231
3232 assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":0\r\n");
3233 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
3234 assert_eq!(f.run(&[b"COPY", b"a", b"b", b"REPLACE"]), ":1\r\n");
3235 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
3236 assert_eq!(f.run(&[b"TTL", b"b"]), ":100\r\n", "the deadline came too");
3237 assert_eq!(f.run(&[b"COPY", b"nosuch", b"z"]), ":0\r\n");
3238 }
3239
3240 #[test]
3241 fn a_copy_into_another_database_is_a_copy_and_onto_itself_there_is_too() {
3242 let mut f = Fixture::new();
3243 f.run(&[b"SET", b"a", b"v1"]);
3244
3245 assert_eq!(f.run(&[b"COPY", b"a", b"a", b"DB", b"1"]), ":1\r\n");
3248 f.run(&[b"SELECT", b"1"]);
3249 assert_eq!(f.run(&[b"GET", b"a"]), "$2\r\nv1\r\n");
3250 assert_eq!(
3251 f.run(&[b"COPY", b"a", b"a", b"DB", b"0"]),
3252 ":0\r\n",
3253 "taken"
3254 );
3255 assert_eq!(
3256 f.run(&[b"COPY", b"a", b"a", b"DB", b"0", b"REPLACE"]),
3257 ":1\r\n"
3258 );
3259 }
3260
3261 #[test]
3262 fn sort_takes_its_options_in_any_order_and_the_last_one_wins() {
3263 let mut f = Fixture::new();
3264 f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
3265 assert_eq!(
3266 f.run(&[b"SORT", b"l"]),
3267 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
3268 );
3269 assert_eq!(
3271 f.run(&[b"SORT", b"l", b"DESC", b"asc"]),
3272 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
3273 );
3274 assert_eq!(
3275 f.run(&[b"sort", b"l", b"LIMIT", b"1", b"1", b"DESC"]),
3276 "*1\r\n$1\r\n2\r\n"
3277 );
3278 }
3279
3280 #[test]
3281 fn sort_reads_a_key_per_element_for_by_and_for_get() {
3282 let mut f = Fixture::new();
3283 f.run(&[b"RPUSH", b"l", b"a", b"b"]);
3284 f.run(&[b"MSET", b"w_a", b"2", b"w_b", b"1", b"d_b", b"bee"]);
3285 assert_eq!(
3288 f.run(&[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"]),
3289 "*4\r\n$1\r\nb\r\n$3\r\nbee\r\n$1\r\na\r\n$-1\r\n"
3290 );
3291 }
3292
3293 #[test]
3294 fn sort_store_writes_a_list_and_answers_its_length() {
3295 let mut f = Fixture::new();
3296 f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
3297 assert_eq!(f.run(&[b"SORT", b"l", b"STORE", b"out"]), ":3\r\n");
3298 assert_eq!(f.run(&[b"TYPE", b"out"]), "+list\r\n");
3299 assert_eq!(
3300 f.run(&[b"LRANGE", b"out", b"0", b"-1"]),
3301 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
3302 );
3303 assert_eq!(f.run(&[b"SORT", b"missing", b"STORE", b"out"]), ":0\r\n");
3306 assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
3307 }
3308
3309 #[test]
3310 fn sort_ro_does_not_know_the_word_store() {
3311 let mut f = Fixture::new();
3312 f.run(&[b"RPUSH", b"l", b"2", b"1"]);
3313 assert_eq!(f.run(&[b"SORT_RO", b"l"]), "*2\r\n$1\r\n1\r\n$1\r\n2\r\n");
3314 assert_eq!(
3315 f.run(&[b"SORT_RO", b"l", b"STORE", b"d"]),
3316 "-ERR syntax error\r\n"
3317 );
3318 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
3319 }
3320
3321 #[test]
3322 fn sort_refuses_what_it_cannot_sort() {
3323 let mut f = Fixture::new();
3324 assert_eq!(f.run(&[b"SORT", b"nosuchkey"]), "*0\r\n");
3325 f.run(&[b"SET", b"s", b"x"]);
3326 assert_eq!(
3327 f.run(&[b"SORT", b"s"]),
3328 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
3329 );
3330 f.run(&[b"RPUSH", b"words", b"one", b"two"]);
3331 assert_eq!(
3332 f.run(&[b"SORT", b"words"]),
3333 "-ERR One or more scores can't be converted into double\r\n"
3334 );
3335 assert_eq!(
3336 f.run(&[b"SORT", b"words", b"ALPHA"]),
3337 "*2\r\n$3\r\none\r\n$3\r\ntwo\r\n"
3338 );
3339 assert_eq!(f.run(&[b"SORT", b"words", b"BY"]), "-ERR syntax error\r\n");
3340 }
3341
3342 #[test]
3343 fn move_takes_the_key_out_of_one_database_and_puts_it_in_another() {
3344 let mut f = Fixture::new();
3345 assert_eq!(f.run(&[b"RPUSH", b"l", b"a", b"b"]), ":2\r\n");
3346 assert_eq!(f.run(&[b"MOVE", b"l", b"1"]), ":1\r\n");
3347 assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
3348 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3349 assert_eq!(
3350 f.run(&[b"LRANGE", b"l", b"0", b"-1"]),
3351 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
3352 );
3353 assert_eq!(f.run(&[b"MOVE", b"l", b"0"]), ":1\r\n");
3356 assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
3357 }
3358
3359 #[test]
3360 fn move_answers_zero_when_either_end_says_no() {
3361 let mut f = Fixture::new();
3362 assert_eq!(f.run(&[b"MOVE", b"nope", b"1"]), ":0\r\n");
3363 assert_eq!(f.run(&[b"SET", b"a", b"here"]), "+OK\r\n");
3364 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3365 assert_eq!(f.run(&[b"SET", b"a", b"there"]), "+OK\r\n");
3366 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
3367 assert_eq!(f.run(&[b"MOVE", b"a", b"1"]), ":0\r\n");
3370 assert_eq!(f.run(&[b"GET", b"a"]), "$4\r\nhere\r\n");
3371 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3372 assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nthere\r\n");
3373 }
3374
3375 #[test]
3376 fn move_refuses_a_database_that_is_not_one_and_the_one_it_is_on() {
3377 let mut f = Fixture::new();
3378 assert_eq!(
3379 f.run(&[b"MOVE", b"a", b"0"]),
3380 "-ERR source and destination objects are the same\r\n"
3381 );
3382 assert_eq!(
3383 f.run(&[b"MOVE", b"a", b"99"]),
3384 "-ERR DB index is out of range\r\n"
3385 );
3386 assert_eq!(
3387 f.run(&[b"MOVE", b"a", b"-1"]),
3388 "-ERR DB index is out of range\r\n"
3389 );
3390 assert_eq!(
3391 f.run(&[b"MOVE", b"a", b"x"]),
3392 "-ERR value is not an integer or out of range\r\n"
3393 );
3394 }
3395
3396 #[test]
3397 fn swapdb_swaps_what_two_connections_would_see() {
3398 let mut f = Fixture::new();
3399 assert_eq!(f.run(&[b"SET", b"k", b"zero"]), "+OK\r\n");
3400 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3401 assert_eq!(f.run(&[b"SET", b"k", b"one"]), "+OK\r\n");
3402 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
3403
3404 assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
3405 assert_eq!(f.run(&[b"GET", b"k"]), "$3\r\none\r\n");
3407 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3408 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
3409 assert_eq!(f.run(&[b"SWAPDB", b"1", b"1"]), "+OK\r\n");
3411 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
3412 }
3413
3414 #[test]
3419 fn a_wide_server_puts_its_databases_on_its_own_clock() {
3420 let mut f = Fixture::striped(8);
3421 f.server.set_clock_ms(1_700_000_000_000);
3422 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"100"]), "+OK\r\n");
3423 assert_eq!(f.run(&[b"EXPIRETIME", b"k"]), ":1700000100\r\n");
3424 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
3425 f.server.set_clock_ms(1_700_000_050_000);
3426 assert_eq!(f.run(&[b"TTL", b"k"]), ":50\r\n");
3427 }
3428
3429 #[test]
3434 fn swapdb_swaps_every_stripe_of_a_wide_database() {
3435 let mut f = Fixture::striped(4);
3436 for i in 0..16u32 {
3437 let key = format!("k{i}");
3438 assert_eq!(f.run(&[b"SET", key.as_bytes(), b"zero"]), "+OK\r\n");
3439 }
3440 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3441 assert_eq!(f.run(&[b"SET", b"only", b"one"]), "+OK\r\n");
3442 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
3443
3444 assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
3445 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
3446 assert_eq!(f.run(&[b"GET", b"only"]), "$3\r\none\r\n");
3447 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3448 assert_eq!(f.run(&[b"DBSIZE"]), ":16\r\n");
3449 for i in 0..16u32 {
3450 let key = format!("k{i}");
3451 assert_eq!(f.run(&[b"GET", key.as_bytes()]), "$4\r\nzero\r\n");
3452 }
3453 }
3454
3455 #[test]
3456 fn swapdb_says_which_index_it_could_not_read() {
3457 let mut f = Fixture::new();
3458 assert_eq!(
3459 f.run(&[b"SWAPDB", b"x", b"1"]),
3460 "-ERR invalid first DB index\r\n"
3461 );
3462 assert_eq!(
3463 f.run(&[b"SWAPDB", b"0", b"y"]),
3464 "-ERR invalid second DB index\r\n"
3465 );
3466 assert_eq!(
3470 f.run(&[b"SWAPDB", b"99999999999999", b"1"]),
3471 "-ERR invalid first DB index\r\n"
3472 );
3473 assert_eq!(
3474 f.run(&[b"SWAPDB", b"0", b"99"]),
3475 "-ERR DB index is out of range\r\n"
3476 );
3477 assert_eq!(
3478 f.run(&[b"SWAPDB", b"-1", b"0"]),
3479 "-ERR DB index is out of range\r\n"
3480 );
3481 }
3482
3483 #[test]
3484 fn wait_answers_zero_replicas_without_waiting() {
3485 let mut f = Fixture::new();
3486 assert_eq!(f.run(&[b"SET", b"a", b"v"]), "+OK\r\n");
3487 assert_eq!(f.run(&[b"WAIT", b"0", b"0"]), ":0\r\n");
3488 assert_eq!(f.run(&[b"WAIT", b"3", b"1000"]), ":0\r\n");
3491 assert_eq!(f.run(&[b"WAIT", b"-1", b"0"]), ":0\r\n");
3494 assert_eq!(
3495 f.run(&[b"WAIT", b"x", b"0"]),
3496 "-ERR value is not an integer or out of range\r\n"
3497 );
3498 assert_eq!(
3499 f.run(&[b"WAIT", b"0", b"-1"]),
3500 "-ERR timeout is negative\r\n"
3501 );
3502 assert_eq!(
3503 f.run(&[b"WAIT", b"0", b"1.5"]),
3504 "-ERR timeout is not an integer or out of range\r\n"
3505 );
3506 }
3507
3508 #[test]
3509 fn waitaof_answers_two_zeroes_and_refuses_a_local_wait() {
3510 let mut f = Fixture::new();
3511 assert_eq!(f.run(&[b"WAITAOF", b"0", b"0", b"0"]), "*2\r\n:0\r\n:0\r\n");
3512 assert_eq!(
3513 f.run(&[b"WAITAOF", b"1", b"0", b"0"]),
3514 "-ERR WAITAOF cannot be used when numlocal is set but appendonly is disabled.\r\n"
3515 );
3516 assert_eq!(
3517 f.run(&[b"WAITAOF", b"2", b"0", b"0"]),
3518 "-ERR value is out of range, value must between 0 and 1\r\n"
3519 );
3520 assert_eq!(
3521 f.run(&[b"WAITAOF", b"0", b"-1", b"0"]),
3522 "-ERR value is out of range, must be positive\r\n"
3523 );
3524 assert_eq!(
3527 f.run(&[b"WAITAOF", b"1", b"0", b"-5"]),
3528 "-ERR timeout is negative\r\n"
3529 );
3530 }
3531
3532 fn payload(reply: &[u8]) -> Vec<u8> {
3536 let head = reply.windows(2).position(|w| w == b"\r\n").unwrap();
3537 reply[head + 2..reply.len() - 2].to_vec()
3538 }
3539
3540 #[test]
3541 fn a_value_survives_a_dump_and_a_restore() {
3542 let mut f = Fixture::new();
3543 f.run(&[b"SET", b"s", b"hello"]);
3544 f.run(&[b"RPUSH", b"l", b"a", b"b", b"c"]);
3545 f.run(&[b"SADD", b"t", b"1", b"2", b"3"]);
3546 f.run(&[b"SADD", b"u", b"x", b"y"]);
3547 f.run(&[b"HSET", b"h", b"f", b"1", b"g", b"2"]);
3548 f.run(&[b"ZADD", b"z", b"1.5", b"a", b"2.5", b"b"]);
3549
3550 for key in [&b"s"[..], b"l", b"t", b"u", b"h", b"z"] {
3551 let mut copy = key.to_vec();
3552 copy.push(b'2');
3553 let bytes = payload(&f.raw(&[b"DUMP", key]));
3554 assert_eq!(f.run(&[b"RESTORE", ©, b"0", &bytes]), "+OK\r\n");
3555 assert_eq!(f.run(&[b"TYPE", ©]), f.run(&[b"TYPE", key]));
3556 }
3557
3558 assert_eq!(f.run(&[b"GET", b"s2"]), "$5\r\nhello\r\n");
3559 assert_eq!(
3560 f.run(&[b"LRANGE", b"l2", b"0", b"-1"]),
3561 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
3562 );
3563 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"t2"])), ["1", "2", "3"]);
3564 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"u2"])), ["x", "y"]);
3565 assert_eq!(f.run(&[b"HGET", b"h2", b"g"]), "$1\r\n2\r\n");
3566 assert_eq!(f.run(&[b"ZSCORE", b"z2", b"b"]), "$3\r\n2.5\r\n");
3567 assert_eq!(
3570 f.run(&[b"OBJECT", b"ENCODING", b"t2"]),
3571 f.run(&[b"OBJECT", b"ENCODING", b"t"])
3572 );
3573 }
3574
3575 #[test]
3576 fn a_dumped_hash_keeps_its_field_deadlines() {
3577 let mut f = Fixture::new();
3578 f.run(&[b"HSET", b"h", b"keep", b"1", b"go", b"2"]);
3579 assert_eq!(
3580 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"go"]),
3581 "*1\r\n:1\r\n"
3582 );
3583 let bytes = payload(&f.raw(&[b"DUMP", b"h"]));
3584 assert_eq!(f.run(&[b"RESTORE", b"h2", b"0", &bytes]), "+OK\r\n");
3585 assert_eq!(
3586 f.run(&[b"HTTL", b"h2", b"FIELDS", b"2", b"keep", b"go"]),
3587 "*2\r\n:-1\r\n:100\r\n"
3588 );
3589 }
3590
3591 #[test]
3592 fn dump_leaves_the_deadline_behind_and_restore_is_given_a_new_one() {
3593 let mut f = Fixture::new();
3594 f.run(&[b"SET", b"a", b"v", b"EX", b"100"]);
3595 let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
3596 assert_eq!(f.run(&[b"RESTORE", b"b", b"0", &bytes]), "+OK\r\n");
3597 assert_eq!(f.run(&[b"TTL", b"b"]), ":-1\r\n");
3598 assert_eq!(f.run(&[b"RESTORE", b"c", b"5000", &bytes]), "+OK\r\n");
3599 assert_eq!(f.run(&[b"TTL", b"c"]), ":5\r\n");
3600 assert_eq!(
3603 f.run(&[b"RESTORE", b"d", b"1", &bytes, b"ABSTTL"]),
3604 "+OK\r\n"
3605 );
3606 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
3607 }
3608
3609 #[test]
3610 fn dump_answers_nothing_for_a_key_that_is_not_there() {
3611 let mut f = Fixture::new();
3612 assert_eq!(f.run(&[b"DUMP", b"nope"]), "$-1\r\n");
3613 f.run(&[b"SET", b"gone", b"v", b"PX", b"10"]);
3614 f.advance(50);
3615 assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
3616 }
3617
3618 #[test]
3619 fn restore_refuses_a_key_that_is_there_unless_it_is_told_to_replace() {
3620 let mut f = Fixture::new();
3621 f.run(&[b"SET", b"a", b"first"]);
3622 f.run(&[b"SET", b"b", b"second"]);
3623 let bytes = payload(&f.raw(&[b"DUMP", b"b"]));
3624 assert_eq!(
3625 f.run(&[b"RESTORE", b"a", b"0", &bytes]),
3626 "-BUSYKEY Target key name already exists.\r\n"
3627 );
3628 assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nfirst\r\n");
3629 assert_eq!(
3630 f.run(&[b"RESTORE", b"a", b"0", &bytes, b"REPLACE"]),
3631 "+OK\r\n"
3632 );
3633 assert_eq!(f.run(&[b"GET", b"a"]), "$6\r\nsecond\r\n");
3634 }
3635
3636 #[test]
3640 fn restore_asks_about_the_key_before_it_looks_at_the_bytes() {
3641 let mut f = Fixture::new();
3642 f.run(&[b"SET", b"a", b"v"]);
3643 assert_eq!(
3644 f.run(&[b"RESTORE", b"a", b"0", b"rubbish"]),
3645 "-BUSYKEY Target key name already exists.\r\n"
3646 );
3647 assert_eq!(
3650 f.run(&[b"RESTORE", b"a", b"0", b"rubbish", b"FREQ", b"300"]),
3651 "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
3652 );
3653 }
3654
3655 #[test]
3656 fn restore_can_tell_a_bad_footer_from_bad_bytes() {
3657 let mut f = Fixture::new();
3658 f.run(&[b"SET", b"a", b"hello"]);
3659 let good = payload(&f.raw(&[b"DUMP", b"a"]));
3660
3661 let mut flipped = good.clone();
3662 flipped[2] ^= 0x40;
3663 assert_eq!(
3664 f.run(&[b"RESTORE", b"b", b"0", &flipped]),
3665 "-ERR DUMP payload version or checksum are wrong\r\n"
3666 );
3667 assert_eq!(
3668 f.run(&[b"RESTORE", b"b", b"0", b"short"]),
3669 "-ERR DUMP payload version or checksum are wrong\r\n"
3670 );
3671 let mut truncated = good[..1].to_vec();
3675 truncated.extend_from_slice(&good[good.len() - 10..good.len() - 8]);
3676 let crc = yo_common::crc::crc64(0, &truncated);
3677 truncated.extend_from_slice(&crc.to_le_bytes());
3678 assert_eq!(
3679 f.run(&[b"RESTORE", b"b", b"0", &truncated]),
3680 "-ERR Bad data format\r\n"
3681 );
3682 assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
3683 }
3684
3685 #[test]
3686 fn restore_checks_the_three_numbers_a_client_can_get_wrong() {
3687 let mut f = Fixture::new();
3688 f.run(&[b"SET", b"a", b"v"]);
3689 let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
3690 assert_eq!(
3691 f.run(&[b"RESTORE", b"b", b"-1", &bytes]),
3692 "-ERR Invalid TTL value, must be >= 0\r\n"
3693 );
3694 assert_eq!(
3695 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"-1"]),
3696 "-ERR Invalid IDLETIME value, must be >= 0\r\n"
3697 );
3698 assert_eq!(
3699 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ", b"256"]),
3700 "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
3701 );
3702 assert_eq!(
3704 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"90"]),
3705 "+OK\r\n"
3706 );
3707 assert_eq!(
3708 f.run(&[b"RESTORE", b"c", b"0", &bytes, b"FREQ", b"200", b"REPLACE"]),
3709 "+OK\r\n"
3710 );
3711 }
3712
3713 #[test]
3717 fn restore_takes_idletime_or_freq_and_not_both() {
3718 let mut f = Fixture::new();
3719 f.run(&[b"SET", b"a", b"v"]);
3720 let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
3721 assert_eq!(
3722 f.run(&[
3723 b"RESTORE",
3724 b"b",
3725 b"0",
3726 &bytes,
3727 b"IDLETIME",
3728 b"1",
3729 b"FREQ",
3730 b"2"
3731 ]),
3732 "-ERR syntax error\r\n"
3733 );
3734 assert_eq!(
3735 f.run(&[
3736 b"RESTORE",
3737 b"b",
3738 b"0",
3739 &bytes,
3740 b"FREQ",
3741 b"2",
3742 b"IDLETIME",
3743 b"1"
3744 ]),
3745 "-ERR syntax error\r\n"
3746 );
3747 assert_eq!(
3748 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ"]),
3749 "-ERR syntax error\r\n"
3750 );
3751 assert_eq!(
3752 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"NOSUCH"]),
3753 "-ERR syntax error\r\n"
3754 );
3755 }
3756
3757 #[test]
3758 fn copy_checks_its_options_before_it_looks_for_anything() {
3759 let mut f = Fixture::new();
3760 assert_eq!(
3763 f.run(&[b"COPY", b"a", b"b", b"DB", b"99"]),
3764 "-ERR DB index is out of range\r\n"
3765 );
3766 assert_eq!(
3767 f.run(&[b"COPY", b"a", b"b", b"DB", b"-1"]),
3768 "-ERR DB index is out of range\r\n"
3769 );
3770 assert_eq!(
3771 f.run(&[b"COPY", b"a", b"b", b"DB", b"x"]),
3772 "-ERR value is not an integer or out of range\r\n"
3773 );
3774 assert_eq!(
3775 f.run(&[b"COPY", b"a", b"b", b"nonsense"]),
3776 "-ERR syntax error\r\n"
3777 );
3778 assert_eq!(
3779 f.run(&[b"COPY", b"a", b"a"]),
3780 "-ERR source and destination objects are the same\r\n"
3781 );
3782 assert_eq!(
3784 f.run(&[b"COPY", b"a", b"b", b"dB", b"1", b"rEpLaCe", b"db", b"2"]),
3785 ":0\r\n"
3786 );
3787 }
3788
3789 #[test]
3790 fn time_is_two_bulk_strings_and_moves() {
3791 let mut f = Fixture::new();
3792 let first = f.run(&[b"TIME"]);
3793 assert!(first.starts_with("*2\r\n$"), "got {first}");
3794 let parts: Vec<&str> = first.split("\r\n").collect();
3795 let secs: i64 = parts[2].parse().expect("seconds as decimal text");
3796 let micros: i64 = parts[4].parse().expect("microseconds as decimal text");
3797 assert!(secs > 1_700_000_000, "a real wall clock, got {secs}");
3798 assert!((0..1_000_000).contains(µs), "got {micros}");
3799 assert_ne!(first, f.run(&[b"TIME"]));
3803 }
3804
3805 #[test]
3806 fn a_keyspace_scan_walks_every_key_once() {
3807 let n = if cfg!(miri) { 96 } else { 500 };
3811 let mut f = Fixture::new();
3812 for i in 0..n {
3813 f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
3814 }
3815
3816 let mut seen: Vec<String> = Vec::new();
3817 let mut cursor = "0".to_owned();
3818 let mut calls = 0;
3819 loop {
3820 let (next, keys) = scan_reply(&f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"32"]));
3821 seen.extend(keys);
3822 cursor = next;
3823 calls += 1;
3824 assert!(calls < 10_000, "the cursor is not advancing");
3825 if cursor == "0" {
3826 break;
3827 }
3828 }
3829
3830 seen.sort();
3831 seen.dedup();
3832 assert_eq!(seen.len(), n, "every key once and only once");
3833 assert!(calls > 1, "{n} keys came back in one batch");
3836 }
3837
3838 #[test]
3839 fn a_scan_narrows_by_pattern_and_by_type() {
3840 let mut f = Fixture::new();
3841 f.run(&[b"SET", b"str", b"v"]);
3842 f.run(&[b"SADD", b"members", b"a"]);
3843 f.run(&[b"HSET", b"fields", b"f", b"v"]);
3844
3845 let all = |f: &mut Fixture, args: &[&[u8]]| {
3846 let mut out: Vec<String> = Vec::new();
3847 let mut cursor = "0".to_owned();
3848 loop {
3849 let mut line: Vec<&[u8]> = vec![b"SCAN", cursor.as_bytes()];
3850 line.extend_from_slice(args);
3851 let (next, keys) = scan_reply(&f.run(&line));
3852 out.extend(keys);
3853 cursor = next;
3854 if cursor == "0" {
3855 break;
3856 }
3857 }
3858 out.sort();
3859 out
3860 };
3861
3862 assert_eq!(all(&mut f, &[]), ["fields", "members", "str"]);
3863 assert_eq!(all(&mut f, &[b"MATCH", b"*e*"]), ["fields", "members"]);
3864 assert_eq!(all(&mut f, &[b"TYPE", b"set"]), ["members"]);
3865 assert_eq!(all(&mut f, &[b"TYPE", b"HASH"]), ["fields"]);
3867 assert!(all(&mut f, &[b"TYPE", b"list"]).is_empty());
3869 assert!(all(&mut f, &[b"TYPE", b"banana"]).is_empty());
3870 assert!(all(&mut f, &[b"MATCH", b"str*", b"TYPE", b"set"]).is_empty());
3872 }
3873
3874 #[test]
3875 fn a_scan_says_what_is_wrong_with_it() {
3876 let mut f = Fixture::new();
3877 assert_eq!(f.run(&[b"SCAN", b"nope"]), "-ERR invalid cursor\r\n");
3878 assert_eq!(f.run(&[b"SCAN", b"-1"]), "-ERR invalid cursor\r\n");
3879 assert_eq!(f.run(&[b"SCAN", b"0", b"MATCH"]), "-ERR syntax error\r\n");
3880 assert_eq!(
3881 f.run(&[b"SCAN", b"0", b"COUNT", b"0"]),
3882 "-ERR syntax error\r\n"
3883 );
3884 assert_eq!(
3885 f.run(&[b"SCAN", b"0", b"COUNT", b"x"]),
3886 "-ERR value is not an integer or out of range\r\n"
3887 );
3888 assert_eq!(
3889 f.run(&[b"SCAN", b"0", b"WAT", b"1"]),
3890 "-ERR syntax error\r\n"
3891 );
3892 assert!(f.run(&[b"SCAN", b"18446744073709551615"]).starts_with("*2"));
3897 }
3898
3899 #[test]
3900 fn keys_and_randomkey_look_at_the_whole_database() {
3901 let mut f = Fixture::new();
3902 assert_eq!(f.run(&[b"KEYS", b"*"]), "*0\r\n");
3903 assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
3904
3905 for name in ["one", "two", "three"] {
3906 f.run(&[b"SET", name.as_bytes(), b"v"]);
3907 }
3908 assert_eq!(sorted(&f.run(&[b"KEYS", b"*"])), ["one", "three", "two"]);
3909 assert_eq!(sorted(&f.run(&[b"KEYS", b"t*"])), ["three", "two"]);
3910 assert_eq!(f.run(&[b"KEYS", b"nothing"]), "*0\r\n");
3911
3912 for _ in 0..50 {
3913 let got = f.run(&[b"RANDOMKEY"]);
3914 assert!(
3915 ["$3\r\none\r\n", "$3\r\ntwo\r\n", "$5\r\nthree\r\n"].contains(&got.as_str()),
3916 "got {got}"
3917 );
3918 }
3919 }
3920
3921 #[test]
3922 fn a_walk_does_not_answer_keys_that_have_expired() {
3923 let mut f = Fixture::new();
3924 f.run(&[b"SET", b"alive", b"v"]);
3925 f.run(&[b"SET", b"dead", b"v", b"PX", b"1"]);
3926 f.server.advance_clock_ms(2);
3927 assert_eq!(
3928 f.run(&[b"DBSIZE"]),
3929 ":2\r\n",
3930 "nothing has collected it yet"
3931 );
3932
3933 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$5\r\nalive\r\n");
3934 let (_, keys) = scan_reply(&f.run(&[b"SCAN", b"0", b"COUNT", b"1000"]));
3935 assert_eq!(keys, ["alive"]);
3936 for _ in 0..20 {
3937 assert_eq!(f.run(&[b"RANDOMKEY"]), "$5\r\nalive\r\n");
3938 }
3939 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
3942 }
3943
3944 #[test]
3945 fn a_key_deadline_goes_on_and_comes_back_in_all_four_units() {
3946 let mut f = Fixture::new();
3947 f.run(&[b"SET", b"k", b"v"]);
3948 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "there and no deadline");
3949 assert_eq!(f.run(&[b"TTL", b"nosuch"]), ":-2\r\n", "not there at all");
3950
3951 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100"]), ":1\r\n");
3952 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
3953 let ms = int(&f.run(&[b"PTTL", b"k"]));
3954 assert!((99_000..=100_000).contains(&ms), "got {ms}");
3955
3956 let at = int(&f.run(&[b"EXPIRETIME", b"k"]));
3958 let at_ms = int(&f.run(&[b"PEXPIRETIME", b"k"]));
3959 assert_eq!(at, (at_ms + 500) / 1000);
3960 assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
3961
3962 assert_eq!(f.run(&[b"PERSIST", b"k"]), ":1\r\n");
3963 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
3964 assert_eq!(
3965 f.run(&[b"PERSIST", b"k"]),
3966 ":0\r\n",
3967 "nothing to take off the second time"
3968 );
3969 assert_eq!(f.run(&[b"PERSIST", b"nosuch"]), ":0\r\n");
3970 assert_eq!(
3971 f.run(&[b"GET", b"k"]),
3972 "$1\r\nv\r\n",
3973 "and the value went through all of that untouched"
3974 );
3975 }
3976
3977 #[test]
3978 fn every_type_can_be_given_a_deadline_and_it_is_the_same_deadline() {
3979 let mut f = Fixture::new();
3980 f.run(&[b"SET", b"str", b"v"]);
3981 f.run(&[b"SADD", b"set", b"a", b"b"]);
3982 f.run(&[b"HSET", b"hash", b"f", b"v"]);
3983
3984 for key in [b"str".as_slice(), b"set", b"hash"] {
3985 assert_eq!(f.run(&[b"EXPIRE", key, b"100"]), ":1\r\n");
3986 assert_eq!(f.run(&[b"TTL", key]), ":100\r\n");
3987 }
3988 assert_eq!(f.run(&[b"SCARD", b"set"]), ":2\r\n");
3991 assert_eq!(f.run(&[b"HGET", b"hash", b"f"]), "$1\r\nv\r\n");
3992 assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
3993 }
3994
3995 #[test]
3996 fn a_deadline_that_has_already_gone_deletes_the_key_now() {
3997 let mut f = Fixture::new();
3998 for key in [b"a".as_slice(), b"b", b"c", b"d"] {
3999 f.run(&[b"SET", key, b"v"]);
4000 }
4001 assert_eq!(f.run(&[b"EXPIRE", b"a", b"0"]), ":1\r\n");
4005 assert_eq!(f.run(&[b"EXPIRE", b"b", b"-1"]), ":1\r\n");
4006 assert_eq!(f.run(&[b"EXPIREAT", b"c", b"1"]), ":1\r\n");
4007 assert_eq!(f.run(&[b"PEXPIREAT", b"d", b"1"]), ":1\r\n");
4008 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4009 assert_eq!(
4010 f.run(&[b"EXPIRE", b"a", b"100"]),
4011 ":0\r\n",
4012 "and the key really went, so there is nothing to put a deadline on"
4013 );
4014 }
4015
4016 #[test]
4017 fn the_four_conditions_decide_whether_the_deadline_moves() {
4018 let mut f = Fixture::new();
4019 f.run(&[b"SET", b"k", b"v"]);
4020
4021 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX"]), ":0\r\n");
4022 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "and XX left it alone");
4023 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"GT"]), ":0\r\n");
4024 assert_eq!(
4025 f.run(&[b"EXPIRE", b"k", b"100", b"LT"]),
4026 ":1\r\n",
4027 "no deadline reads as infinitely far away, so LT passes where GT fails"
4028 );
4029
4030 assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"NX"]), ":0\r\n");
4031 assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"GT"]), ":0\r\n");
4032 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
4033 assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"LT"]), ":1\r\n");
4034 assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"GT"]), ":1\r\n");
4035 assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
4036
4037 assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"NX"]), ":0\r\n");
4040 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":1\r\n");
4041 assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"XX"]), ":1\r\n");
4042 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n", "and XX let it through");
4043 }
4044
4045 #[test]
4046 fn the_conditions_are_a_set_and_not_a_keyword() {
4047 let mut f = Fixture::new();
4048 f.run(&[b"SET", b"k", b"v"]);
4049
4050 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"nx"]), ":1\r\n");
4051 assert_eq!(
4052 f.run(&[b"EXPIRE", b"k", b"100", b"nx", b"nx"]),
4053 ":0\r\n",
4054 "the same keyword twice means it once, and NX now has a deadline to fail on"
4055 );
4056
4057 assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"xx", b"gt"]), ":1\r\n");
4060 assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
4061 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"gt", b"xx"]), ":0\r\n");
4062 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]), ":1\r\n");
4063 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
4064 f.run(&[b"PERSIST", b"k"]);
4065 assert_eq!(
4066 f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]),
4067 ":0\r\n",
4068 "where LT on its own would have taken it"
4069 );
4070 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"LT"]), ":1\r\n");
4071 }
4072
4073 #[test]
4074 fn a_key_is_gone_once_its_moment_passes() {
4075 let mut f = Fixture::new();
4076 f.run(&[b"SET", b"k", b"v"]);
4077 f.run(&[b"EXPIRE", b"k", b"100"]);
4078
4079 let at = int(&f.run(&[b"PEXPIRETIME", b"k"]));
4080 f.server.set_clock_ms(at as u64 + 1);
4081 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
4082 assert_eq!(f.run(&[b"TTL", b"k"]), ":-2\r\n");
4083 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
4084 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4085 }
4086
4087 #[test]
4088 fn the_expiry_commands_refuse_what_a_real_server_refuses() {
4089 let mut f = Fixture::new();
4090 f.run(&[b"SET", b"k", b"v"]);
4091 for (bad, want) in [
4092 (
4093 &[b"EXPIRE".as_slice(), b"k", b"soon"][..],
4094 "-ERR value is not an integer or out of range\r\n",
4095 ),
4096 (
4097 &[b"EXPIRE", b"k", b"100", b"MAYBE"],
4098 "-ERR Unsupported option MAYBE\r\n",
4099 ),
4100 (
4101 &[b"EXPIRE", b"k", b"100", b"NX", b"XX"],
4102 "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
4103 ),
4104 (
4105 &[b"EXPIRE", b"k", b"100", b"NX", b"GT"],
4106 "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
4107 ),
4108 (
4109 &[b"EXPIRE", b"k", b"100", b"GT", b"LT", b"GT"],
4110 "-ERR GT and LT options at the same time are not compatible\r\n",
4111 ),
4112 (
4115 &[b"EXPIRE", b"k", b"9223372036854775807"],
4116 "-ERR invalid expire time in 'expire' command\r\n",
4117 ),
4118 (
4119 &[b"EXPIREAT", b"k", b"9223372036854775807"],
4120 "-ERR invalid expire time in 'expireat' command\r\n",
4121 ),
4122 (
4123 &[b"PEXPIRE", b"k", b"9223372036854775807"],
4124 "-ERR invalid expire time in 'pexpire' command\r\n",
4125 ),
4126 ] {
4127 assert_eq!(f.run(bad), want, "for {bad:?}");
4128 }
4129 assert_eq!(
4130 f.run(&[b"TTL", b"k"]),
4131 ":-1\r\n",
4132 "and none of those put a deadline on anything"
4133 );
4134
4135 assert_eq!(
4139 f.run(&[b"PEXPIREAT", b"k", b"9223372036854775807"]),
4140 ":1\r\n"
4141 );
4142 assert_eq!(f.run(&[b"PEXPIRETIME", b"k"]), ":70368744177663\r\n");
4143 }
4144
4145 #[test]
4146 fn flushing_empties_this_database_or_every_one_of_them() {
4147 let mut f = Fixture::new();
4148 f.run(&[b"SELECT", b"0"]);
4149 f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
4150 f.run(&[b"SELECT", b"1"]);
4151 f.run(&[b"SET", b"c", b"3"]);
4152 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
4153 assert_eq!(f.run(&[b"FLUSHDB", b"async"]), "+OK\r\n");
4156 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4157 f.run(&[b"SELECT", b"0"]);
4159 assert_eq!(f.run(&[b"DBSIZE"]), ":2\r\n");
4160 assert_eq!(f.run(&[b"FLUSHALL", b"SYNC"]), "+OK\r\n");
4161 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4162 f.run(&[b"SELECT", b"1"]);
4163 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4164 assert_eq!(f.run(&[b"FLUSHALL", b"nope"]), "-ERR syntax error\r\n");
4167 assert_eq!(
4168 f.run(&[b"FLUSHDB", b"sync", b"sync"]),
4169 "-ERR syntax error\r\n"
4170 );
4171 }
4172
4173 #[test]
4174 fn the_script_cache_and_the_library_set_answer_for_being_empty() {
4175 let mut f = Fixture::new();
4176 assert_eq!(f.run(&[b"SCRIPT", b"FLUSH"]), "+OK\r\n");
4177 assert_eq!(f.run(&[b"SCRIPT", b"FLUSH", b"async"]), "+OK\r\n");
4178 assert_eq!(f.run(&[b"FUNCTION", b"FLUSH", b"SYNC"]), "+OK\r\n");
4179 assert_eq!(
4182 f.run(&[b"SCRIPT", b"EXISTS", b"aaaa", b"bbbb"]),
4183 "*2\r\n:0\r\n:0\r\n"
4184 );
4185 assert_eq!(f.run(&[b"FUNCTION", b"LIST"]), "*0\r\n");
4186 assert_eq!(
4187 f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"x", b"WITHCODE"]),
4188 "*0\r\n"
4189 );
4190 assert_eq!(
4191 f.run(&[b"FUNCTION", b"DELETE", b"nosuch"]),
4192 "-ERR Library not found\r\n"
4193 );
4194
4195 assert_eq!(
4198 f.run(&[b"SCRIPT", b"FLUSH", b"nope"]),
4199 "-ERR SCRIPT FLUSH only support SYNC|ASYNC option\r\n"
4200 );
4201 assert_eq!(
4202 f.run(&[b"FUNCTION", b"FLUSH", b"nope"]),
4203 "-ERR FUNCTION FLUSH only supports SYNC|ASYNC option\r\n"
4204 );
4205 assert_eq!(
4210 f.run(&[b"FUNCTION", b"FLUSH", b"sync", b"sync"]),
4211 "-ERR unknown subcommand or wrong number of arguments for 'FLUSH'. Try FUNCTION HELP.\r\n"
4212 );
4213 assert_eq!(
4214 f.run(&[b"FUNCTION", b"LIST", b"bogus"]),
4215 "-ERR Unknown argument bogus\r\n"
4216 );
4217 assert_eq!(
4218 f.run(&[b"SCRIPT", b"EXISTS"]),
4219 "-ERR wrong number of arguments for 'script|exists' command\r\n"
4220 );
4221
4222 assert_eq!(
4223 f.run(&[b"FUNCTION", b"NOPE"]),
4224 "-ERR unknown subcommand 'NOPE'. Try FUNCTION HELP.\r\n"
4225 );
4226 }
4227
4228 #[test]
4229 fn the_script_cache_holds_what_was_loaded_into_it() {
4230 let mut f = Fixture::new();
4231 let sha = b"e0e1f9fabfc9d4800c877a703b823ac0578ff8db";
4234 assert_eq!(
4235 f.run(&[b"SCRIPT", b"LOAD", b"return 1"]),
4236 "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
4237 );
4238 assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:1\r\n");
4239 assert_eq!(f.run(&[b"EVALSHA", sha, b"0"]), ":1\r\n");
4240 assert_eq!(
4243 f.run(&[b"SCRIPT", b"LOAD", b"return 1"]),
4244 "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
4245 );
4246 assert!(
4247 f.run(&[b"SCRIPT", b"LOAD", b"this is not lua"])
4248 .starts_with("-ERR Error compiling script"),
4249 );
4250
4251 assert_eq!(f.run(&[b"SCRIPT", b"FLUSH"]), "+OK\r\n");
4252 assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:0\r\n");
4253 assert_eq!(
4254 f.run(&[b"EVALSHA", sha, b"0"]),
4255 "-NOSCRIPT No matching script. Please use EVAL.\r\n"
4256 );
4257
4258 assert_eq!(f.run(&[b"EVAL", b"return 1", b"0"]), ":1\r\n");
4261 assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:1\r\n");
4262
4263 assert_eq!(
4266 f.run(&[b"SCRIPT", b"KILL"]),
4267 "-NOTBUSY No scripts in execution right now.\r\n"
4268 );
4269 assert_eq!(f.run(&[b"SCRIPT", b"DEBUG", b"NO"]), "+OK\r\n");
4270 assert_eq!(f.run(&[b"SCRIPT", b"DEBUG", b"yes"]), "+OK\r\n");
4271 assert_eq!(
4272 f.run(&[b"SCRIPT", b"DEBUG", b"maybe"]),
4273 "-ERR Use SCRIPT DEBUG YES/SYNC/NO\r\n"
4274 );
4275 }
4276
4277 #[test]
4278 fn eval_counts_its_keys_before_it_compiles_anything() {
4279 let mut f = Fixture::new();
4280 assert_eq!(
4281 f.run(&[b"EVAL", b"return 1"]),
4282 "-ERR wrong number of arguments for 'eval' command\r\n"
4283 );
4284 assert_eq!(
4285 f.run(&[b"EVAL", b"return 1", b"abc"]),
4286 "-ERR value is not an integer or out of range\r\n"
4287 );
4288 assert_eq!(
4289 f.run(&[b"EVAL", b"return 1", b"-1"]),
4290 "-ERR Number of keys can't be negative\r\n"
4291 );
4292 assert_eq!(
4293 f.run(&[b"EVAL", b"return 1", b"1"]),
4294 "-ERR Number of keys can't be greater than number of args\r\n"
4295 );
4296 assert_eq!(
4298 f.run(&[
4299 b"EVAL",
4300 b"return {KEYS[1],KEYS[2],ARGV[1]}",
4301 b"2",
4302 b"a",
4303 b"b",
4304 b"c"
4305 ]),
4306 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
4307 );
4308 assert_eq!(
4309 f.run(&[b"EVAL", b"return #KEYS", b"0", b"a", b"b"]),
4310 ":0\r\n"
4311 );
4312 assert_eq!(
4313 f.run(&[b"EVAL", b"return #ARGV", b"0", b"a", b"b"]),
4314 ":2\r\n"
4315 );
4316 }
4317
4318 #[test]
4319 fn a_lua_value_comes_back_as_the_reply_it_maps_to() {
4320 let mut f = Fixture::new();
4321 let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
4322
4323 assert_eq!(eval(&mut f, b"return 3.99"), ":3\r\n");
4326 assert_eq!(eval(&mut f, b"return -3.99"), ":-3\r\n");
4327 assert_eq!(eval(&mut f, b"return 0.5"), ":0\r\n");
4328 assert_eq!(eval(&mut f, b"return 2^63"), ":9223372036854775807\r\n");
4329 assert_eq!(eval(&mut f, b"return -2^63"), ":-9223372036854775808\r\n");
4330 assert_eq!(eval(&mut f, b"return 1/0"), ":9223372036854775807\r\n");
4331 assert_eq!(eval(&mut f, b"return 0/0"), ":0\r\n");
4332
4333 assert_eq!(eval(&mut f, b"return 'hello'"), "$5\r\nhello\r\n");
4334 assert_eq!(eval(&mut f, b"return true"), ":1\r\n");
4335 assert_eq!(eval(&mut f, b"return false"), "$-1\r\n");
4337 assert_eq!(eval(&mut f, b"return nil"), "$-1\r\n");
4338 assert_eq!(eval(&mut f, b"return"), "$-1\r\n");
4339 assert_eq!(eval(&mut f, b""), "$-1\r\n");
4340
4341 assert_eq!(eval(&mut f, b"return {}"), "*0\r\n");
4344 assert_eq!(eval(&mut f, b"return {1,2,nil,4}"), "*2\r\n:1\r\n:2\r\n");
4345 assert_eq!(
4346 eval(&mut f, b"return {1,'a',{2}}"),
4347 "*3\r\n:1\r\n$1\r\na\r\n*1\r\n:2\r\n"
4348 );
4349
4350 assert_eq!(eval(&mut f, b"return {ok='fine'}"), "+fine\r\n");
4352 assert_eq!(eval(&mut f, b"return {err='mine'}"), "-mine\r\n");
4353 assert_eq!(eval(&mut f, b"return {err='a', ok='b'}"), "-a\r\n");
4354 assert_eq!(eval(&mut f, b"return {ok='b', double=1.5}"), "+b\r\n");
4355 assert_eq!(eval(&mut f, b"return {ok='a\\r\\nb'}"), "+a b\r\n");
4358 assert_eq!(eval(&mut f, b"return {ok=1}"), "*0\r\n");
4361 assert_eq!(eval(&mut f, b"return {err={}}"), "*0\r\n");
4362 }
4363
4364 #[test]
4365 fn the_protocol_the_client_asked_for_is_the_one_a_table_answers_in() {
4366 let mut f = Fixture::new();
4367 assert_eq!(
4370 f.run(&[b"EVAL", b"return {double=3.5}", b"0"]),
4371 "$3\r\n3.5\r\n"
4372 );
4373 assert_eq!(
4374 f.run(&[b"EVAL", b"return {big_number='123'}", b"0"]),
4375 "$3\r\n123\r\n"
4376 );
4377 assert_eq!(
4378 f.run(&[b"EVAL", b"return {map={a='b'}}", b"0"]),
4379 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
4380 );
4381 assert_eq!(
4382 f.run(&[b"EVAL", b"return {set={a=true}}", b"0"]),
4383 "*1\r\n$1\r\na\r\n"
4384 );
4385 assert_eq!(f.run(&[b"EVAL", b"return false", b"0"]), "$-1\r\n");
4386
4387 f.out = Out::new(Proto::Resp3);
4388 assert_eq!(f.run(&[b"EVAL", b"return {double=3.5}", b"0"]), ",3.5\r\n");
4389 assert_eq!(
4390 f.run(&[b"EVAL", b"return {big_number='123'}", b"0"]),
4391 "(123\r\n"
4392 );
4393 assert_eq!(
4394 f.run(&[b"EVAL", b"return {map={a='b'}}", b"0"]),
4395 "%1\r\n$1\r\na\r\n$1\r\nb\r\n"
4396 );
4397 assert_eq!(
4398 f.run(&[b"EVAL", b"return {set={a=true}}", b"0"]),
4399 "~1\r\n$1\r\na\r\n"
4400 );
4401 assert_eq!(f.run(&[b"EVAL", b"return false", b"0"]), "_\r\n");
4402 }
4403
4404 #[test]
4405 fn a_reply_comes_back_into_lua_as_the_value_it_maps_to() {
4406 let mut f = Fixture::new();
4407 f.run(&[b"SET", b"s", b"hello"]);
4408 f.run(&[b"RPUSH", b"l", b"a", b"b"]);
4409 let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
4410
4411 assert_eq!(
4412 eval(&mut f, b"return type(redis.call('get','s'))"),
4413 "$6\r\nstring\r\n"
4414 );
4415 assert_eq!(
4416 eval(&mut f, b"return type(redis.call('llen','l'))"),
4417 "$6\r\nnumber\r\n"
4418 );
4419 assert_eq!(
4420 eval(&mut f, b"return type(redis.call('lrange','l',0,-1))"),
4421 "$5\r\ntable\r\n"
4422 );
4423 assert_eq!(
4426 eval(&mut f, b"return redis.call('set','s','v')['ok']"),
4427 "$2\r\nOK\r\n"
4428 );
4429 assert_eq!(
4432 eval(&mut f, b"return tostring(redis.call('get','nosuch'))"),
4433 "$5\r\nfalse\r\n"
4434 );
4435 assert_eq!(
4436 eval(
4437 &mut f,
4438 b"redis.setresp(3) return tostring(redis.call('get','nosuch'))"
4439 ),
4440 "$3\r\nnil\r\n"
4441 );
4442 assert_eq!(
4444 eval(&mut f, b"return tostring(redis.call('get','nosuch'))"),
4445 "$5\r\nfalse\r\n"
4446 );
4447 }
4448
4449 #[test]
4450 fn an_error_from_a_script_names_the_line_it_came_from() {
4451 let mut f = Fixture::new();
4452 assert_eq!(
4455 f.run(&[b"EVAL", b"error('boom')", b"0"]),
4456 "-ERR user_script:1: boom script: \
4457 82903a0434f1503e152f89c03c9acd881a0e8150, on @user_script:1.\r\n"
4458 );
4459 assert_eq!(
4461 f.run(&[b"EVAL", b"error('boom', 0)", b"0"]),
4462 "-ERR boom script: 90724e16396e5864c1184910ba6d7440461cee4f, on @user_script:1.\r\n"
4463 );
4464 assert!(
4466 f.run(&[b"EVAL", b"error({err='structured'})", b"0"])
4467 .starts_with("-structured script: "),
4468 );
4469 assert_eq!(
4472 f.run(&[b"EVAL", b"return this is not lua", b"0"]),
4473 "-ERR Error compiling script (new function): user_script:1: '<eof>' expected near 'is'\r\n"
4474 );
4475
4476 assert_eq!(
4479 f.run(&[
4480 b"EVAL",
4481 b"local a, b = pcall(function() error({err='z'}) end) return type(b) .. ':' .. tostring(b)",
4482 b"0"
4483 ]),
4484 "$8\r\nstring:z\r\n"
4485 );
4486 assert_eq!(
4487 f.run(&[
4488 b"EVAL",
4489 b"local a, b = pcall(function() error({a=1}) end) return type(b)",
4490 b"0"
4491 ]),
4492 "$5\r\ntable\r\n"
4493 );
4494 }
4495
4496 #[test]
4497 fn redis_call_refuses_what_it_cannot_run_and_pcall_hands_it_back() {
4498 let mut f = Fixture::new();
4499 let sentence = |f: &mut Fixture, body: &[u8]| {
4500 let reply = f.run(&[b"EVAL", body, b"0"]);
4501 reply.split(" script: ").next().unwrap().to_owned()
4502 };
4503
4504 assert_eq!(
4505 sentence(&mut f, b"return redis.call()"),
4506 "-ERR Please specify at least one argument for this redis lib call"
4507 );
4508 assert_eq!(
4509 sentence(&mut f, b"return redis.call('get', {})"),
4510 "-ERR Lua redis lib command arguments must be strings or integers"
4511 );
4512 assert_eq!(
4513 sentence(&mut f, b"return redis.call('nosuchcmd')"),
4514 "-ERR Unknown Redis command called from script"
4515 );
4516 assert_eq!(
4517 sentence(&mut f, b"return redis.call('get')"),
4518 "-ERR Wrong number of args calling Redis command from script"
4519 );
4520 for name in [
4524 &b"return redis.call('multi')"[..],
4525 b"return redis.call('exec')",
4526 b"return redis.call('watch','k')",
4527 b"return redis.call('subscribe','c')",
4528 b"return redis.call('debug','jmap')",
4529 b"return redis.call('eval','return 1',0)",
4530 b"return redis.call('config','get','maxmemory')",
4531 ] {
4532 assert_eq!(
4533 sentence(&mut f, name),
4534 "-ERR This Redis command is not allowed from script",
4535 "for {}",
4536 String::from_utf8_lossy(name)
4537 );
4538 }
4539 assert!(
4542 f.run(&[b"EVAL", b"return redis.call('config','help')", b"0"])
4543 .starts_with('*'),
4544 );
4545
4546 assert_eq!(
4549 f.run(&[
4550 b"EVAL",
4551 b"local x = redis.pcall('nosuchcmd') return x.err",
4552 b"0"
4553 ]),
4554 "$44\r\nERR Unknown Redis command called from script\r\n"
4555 );
4556 assert_eq!(
4558 f.run(&[b"EVAL", b"return redis.pcall('nosuchcmd')", b"0"]),
4559 "-ERR Unknown Redis command called from script\r\n"
4560 );
4561 }
4562
4563 #[test]
4564 fn a_read_only_script_is_stopped_at_the_write_and_not_at_the_door() {
4565 let mut f = Fixture::new();
4566 f.run(&[b"SET", b"k", b"v"]);
4567 assert_eq!(
4568 f.run(&[b"EVAL_RO", b"return redis.call('get', KEYS[1])", b"1", b"k"]),
4569 "$1\r\nv\r\n"
4570 );
4571 assert!(
4572 f.run(&[
4573 b"EVAL_RO",
4574 b"return redis.call('set', KEYS[1], 'x')",
4575 b"1",
4576 b"k"
4577 ])
4578 .starts_with("-ERR Write commands are not allowed from read-only scripts."),
4579 );
4580 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
4582 assert_eq!(
4583 f.run(&[
4584 b"EVAL",
4585 b"return redis.call('set', KEYS[1], 'x')",
4586 b"1",
4587 b"k"
4588 ]),
4589 "+OK\r\n"
4590 );
4591 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nx\r\n");
4592
4593 let sha = b"e0e1f9fabfc9d4800c877a703b823ac0578ff8db";
4595 f.run(&[b"SCRIPT", b"LOAD", b"return 1"]);
4596 assert_eq!(f.run(&[b"EVALSHA_RO", sha, b"0"]), ":1\r\n");
4597 }
4598
4599 #[test]
4600 fn a_script_cannot_leave_anything_behind_for_the_next_one() {
4601 let mut f = Fixture::new();
4602 for body in [&b"x = 1"[..], b"pcall = 1", b"redis = 1", b"redis.call = 1"] {
4605 let reply = f.run(&[b"EVAL", body, b"0"]);
4606 assert!(
4607 reply
4608 .starts_with("-ERR user_script:1: Attempt to modify a readonly table script: "),
4609 "{body:?} gave {reply}",
4610 );
4611 }
4612 for body in [
4615 &b"rawset(redis, 'call', 1)"[..],
4616 b"rawset(_G, 'zz', 1)",
4617 b"setmetatable(_G, {})",
4618 b"setmetatable(redis, {})",
4619 ] {
4620 let reply = f.run(&[b"EVAL", body, b"0"]);
4621 assert!(
4622 reply.starts_with("-ERR Attempt to modify a readonly table script: "),
4623 "{body:?} gave {reply}",
4624 );
4625 }
4626 assert!(
4629 f.run(&[b"EVAL", b"return nosuchglobal", b"0"])
4630 .contains("Script attempted to access nonexistent global variable 'nosuchglobal'"),
4631 );
4632 assert_eq!(
4635 f.run(&[b"EVAL", b"return tostring(redis.nosuchfield)", b"0"]),
4636 "$3\r\nnil\r\n"
4637 );
4638
4639 assert_eq!(f.run(&[b"EVAL", b"_G.pcall = 1 return 1", b"0"]), ":1\r\n");
4642 assert_eq!(
4643 f.run(&[b"EVAL", b"return type(pcall)", b"0"]),
4644 "$8\r\nfunction\r\n"
4645 );
4646 assert_eq!(
4647 f.run(&[b"EVAL", b"return type(redis.call)", b"0"]),
4648 "$8\r\nfunction\r\n"
4649 );
4650 }
4651
4652 #[test]
4653 fn a_script_can_walk_the_redis_table_it_is_not_allowed_to_write_to() {
4654 let mut f = Fixture::new();
4655 assert_eq!(
4659 f.run(&[
4660 b"EVAL",
4661 b"local n = 0 for k in pairs(redis) do n = n + 1 end return n",
4662 b"0",
4663 ]),
4664 ":23\r\n"
4665 );
4666 assert_eq!(
4667 f.run(&[
4668 b"EVAL",
4669 b"local t = {} for k in pairs(redis) do t[#t+1] = k end \
4670 table.sort(t) return table.concat(t, ' ')",
4671 b"0",
4672 ]),
4673 "$243\r\nLOG_DEBUG LOG_NOTICE LOG_VERBOSE LOG_WARNING REDIS_VERSION \
4674 REDIS_VERSION_NUM REPL_ALL REPL_AOF REPL_NONE REPL_REPLICA REPL_SLAVE \
4675 acl_check_cmd breakpoint call debug error_reply log pcall replicate_commands \
4676 set_repl setresp sha1hex status_reply\r\n"
4677 );
4678 assert_eq!(
4681 f.run(&[
4682 b"EVAL",
4683 b"local n = 0 for k, v in pairs(redis) do \
4684 if type(v) == 'function' then n = n + 1 end end return n",
4685 b"0",
4686 ]),
4687 ":12\r\n"
4688 );
4689 assert_eq!(
4691 f.run(&[b"EVAL", b"return type(next(redis))", b"0"]),
4692 "$6\r\nstring\r\n"
4693 );
4694 assert_eq!(
4695 f.run(&[b"EVAL", b"return type(rawget(redis, 'call'))", b"0"]),
4696 "$8\r\nfunction\r\n"
4697 );
4698 assert_eq!(
4699 f.run(&[
4700 b"EVAL",
4701 b"return tostring(rawget(redis, 'nosuchfield'))",
4702 b"0",
4703 ]),
4704 "$3\r\nnil\r\n"
4705 );
4706 for body in [&b"redis.call = 1"[..], b"rawset(redis, 'call', 1)"] {
4709 assert!(
4710 f.run(&[b"EVAL", body, b"0"])
4711 .contains("Attempt to modify a readonly table script: "),
4712 "{body:?}",
4713 );
4714 }
4715 assert_eq!(
4718 f.run(&[
4719 b"EVAL",
4720 b"local t = {a=1,b=2} local n = 0 for k in pairs(t) do n = n + 1 end return n",
4721 b"0",
4722 ]),
4723 ":2\r\n"
4724 );
4725 assert_eq!(
4726 f.run(&[b"EVAL", b"return tostring(next({}))", b"0"]),
4727 "$3\r\nnil\r\n"
4728 );
4729 assert_eq!(
4730 f.run(&[
4731 b"EVAL",
4732 b"local f for k, v in pairs(string) do if k == 'sub' then f = v end end \
4733 return type(f)",
4734 b"0",
4735 ]),
4736 "$8\r\nfunction\r\n"
4737 );
4738 }
4739
4740 #[test]
4741 fn a_script_gets_the_bit_library_a_real_server_carries() {
4742 let mut f = Fixture::new();
4743 for (body, want) in [
4746 ("bit.tobit(1)", ":1\r\n"),
4747 ("bit.tobit(2^32 + 1)", ":1\r\n"),
4748 ("bit.tobit(2^31)", ":-2147483648\r\n"),
4749 ("bit.tobit(0xffffffff)", ":-1\r\n"),
4750 ("bit.tobit(1.5)", ":2\r\n"),
4752 ("bit.tobit(2.5)", ":2\r\n"),
4753 ("bit.bnot(0)", ":-1\r\n"),
4754 ("bit.band(0xff, 0x0f)", ":15\r\n"),
4755 ("bit.band(1, 2, 3)", ":0\r\n"),
4756 ("bit.bor(1, 2, 4)", ":7\r\n"),
4757 ("bit.bxor(0xff, 0x0f)", ":240\r\n"),
4758 ("bit.lshift(1, 31)", ":-2147483648\r\n"),
4760 ("bit.lshift(1, 32)", ":1\r\n"),
4761 ("bit.lshift(1, 33)", ":2\r\n"),
4762 ("bit.rshift(-1, 1)", ":2147483647\r\n"),
4763 ("bit.arshift(-1, 1)", ":-1\r\n"),
4764 ("bit.rol(0x12345678, 8)", ":878082066\r\n"),
4765 ("bit.ror(0x12345678, 8)", ":2014458966\r\n"),
4766 ("bit.bswap(0x12345678)", ":2018915346\r\n"),
4767 ("bit.tobit('0x10')", ":16\r\n"),
4770 ] {
4771 let script = format!("return {body}");
4772 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
4773 }
4774 for (body, want) in [
4777 ("bit.tohex(1)", "00000001"),
4778 ("bit.tohex(-1)", "ffffffff"),
4779 ("bit.tohex(255, 2)", "ff"),
4780 ("bit.tohex(255, -8)", "000000FF"),
4781 ("bit.tohex(0x87654321, 4)", "4321"),
4782 ("bit.tohex(1, 0)", ""),
4783 ("bit.tohex(1, 9)", "00000001"),
4784 ] {
4785 let script = format!("return {body}");
4786 assert_eq!(
4787 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
4788 format!("${}\r\n{want}\r\n", want.len()),
4789 "{body}",
4790 );
4791 }
4792 for (body, want) in [
4795 (
4796 "return bit.band()",
4797 "bad argument #1 to 'band' (number expected, got no value)",
4798 ),
4799 (
4800 "return bit.band('x')",
4801 "bad argument #1 to 'band' (number expected, got string)",
4802 ),
4803 (
4804 "return bit.tobit(true)",
4805 "bad argument #1 to 'tobit' (number expected, got boolean)",
4806 ),
4807 (
4808 "return bit.lshift(1)",
4809 "bad argument #2 to 'lshift' (number expected, got no value)",
4810 ),
4811 ] {
4812 let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
4813 assert!(
4814 reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
4815 "{body} gave {reply}",
4816 );
4817 }
4818 assert_eq!(
4821 f.run(&[
4822 b"EVAL",
4823 b"local ok, e = pcall(bit.band, 'x') return tostring(e)",
4824 b"0",
4825 ]),
4826 "$52\r\nbad argument #1 to '?' (number expected, got string)\r\n"
4827 );
4828 assert_eq!(
4830 f.run(&[
4831 b"EVAL",
4832 b"local t = {} for k in pairs(bit) do t[#t+1] = k end \
4833 table.sort(t) return table.concat(t, ' ')",
4834 b"0",
4835 ]),
4836 "$66\r\narshift band bnot bor bswap bxor lshift rol ror rshift tobit tohex\r\n"
4837 );
4838 for body in [&b"bit.band = 1"[..], b"rawset(bit, 'zz', 1)"] {
4839 assert!(
4840 f.run(&[b"EVAL", body, b"0"])
4841 .contains("Attempt to modify a readonly table script: "),
4842 "{body:?}",
4843 );
4844 }
4845 }
4846
4847 #[test]
4848 fn a_script_gets_the_cjson_library_a_real_server_carries() {
4849 let mut f = Fixture::new();
4850 for (body, want) in [
4854 ("cjson.encode(nil)", "null"),
4855 ("cjson.encode(true)", "true"),
4856 ("cjson.encode(cjson.null)", "null"),
4857 ("cjson.encode(100)", "100"),
4858 ("cjson.encode(1/3)", "0.33333333333333"),
4859 ("cjson.encode(1e300)", "1e+300"),
4860 ("cjson.encode(2^53)", "9.007199254741e+15"),
4861 ("cjson.encode({})", "{}"),
4862 ("cjson.encode({1,2,3})", "[1,2,3]"),
4863 ("cjson.encode({a=1})", "{\"a\":1}"),
4864 ("cjson.encode({[1]=1,[3]=3})", "[1,null,3]"),
4865 ("cjson.encode({[0]=1})", "{\"0\":1}"),
4866 ("cjson.encode('a\\nb')", "\"a\\nb\""),
4867 ("cjson.encode('\\t\\\\')", "\"\\t\\\\\""),
4869 ("cjson.encode('\\11')", "\"\\u000b\""),
4870 (
4873 "cjson.encode(cjson.decode('[1,[2,{\"a\":null}]]'))",
4874 "[1,[2,{\"a\":null}]]",
4875 ),
4876 ("cjson.encode(cjson.decode('[]'))", "{}"),
4879 ] {
4880 let script = format!("return {body}");
4881 assert_eq!(
4882 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
4883 format!("${}\r\n{want}\r\n", want.len()),
4884 "{body}",
4885 );
4886 }
4887 for (body, want) in [
4890 ("cjson.decode('[1,2,3]')[2]", ":2\r\n"),
4891 ("cjson.decode('{\"a\":41}').a + 1", ":42\r\n"),
4892 ("cjson.decode('0x10')", ":16\r\n"),
4893 ("cjson.decode('+1')", ":1\r\n"),
4894 ("cjson.decode('01')", ":1\r\n"),
4895 ("cjson.decode(1) + 1", ":2\r\n"),
4896 ("cjson.decode([[\"\\u0041\"]]) == 'A' and 1 or 0", ":1\r\n"),
4898 ("cjson.decode('null') == cjson.null and 1 or 0", ":1\r\n"),
4899 ("cjson.decode('null') == nil and 1 or 0", ":0\r\n"),
4900 ] {
4901 let script = format!("return {body}");
4902 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
4903 }
4904 for (body, want) in [
4906 (
4907 "cjson.encode_number_precision(3) return cjson.encode(1/3)",
4908 "0.333",
4909 ),
4910 (
4911 "cjson.encode_invalid_numbers('null') return cjson.encode(1/0)",
4912 "null",
4913 ),
4914 (
4915 "cjson.encode_invalid_numbers(true) return cjson.encode(1/0)",
4916 "inf",
4917 ),
4918 (
4919 "cjson.encode_sparse_array(true) return cjson.encode({[1]=1,[100]=1})",
4920 "{\"1\":1,\"100\":1}",
4921 ),
4922 (
4923 "cjson.decode_array_with_array_mt(true) return cjson.encode(cjson.decode('[]'))",
4924 "[]",
4925 ),
4926 ("return tostring(cjson.encode_max_depth())", "1000"),
4927 ("return tostring(cjson.encode_keep_buffer(false))", "false"),
4928 ("return tostring(cjson.encode_sparse_array())", "false"),
4929 ("return tostring(cjson.encode_number_precision())", "14"),
4932 ] {
4933 assert_eq!(
4934 f.run(&[b"EVAL", body.as_bytes(), b"0"]),
4935 format!("${}\r\n{want}\r\n", want.len()),
4936 "{body}",
4937 );
4938 }
4939 for (body, want) in [
4941 (
4942 "return cjson.encode(1/0)",
4943 "Cannot serialise number: must not be NaN or Inf",
4944 ),
4945 (
4946 "return cjson.encode({[1]=1,[100]=1})",
4947 "Cannot serialise table: excessively sparse array",
4948 ),
4949 (
4950 "return cjson.encode({[true]=1})",
4951 "Cannot serialise boolean: table key must be a number or string",
4952 ),
4953 (
4954 "return cjson.encode(tostring)",
4955 "Cannot serialise function: type not supported",
4956 ),
4957 (
4958 "return cjson.encode()",
4959 "bad argument #1 to 'encode' (expected 1 argument)",
4960 ),
4961 (
4962 "return cjson.decode('[1,2')",
4963 "Expected comma or array end but found T_END at character 5",
4964 ),
4965 (
4966 "return cjson.decode('{\"a\" 1}')",
4967 "Expected colon but found T_NUMBER at character 6",
4968 ),
4969 (
4970 "return cjson.decode('tru')",
4971 "Expected value but found invalid token at character 1",
4972 ),
4973 (
4974 "return cjson.decode('[1] 2')",
4975 "Expected the end but found T_NUMBER at character 5",
4976 ),
4977 (
4978 "return cjson.encode_max_depth(0)",
4979 "bad argument #1 to 'encode_max_depth' (expected integer between 1 and 2147483647)",
4980 ),
4981 (
4982 "return cjson.encode_invalid_numbers('yes')",
4983 "bad argument #1 to 'encode_invalid_numbers' (invalid option 'yes')",
4984 ),
4985 (
4986 "return cjson.encode_max_depth(1, 2)",
4987 "bad argument #2 to 'encode_max_depth' (found too many arguments)",
4988 ),
4989 ] {
4990 let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
4991 assert!(
4992 reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
4993 "{body} gave {reply}",
4994 );
4995 }
4996 assert_eq!(
4999 f.run(&[
5000 b"EVAL",
5001 b"local n = cjson.new() n.encode_number_precision(3) \
5002 return cjson.encode(1/3) .. ' ' .. n.encode(1/3)",
5003 b"0",
5004 ]),
5005 "$22\r\n0.33333333333333 0.333\r\n"
5006 );
5007 let names = "_NAME _VERSION decode decode_array_with_array_mt decode_invalid_numbers \
5009 decode_max_depth encode encode_invalid_numbers encode_keep_buffer \
5010 encode_max_depth encode_number_precision encode_sparse_array new null";
5011 assert_eq!(
5012 f.run(&[
5013 b"EVAL",
5014 b"local t = {} for k in pairs(cjson) do t[#t+1] = k end \
5015 table.sort(t) return table.concat(t, ' ')",
5016 b"0",
5017 ]),
5018 format!("${}\r\n{names}\r\n", names.len())
5019 );
5020 for body in [&b"cjson.encode = 1"[..], b"rawset(cjson, 'zz', 1)"] {
5021 assert!(
5022 f.run(&[b"EVAL", body, b"0"])
5023 .contains("Attempt to modify a readonly table script: "),
5024 "{body:?}",
5025 );
5026 }
5027 }
5028
5029 #[test]
5030 fn a_script_gets_the_struct_library_a_real_server_carries() {
5031 let mut f = Fixture::new();
5032 for (body, want) in [
5035 ("#struct.pack('i4', 1)", ":4\r\n"),
5036 ("#struct.pack('l', 1)", ":8\r\n"),
5037 ("#struct.pack('d', 1)", ":8\r\n"),
5038 ("#struct.pack('f', 1)", ":4\r\n"),
5039 ("#struct.pack('s', 'abc')", ":4\r\n"),
5040 ("#struct.pack('c3', 'abcdef')", ":3\r\n"),
5041 ("#struct.pack('x')", ":1\r\n"),
5042 ("string.byte(struct.pack('i4', 1), 1)", ":1\r\n"),
5043 ("string.byte(struct.pack('>i4', 1), 4)", ":1\r\n"),
5044 ("string.byte(struct.pack('<i4', 1), 1)", ":1\r\n"),
5045 ("string.byte(struct.pack('i16', -1), 9)", ":0\r\n"),
5048 ("string.byte(struct.pack('i8', -1), 8)", ":255\r\n"),
5049 ("#struct.pack('c0', 'abcd')", ":4\r\n"),
5052 ("string.byte(struct.pack('s', 'a'), 2)", ":0\r\n"),
5053 ("string.byte(struct.pack('bxb', 1, 2), 2)", ":0\r\n"),
5054 ] {
5055 let script = format!("return {body}");
5056 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
5057 }
5058 for (body, want) in [
5061 ("struct.size('i')", ":4\r\n"),
5062 ("struct.size('l')", ":8\r\n"),
5063 ("struct.size('T')", ":8\r\n"),
5064 ("struct.size('h')", ":2\r\n"),
5065 ("struct.size('c10')", ":10\r\n"),
5066 ("struct.size('ic')", ":5\r\n"),
5067 ("struct.size('!8ic')", ":5\r\n"),
5068 ("struct.size('!4i')", ":4\r\n"),
5069 ("struct.size('bd')", ":9\r\n"),
5072 ("struct.size('!bd')", ":16\r\n"),
5073 ("struct.size('A')", ":0\r\n"),
5074 ("struct.size('7')", ":0\r\n"),
5075 ] {
5076 let script = format!("return {body}");
5077 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
5078 }
5079 for (body, want) in [
5082 ("select('#', struct.unpack('i4', '\\1\\0\\0\\0'))", ":2\r\n"),
5083 ("select(1, struct.unpack('i4', '\\1\\0\\0\\0'))", ":1\r\n"),
5084 ("select(2, struct.unpack('i4', '\\1\\0\\0\\0'))", ":5\r\n"),
5085 ("select(1, struct.unpack('i1', '\\255'))", ":-1\r\n"),
5086 ("select(1, struct.unpack('I1', '\\255'))", ":255\r\n"),
5087 (
5088 "select(1, struct.unpack('i4', struct.pack('i4', -70000)))",
5089 ":-70000\r\n",
5090 ),
5091 ("select(2, struct.unpack('i1', 'abc', 2))", ":3\r\n"),
5092 ("select('#', struct.unpack('bc0', '\\3abcd'))", ":2\r\n"),
5096 ("select(2, struct.unpack('bc0', '\\3abcd'))", ":5\r\n"),
5097 ] {
5098 let script = format!("return {body}");
5099 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
5100 }
5101 for (body, want) in [
5102 ("select(1, struct.unpack('bc0', '\\3abcd'))", "abc"),
5103 ("select(1, struct.unpack('s', 'ab\\0cd'))", "ab"),
5104 ("select(1, struct.unpack('c3', 'abcdef'))", "abc"),
5105 ] {
5106 let script = format!("return {body}");
5107 assert_eq!(
5108 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5109 format!("${}\r\n{want}\r\n", want.len()),
5110 "{body}",
5111 );
5112 }
5113 for (body, want) in [
5116 (
5117 "return struct.pack()",
5118 "bad argument #1 to 'pack' (string expected, got no value)",
5119 ),
5120 (
5123 "return struct.pack('i4')",
5124 "bad argument #2 to 'pack' (number expected, got nil)",
5125 ),
5126 (
5129 "return struct.pack('c6', 'abc')",
5130 "bad argument #3 to 'pack' (string too short)",
5131 ),
5132 (
5133 "return struct.pack('A', 'x')",
5134 "bad argument #1 to 'pack' (invalid format option 'A')",
5135 ),
5136 (
5137 "return struct.pack('i33', 1)",
5138 "integral size 33 is larger than limit of 32",
5139 ),
5140 (
5141 "return struct.pack('!3i', 1)",
5142 "alignment 3 is not a power of 2",
5143 ),
5144 (
5145 "return struct.unpack()",
5146 "bad argument #1 to 'unpack' (string expected, got no value)",
5147 ),
5148 (
5149 "return struct.unpack('i4')",
5150 "bad argument #2 to 'unpack' (string expected, got no value)",
5151 ),
5152 (
5153 "return struct.unpack('i4', 'ab')",
5154 "bad argument #2 to 'unpack' (data string too short)",
5155 ),
5156 (
5157 "return struct.unpack('i1', 'abc', 0)",
5158 "bad argument #3 to 'unpack' (offset must be 1 or greater)",
5159 ),
5160 (
5161 "return struct.unpack('c0', 'abc')",
5162 "format 'c0' needs a previous size",
5163 ),
5164 (
5165 "return struct.unpack('s', 'abc')",
5166 "unfinished string in data",
5167 ),
5168 (
5169 "return struct.size()",
5170 "bad argument #1 to 'size' (string expected, got no value)",
5171 ),
5172 (
5173 "return struct.size('s')",
5174 "bad argument #1 to 'size' (option 's' has no fixed size)",
5175 ),
5176 (
5177 "return struct.size('c0')",
5178 "bad argument #1 to 'size' (option 'c0' has no fixed size)",
5179 ),
5180 ] {
5181 let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
5182 assert!(
5183 reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
5184 "{body} gave {reply}",
5185 );
5186 }
5187 let names = "pack size unpack";
5189 assert_eq!(
5190 f.run(&[
5191 b"EVAL",
5192 b"local t = {} for k in pairs(struct) do t[#t+1] = k end \
5193 table.sort(t) return table.concat(t, ' ')",
5194 b"0",
5195 ]),
5196 format!("${}\r\n{names}\r\n", names.len())
5197 );
5198 for body in [&b"struct.pack = 1"[..], b"rawset(struct, 'zz', 1)"] {
5199 assert!(
5200 f.run(&[b"EVAL", body, b"0"])
5201 .contains("Attempt to modify a readonly table script: "),
5202 "{body:?}",
5203 );
5204 }
5205 }
5206
5207 #[test]
5208 fn a_script_gets_the_cmsgpack_library_a_real_server_carries() {
5209 let mut f = Fixture::new();
5210 let hex = "local function hx(s) return (string.gsub(s, '.', \
5213 function(c) return string.format('%02x', string.byte(c)) end)) end ";
5214 for (body, want) in [
5215 ("cmsgpack.pack(nil)", "c0"),
5216 ("cmsgpack.pack(true)", "c3"),
5217 ("cmsgpack.pack(false)", "c2"),
5218 ("cmsgpack.pack(0)", "00"),
5219 ("cmsgpack.pack(127)", "7f"),
5220 ("cmsgpack.pack(128)", "cc80"),
5221 ("cmsgpack.pack(-1)", "ff"),
5222 ("cmsgpack.pack(-33)", "d0df"),
5223 ("cmsgpack.pack(65535)", "cdffff"),
5224 ("cmsgpack.pack(4294967296)", "cf0000000100000000"),
5225 ("cmsgpack.pack(2^53)", "cf0020000000000000"),
5226 ("cmsgpack.pack(-2^63)", "d38000000000000000"),
5227 ("cmsgpack.pack(2^64)", "ca5f800000"),
5230 ("cmsgpack.pack(1.5)", "ca3fc00000"),
5231 ("cmsgpack.pack(0.1)", "cb3fb999999999999a"),
5232 ("cmsgpack.pack('abc')", "a3616263"),
5233 ("cmsgpack.pack('')", "a0"),
5234 ("cmsgpack.pack({})", "90"),
5235 ("cmsgpack.pack({1, 2})", "920102"),
5236 ("cmsgpack.pack({a = 1})", "81a16101"),
5237 ("cmsgpack.pack(1, 'a', true)", "01a161c3"),
5238 (
5241 "(function() local t = {} local c = t \
5242 for i = 1, 20 do c.n = {} c = c.n end return cmsgpack.pack(t) end)()",
5243 "81a16e81a16e81a16e81a16e81a16e81a16e81a16e81a16e\
5244 81a16e81a16e81a16e81a16e81a16e81a16e81a16e81a16ec0",
5245 ),
5246 ] {
5247 let script = format!("{hex} return hx({body})");
5248 assert_eq!(
5249 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5250 format!("${}\r\n{want}\r\n", want.len()),
5251 "{body}",
5252 );
5253 }
5254 for (body, want) in [
5258 ("cmsgpack.unpack(cmsgpack.pack(42))", 42),
5259 ("select('#', cmsgpack.unpack('\\1\\2\\3'))", 3),
5260 ("select(3, cmsgpack.unpack('\\1\\2\\3'))", 3),
5261 ("select('#', cmsgpack.unpack(''))", 0),
5262 ("select('#', cmsgpack.unpack_one('\\1\\2\\3'))", 2),
5263 ("select(1, cmsgpack.unpack_one('\\1\\2\\3'))", 1),
5264 ("select(2, cmsgpack.unpack_one('\\1\\2\\3'))", 1),
5265 ("select(1, cmsgpack.unpack_one('\\1\\2\\3', 2))", -1),
5266 ("select(1, cmsgpack.unpack_one('\\1'))", -1),
5267 ("select(1, cmsgpack.unpack_one('', 0))", -1),
5268 ("select('#', cmsgpack.unpack_limit('\\1\\2\\3', 2))", 3),
5269 ("select(1, cmsgpack.unpack_limit('\\1\\2\\3', 2))", 2),
5270 ("select('#', cmsgpack.unpack_limit('\\1\\2\\3', 0, 0))", 3),
5273 ("cmsgpack.unpack(cmsgpack.pack({1, 2, 3}))[2]", 2),
5274 ] {
5275 let script = format!("return {body}");
5276 assert_eq!(
5277 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5278 format!(":{want}\r\n"),
5279 "{body}",
5280 );
5281 }
5282 for (body, want) in [
5283 ("cmsgpack.unpack(cmsgpack.pack({a = 'b'})).a", "b"),
5284 ("tostring(cmsgpack.unpack(cmsgpack.pack(1.5)))", "1.5"),
5285 ("tostring(cmsgpack.unpack(cmsgpack.pack(nil)))", "nil"),
5286 (
5287 "tostring(cmsgpack.unpack(string.char(0xcb, 0x7f, 0xf0, 0, 0, 0, 0, 0, 0)))",
5288 "inf",
5289 ),
5290 ("cmsgpack._NAME", "cmsgpack"),
5291 ("cmsgpack._VERSION", "lua-cmsgpack 0.4.0"),
5292 (
5293 "cmsgpack._COPYRIGHT",
5294 "Copyright (C) 2012, Salvatore Sanfilippo",
5295 ),
5296 (
5297 "cmsgpack._DESCRIPTION",
5298 "MessagePack C implementation for Lua",
5299 ),
5300 ] {
5301 let script = format!("return {body}");
5302 assert_eq!(
5303 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5304 format!("${}\r\n{want}\r\n", want.len()),
5305 "{body}",
5306 );
5307 }
5308 for (body, want) in [
5309 (
5312 "return cmsgpack.pack()",
5313 "bad argument #0 to 'pack' (MessagePack pack needs input.)",
5314 ),
5315 (
5316 "return cmsgpack.unpack()",
5317 "bad argument #1 to 'unpack' (string expected, got no value)",
5318 ),
5319 (
5320 "return cmsgpack.unpack(string.char(193))",
5321 "Bad data format in input.",
5322 ),
5323 (
5324 "return cmsgpack.unpack(string.char(204))",
5325 "Missing bytes in input.",
5326 ),
5327 (
5328 "return cmsgpack.unpack(string.char(146, 1))",
5329 "Missing bytes in input.",
5330 ),
5331 (
5332 "return cmsgpack.unpack_one('\\1', 5)",
5333 "Start offset 5 greater than input length 1.",
5334 ),
5335 (
5336 "return cmsgpack.unpack_limit('\\1\\2', 1, 5)",
5337 "Start offset 5 greater than input length 2.",
5338 ),
5339 (
5342 "return cmsgpack.unpack_one('\\1', -1)",
5343 "Invalid request to unpack with offset of -1 and limit of 1.",
5344 ),
5345 (
5346 "return cmsgpack.unpack_limit('\\1', -1, 0)",
5347 "Invalid request to unpack with offset of 0 and limit of 1.",
5348 ),
5349 ] {
5350 let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
5351 assert!(
5352 reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
5353 "{body} gave {reply}",
5354 );
5355 }
5356 let names = "_COPYRIGHT _DESCRIPTION _NAME _VERSION pack unpack unpack_limit unpack_one";
5358 assert_eq!(
5359 f.run(&[
5360 b"EVAL",
5361 b"local t = {} for k in pairs(cmsgpack) do t[#t+1] = k end \
5362 table.sort(t) return table.concat(t, ' ')",
5363 b"0",
5364 ]),
5365 format!("${}\r\n{names}\r\n", names.len())
5366 );
5367 for body in [&b"cmsgpack.pack = 1"[..], b"rawset(cmsgpack, 'zz', 1)"] {
5368 assert!(
5369 f.run(&[b"EVAL", body, b"0"])
5370 .contains("Attempt to modify a readonly table script: "),
5371 "{body:?}",
5372 );
5373 }
5374 assert_eq!(
5377 f.run(&[
5378 b"EVAL",
5379 b"return cmsgpack.unpack(cmsgpack.pack(cmsgpack))._NAME",
5380 b"0",
5381 ]),
5382 "$8\r\ncmsgpack\r\n"
5383 );
5384 }
5385
5386 const LIB: &[u8] = b"#!lua name=mylib\n\
5392 local counter = 0\n\
5393 redis.register_function{function_name = 'ping', description = 'says pong',\n\
5394 callback = function(keys, args) return 'pong' end, flags = {'no-writes'}}\n\
5395 redis.register_function('count', function() counter = counter + 1 return counter end)\n\
5396 redis.register_function('echo', function(keys, args) return {keys, args} end)\n\
5397 redis.register_function('setit', function(keys, args) \
5398 return redis.call('SET', keys[1], args[1]) end)\n\
5399 redis.register_function('raise', function() error('boom') end)\n";
5400
5401 const OTHER: &[u8] = b"#!lua name=other\n\
5403 redis.register_function('twice', function(keys, args) return 2 end)\n";
5404
5405 #[test]
5406 fn a_library_is_loaded_once_and_called_by_name_forever_after() {
5407 let mut f = Fixture::new();
5408 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5409 assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
5410 assert_eq!(f.run(&[b"FCALL", b"PiNg", b"0"]), "$4\r\npong\r\n");
5413 assert_eq!(
5417 f.run(&[b"FCALL", b"echo", b"1", b"k", b"a", b"b"]),
5418 "*2\r\n*1\r\n$1\r\nk\r\n*2\r\n$1\r\na\r\n$1\r\nb\r\n"
5419 );
5420 assert_eq!(f.run(&[b"FCALL", b"setit", b"1", b"s", b"v"]), "+OK\r\n");
5421 assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nv\r\n");
5422 assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":1\r\n");
5425 assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":2\r\n");
5426 assert_eq!(
5429 f.run(&[b"FCALL", b"raise", b"0"]),
5430 "-ERR user_function:8: boom script: raise, on @user_function:8.\r\n"
5431 );
5432 assert_eq!(
5435 f.run(&[b"FUNCTION", b"DELETE", b"MYLIB"]),
5436 "-ERR Library not found\r\n"
5437 );
5438 assert_eq!(f.run(&[b"FUNCTION", b"DELETE", b"mylib"]), "+OK\r\n");
5439 assert_eq!(
5440 f.run(&[b"FCALL", b"ping", b"0"]),
5441 "-ERR Function not found\r\n"
5442 );
5443 }
5444
5445 #[test]
5446 fn a_library_that_is_wrong_says_which_way_it_is_wrong() {
5447 let mut f = Fixture::new();
5448 for (code, want) in [
5449 (&b"return 1"[..], "ERR Missing library metadata"),
5450 (b"#!lua name=x", "ERR Invalid library metadata"),
5451 (b"#!\n", "ERR Library name was not given"),
5452 (b"#!lua\nx", "ERR Library name was not given"),
5453 (
5454 b"#!lua name=a name=b\nx",
5455 "ERR Invalid metadata value, name argument was given multiple times",
5456 ),
5457 (
5458 b"#!lua nome=a\nx",
5459 "ERR Invalid metadata value given: nome=a",
5460 ),
5461 (b"#!lua name=\"q\nx", "ERR Invalid library metadata"),
5462 (
5463 b"#!lua name=a-b\nx",
5464 "ERR Library names can only contain letters, numbers, or underscores(_) \
5465 and must be at least one character long",
5466 ),
5467 (b"#!zz name=x\nx", "ERR Engine 'zz' not found"),
5468 (
5469 b"#!lua name=c\nthis is not lua",
5470 "ERR Error compiling function: user_function:2: '=' expected near 'is'",
5471 ),
5472 (
5476 b"#!lua name=r\nerror('boom')",
5477 "ERR Error registering functions: ERR user_function:2: \
5478 Script attempted to access nonexistent global variable 'error'",
5479 ),
5480 (
5483 b"#!lua name=r\nredis.call('PING')",
5484 "ERR Error registering functions: ERR user_function:2: \
5485 Script attempted to access nonexistent global variable 'call'",
5486 ),
5487 (
5488 b"#!lua name=r\nx = 1",
5489 "ERR Error registering functions: ERR user_function:2: \
5490 Attempt to modify a readonly table",
5491 ),
5492 (b"#!lua name=n\nlocal x = 1", "ERR No functions registered"),
5493 ] {
5494 assert_eq!(
5495 f.run(&[b"FUNCTION", b"LOAD", code]),
5496 format!("-{want}\r\n"),
5497 "{}",
5498 String::from_utf8_lossy(code),
5499 );
5500 }
5501 }
5502
5503 #[test]
5504 fn register_function_turns_away_every_call_it_cannot_make_sense_of() {
5505 let mut f = Fixture::new();
5506 for (call, want) in [
5507 (
5508 &b"redis.register_function()"[..],
5509 "wrong number of arguments to redis.register_function",
5510 ),
5511 (
5512 b"redis.register_function('a', function() end, 1)",
5513 "wrong number of arguments to redis.register_function",
5514 ),
5515 (
5516 b"redis.register_function('a')",
5517 "calling redis.register_function with a single argument is only \
5518 applicable to Lua table (representing named arguments).",
5519 ),
5520 (
5521 b"redis.register_function({foo = 'a'})",
5522 "unknown argument given to redis.register_function",
5523 ),
5524 (
5525 b"redis.register_function({callback = function() end})",
5526 "redis.register_function must get a function name argument",
5527 ),
5528 (
5529 b"redis.register_function({function_name = 'a'})",
5530 "redis.register_function must get a callback argument",
5531 ),
5532 (
5533 b"redis.register_function({function_name = {}, callback = function() end})",
5534 "function_name argument given to redis.register_function must be a string",
5535 ),
5536 (
5537 b"redis.register_function({function_name = 'a', description = {}, \
5538 callback = function() end})",
5539 "description argument given to redis.register_function must be a string",
5540 ),
5541 (
5542 b"redis.register_function({function_name = 'a', callback = 1})",
5543 "callback argument given to redis.register_function must be a function",
5544 ),
5545 (
5546 b"redis.register_function({function_name = 'a', callback = function() end, \
5547 flags = 1})",
5548 "flags argument to redis.register_function must be a table \
5549 representing function flags",
5550 ),
5551 (
5552 b"redis.register_function({function_name = 'a', callback = function() end, \
5553 flags = {'zz'}})",
5554 "unknown flag given",
5555 ),
5556 (
5557 b"redis.register_function({}, function() end)",
5558 "first argument to redis.register_function must be a string",
5559 ),
5560 (
5561 b"redis.register_function('a', 1)",
5562 "second argument to redis.register_function must be a function",
5563 ),
5564 (
5565 b"redis.register_function('a-b', function() end)",
5566 "Library names can only contain letters, numbers, or underscores(_) \
5567 and must be at least one character long",
5568 ),
5569 (
5570 b"redis.register_function('d', function() end) \
5571 redis.register_function('d', function() end)",
5572 "Function already exists in the library",
5573 ),
5574 ] {
5575 let mut code = b"#!lua name=e\n".to_vec();
5576 code.extend_from_slice(call);
5577 assert_eq!(
5581 f.run(&[b"FUNCTION", b"LOAD", &code]),
5582 format!("-ERR Error registering functions: ERR {want}\r\n"),
5583 "{}",
5584 String::from_utf8_lossy(call),
5585 );
5586 }
5587 assert_eq!(
5590 f.run(&[
5591 b"FUNCTION",
5592 b"LOAD",
5593 b"#!lua name=n\nredis.register_function(12, function() return 1 end)",
5594 ]),
5595 "$1\r\nn\r\n"
5596 );
5597 assert_eq!(f.run(&[b"FCALL", b"12", b"0"]), ":1\r\n");
5598 assert_eq!(
5601 f.run(&[
5602 b"FUNCTION",
5603 b"LOAD",
5604 b"#!lua name=c\nredis.register_function('d', function() return 1 end) \
5605 redis.register_function('D', function() return 2 end)",
5606 ]),
5607 "$1\r\nc\r\n"
5608 );
5609 }
5610
5611 #[test]
5612 fn a_library_cannot_take_a_name_another_library_already_has() {
5613 let mut f = Fixture::new();
5614 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5615 assert_eq!(
5616 f.run(&[b"FUNCTION", b"LOAD", LIB]),
5617 "-ERR Library 'mylib' already exists\r\n"
5618 );
5619 assert_eq!(
5623 f.run(&[
5624 b"FUNCTION",
5625 b"LOAD",
5626 b"#!lua name=other\nredis.register_function('PING', function() return 1 end)",
5627 ]),
5628 "-ERR Function PING already exists\r\n"
5629 );
5630 assert_eq!(
5633 f.run(&[b"FUNCTION", b"LOAD", b"REPLACE", LIB]),
5634 "$5\r\nmylib\r\n"
5635 );
5636 assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":1\r\n");
5639 assert_eq!(
5640 f.run(&[b"FUNCTION", b"LOAD", b"NOPE", LIB]),
5641 "-ERR Unknown option given: NOPE\r\n"
5642 );
5643 assert_eq!(
5646 f.run(&[b"FUNCTION", b"LOAD", b"REPLACE"]),
5647 "-ERR Missing library metadata\r\n"
5648 );
5649 assert_eq!(
5650 f.run(&[b"FUNCTION", b"LOAD"]),
5651 "-ERR wrong number of arguments for 'function|load' command\r\n"
5652 );
5653 }
5654
5655 #[test]
5656 fn fcall_checks_the_name_before_it_looks_at_anything_else() {
5657 let mut f = Fixture::new();
5658 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5659 for (args, want) in [
5660 (&[&b"nosuch"[..], b"x"][..], "ERR Function not found"),
5661 (&[b"ping", b"x"], "ERR Bad number of keys provided"),
5662 (&[b"ping", b"1.5"], "ERR Bad number of keys provided"),
5663 (&[b"ping", b"+1"], "ERR Bad number of keys provided"),
5664 (
5665 &[b"ping", b"99999999999999999999"],
5666 "ERR Bad number of keys provided",
5667 ),
5668 (
5669 &[b"ping", b"3", b"a"],
5670 "ERR Number of keys can't be greater than number of args",
5671 ),
5672 (&[b"ping", b"-1"], "ERR Number of keys can't be negative"),
5673 ] {
5674 let mut wire: Vec<&[u8]> = vec![b"FCALL"];
5675 wire.extend_from_slice(args);
5676 assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
5677 }
5678 assert_eq!(
5681 f.run(&[b"FCALL_RO", b"setit", b"1", b"s", b"v"]),
5682 "-ERR Can not execute a script with write flag using *_ro command.\r\n"
5683 );
5684 assert_eq!(f.run(&[b"FCALL_RO", b"ping", b"0"]), "$4\r\npong\r\n");
5685 assert_eq!(
5686 f.run(&[b"FCALL_RO", b"nosuch", b"0"]),
5687 "-ERR Function not found\r\n"
5688 );
5689 assert_eq!(
5692 f.run(&[
5693 b"FUNCTION",
5694 b"LOAD",
5695 b"#!lua name=w\nredis.register_function{function_name = 'w', \
5696 flags = {'no-writes'}, callback = function(keys) \
5697 return redis.call('SET', keys[1], 'x') end}",
5698 ]),
5699 "$1\r\nw\r\n"
5700 );
5701 assert!(
5702 f.run(&[b"FCALL", b"w", b"1", b"k"])
5703 .starts_with("-ERR Write commands are not allowed from read-only scripts."),
5704 );
5705 }
5706
5707 #[test]
5708 fn a_function_gets_the_globals_a_script_gets_minus_the_ones_only_eval_has() {
5709 let mut f = Fixture::new();
5710 let names = "LOG_DEBUG LOG_NOTICE LOG_VERBOSE LOG_WARNING REDIS_VERSION \
5713 REDIS_VERSION_NUM REPL_ALL REPL_AOF REPL_NONE REPL_REPLICA REPL_SLAVE \
5714 acl_check_cmd call error_reply log pcall set_repl setresp sha1hex \
5715 status_reply";
5716 let globals = "_G _VERSION assert bit cjson cmsgpack collectgarbage coroutine error \
5717 gcinfo getmetatable ipairs load loadstring math next os pairs pcall \
5718 rawequal rawget rawset redis select setmetatable string struct table \
5719 tonumber tostring type unpack xpcall";
5720 assert_eq!(
5721 f.run(&[
5722 b"FUNCTION",
5723 b"LOAD",
5724 b"#!lua name=g\n\
5725 local function sorted(t) local o = {} for k in pairs(t) do o[#o+1] = k end \
5726 table.sort(o) return table.concat(o, ' ') end\n\
5727 redis.register_function('names', function() return sorted(redis) end)\n\
5728 redis.register_function('globals', function() return sorted(_G) end)\n\
5729 redis.register_function('keysg', function() return KEYS[1] end)\n\
5730 redis.register_function('zzz', function() return tostring(redis.zzz) end)\n\
5731 redis.register_function('wr', function() rawset(_G, 'x', 1) end)\n\
5732 redis.register_function('gwr', function() _G.pcall = 1 end)\n",
5733 ]),
5734 "$1\r\ng\r\n"
5735 );
5736 assert_eq!(
5737 f.run(&[b"FCALL", b"names", b"0"]),
5738 format!("${}\r\n{names}\r\n", names.len())
5739 );
5740 assert_eq!(
5741 f.run(&[b"FCALL", b"globals", b"0"]),
5742 format!("${}\r\n{globals}\r\n", globals.len())
5743 );
5744 assert!(
5747 f.run(&[b"FCALL", b"keysg", b"1", b"k"])
5748 .contains("nonexistent global variable 'KEYS'"),
5749 );
5750 assert_eq!(f.run(&[b"FCALL", b"zzz", b"0"]), "$3\r\nnil\r\n");
5753 for name in [&b"wr"[..], b"gwr"] {
5756 assert!(
5757 f.run(&[b"FCALL", name, b"0"])
5758 .contains("Attempt to modify a readonly table"),
5759 "{}",
5760 String::from_utf8_lossy(name),
5761 );
5762 }
5763 }
5764
5765 #[test]
5766 fn function_list_says_what_every_library_registered() {
5767 let mut f = Fixture::new();
5768 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5769 f.out = Out::new(Proto::Resp3);
5772 let listed = f.run(&[b"FUNCTION", b"LIST"]);
5773 assert!(listed.starts_with("*1\r\n%3\r\n$12\r\nlibrary_name\r\n$5\r\nmylib\r\n"));
5774 assert!(listed.contains("$6\r\nengine\r\n$3\r\nLUA\r\n"));
5775 assert!(listed.contains(
5776 "%3\r\n$4\r\nname\r\n$4\r\nping\r\n\
5777 $11\r\ndescription\r\n$9\r\nsays pong\r\n$5\r\nflags\r\n~1\r\n+no-writes\r\n"
5778 ));
5779 assert!(listed.contains(
5782 "$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"
5783 ));
5784 assert!(!listed.contains("library_code"));
5785 assert!(
5786 f.run(&[b"FUNCTION", b"LIST", b"WITHCODE"])
5787 .contains("library_code")
5788 );
5789 assert!(
5792 f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"MY*"])
5793 .starts_with("*1\r\n")
5794 );
5795 assert_eq!(
5796 f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"zz*"]),
5797 "*0\r\n"
5798 );
5799 f.out = Out::new(Proto::Resp2);
5802 assert!(f.run(&[b"FUNCTION", b"LIST"]).starts_with("*1\r\n*6\r\n"));
5803 for (args, want) in [
5804 (&[&b"ZZ"[..]][..], "ERR Unknown argument ZZ"),
5805 (&[b"WITHCODE", b"WITHCODE"], "ERR Unknown argument WITHCODE"),
5806 (
5807 &[b"LIBRARYNAME", b"a", b"LIBRARYNAME", b"b"],
5808 "ERR Unknown argument LIBRARYNAME",
5809 ),
5810 (&[b"LIBRARYNAME"], "ERR library name argument was not given"),
5811 ] {
5812 let mut wire: Vec<&[u8]> = vec![b"FUNCTION", b"LIST"];
5813 wire.extend_from_slice(args);
5814 assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
5815 }
5816 }
5817
5818 #[test]
5819 fn function_stats_counts_what_is_loaded_and_says_nothing_is_running() {
5820 let mut f = Fixture::new();
5821 f.out = Out::new(Proto::Resp3);
5822 assert_eq!(
5823 f.run(&[b"FUNCTION", b"STATS"]),
5824 "%2\r\n$14\r\nrunning_script\r\n_\r\n$7\r\nengines\r\n%1\r\n$3\r\nLUA\r\n\
5825 %2\r\n$15\r\nlibraries_count\r\n:0\r\n$15\r\nfunctions_count\r\n:0\r\n"
5826 );
5827 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5828 assert!(
5829 f.run(&[b"FUNCTION", b"STATS"])
5830 .ends_with("libraries_count\r\n:1\r\n$15\r\nfunctions_count\r\n:5\r\n"),
5831 );
5832 assert_eq!(f.run(&[b"FUNCTION", b"FLUSH"]), "+OK\r\n");
5833 assert!(
5834 f.run(&[b"FUNCTION", b"STATS"])
5835 .ends_with(":0\r\n$15\r\nfunctions_count\r\n:0\r\n")
5836 );
5837 }
5838
5839 #[test]
5840 fn every_function_subcommand_complains_about_its_own_arity() {
5841 let mut f = Fixture::new();
5842 for (args, want) in [
5843 (
5844 &[&b"STATS"[..], b"X"][..],
5845 "ERR wrong number of arguments for 'function|stats' command",
5846 ),
5847 (
5848 &[b"KILL", b"X"],
5849 "ERR wrong number of arguments for 'function|kill' command",
5850 ),
5851 (
5852 &[b"HELP", b"X"],
5853 "ERR wrong number of arguments for 'function|help' command",
5854 ),
5855 (
5856 &[b"DELETE"],
5857 "ERR wrong number of arguments for 'function|delete' command",
5858 ),
5859 (
5860 &[b"DELETE", b"a", b"b"],
5861 "ERR wrong number of arguments for 'function|delete' command",
5862 ),
5863 (
5864 &[b"DUMP", b"X"],
5865 "ERR wrong number of arguments for 'function|dump' command",
5866 ),
5867 (
5868 &[b"RESTORE"],
5869 "ERR wrong number of arguments for 'function|restore' command",
5870 ),
5871 (
5874 &[b"RESTORE", b"a", b"FLUSH", b"X"],
5875 "ERR unknown subcommand or wrong number of arguments for 'RESTORE'. \
5876 Try FUNCTION HELP.",
5877 ),
5878 (
5879 &[b"RESTORE", b"a", b"ZZ"],
5880 "ERR Wrong restore policy given, value should be either FLUSH, APPEND \
5881 or REPLACE.",
5882 ),
5883 (
5886 &[b"FLUSH", b"SYNC", b"X"],
5887 "ERR unknown subcommand or wrong number of arguments for 'FLUSH'. \
5888 Try FUNCTION HELP.",
5889 ),
5890 (
5891 &[b"FLUSH", b"ZZ"],
5892 "ERR FUNCTION FLUSH only supports SYNC|ASYNC option",
5893 ),
5894 (&[b"ZZ"], "ERR unknown subcommand 'ZZ'. Try FUNCTION HELP."),
5895 ] {
5896 let mut wire: Vec<&[u8]> = vec![b"FUNCTION"];
5897 wire.extend_from_slice(args);
5898 assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
5899 }
5900 assert_eq!(
5901 f.run(&[b"FUNCTION"]),
5902 "-ERR wrong number of arguments for 'function' command\r\n"
5903 );
5904 assert_eq!(
5905 f.run(&[b"FUNCTION", b"KILL"]),
5906 "-NOTBUSY No scripts in execution right now.\r\n"
5907 );
5908 }
5909
5910 #[test]
5916 fn a_library_survives_a_dump_and_a_restore() {
5917 let mut f = Fixture::new();
5918 let empty = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
5919 assert_eq!(empty.len(), 10);
5920 assert_eq!(f.run(&[b"FUNCTION", b"RESTORE", &empty]), "+OK\r\n");
5921
5922 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5923 let full = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
5924 assert!(full.len() > empty.len());
5925
5926 assert_eq!(
5929 f.run(&[b"FUNCTION", b"RESTORE", &full]),
5930 "-ERR Library mylib already exists\r\n"
5931 );
5932 assert_eq!(
5933 f.run(&[b"FUNCTION", b"RESTORE", &full, b"REPLACE"]),
5934 "+OK\r\n"
5935 );
5936 assert_eq!(
5937 f.run(&[b"FUNCTION", b"RESTORE", &full, b"FLUSH"]),
5938 "+OK\r\n"
5939 );
5940 assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
5942
5943 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", OTHER]), "$5\r\nother\r\n");
5946 assert_eq!(
5947 f.run(&[b"FUNCTION", b"RESTORE", &full, b"FLUSH"]),
5948 "+OK\r\n"
5949 );
5950 assert_eq!(
5951 f.run(&[b"FUNCTION", b"DELETE", b"other"]),
5952 "-ERR Library not found\r\n"
5953 );
5954 }
5955
5956 #[test]
5963 fn a_restore_that_fails_changes_nothing() {
5964 let mut f = Fixture::new();
5965 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5966 let good = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
5967
5968 let reseal = |body: &[u8], version: u16| {
5972 let mut out = body.to_vec();
5973 out.extend_from_slice(&version.to_le_bytes());
5974 let crc = yo_common::crc::crc64(0, &out);
5975 out.extend_from_slice(&crc.to_le_bytes());
5976 out
5977 };
5978 let body = &good[..good.len() - 10];
5979
5980 let mut torn = good.clone();
5981 let n = torn.len();
5982 torn[n - 1] ^= 0xff;
5983 let future = reseal(body, 999);
5984 let mut pre_ga = body.to_vec();
5987 pre_ga[0] = 246;
5988 let pre_ga = reseal(&pre_ga, yo_kv::rdb::VERSION);
5989 let mut other = body.to_vec();
5990 other[0] = 0;
5991 let other = reseal(&other, yo_kv::rdb::VERSION);
5992 let mut cut = body.to_vec();
5994 cut.truncate(body.len() - 1);
5995 let cut = reseal(&cut, yo_kv::rdb::VERSION);
5996
5997 for (bytes, want) in [
5998 (vec![], "ERR DUMP payload version or checksum are wrong"),
5999 (
6000 b"0123456789".to_vec(),
6001 "ERR DUMP payload version or checksum are wrong",
6002 ),
6003 (torn, "ERR DUMP payload version or checksum are wrong"),
6004 (future, "ERR DUMP payload version or checksum are wrong"),
6005 (pre_ga, "ERR Pre-GA function format not supported"),
6006 (other, "ERR given type is not a function"),
6007 (cut, "ERR Failed loading library payload"),
6008 ] {
6009 assert_eq!(
6010 f.run(&[b"FUNCTION", b"RESTORE", &bytes]),
6011 format!("-{want}\r\n")
6012 );
6013 }
6014
6015 assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
6017 let again = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
6018 assert_eq!(again, good);
6019 }
6020
6021 #[test]
6024 fn a_restore_will_not_take_a_function_name_off_a_library_it_keeps() {
6025 let mut f = Fixture::new();
6026 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
6027 let full = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
6028 let clash =
6030 b"#!lua name=cl\nredis.register_function('ping', function() return 'other' end)"
6031 .as_slice();
6032 assert_eq!(f.run(&[b"FUNCTION", b"FLUSH"]), "+OK\r\n");
6033 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", clash]), "$2\r\ncl\r\n");
6034 assert_eq!(
6035 f.run(&[b"FUNCTION", b"RESTORE", &full, b"REPLACE"]),
6036 "-ERR Function ping already exists\r\n"
6037 );
6038 assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$5\r\nother\r\n");
6040 }
6041
6042 #[test]
6043 fn command_getkeys_reads_the_key_count_out_of_a_script_call() {
6044 let mut f = Fixture::new();
6045 assert_eq!(
6046 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"1", b"k"]),
6047 "*1\r\n$1\r\nk\r\n"
6048 );
6049 assert_eq!(
6050 f.run(&[
6051 b"COMMAND", b"GETKEYS", b"EVALSHA", b"abc", b"2", b"k1", b"k2"
6052 ]),
6053 "*2\r\n$2\r\nk1\r\n$2\r\nk2\r\n"
6054 );
6055 assert_eq!(
6058 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL_RO", b"return 1", b"0", b"a"]),
6059 "*0\r\n"
6060 );
6061 assert_eq!(
6064 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"3", b"k"]),
6065 "*0\r\n"
6066 );
6067 assert_eq!(
6068 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"-1"]),
6069 "*0\r\n"
6070 );
6071 assert_eq!(
6072 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"abc"]),
6073 "*0\r\n"
6074 );
6075 assert_eq!(
6077 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1"]),
6078 "-ERR Invalid number of arguments specified for command\r\n"
6079 );
6080 }
6081
6082 #[test]
6083 fn the_helpers_on_the_redis_table_answer_the_way_they_are_documented() {
6084 let mut f = Fixture::new();
6085 let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
6086
6087 assert_eq!(
6088 eval(&mut f, b"return redis.sha1hex('')"),
6089 "$40\r\nda39a3ee5e6b4b0d3255bfef95601890afd80709\r\n"
6090 );
6091 assert_eq!(
6092 eval(&mut f, b"return redis.sha1hex('return 1')"),
6093 "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
6094 );
6095 assert_eq!(
6098 eval(&mut f, b"return redis.error_reply('boom')"),
6099 "-ERR boom\r\n"
6100 );
6101 assert_eq!(
6102 eval(&mut f, b"return redis.error_reply('WRONGTYPE nope')"),
6103 "-WRONGTYPE nope\r\n"
6104 );
6105 assert_eq!(
6106 eval(&mut f, b"return redis.status_reply('fine')"),
6107 "+fine\r\n"
6108 );
6109 assert_eq!(
6112 eval(&mut f, b"return redis.error_reply(1)"),
6113 "-ERR wrong number or type of arguments\r\n"
6114 );
6115 assert_eq!(
6116 eval(&mut f, b"local x = redis.status_reply() return x.err"),
6117 "$37\r\nERR wrong number or type of arguments\r\n"
6118 );
6119
6120 assert_eq!(
6122 eval(
6123 &mut f,
6124 b"return redis.LOG_DEBUG .. redis.LOG_VERBOSE .. redis.LOG_NOTICE .. redis.LOG_WARNING"
6125 ),
6126 "$4\r\n0123\r\n"
6127 );
6128 assert_eq!(
6129 eval(
6130 &mut f,
6131 b"return redis.REPL_NONE .. redis.REPL_AOF .. redis.REPL_SLAVE .. redis.REPL_REPLICA .. redis.REPL_ALL"
6132 ),
6133 "$5\r\n01223\r\n"
6134 );
6135 assert_eq!(eval(&mut f, b"return redis.replicate_commands()"), ":1\r\n");
6137 assert_eq!(
6138 eval(&mut f, b"redis.set_repl(redis.REPL_ALL) return 1"),
6139 ":1\r\n"
6140 );
6141 assert_eq!(
6142 eval(&mut f, b"redis.log(redis.LOG_WARNING, 'x') return 1"),
6143 ":1\r\n"
6144 );
6145 assert_eq!(
6146 eval(&mut f, b"return redis.acl_check_cmd('get', 'k')"),
6147 ":1\r\n"
6148 );
6149 assert!(eval(&mut f, b"redis.setresp(4)").contains("RESP version must be 2 or 3."),);
6151 assert!(eval(&mut f, b"redis.set_repl(9)").contains("Invalid replication flags."));
6152 assert!(
6153 eval(&mut f, b"redis.log('x', 'y')")
6154 .contains("First argument must be a number (log level)."),
6155 );
6156 assert!(
6157 eval(&mut f, b"return redis.acl_check_cmd('nosuchcmd')")
6158 .contains("Invalid command passed to redis.acl_check_cmd()"),
6159 );
6160 assert!(
6161 eval(&mut f, b"return redis.acl_check_cmd('get')")
6162 .contains("Wrong number of args for redis.acl_check_cmd()"),
6163 );
6164 }
6165
6166 #[test]
6167 fn a_counter_is_an_integer_and_not_a_string_of_digits() {
6168 let mut f = Fixture::new();
6169 assert_eq!(f.run(&[b"INCR", b"c"]), ":1\r\n");
6170 assert_eq!(f.run(&[b"INCRBY", b"c", b"41"]), ":42\r\n");
6171 assert_eq!(f.run(&[b"DECRBY", b"c", b"2"]), ":40\r\n");
6172 assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\n40\r\n");
6175 assert_eq!(f.run(&[b"INCRBYFLOAT", b"c", b"0.5"]), "$4\r\n40.5\r\n");
6176 f.run(&[b"SET", b"k", b"hello"]);
6179 assert_eq!(
6180 f.run(&[b"INCR", b"k"]),
6181 "-ERR value is not an integer or out of range\r\n"
6182 );
6183 assert_eq!(
6184 f.run(&[b"INCRBYFLOAT", b"c", b"inf"]),
6185 "-ERR increment would produce NaN or Infinity\r\n"
6186 );
6187 }
6188
6189 #[test]
6194 fn the_newer_commands_reply_in_the_shapes_a_real_server_sends() {
6195 let mut f = Fixture::new();
6196 assert_eq!(f.run(&[b"SET", b"k", b"hello"]), "+OK\r\n");
6197 assert_eq!(f.run(&[b"DIGEST", b"k"]), "$16\r\n9555e8555c62dcfd\r\n");
6200 assert_eq!(f.run(&[b"DIGEST", b"nosuch"]), "$-1\r\n");
6201 assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"1"]), ":1\r\n");
6202 assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"2", b"NX"]), ":0\r\n");
6203 assert_eq!(f.run(&[b"GET", b"a"]), "$1\r\n1\r\n");
6204 assert_eq!(f.run(&[b"INCREX", b"n"]), "*2\r\n:1\r\n:1\r\n");
6205 assert_eq!(
6206 f.run(&[b"INCREX", b"n", b"BYINT", b"5", b"UBOUND", b"3"]),
6207 "*2\r\n:1\r\n:0\r\n",
6208 "a refused increment reports the value it left alone and applied nothing"
6209 );
6210 assert_eq!(
6211 f.run(&[
6212 b"INCREX",
6213 b"n",
6214 b"BYINT",
6215 b"5",
6216 b"UBOUND",
6217 b"3",
6218 b"SATURATE"
6219 ]),
6220 "*2\r\n:3\r\n:2\r\n"
6221 );
6222 assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"2"]), ":0\r\n");
6223 assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"1"]), ":1\r\n");
6224 }
6225
6226 #[test]
6227 fn the_same_answers_come_out_in_resp3_spelling() {
6228 let mut f = Fixture::new();
6229 assert!(f.run(&[b"HELLO", b"3"]).starts_with("%7\r\n"));
6230 assert_eq!(f.run(&[b"GET", b"nosuch"]), "_\r\n");
6231 assert_eq!(
6234 f.run(&[b"INCREX", b"c", b"BYFLOAT", b"1.5"]),
6235 "*2\r\n,1.5\r\n,1.5\r\n"
6236 );
6237 assert_eq!(f.run(&[b"INCRBYFLOAT", b"f", b"2.5"]), "$3\r\n2.5\r\n");
6238 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
6241 assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
6242 }
6243
6244 #[test]
6245 fn a_command_nobody_has_heard_of_is_an_error_and_not_a_closed_socket() {
6246 let mut f = Fixture::new();
6247 let (flow, reply) = f.flow(&[b"NOPE", b"a", b"b"]);
6248 assert_eq!(flow, Flow::Continue);
6249 assert_eq!(
6250 reply,
6251 "-ERR unknown command 'NOPE', with args beginning with: 'a' 'b' \r\n"
6252 );
6253 let reply = f.run(&[b"NO\r\n+PONG\r\nPE"]);
6256 assert_eq!(reply.matches("\r\n").count(), 1);
6257 }
6258
6259 #[test]
6260 fn arity_is_checked_before_the_command_is() {
6261 let mut f = Fixture::new();
6262 assert_eq!(
6263 f.run(&[b"GET"]),
6264 "-ERR wrong number of arguments for 'get' command\r\n"
6265 );
6266 assert_eq!(
6267 f.run(&[b"MSET", b"k"]),
6268 "-ERR wrong number of arguments for 'mset' command\r\n"
6269 );
6270 assert_eq!(
6274 f.run(&[b"PING", b"a", b"b"]),
6275 "-ERR wrong number of arguments for 'ping' command\r\n"
6276 );
6277 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
6278 assert_eq!(f.run(&[b"PING", b"hi"]), "$2\r\nhi\r\n");
6279 assert_eq!(
6281 f.run(&[b"DELEX", b"k", b"IFEQ"]),
6282 "-ERR wrong number of arguments for 'delex' command\r\n"
6283 );
6284 }
6285
6286 #[test]
6290 fn the_option_combinations_are_the_ones_a_real_server_accepts() {
6291 let mut f = Fixture::new();
6292 let syntax = "-ERR syntax error\r\n";
6293 assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"XX"]), syntax);
6294 assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"IFEQ", b"a"]), syntax);
6295 assert_eq!(
6296 f.run(&[b"SET", b"k", b"v", b"KEEPTTL", b"EX", b"5"]),
6297 syntax
6298 );
6299 assert_eq!(
6300 f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"PX", b"5"]),
6301 syntax
6302 );
6303 assert_eq!(f.run(&[b"SET", b"k", b"v", b"PERSIST"]), syntax);
6304 assert_eq!(
6306 f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"EX", b"100"]),
6307 "+OK\r\n"
6308 );
6309 assert_eq!(f.run(&[b"SET", b"k", b"v", b"XX", b"XX"]), "+OK\r\n");
6310 assert_eq!(f.run(&[b"SET", b"k", b"v", b"GET", b"GET"]), "$1\r\nv\r\n");
6311 assert_eq!(
6313 f.run(&[b"INCREX", b"n", b"BYINT", b"1", b"BYINT", b"2"]),
6314 syntax
6315 );
6316 assert_eq!(
6317 f.run(&[b"INCREX", b"n", b"ENX"]),
6318 "-ERR ENX flag requires an expiration\r\n"
6319 );
6320 assert_eq!(
6321 f.run(&[b"INCREX", b"n", b"UBOUND", b"abc"]),
6322 "-ERR UBOUND is not an integer or out of range\r\n"
6323 );
6324 assert_eq!(
6325 f.run(&[b"INCREX", b"n", b"LBOUND", b"10", b"UBOUND", b"5"]),
6326 "-ERR LBOUND can't be greater than UBOUND\r\n"
6327 );
6328 assert_eq!(
6329 f.run(&[b"LCS", b"a", b"b", b"LEN", b"IDX"]),
6330 "-ERR If you want both the length and indexes, please just use IDX.\r\n"
6331 );
6332 }
6333
6334 #[test]
6338 fn the_expiry_rules_are_redis_own() {
6339 let mut f = Fixture::new();
6340 let bad = "-ERR invalid expire time in 'set' command\r\n";
6341 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"0"]), bad);
6342 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"-1"]), bad);
6343 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"0"]), bad);
6344 assert_eq!(
6345 f.run(&[b"SET", b"k", b"v", b"EX", b"9999999999999999"]),
6346 bad
6347 );
6348 assert_eq!(
6349 f.run(&[b"SET", b"k", b"v", b"PX", b"99999999999999999999"]),
6350 "-ERR value is not an integer or out of range\r\n"
6351 );
6352 assert_eq!(
6353 f.run(&[b"SETEX", b"k", b"0", b"v"]),
6354 "-ERR invalid expire time in 'setex' command\r\n"
6355 );
6356 assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"0"]), "$-1\r\n");
6357 assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"abc"]), "$-1\r\n");
6358 assert_eq!(
6359 f.run(&[b"GETEX", b"nosuch", b"KEEPTTL"]),
6360 "-ERR syntax error\r\n",
6361 "the option list is still checked before the key is looked up"
6362 );
6363 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
6365 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"1"]), "+OK\r\n");
6366 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
6367 }
6368
6369 #[test]
6370 fn mset_takes_its_pairs_from_the_read_buffer() {
6371 let mut f = Fixture::new();
6372 assert_eq!(f.run(&[b"MSET", b"a", b"1", b"b", b"2"]), "+OK\r\n");
6373 assert_eq!(
6374 f.run(&[b"MGET", b"a", b"b", b"nosuch"]),
6375 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$-1\r\n"
6376 );
6377 assert_eq!(f.run(&[b"MSETNX", b"b", b"9", b"c", b"3"]), ":0\r\n");
6378 assert_eq!(f.run(&[b"MSETNX", b"c", b"3", b"d", b"4"]), ":1\r\n");
6379 assert_eq!(
6380 f.run(&[b"MSETEX", b"2", b"e", b"5"]),
6381 "-ERR wrong number of key-value pairs\r\n"
6382 );
6383 assert_eq!(
6384 f.run(&[b"MSETEX", b"0", b"e", b"5"]),
6385 "-ERR invalid numkeys value\r\n"
6386 );
6387 assert_eq!(
6388 f.run(&[b"MSETEX", b"abc", b"e", b"5"]),
6389 "-ERR invalid numkeys value\r\n"
6390 );
6391 }
6392
6393 #[test]
6394 fn lcs_answers_the_length_the_string_and_the_runs() {
6395 let mut f = Fixture::new();
6396 f.run(&[b"MSET", b"a", b"ohmytext", b"b", b"mynewtext"]);
6397 assert_eq!(f.run(&[b"LCS", b"a", b"b"]), "$6\r\nmytext\r\n");
6398 assert_eq!(f.run(&[b"LCS", b"a", b"b", b"LEN"]), ":6\r\n");
6399 assert_eq!(
6400 f.run(&[b"LCS", b"a", b"b", b"IDX", b"MINMATCHLEN", b"4"]),
6401 "*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"
6402 );
6403 assert_eq!(
6406 f.run(&[b"LCS", b"a", b"b", b"MINMATCHLEN", b"4", b"WITHMATCHLEN"]),
6407 "$6\r\nmytext\r\n"
6408 );
6409 }
6410
6411 #[test]
6412 fn select_moves_the_connection_and_the_databases_stay_apart() {
6413 let mut f = Fixture::new();
6414 f.run(&[b"SET", b"k", b"zero"]);
6415 assert_eq!(f.run(&[b"SELECT", b"4"]), "+OK\r\n");
6416 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
6417 f.run(&[b"SET", b"k", b"four"]);
6418 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
6419 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
6420 assert_eq!(
6421 f.run(&[b"SELECT", b"99"]),
6422 "-ERR DB index is out of range\r\n"
6423 );
6424 assert_eq!(
6425 f.run(&[b"SELECT", b"-1"]),
6426 "-ERR DB index is out of range\r\n"
6427 );
6428 assert_eq!(
6429 f.run(&[b"SELECT", b"abc"]),
6430 "-ERR value is not an integer or out of range\r\n"
6431 );
6432 f.run(&[b"SELECT", b"4"]);
6434 f.run(&[b"RESET"]);
6435 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
6436 }
6437
6438 #[test]
6439 fn hello_agrees_on_a_protocol_and_refuses_the_ones_that_do_not_exist() {
6440 let mut f = Fixture::new();
6441 let reply = f.run(&[b"HELLO"]);
6442 assert!(reply.starts_with("*14\r\n"), "{reply}");
6443 assert!(reply.contains("$5\r\nredis\r\n"), "{reply}");
6444 assert!(reply.contains("$5\r\n8.8.0\r\n"), "{reply}");
6445 assert!(
6446 reply.contains(":7\r\n"),
6447 "the connection id is in there: {reply}"
6448 );
6449 assert_eq!(
6450 f.run(&[b"HELLO", b"4"]),
6451 "-NOPROTO unsupported protocol version\r\n"
6452 );
6453 assert_eq!(
6454 f.run(&[b"HELLO", b"abc"]),
6455 "-ERR Protocol version is not an integer or out of range\r\n"
6456 );
6457 assert_eq!(
6458 f.run(&[b"HELLO", b"3", b"SETNAME"]),
6459 "-ERR Syntax error in HELLO option 'SETNAME'\r\n"
6460 );
6461 assert!(
6462 f.run(&[b"HELLO", b"3", b"SETNAME", b"bob"])
6463 .starts_with("%7\r\n")
6464 );
6465 assert_eq!(f.session.name(), b"bob");
6466 f.run(&[b"RESET"]);
6467 assert_eq!(f.session.name(), b"");
6468 }
6469
6470 #[test]
6471 fn command_describes_this_server_in_the_shape_a_driver_reads() {
6472 let mut f = Fixture::new();
6473 let count = format!(":{}\r\n", COMMANDS.len());
6474 assert_eq!(f.run(&[b"COMMAND", b"COUNT"]), count);
6475 let info = f.run(&[b"COMMAND", b"INFO", b"get"]);
6476 assert_eq!(
6477 info,
6478 "*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\
6479 *3\r\n+@read\r\n+@string\r\n+@fast\r\n*0\r\n*0\r\n*0\r\n"
6480 );
6481 assert_eq!(f.run(&[b"COMMAND", b"INFO", b"nosuch"]), "*1\r\n$-1\r\n");
6483 assert_eq!(
6484 f.run(&[b"COMMAND", b"LIST", b"FILTERBY", b"PATTERN", b"getr*"]),
6485 "*1\r\n$8\r\ngetrange\r\n"
6486 );
6487 assert_eq!(
6488 f.run(&[b"COMMAND", b"NOPE"]),
6489 "-ERR unknown subcommand 'NOPE'. Try COMMAND HELP.\r\n"
6490 );
6491 }
6492
6493 #[test]
6497 fn command_getkeys_finds_the_keys_including_the_hidden_ones() {
6498 let mut f = Fixture::new();
6499 assert_eq!(
6500 f.run(&[b"COMMAND", b"GETKEYS", b"get", b"k"]),
6501 "*1\r\n$1\r\nk\r\n"
6502 );
6503 assert_eq!(
6504 f.run(&[b"COMMAND", b"GETKEYS", b"mset", b"a", b"1", b"b", b"2"]),
6505 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
6506 );
6507 assert_eq!(
6508 f.run(&[
6509 b"COMMAND", b"GETKEYS", b"msetex", b"2", b"a", b"1", b"b", b"2"
6510 ]),
6511 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
6512 );
6513 assert_eq!(
6514 f.run(&[b"COMMAND", b"GETKEYS", b"ping"]),
6515 "-ERR The command has no key arguments\r\n"
6516 );
6517 assert_eq!(
6518 f.run(&[b"COMMAND", b"GETKEYS", b"set"]),
6519 "-ERR Invalid number of arguments specified for command\r\n"
6520 );
6521 }
6522
6523 #[test]
6524 fn config_answers_what_it_can_and_refuses_what_it_cannot() {
6525 let mut f = Fixture::new();
6526 assert_eq!(
6527 f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
6528 "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n"
6529 );
6530 let both = f.run(&[b"CONFIG", b"GET", b"maxmemory*", b"maxmemory"]);
6533 assert!(both.starts_with("*6\r\n"), "{both}");
6534 assert_eq!(f.run(&[b"CONFIG", b"GET", b"nosuch"]), "*0\r\n");
6535 assert_eq!(f.run(&[b"CONFIG", b"SET", b"appendonly", b"no"]), "+OK\r\n");
6536 assert_eq!(
6537 f.run(&[b"CONFIG", b"SET", b"appendonly", b"yes"]),
6538 "-ERR CONFIG SET failed (possibly related to argument 'appendonly') - can't set immutable config\r\n"
6539 );
6540 assert_eq!(
6541 f.run(&[b"CONFIG", b"SET", b"nosuch", b"1"]),
6542 "-ERR Unknown option or number of arguments for CONFIG SET - 'nosuch'\r\n"
6543 );
6544 assert_eq!(
6545 f.run(&[b"CONFIG", b"GET"]),
6546 "-ERR wrong number of arguments for 'config|get' command\r\n"
6547 );
6548 assert_eq!(
6552 f.run(&[b"CONFIG", b"SET", b"appendonly"]),
6553 "-ERR wrong number of arguments for 'config|set' command\r\n"
6554 );
6555 assert_eq!(
6556 f.run(&[b"CONFIG", b"SET", b"appendonly", b"no", b"maxmemory"]),
6557 "-ERR syntax error\r\n"
6558 );
6559 assert_eq!(f.run(&[b"CONFIG", b"RESETSTAT"]), "+OK\r\n");
6560 assert_eq!(
6561 f.run(&[b"CONFIG", b"REWRITE"]),
6562 "-ERR The server is running without a config file\r\n"
6563 );
6564 }
6565
6566 #[test]
6567 fn the_eviction_policy_reads_back_what_was_written_to_it() {
6568 let mut f = Fixture::new();
6569 assert_eq!(
6570 f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
6571 "*2\r\n$16\r\nmaxmemory-policy\r\n$10\r\nnoeviction\r\n"
6572 );
6573 assert_eq!(
6574 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"AllKeys-LFU"]),
6575 "+OK\r\n",
6576 "the name is matched without regard to case, like every other one"
6577 );
6578 assert_eq!(
6579 f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
6580 "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
6581 );
6582 assert!(
6584 f.run(&[b"INFO", b"memory"])
6585 .contains("maxmemory_policy:allkeys-lfu"),
6586 "INFO and CONFIG disagree about the policy"
6587 );
6588 assert_eq!(
6592 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"garbage"]),
6593 "-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"
6594 );
6595 assert_eq!(
6598 f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
6599 "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
6600 );
6601 f.run(&[
6602 b"CONFIG",
6603 b"SET",
6604 b"hash-max-listpack-entries",
6605 b"7",
6606 b"maxmemory-policy",
6607 b"nonsense",
6608 ]);
6609 assert_eq!(
6610 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
6611 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n"
6612 );
6613 }
6614
6615 #[test]
6616 fn the_three_eviction_numbers_read_back_too() {
6617 let mut f = Fixture::new();
6618 for (name, default, set) in [
6619 ("maxmemory-samples", "5", "12"),
6620 ("lfu-log-factor", "10", "3"),
6621 ("lfu-decay-time", "1", "60"),
6622 ] {
6623 let get = || {
6624 format!(
6625 "*2\r\n${}\r\n{name}\r\n${}\r\n{default}\r\n",
6626 name.len(),
6627 default.len()
6628 )
6629 };
6630 assert_eq!(f.run(&[b"CONFIG", b"GET", name.as_bytes()]), get());
6631 assert_eq!(
6632 f.run(&[b"CONFIG", b"SET", name.as_bytes(), set.as_bytes()]),
6633 "+OK\r\n"
6634 );
6635 assert_eq!(
6636 f.run(&[b"CONFIG", b"GET", name.as_bytes()]),
6637 format!(
6638 "*2\r\n${}\r\n{name}\r\n${}\r\n{set}\r\n",
6639 name.len(),
6640 set.len()
6641 )
6642 );
6643 assert_eq!(
6646 f.run(&[b"CONFIG", b"SET", name.as_bytes(), b"soon"]),
6647 format!(
6648 "-ERR CONFIG SET failed (possibly related to argument '{name}') - argument couldn't be parsed into an integer\r\n"
6649 )
6650 );
6651 }
6652 }
6653
6654 #[test]
6655 fn the_memory_limit_reads_back_in_bytes_whatever_the_unit_was() {
6656 let mut f = Fixture::new();
6657 assert_eq!(
6658 f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
6659 "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n",
6660 "no limit is the default"
6661 );
6662 for (typed, bytes) in [
6665 (&b"1024"[..], "1024"),
6666 (b"1k", "1000"),
6667 (b"1kb", "1024"),
6668 (b"1M", "1000000"),
6669 (b"1Mb", "1048576"),
6670 (b"1gb", "1073741824"),
6671 (b"100mb", "104857600"),
6672 ] {
6673 assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxmemory", typed]), "+OK\r\n");
6674 assert_eq!(
6675 f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
6676 format!("*2\r\n$9\r\nmaxmemory\r\n${}\r\n{bytes}\r\n", bytes.len()),
6677 "set {}",
6678 String::from_utf8_lossy(typed)
6679 );
6680 }
6681 assert!(
6682 f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
6683 "the report agrees with the setting"
6684 );
6685
6686 for bad in [&b"1tb"[..], b"-1", b"", b"lots"] {
6689 assert_eq!(
6690 f.run(&[b"CONFIG", b"SET", b"maxmemory", bad]),
6691 "-ERR CONFIG SET failed (possibly related to argument 'maxmemory') - argument must be a memory value\r\n",
6692 "refused {}",
6693 String::from_utf8_lossy(bad)
6694 );
6695 }
6696 assert!(
6697 f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
6698 "and the refusal left the old one alone"
6699 );
6700 }
6701
6702 #[test]
6703 fn a_write_is_refused_when_there_is_no_room_and_nothing_to_evict() {
6704 let mut f = Fixture::new();
6705 f.run(&[b"SET", b"here", b"already"]);
6706 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1"]);
6710 assert_eq!(
6711 f.run(&[b"SET", b"k", b"v"]),
6712 "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
6713 );
6714 assert_eq!(
6715 f.run(&[b"LPUSH", b"l", b"v"]),
6716 "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
6717 );
6718 assert_eq!(f.run(&[b"GET", b"here"]), "$7\r\nalready\r\n");
6720 assert_eq!(f.run(&[b"DEL", b"here"]), ":1\r\n");
6721 assert!(f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"));
6722
6723 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
6725 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
6726 }
6727
6728 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
6732 #[test]
6733 fn an_allkeys_policy_makes_room_instead_of_refusing() {
6734 let mut f = Fixture::new();
6735 let val = vec![b'v'; 256];
6736 for i in 0..24000u32 {
6737 let k = format!("key:{i:08}");
6738 f.run(&[b"SET", k.as_bytes(), &val]);
6739 }
6740 let full = f.server.memory_bytes();
6741 assert!(
6742 full > 3 * 1024 * 1024,
6743 "the arena is several segments: {full}"
6744 );
6745
6746 let limit = full - 2 * 1024 * 1024;
6750 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
6751 f.run(&[
6752 b"CONFIG",
6753 b"SET",
6754 b"maxmemory",
6755 limit.to_string().as_bytes(),
6756 ]);
6757
6758 for i in 0..2000u32 {
6762 let k = format!("new:{i:08}");
6763 assert_eq!(
6764 f.run(&[b"SET", k.as_bytes(), &val]),
6765 "+OK\r\n",
6766 "write {i} was refused"
6767 );
6768 f.server.refresh_memory();
6769 if f.server.memory_bytes() <= limit {
6770 break;
6771 }
6772 }
6773 assert!(
6774 f.server.memory_bytes() <= limit,
6775 "it never got under: {} against {limit}",
6776 f.server.memory_bytes()
6777 );
6778 let info = f.run(&[b"INFO", b"stats"]);
6779 assert!(!info.contains("evicted_keys:0"), "{info}");
6780 assert!(
6781 f.run(&[b"DBSIZE"]) != ":0\r\n",
6782 "and it did not empty the database to get there"
6783 );
6784 }
6785
6786 #[cfg_attr(miri, ignore = "an hour of commands, and they cannot come down")]
6797 #[test]
6798 fn the_running_total_and_the_walk_agree_on_a_mixed_keyspace() {
6799 let mut f = Fixture::new();
6806 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
6807 let big = vec![b'v'; 200];
6808
6809 for i in 0..400u32 {
6810 let n = i.to_string();
6811 let n = n.as_bytes();
6812 f.run(&[b"SADD", b"s", n]);
6813 f.run(&[b"SADD", b"s2", &big]);
6814 f.run(&[b"HSET", b"h", n, &big]);
6815 f.run(&[b"RPUSH", b"l", &big]);
6816 f.run(&[b"ZADD", b"z", n, n]);
6817 f.run(&[b"ARSET", b"a", n, &big]);
6818 if i % 7 == 0 {
6819 f.run(&[b"SREM", b"s", n]);
6820 f.run(&[b"HDEL", b"h", n]);
6821 f.run(&[b"LPOP", b"l"]);
6822 f.run(&[b"ZREM", b"z", n]);
6823 f.run(&[b"ARDEL", b"a", n]);
6824 }
6825 if i % 53 == 0 {
6826 f.run(&[b"DEL", b"s2"]);
6829 }
6830 assert_eq!(
6831 f.server.settled_memory(),
6832 f.server.memory_bytes(),
6833 "after round {i}"
6834 );
6835 }
6836
6837 assert_eq!(f.run(&[b"DBSIZE"]), ":6\r\n");
6840 assert!(
6841 f.server.memory_bytes() > 512 * 1024,
6842 "{}",
6843 f.server.memory_bytes()
6844 );
6845
6846 f.run(&[b"FLUSHALL"]);
6848 assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
6849 }
6850
6851 #[test]
6852 fn taking_the_limit_away_stops_the_counting_and_putting_it_back_starts_again() {
6853 let mut f = Fixture::new();
6858 for i in 0..200u32 {
6859 let n = i.to_string();
6860 f.run(&[b"SADD", b"s", n.as_bytes()]);
6861 f.run(&[b"HSET", b"h", n.as_bytes(), b"value"]);
6862 }
6863 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
6864 assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
6865
6866 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
6867 for i in 200..400u32 {
6868 let n = i.to_string();
6869 f.run(&[b"SADD", b"s", n.as_bytes()]);
6870 }
6871 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
6872 assert_eq!(
6873 f.server.settled_memory(),
6874 f.server.memory_bytes(),
6875 "the writes it was not watching are in the number it started from"
6876 );
6877 }
6878
6879 #[test]
6880 fn evicted_keys_and_expired_keys_are_different_numbers() {
6881 let mut f = Fixture::new();
6882 f.run(&[b"SET", b"gone", b"v", b"PX", b"1"]);
6885 f.server.advance_clock_ms(20);
6886 f.run(&[b"GET", b"gone"]);
6887 let info = f.run(&[b"INFO", b"stats"]);
6888 assert!(info.contains("expired_keys:1"), "{info}");
6889 assert!(info.contains("evicted_keys:0"), "{info}");
6890 }
6891
6892 #[test]
6893 fn the_object_subcommands_follow_the_policy() {
6894 let mut f = Fixture::new();
6895 f.run(&[b"SET", b"s", b"v"]);
6896 assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
6900 assert!(
6901 f.run(&[b"OBJECT", b"FREQ", b"s"])
6902 .starts_with("-ERR An LFU maxmemory policy is not selected"),
6903 );
6904
6905 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lfu"]);
6906 assert!(
6907 f.run(&[b"OBJECT", b"IDLETIME", b"s"])
6908 .starts_with("-ERR An LFU maxmemory policy is selected"),
6909 );
6910 assert!(
6915 f.run(&[b"OBJECT", b"FREQ", b"s"]).starts_with(':'),
6916 "FREQ should answer under an LFU policy"
6917 );
6918 }
6919
6920 #[test]
6921 fn object_says_which_rung_of_the_ladder_a_key_is_on() {
6922 let mut f = Fixture::new();
6923 f.run(&[b"SET", b"s", b"hello"]);
6924 f.run(&[b"SET", b"n", b"123"]);
6925 f.run(&[b"SADD", b"si", b"1", b"2", b"3"]);
6926 f.run(&[b"SADD", b"ss", b"a", b"b"]);
6927 f.run(&[b"HSET", b"h", b"f", b"v"]);
6928 for (key, want) in [
6929 (b"s".as_slice(), "embstr"),
6930 (b"n", "int"),
6931 (b"si", "intset"),
6932 (b"ss", "listpack"),
6933 (b"h", "listpack"),
6934 ] {
6935 let reply = f.run(&[b"OBJECT", b"ENCODING", key]);
6936 assert_eq!(reply, format!("${}\r\n{want}\r\n", want.len()));
6937 }
6938
6939 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"f"]);
6942 assert_eq!(
6943 f.run(&[b"OBJECT", b"ENCODING", b"h"]),
6944 "$10\r\nlistpackex\r\n"
6945 );
6946
6947 assert_eq!(f.run(&[b"OBJECT", b"REFCOUNT", b"s"]), ":1\r\n");
6948 assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
6949 assert!(f.run(&[b"OBJECT", b"HELP"]).starts_with("*14\r\n+OBJECT "));
6950 }
6951
6952 #[test]
6953 fn object_answers_nil_for_a_key_that_is_not_there() {
6954 let mut f = Fixture::new();
6955 for sub in [b"ENCODING".as_slice(), b"REFCOUNT", b"IDLETIME", b"FREQ"] {
6956 assert_eq!(
6957 f.run(&[b"OBJECT", sub, b"nokey"]),
6958 "$-1\r\n",
6959 "a nil and not an error, which is what 8.10.1 does"
6960 );
6961 }
6962 f.run(&[b"SET", b"s", b"v"]);
6965 assert!(
6966 f.run(&[b"OBJECT", b"FREQ", b"s"])
6967 .starts_with("-ERR An LFU maxmemory policy is not"),
6968 );
6969 assert_eq!(
6970 f.run(&[b"OBJECT", b"NOPE", b"s"]),
6971 "-ERR unknown subcommand 'NOPE'. Try OBJECT HELP.\r\n"
6972 );
6973 assert_eq!(
6974 f.run(&[b"OBJECT", b"ENCODING"]),
6975 "-ERR wrong number of arguments for 'object|encoding' command\r\n"
6976 );
6977 assert_eq!(
6978 f.run(&[b"OBJECT", b"ENCODING", b"s", b"extra"]),
6979 "-ERR wrong number of arguments for 'object|encoding' command\r\n"
6980 );
6981 assert_eq!(
6982 f.run(&[b"OBJECT"]),
6983 "-ERR wrong number of arguments for 'object' command\r\n"
6984 );
6985 }
6986
6987 #[test]
6988 fn config_moves_the_ladder_and_object_encoding_agrees() {
6989 let mut f = Fixture::new();
6990 assert_eq!(
6991 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
6992 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
6993 "512 and not the 128 everyone remembers, which is what 8.10.1 says"
6994 );
6995 assert_eq!(
6998 f.run(&[b"CONFIG", b"GET", b"hash-max-ziplist-entries"]),
6999 "*2\r\n$24\r\nhash-max-ziplist-entries\r\n$3\r\n512\r\n"
7000 );
7001 assert!(
7002 f.run(&[b"CONFIG", b"GET", b"hash-max-*"])
7003 .starts_with("*8\r\n")
7004 );
7005 assert!(
7006 f.run(&[b"CONFIG", b"GET", b"set-max-*"])
7007 .starts_with("*6\r\n")
7008 );
7009
7010 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2", b"c", b"3"]);
7011 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$8\r\nlistpack\r\n");
7012
7013 assert_eq!(
7014 f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"2"]),
7015 "+OK\r\n",
7016 "written under the old name and read back under the new one"
7017 );
7018 assert_eq!(
7019 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
7020 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n"
7021 );
7022 assert_eq!(
7023 f.run(&[b"OBJECT", b"ENCODING", b"h"]),
7024 "$8\r\nlistpack\r\n",
7025 "the hash that already exists is left exactly where it was"
7026 );
7027 f.run(&[b"HSET", b"h2", b"a", b"1", b"b", b"2", b"c", b"3"]);
7028 assert_eq!(
7029 f.run(&[b"OBJECT", b"ENCODING", b"h2"]),
7030 "$9\r\nhashtable\r\n",
7031 "and the next one built goes straight to a table"
7032 );
7033
7034 f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"2"]);
7036 f.run(&[b"SADD", b"s", b"1", b"2", b"3"]);
7037 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$8\r\nlistpack\r\n");
7038 f.run(&[b"CONFIG", b"SET", b"set-max-listpack-value", b"2"]);
7039 f.run(&[b"SADD", b"s2", b"abcdefgh"]);
7040 assert_eq!(
7041 f.run(&[b"OBJECT", b"ENCODING", b"s2"]),
7042 "$9\r\nhashtable\r\n"
7043 );
7044 }
7045
7046 #[test]
7047 fn config_set_takes_all_of_the_ladder_or_none_of_it() {
7048 let mut f = Fixture::new();
7049 assert_eq!(
7050 f.run(&[
7051 b"CONFIG",
7052 b"SET",
7053 b"hash-max-listpack-entries",
7054 b"7",
7055 b"set-max-listpack-entries",
7056 b"abc"
7057 ]),
7058 "-ERR CONFIG SET failed (possibly related to argument 'set-max-listpack-entries') - argument couldn't be parsed into an integer\r\n"
7059 );
7060 assert_eq!(
7061 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
7062 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
7063 "the pair in front of the bad one did not go in"
7064 );
7065 assert_eq!(
7068 f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"abc"]),
7069 "-ERR CONFIG SET failed (possibly related to argument 'hash-max-ziplist-entries') - argument couldn't be parsed into an integer\r\n"
7070 );
7071 assert_eq!(
7072 f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"-1"]),
7073 "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument must be between 0 and 9223372036854775807 inclusive\r\n"
7074 );
7075 assert_eq!(
7078 f.run(&[
7079 b"CONFIG",
7080 b"SET",
7081 b"set-max-intset-entries",
7082 b"99999999999999999999"
7083 ]),
7084 "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument couldn't be parsed into an integer\r\n"
7085 );
7086 assert_eq!(
7087 f.run(&[
7088 b"CONFIG",
7089 b"SET",
7090 b"set-max-intset-entries",
7091 b"9223372036854775807"
7092 ]),
7093 "+OK\r\n"
7094 );
7095 }
7096
7097 #[test]
7098 fn a_setting_moved_on_one_database_moved_on_all_of_them() {
7099 let mut f = Fixture::new();
7100 f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"1"]);
7101 f.run(&[b"SELECT", b"3"]);
7102 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
7103 assert_eq!(
7104 f.run(&[b"OBJECT", b"ENCODING", b"h"]),
7105 "$9\r\nhashtable\r\n",
7106 "these are one server wide number in Redis, whatever a Keyspace carries"
7107 );
7108 }
7109
7110 #[test]
7111 fn info_reports_the_numbers_it_can_stand_behind() {
7112 let mut f = Fixture::new();
7113 f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
7114 let all = f.run(&[b"INFO"]);
7115 assert!(all.contains("redis_version:8.8.0"), "{all}");
7116 assert!(
7117 all.contains(concat!("yo_version:", env!("CARGO_PKG_VERSION"))),
7118 "{all}"
7119 );
7120 assert!(all.contains("db0:keys=2,expires=0,avg_ttl=0"), "{all}");
7121 assert!(all.contains("role:master"), "{all}");
7122 let clients = f.run(&[b"INFO", b"clients"]);
7124 assert!(clients.contains("connected_clients:0"), "{clients}");
7125 assert!(!clients.contains("redis_version"), "{clients}");
7126 assert_eq!(f.run(&[b"INFO", b"nosuch"]), "$0\r\n\r\n");
7127 }
7128
7129 #[test]
7136 fn commandstats_is_asked_for_and_replication_is_not() {
7137 let mut f = Fixture::new();
7138 for arg in ["", "all", "default", "everything"] {
7139 let info = if arg.is_empty() {
7140 f.run(&[b"INFO"])
7141 } else {
7142 f.run(&[b"INFO", arg.as_bytes()])
7143 };
7144 assert!(info.contains("redis_version"), "{arg}: {info}");
7145 assert!(info.contains("used_cpu_user"), "{arg}: {info}");
7146 assert!(info.contains("used_memory"), "{arg}: {info}");
7147 assert!(!info.contains("sentinel_tilt"), "{arg}: {info}");
7148 let asked = arg == "all" || arg == "everything";
7149 assert_eq!(
7150 info.contains("rejected_calls"),
7151 asked,
7152 "{arg} should{} carry the command counters: {info}",
7153 if asked { "" } else { " not" }
7154 );
7155 }
7156
7157 let cpu = f.run(&[b"INFO", b"cpu"]);
7158 assert!(cpu.contains("used_cpu_user"), "{cpu}");
7159 assert!(!cpu.contains("used_memory"), "{cpu}");
7160
7161 let stats = f.run(&[b"INFO", b"commandSTATS"]);
7164 assert!(!stats.contains("used_memory"), "{stats}");
7165 assert!(stats.contains("rejected_calls"), "{stats}");
7166
7167 let pair = f.run(&[b"INFO", b"cpu", b"sentinel"]);
7169 assert!(pair.contains("used_cpu_user"), "{pair}");
7170 assert!(!pair.contains("master_repl_offset"), "{pair}");
7171
7172 let with_all = f.run(&[b"INFO", b"cpu", b"all"]);
7173 assert!(with_all.contains("used_memory"), "{with_all}");
7174 assert!(with_all.contains("master_repl_offset"), "{with_all}");
7175 assert!(with_all.contains("rejected_calls"), "{with_all}");
7176 assert_eq!(
7178 with_all.matches("used_cpu_user_children").count(),
7179 1,
7180 "{with_all}"
7181 );
7182
7183 let with_default = f.run(&[b"INFO", b"cpu", b"default"]);
7184 assert!(with_default.contains("used_memory"), "{with_default}");
7185 assert!(
7186 with_default.contains("master_repl_offset"),
7187 "{with_default}"
7188 );
7189 assert!(!with_default.contains("rejected_calls"), "{with_default}");
7190 assert_eq!(
7191 with_default.matches("used_cpu_user_children").count(),
7192 1,
7193 "{with_default}"
7194 );
7195 }
7196
7197 #[test]
7206 fn info_memory_reports_the_cap_and_the_quarter_of_it_that_gets_used() {
7207 let mut f = Fixture::new();
7208 let info = f.run(&[b"INFO", b"memory"]);
7209 for field in [
7210 "total_system_memory:",
7211 "mem_cgroup_limit:",
7212 "mem_limit:",
7213 "mem_budget:",
7214 ] {
7215 assert!(info.contains(field), "no {field} in {info}");
7216 }
7217
7218 let field = |name: &str| -> u64 {
7219 info.lines()
7220 .find_map(|l| l.strip_prefix(name))
7221 .unwrap_or_else(|| panic!("no {name} in {info}"))
7222 .trim()
7223 .parse()
7224 .unwrap_or_else(|_| panic!("{name} is not a number in {info}"))
7225 };
7226 let limit = field("mem_limit:");
7227 assert_eq!(field("mem_budget:"), limit / 4, "{info}");
7228 if limit != 0 {
7231 let host = field("total_system_memory:");
7232 let cgroup = field("mem_cgroup_limit:");
7233 assert!(
7234 limit == host || limit == cgroup,
7235 "the limit came from neither number: {info}"
7236 );
7237 }
7238 }
7239
7240 #[test]
7249 fn a_command_counts_what_it_did_separately_from_what_it_refused() {
7250 let mut f = Fixture::new();
7251 f.run(&[b"SET", b"k", b"v"]);
7252 f.run(&[b"SET", b"k", b"w"]);
7253 f.run(&[b"LPUSH", b"k", b"x"]);
7255 f.run(&[b"LPUSH", b"k"]);
7257
7258 let stats = f.run(&[b"INFO", b"commandstats"]);
7259 assert!(
7260 stats.contains("cmdstat_set:calls=2,rejected_calls=0,failed_calls=0"),
7261 "{stats}"
7262 );
7263 assert!(
7264 stats.contains("cmdstat_lpush:calls=1,rejected_calls=1,failed_calls=1"),
7265 "{stats}"
7266 );
7267 assert!(
7268 !stats.contains("cmdstat_zadd"),
7269 "a command nobody has sent has no row: {stats}"
7270 );
7271 }
7272
7273 #[test]
7277 fn the_active_sweep_reclaims_keys_no_client_comes_back_for() {
7278 let (dead, live) = if cfg!(miri) {
7285 (375, 125)
7286 } else {
7287 (3_000, 1_000)
7288 };
7289 let mut f = Fixture::new();
7290 for i in 0..dead {
7291 f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
7292 }
7293 for i in 0..live {
7294 f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
7295 }
7296 let all = format!(":{}\r\n", dead + live);
7297 assert_eq!(f.run(&[b"DBSIZE"]), all);
7298 f.advance(100);
7299 assert_eq!(
7300 f.run(&[b"DBSIZE"]),
7301 all,
7302 "DBSIZE counts records and nothing has read past the dead ones yet"
7303 );
7304
7305 let rest = format!(":{live}\r\n");
7307 let mut spent = 0;
7308 for _ in 0..2_000 {
7309 spent += f.server.expire_step(4096);
7310 if f.run(&[b"DBSIZE"]) == rest {
7311 break;
7312 }
7313 }
7314 assert_eq!(f.run(&[b"DBSIZE"]), rest, "spent {spent} looks");
7315 assert!(
7316 f.run(&[b"INFO", b"stats"])
7317 .contains(&format!("expired_keys:{dead}"))
7318 );
7319 for i in 0..live {
7320 assert_eq!(
7321 f.run(&[b"GET", format!("k{i}").as_bytes()]),
7322 "$1\r\nv\r\n",
7323 "it took a key that had no deadline"
7324 );
7325 }
7326 }
7327
7328 #[test]
7329 fn a_sweep_of_a_server_with_no_deadlines_anywhere_costs_nothing() {
7330 let n = if cfg!(miri) { 200 } else { 2_000 };
7334 let mut f = Fixture::new();
7335 for i in 0..n {
7336 f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
7337 }
7338 assert_eq!(f.server.expire_step(4096), 0);
7339 f.run(&[b"SELECT", b"3"]);
7341 f.run(&[b"SET", b"x", b"v", b"PX", b"50"]);
7342 f.advance(100);
7343 for _ in 0..64 {
7344 f.server.expire_step(4096);
7345 }
7346 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
7347 f.run(&[b"SELECT", b"0"]);
7348 assert_eq!(f.run(&[b"DBSIZE"]), format!(":{n}\r\n"));
7349 assert_eq!(f.server.expire_step(4096), 0, "and it is quiet again");
7350 }
7351
7352 #[test]
7355 fn the_sweep_the_loop_calls_runs_at_most_once_a_millisecond() {
7356 let mut f = Fixture::new();
7357 for i in 0..500u32 {
7358 f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
7359 }
7360 f.advance(100);
7361 let at = f.server.striped(0).now_ms();
7362 f.server.set_clock_ms(at);
7363 assert!(f.server.expire_slice(8) > 0, "the first one works");
7367 for _ in 0..1_000 {
7368 assert_eq!(
7369 f.server.expire_slice(8),
7370 0,
7371 "the millisecond has not moved and neither should this"
7372 );
7373 }
7374 assert!(
7375 f.server.striped(0).expires() > 400,
7376 "there is plenty left to take"
7377 );
7378 f.server.set_clock_ms(at + 1);
7379 assert!(f.server.expire_slice(8) > 0, "and then it goes again");
7380 }
7381
7382 #[test]
7385 fn info_keyspace_counts_the_keys_that_have_a_deadline() {
7386 let mut f = Fixture::new();
7387 f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
7388 assert!(
7389 f.run(&[b"INFO", b"keyspace"])
7390 .contains("db0:keys=3,expires=0"),
7391 "none of them has one yet"
7392 );
7393 f.run(&[b"EXPIRE", b"a", b"1000"]);
7394 f.run(&[b"EXPIRE", b"b", b"1000"]);
7395 let two = f.run(&[b"INFO", b"keyspace"]);
7396 assert!(two.contains("db0:keys=3,expires=2"), "{two}");
7397 f.run(&[b"PERSIST", b"a"]);
7398 f.run(&[b"DEL", b"b"]);
7399 let none = f.run(&[b"INFO", b"keyspace"]);
7400 assert!(none.contains("db0:keys=2,expires=0"), "{none}");
7401
7402 f.run(&[b"SELECT", b"1"]);
7404 f.run(&[b"SET", b"x", b"1", b"EX", b"1000"]);
7405 let both = f.run(&[b"INFO", b"keyspace"]);
7406 assert!(both.contains("db0:keys=2,expires=0"), "{both}");
7407 assert!(both.contains("db1:keys=1,expires=1"), "{both}");
7408 }
7409
7410 #[cfg(unix)]
7417 #[cfg_attr(miri, ignore = "no getrusage under Miri, so the number is fixed")]
7418 #[test]
7419 fn info_cpu_reports_processor_time_that_was_really_measured() {
7420 let mut f = Fixture::new();
7421 let cpu = f.run(&[b"INFO", b"cpu"]);
7422 assert!(cpu.contains("# CPU"), "{cpu}");
7423 assert!(cpu.contains("used_cpu_user:"), "{cpu}");
7425 assert!(cpu.contains("used_cpu_sys:"), "{cpu}");
7426 assert!(cpu.contains("used_cpu_user_children:0.000000"), "{cpu}");
7427 assert!(!cpu.contains("redis_version"), "{cpu}");
7428
7429 let before = used_cpu_user(&cpu);
7433 let mut n = 0u64;
7434 let mut rounds = 0;
7435 while used_cpu_user(&f.run(&[b"INFO", b"cpu"])) <= before {
7436 for i in 0..1_000_000u64 {
7437 n = n.wrapping_add(i.wrapping_mul(i));
7438 }
7439 rounds += 1;
7440 assert!(rounds < 1_000, "cpu time never moved, n is {n}");
7444 }
7445 }
7446
7447 #[cfg(unix)]
7449 fn used_cpu_user(info: &str) -> f64 {
7450 info.lines()
7451 .find_map(|l| l.strip_prefix("used_cpu_user:"))
7452 .expect("no used_cpu_user in the reply")
7453 .trim()
7454 .parse()
7455 .expect("used_cpu_user is not a number")
7456 }
7457
7458 #[test]
7464 fn a_command_that_fails_leaves_nothing_half_written() {
7465 let mut f = Fixture::new();
7466 let reply = f.run(&[b"SETRANGE", b"k", b"-1", b"x"]);
7467 assert_eq!(reply, "-ERR offset is out of range\r\n");
7468 assert!(!reply.contains(':'), "no integer went out in front of it");
7469 }
7470
7471 #[test]
7472 fn quit_answers_first_and_closes_after() {
7473 let mut f = Fixture::new();
7474 let (flow, reply) = f.flow(&[b"QUIT"]);
7475 assert_eq!(reply, "+OK\r\n");
7476 assert_eq!(flow, Flow::Close);
7477 }
7478
7479 #[test]
7486 fn shutdown_writes_nothing_and_sets_the_flag() {
7487 let mut f = Fixture::new();
7488 assert!(!f.server.stopping(), "nobody has asked yet");
7489
7490 let (flow, reply) = f.flow(&[b"SHUTDOWN"]);
7491 assert_eq!(reply, "");
7492 assert_eq!(flow, Flow::Close);
7493 assert!(f.server.stopping());
7494 }
7495
7496 #[test]
7504 fn shutdown_takes_the_flags_redis_takes() {
7505 for flags in [
7506 &[b"NOSAVE".as_slice()][..],
7507 &[b"SAVE"],
7508 &[b"NOW"],
7509 &[b"FORCE"],
7510 &[b"nosave"],
7511 &[b"NOW", b"NOW"],
7512 &[b"SAVE", b"SAVE"],
7513 &[b"NOSAVE", b"NOW", b"FORCE"],
7514 ] {
7515 let mut f = Fixture::new();
7516 let mut parts = vec![b"SHUTDOWN".as_slice()];
7517 parts.extend_from_slice(flags);
7518 let (flow, reply) = f.flow(&parts);
7519 assert_eq!(reply, "", "SHUTDOWN {flags:?} answered something");
7520 assert_eq!(flow, Flow::Close, "SHUTDOWN {flags:?} did not close");
7521 assert!(f.server.stopping(), "SHUTDOWN {flags:?} did not stop");
7522 }
7523
7524 for flags in [
7525 &[b"BOGUS".as_slice()][..],
7526 &[b"SAVE", b"NOSAVE"],
7527 &[b"NOSAVE", b"SAVE"],
7528 &[b"ABORT", b"NOW"],
7529 &[b"NOSAVE", b"ABORT"],
7530 &[b"NOW", b"FORCE", b"ABORT"],
7531 ] {
7532 let mut f = Fixture::new();
7533 let mut parts = vec![b"SHUTDOWN".as_slice()];
7534 parts.extend_from_slice(flags);
7535 assert_eq!(
7536 f.run(&parts),
7537 "-ERR syntax error\r\n",
7538 "SHUTDOWN {flags:?} was accepted"
7539 );
7540 assert!(!f.server.stopping(), "SHUTDOWN {flags:?} stopped anyway");
7541 }
7542 }
7543
7544 #[test]
7552 fn shutdown_abort_never_has_anything_to_abort() {
7553 let mut f = Fixture::new();
7554 for parts in [
7555 &[b"SHUTDOWN".as_slice(), b"ABORT"][..],
7556 &[b"SHUTDOWN", b"ABORT", b"ABORT"],
7557 ] {
7558 assert_eq!(f.run(parts), "-ERR No shutdown in progress.\r\n");
7559 assert!(!f.server.stopping(), "an abort stopped the server");
7560 }
7561 }
7562
7563 struct Backups {
7571 f: Fixture,
7572 dir: PathBuf,
7573 }
7574
7575 impl Backups {
7576 fn new(name: &str) -> Backups {
7577 let dir = std::env::temp_dir().join(format!("yo-backup-{name}-{}", std::process::id()));
7578 let _ = std::fs::remove_dir_all(&dir);
7579 std::fs::create_dir_all(&dir).expect("could not make a temporary directory");
7580 let mut f = Fixture::new();
7581 f.server.set_dir(dir.clone());
7582 Backups { f, dir }
7583 }
7584
7585 fn run(&mut self, parts: &[&[u8]]) -> String {
7586 self.f.run(parts)
7587 }
7588
7589 fn files(&self) -> Vec<String> {
7591 let mut names: Vec<String> = match std::fs::read_dir(self.dir.join("backupdir")) {
7592 Ok(entries) => entries
7593 .filter_map(|e| e.ok())
7594 .map(|e| e.file_name().to_string_lossy().into_owned())
7595 .collect(),
7596 Err(_) => Vec::new(),
7597 };
7598 names.sort();
7599 names
7600 }
7601
7602 fn read(&self, name: &str) -> Vec<u8> {
7603 std::fs::read(self.dir.join("backupdir").join(name)).expect("could not read")
7604 }
7605 }
7606
7607 impl Drop for Backups {
7608 fn drop(&mut self) {
7609 let _ = std::fs::remove_dir_all(&self.dir);
7610 }
7611 }
7612
7613 #[test]
7616 fn backup_walks_the_states_the_reference_walks() {
7617 let mut b = Backups::new("states");
7618 let status = |b: &mut Backups| b.run(&[b"BACKUP", b"STATUS"]);
7619
7620 assert!(status(&mut b).contains("idle"));
7621 assert!(b.files().is_empty(), "an idle server has written a backup");
7622
7623 assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
7624 assert!(status(&mut b).contains("incrementing"));
7625 assert_eq!(b.files(), ["appendonly.aof.1.base.rdb"]);
7626
7627 assert_eq!(b.run(&[b"BACKUP", b"SEAL"]), "+OK\r\n");
7628 assert!(status(&mut b).contains("sealed"));
7629 assert_eq!(
7630 b.files(),
7631 [
7632 "appendonly.aof.1.base.rdb",
7633 "appendonly.aof.1.incr.aof",
7634 "appendonly.aof.manifest",
7635 ]
7636 );
7637
7638 assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
7639 assert!(status(&mut b).contains("idle"));
7640 assert!(b.files().is_empty(), "cleanup left something behind");
7641 }
7642
7643 #[test]
7645 fn backup_refuses_the_moves_the_reference_refuses() {
7646 let mut b = Backups::new("refusals");
7647
7648 assert_eq!(
7649 b.run(&[b"BACKUP", b"SEAL"]),
7650 "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
7651 );
7652 assert_eq!(
7653 b.run(&[b"BACKUP", b"ABORT"]),
7654 "-ERR No backup in progress\r\n"
7655 );
7656 assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
7659
7660 b.run(&[b"BACKUP", b"START"]);
7661 assert_eq!(
7662 b.run(&[b"BACKUP", b"START"]),
7663 "-ERR A backup is already in progress, ABORT it first\r\n"
7664 );
7665 assert_eq!(
7666 b.run(&[b"BACKUP", b"CLEANUP"]),
7667 "-ERR Backup is in progress\r\n"
7668 );
7669
7670 b.run(&[b"BACKUP", b"SEAL"]);
7671 assert_eq!(
7672 b.run(&[b"BACKUP", b"START"]),
7673 "-ERR A sealed backup exists, CLEANUP it first\r\n"
7674 );
7675 assert_eq!(
7676 b.run(&[b"BACKUP", b"SEAL"]),
7677 "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
7678 );
7679 assert_eq!(
7680 b.run(&[b"BACKUP", b"ABORT"]),
7681 "-ERR No backup in progress\r\n"
7682 );
7683 }
7684
7685 #[test]
7692 fn backup_abort_removes_the_file_and_says_who_did_it() {
7693 let mut b = Backups::new("abort");
7694 b.run(&[b"BACKUP", b"START"]);
7695 assert_eq!(b.run(&[b"BACKUP", b"ABORT"]), "+OK\r\n");
7696
7697 let status = b.run(&[b"BACKUP", b"STATUS"]);
7698 assert!(status.contains("failed"), "{status}");
7699 assert!(status.contains("aborted by user"), "{status}");
7700 assert!(b.files().is_empty(), "abort left the base file behind");
7701 assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
7702
7703 assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
7705 assert_eq!(b.files(), ["appendonly.aof.2.base.rdb"]);
7706 let status = b.run(&[b"BACKUP", b"STATUS"]);
7707 assert!(status.contains("incrementing"), "{status}");
7708 assert!(!status.contains("aborted"), "the old error was kept");
7709 }
7710
7711 #[test]
7713 fn backup_list_names_the_files_that_are_pinned_so_far() {
7714 let mut b = Backups::new("list");
7715 assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
7716
7717 b.run(&[b"BACKUP", b"START"]);
7718 let base = b.dir.join("backupdir").join("appendonly.aof.1.base.rdb");
7719 let base = base.to_string_lossy().into_owned();
7720 assert_eq!(
7721 b.run(&[b"BACKUP", b"LIST"]),
7722 format!("*1\r\n${}\r\n{base}\r\n", base.len())
7723 );
7724
7725 b.run(&[b"BACKUP", b"SEAL"]);
7726 let listed = b.run(&[b"BACKUP", b"LIST"]);
7727 assert!(listed.starts_with("*3\r\n"), "{listed}");
7728 let names: Vec<&str> = listed
7731 .lines()
7732 .filter(|l| l.starts_with('/') || l.contains(":\\"))
7733 .collect();
7734 assert_eq!(names.len(), 3, "{listed}");
7735 assert!(names[0].ends_with("appendonly.aof.1.base.rdb"), "{listed}");
7736 assert!(names[1].ends_with("appendonly.aof.1.incr.aof"), "{listed}");
7737 assert!(names[2].ends_with("appendonly.aof.manifest"), "{listed}");
7738 }
7739
7740 #[test]
7748 fn a_backup_holds_the_dataset_as_it_was_at_start() {
7749 let mut b = Backups::new("contents");
7750 b.run(&[b"SET", b"bk", b"v1"]);
7751 b.run(&[b"BACKUP", b"START"]);
7752 b.run(&[b"SET", b"bk", b"v2"]);
7753 b.run(&[b"BACKUP", b"SEAL"]);
7754
7755 let base = b.read("appendonly.aof.1.base.rdb");
7756 assert!(base.starts_with(b"REDIS"), "not an RDB file");
7757 assert!(base.windows(2).any(|w| w == b"v1"), "the value is missing");
7758 assert!(
7759 !base.windows(2).any(|w| w == b"v2"),
7760 "the base file moved on after START"
7761 );
7762 let at = base
7767 .windows(8)
7768 .position(|w| w == b"aof-base")
7769 .expect("no aof-base aux field");
7770 assert_eq!(&base[at + 8..at + 10], b"\xc0\x01", "{:?}", &base[at..]);
7771
7772 assert!(b.read("appendonly.aof.1.incr.aof").is_empty());
7773 assert_eq!(
7774 String::from_utf8(b.read("appendonly.aof.manifest")).expect("the manifest is text"),
7775 "file appendonly.aof.1.base.rdb seq 1 type b\n\
7776 file appendonly.aof.1.incr.aof seq 1 type i startoffset 0 endoffset 0\n"
7777 );
7778 }
7779
7780 #[test]
7783 fn backup_status_is_a_map_on_resp3_and_a_flat_array_on_resp2() {
7784 let mut b = Backups::new("status");
7785 b.f.server.set_clock_ms(1_700_000_000_000);
7786
7787 assert_eq!(
7788 b.run(&[b"BACKUP", b"STATUS"]),
7789 "*8\r\n$5\r\nstate\r\n$4\r\nidle\r\n$5\r\nerror\r\n$0\r\n\r\n\
7790 $10\r\nstart_time\r\n:0\r\n$8\r\nend_time\r\n:0\r\n"
7791 );
7792
7793 b.f.out = Out::new(Proto::Resp3);
7794 b.run(&[b"BACKUP", b"START"]);
7795 assert_eq!(
7796 b.run(&[b"BACKUP", b"STATUS"]),
7797 "%4\r\n$5\r\nstate\r\n$12\r\nincrementing\r\n$5\r\nerror\r\n$0\r\n\r\n\
7798 $10\r\nstart_time\r\n:1700000000\r\n$8\r\nend_time\r\n:0\r\n"
7799 );
7800
7801 b.run(&[b"BACKUP", b"SEAL"]);
7802 let sealed = b.run(&[b"BACKUP", b"STATUS"]);
7803 assert!(sealed.contains("end_time\r\n:1700000000"), "{sealed}");
7804 }
7805
7806 #[test]
7809 fn a_sealed_backup_is_swept_away_after_the_timeout() {
7810 let mut b = Backups::new("ttl");
7811 b.f.server.set_clock_ms(1_000_000);
7812 assert_eq!(
7813 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"60"]),
7814 "+OK\r\n"
7815 );
7816 b.run(&[b"BACKUP", b"START"]);
7817 b.run(&[b"BACKUP", b"SEAL"]);
7818
7819 b.f.server.set_clock_ms(1_000_000 + 59_000);
7821 b.f.server.backup_expire();
7822 assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
7823 assert_eq!(b.files().len(), 3);
7824
7825 b.f.server.set_clock_ms(1_000_000 + 60_000);
7826 b.f.server.backup_expire();
7827 let status = b.run(&[b"BACKUP", b"STATUS"]);
7828 assert!(status.contains("idle"), "{status}");
7829 assert!(b.files().is_empty(), "the timeout left the files behind");
7830
7831 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"0"]);
7833 b.run(&[b"BACKUP", b"START"]);
7834 b.run(&[b"BACKUP", b"SEAL"]);
7835 b.f.server.set_clock_ms(9_000_000_000);
7836 b.f.server.backup_expire();
7837 assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
7838 }
7839
7840 #[test]
7843 fn the_backup_settings_behave_the_way_the_reference_does() {
7844 let mut b = Backups::new("config");
7845 let dir = b.dir.to_string_lossy().into_owned();
7846
7847 assert_eq!(
7848 b.run(&[b"CONFIG", b"GET", b"dir"]),
7849 format!("*2\r\n$3\r\ndir\r\n${}\r\n{dir}\r\n", dir.len())
7850 );
7851 assert_eq!(
7852 b.run(&[b"CONFIG", b"GET", b"backupdirname"]),
7853 "*2\r\n$13\r\nbackupdirname\r\n$9\r\nbackupdir\r\n"
7854 );
7855 assert_eq!(
7856 b.run(&[b"CONFIG", b"GET", b"backup-sealed-ttl"]),
7857 "*2\r\n$17\r\nbackup-sealed-ttl\r\n$1\r\n0\r\n"
7858 );
7859
7860 assert_eq!(
7863 b.run(&[b"CONFIG", b"SET", b"dir", dir.as_bytes()]),
7864 "-ERR CONFIG SET failed (possibly related to argument 'dir') - can't set protected config\r\n"
7865 );
7866 assert_eq!(
7867 b.run(&[b"CONFIG", b"SET", b"backupdirname", b"other"]),
7868 "-ERR CONFIG SET failed (possibly related to argument 'backupdirname') - can't set immutable config\r\n"
7869 );
7870 assert!(
7871 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"abc"])
7872 .contains("argument couldn't be parsed into an integer")
7873 );
7874 assert!(
7875 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"-1"])
7876 .contains("argument must be between 0 and 9223372036854775807 inclusive")
7877 );
7878 }
7879
7880 #[test]
7882 fn backup_help_is_the_text_the_reference_sends() {
7883 let mut f = Fixture::new();
7884 let help = f.run(&[b"BACKUP", b"HELP"]);
7885 assert!(help.starts_with("*17\r\n"), "{help}");
7886 assert!(
7887 help.contains("+BACKUP <subcommand> [<arg> [value] [opt] ...]. Subcommands are:\r\n")
7888 );
7889 assert!(help.contains("+ Start a new backup into the configured 'backupdirname'.\r\n"));
7890 assert!(help.contains("+ Freeze the current backup (BASE + INCR + manifest).\r\n"));
7891 assert!(help.contains("+ Return this help.\r\n+HELP\r\n+ Print this help.\r\n"));
7892 }
7893
7894 #[test]
7902 fn backup_refuses_what_it_cannot_read() {
7903 let mut f = Fixture::new();
7904 assert_eq!(
7905 f.run(&[b"BACKUP"]),
7906 "-ERR wrong number of arguments for 'backup' command\r\n"
7907 );
7908 assert_eq!(
7909 f.run(&[b"BACKUP", b"START", b"x"]),
7910 "-ERR wrong number of arguments for 'backup' command\r\n"
7911 );
7912 assert_eq!(
7913 f.run(&[b"BACKUP", b"NOPE"]),
7914 "-ERR unknown subcommand 'NOPE'. Try BACKUP HELP.\r\n"
7915 );
7916 }
7917
7918 #[test]
7919 fn the_command_counter_counts_every_command_including_the_bad_ones() {
7920 let mut f = Fixture::new();
7921 f.run(&[b"PING"]);
7922 f.run(&[b"NOPE"]);
7923 f.run(&[b"GET"]);
7924 assert_eq!(f.server.totals().commands, 3);
7925 }
7926
7927 #[test]
7928 fn what_a_thread_marked_is_taken_by_the_maintenance_turn() {
7929 let mut server = Server::new();
7930 server.set_threads(2);
7931 server.mine().turn.store(0, Relaxed);
7934 server.locals[1].mark(1 << 9);
7935 server.collect_marks();
7936 assert!(server.mine().wanted(9));
7937 assert_eq!(server.locals[1].dirty.load(Relaxed), 0);
7939 }
7940
7941 #[test]
7942 fn what_two_threads_counted_is_added_up_when_info_asks() {
7943 let mut server = Server::new();
7944 server.set_threads(2);
7945 let ping = lookup(b"PING").expect("PING is a command");
7949 for (at, calls) in [(0, 2), (1, 3)] {
7950 let counters = &server.locals[at];
7951 for _ in 0..calls {
7952 counters.stats.commands.bump();
7953 counters.cmdstats.at(ping).calls.bump();
7954 }
7955 counters.stats.opened();
7956 }
7957 assert_eq!(server.totals().commands, 5);
7958 assert_eq!(server.totals().clients, 2);
7959 assert_eq!(server.totals().connections, 2);
7960 let rows: Vec<_> = server.command_stats().collect();
7961 assert_eq!(rows.len(), 1);
7962 assert_eq!(rows[0].0, "ping");
7963 assert_eq!(rows[0].1.calls, 5);
7964 server.reset_stats();
7967 assert_eq!(server.totals().commands, 0);
7968 assert_eq!(server.totals().connections, 0);
7969 assert_eq!(server.totals().clients, 2);
7970 }
7971
7972 #[test]
7973 fn the_parked_count_says_what_the_waiter_list_says() {
7974 let mut f = Fixture::new();
7975 assert_eq!(f.server.parked(), 0);
7976 for client in 1..=3u64 {
7977 f.session = Session::new(client);
7978 assert_eq!(f.flow(&[b"BLPOP", b"q", b"0"]).0, Flow::Block);
7979 }
7980 assert_eq!(f.server.parked(), 3);
7981 assert_eq!(f.server.waiters().len(), 3);
7982
7983 f.server.forget_waiters(2);
7987 assert_eq!(f.server.parked(), f.server.waiters().len());
7988 f.server.forget_waiters(1);
7989 assert_eq!(f.server.parked(), f.server.waiters().len());
7990 f.run(&[b"RPUSH", b"q", b"v"]);
7991 let mut out = Out::new(Proto::Resp2);
7992 assert!(f.server.serve_waiter(3, 0, &mut out));
7993 f.server.forget_waiters(3);
7994 assert_eq!(f.server.parked(), 0);
7995 assert!(f.server.waiters().is_empty());
7996 }
7997
7998 #[test]
7999 fn a_set_goes_from_bytes_to_bytes() {
8000 let mut f = Fixture::new();
8001 assert_eq!(f.run(&[b"SADD", b"s", b"a", b"b", b"c"]), ":3\r\n");
8002 assert_eq!(f.run(&[b"SADD", b"s", b"b", b"d"]), ":1\r\n");
8003 assert_eq!(f.run(&[b"SCARD", b"s"]), ":4\r\n");
8004 assert_eq!(f.run(&[b"SISMEMBER", b"s", b"a"]), ":1\r\n");
8005 assert_eq!(f.run(&[b"SISMEMBER", b"s", b"z"]), ":0\r\n");
8006 assert_eq!(f.run(&[b"TYPE", b"s"]), "+set\r\n");
8007 assert_eq!(
8008 f.run(&[b"SMISMEMBER", b"s", b"a", b"z", b"d"]),
8009 "*3\r\n:1\r\n:0\r\n:1\r\n"
8010 );
8011 assert_eq!(f.run(&[b"SREM", b"s", b"a", b"z"]), ":1\r\n");
8012 assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
8013 }
8014
8015 #[test]
8016 fn a_set_command_at_a_key_that_is_not_there_answers_empty() {
8017 let mut f = Fixture::new();
8018 assert_eq!(f.run(&[b"SCARD", b"nope"]), ":0\r\n");
8019 assert_eq!(f.run(&[b"SISMEMBER", b"nope", b"a"]), ":0\r\n");
8020 assert_eq!(f.run(&[b"SREM", b"nope", b"a"]), ":0\r\n");
8021 assert_eq!(f.run(&[b"SMEMBERS", b"nope"]), "*0\r\n");
8022 assert_eq!(
8023 f.run(&[b"SMISMEMBER", b"nope", b"a", b"b"]),
8024 "*2\r\n:0\r\n:0\r\n"
8025 );
8026 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n", "and made nothing");
8027 }
8028
8029 #[test]
8030 fn smembers_answers_a_set_on_resp3_and_an_array_on_resp2() {
8031 let mut f = Fixture::new();
8035 f.run(&[b"SADD", b"s", b"one"]);
8036 assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$3\r\none\r\n");
8037
8038 f.run(&[b"HELLO", b"3"]);
8039 assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "~1\r\n$3\r\none\r\n");
8040 }
8041
8042 #[test]
8043 fn an_integer_member_comes_back_as_the_digits_it_never_stored() {
8044 let mut f = Fixture::new();
8047 f.run(&[b"SADD", b"s", b"42"]);
8048 assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$2\r\n42\r\n");
8049 assert_eq!(f.run(&[b"SISMEMBER", b"s", b"42"]), ":1\r\n");
8050 assert_eq!(
8051 f.run(&[b"SISMEMBER", b"s", b"042"]),
8052 ":0\r\n",
8053 "the member is the bytes and not the number they parse to"
8054 );
8055 }
8056
8057 #[test]
8058 fn the_wrong_command_at_the_wrong_type_says_so_both_ways() {
8059 let mut f = Fixture::new();
8060 f.run(&[b"SET", b"str", b"v"]);
8061 f.run(&[b"SADD", b"set", b"a"]);
8062
8063 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8064 assert_eq!(f.run(&[b"SADD", b"str", b"a"]), wrong);
8065 assert_eq!(f.run(&[b"SCARD", b"str"]), wrong);
8066 assert_eq!(f.run(&[b"SMEMBERS", b"str"]), wrong);
8067 assert_eq!(f.run(&[b"SMISMEMBER", b"str", b"a"]), wrong);
8068 assert_eq!(f.run(&[b"GET", b"set"]), wrong);
8069 assert_eq!(f.run(&[b"APPEND", b"set", b"x"]), wrong);
8070 assert_eq!(f.run(&[b"INCR", b"set"]), wrong);
8071 assert_eq!(f.run(&[b"STRLEN", b"set"]), wrong);
8072
8073 assert_eq!(
8076 f.run(&[b"MGET", b"str", b"set", b"nope"]),
8077 "*3\r\n$1\r\nv\r\n$-1\r\n$-1\r\n"
8078 );
8079 assert_eq!(f.run(&[b"SET", b"set", b"now a string"]), "+OK\r\n");
8081 assert_eq!(f.run(&[b"TYPE", b"set"]), "+string\r\n");
8082 }
8083
8084 #[test]
8085 fn a_wrongtype_leaves_nothing_half_written() {
8086 let mut f = Fixture::new();
8090 f.run(&[b"SET", b"k", b"v"]);
8091 let reply = f.run(&[b"SMISMEMBER", b"k", b"a", b"b"]);
8092 assert!(reply.starts_with("-WRONGTYPE"), "got {reply}");
8093 assert!(!reply.contains('*'), "an array header went out in front");
8094 }
8095
8096 #[test]
8097 fn emptying_a_set_takes_the_key_with_it() {
8098 let mut f = Fixture::new();
8099 f.run(&[b"SADD", b"s", b"a", b"b"]);
8100 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
8101 assert_eq!(f.run(&[b"SREM", b"s", b"a", b"b"]), ":2\r\n");
8102 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
8103 assert_eq!(f.run(&[b"TYPE", b"s"]), "+none\r\n");
8104 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
8105 }
8106
8107 fn split_scan(reply: &str) -> (String, Vec<String>) {
8113 let mut lines = reply.split("\r\n");
8114 assert_eq!(lines.next(), Some("*2"), "got {reply}");
8115 lines.next().expect("the cursor header");
8116 let cursor = lines.next().expect("the cursor").to_owned();
8117 let header = lines.next().expect("the member header");
8118 let n: usize = header[1..].parse().expect("a member count");
8119 let mut members = Vec::with_capacity(n);
8120 for _ in 0..n {
8121 lines.next().expect("a member header");
8122 members.push(lines.next().expect("a member").to_owned());
8123 }
8124 (cursor, members)
8125 }
8126
8127 #[test]
8128 fn popping_takes_a_member_off_the_set_and_hands_it_back() {
8129 let mut f = Fixture::new();
8130 f.run(&[b"SADD", b"s", b"a", b"b", b"c", b"d"]);
8131
8132 let one = f.run(&[b"SPOP", b"s"]);
8133 assert!(
8134 ["$1\r\na\r\n", "$1\r\nb\r\n", "$1\r\nc\r\n", "$1\r\nd\r\n"].contains(&one.as_str()),
8135 "got {one}"
8136 );
8137 assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
8138
8139 let (_, rest) = ("", f.run(&[b"SPOP", b"s", b"3"]));
8141 assert!(rest.starts_with("*3\r\n"), "got {rest}");
8142 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
8143 assert_eq!(f.run(&[b"SPOP", b"s"]), "$-1\r\n");
8145 assert_eq!(f.run(&[b"SPOP", b"s", b"2"]), "*0\r\n");
8146 }
8147
8148 #[test]
8149 fn the_two_draws_disagree_about_the_reply_type_and_they_are_right_to() {
8150 let mut f = Fixture::new();
8155 f.run(&[b"HELLO", b"3"]);
8156 f.run(&[b"SADD", b"s", b"a", b"b", b"c"]);
8157
8158 assert!(f.run(&[b"SPOP", b"s", b"2"]).starts_with("~2\r\n"));
8159 assert!(f.run(&[b"SRANDMEMBER", b"s", b"1"]).starts_with("*1\r\n"));
8161
8162 f.run(&[b"SADD", b"one", b"z"]);
8166 assert_eq!(
8167 f.run(&[b"SRANDMEMBER", b"one", b"-3"]),
8168 "*3\r\n$1\r\nz\r\n$1\r\nz\r\n$1\r\nz\r\n"
8169 );
8170 }
8171
8172 #[test]
8173 fn drawing_a_member_removes_nothing_and_says_nil_at_a_missing_key() {
8174 let mut f = Fixture::new();
8175 f.run(&[b"SADD", b"s", b"only"]);
8176 assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
8177 assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
8178 assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n");
8179
8180 assert_eq!(f.run(&[b"SRANDMEMBER", b"nope"]), "$-1\r\n");
8181 assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"3"]), "*0\r\n");
8184 assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"-3"]), "*0\r\n");
8185 assert_eq!(f.run(&[b"SRANDMEMBER", b"s", b"9"]), "*1\r\n$4\r\nonly\r\n");
8187 }
8188
8189 #[test]
8190 fn a_pop_count_that_is_not_a_positive_number_says_so() {
8191 let mut f = Fixture::new();
8192 f.run(&[b"SADD", b"s", b"a"]);
8193 let bad = "-ERR value is out of range, must be positive\r\n";
8194 assert_eq!(f.run(&[b"SPOP", b"s", b"-1"]), bad);
8195 assert_eq!(f.run(&[b"SPOP", b"s", b"abc"]), bad);
8196 assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n", "and took nothing");
8197 assert_eq!(f.run(&[b"SPOP", b"s", b"0"]), "*0\r\n");
8199 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
8200 }
8201
8202 #[test]
8203 fn a_scan_walks_a_set_of_any_size_exactly_once() {
8204 let mut f = Fixture::new();
8205 let members: Vec<Vec<u8>> = (0..300).map(|i| format!("m{i}").into_bytes()).collect();
8206 let args: Vec<&[u8]> = [&b"SADD"[..], &b"s"[..]]
8207 .into_iter()
8208 .chain(members.iter().map(Vec::as_slice))
8209 .collect();
8210 f.run(&args);
8211
8212 let mut seen = Vec::new();
8213 let mut cursor = "0".to_owned();
8214 loop {
8215 let reply = f.run(&[b"SSCAN", b"s", cursor.as_bytes()]);
8216 let (next, got) = split_scan(&reply);
8217 seen.extend(got);
8218 cursor = next;
8219 if cursor == "0" {
8220 break;
8221 }
8222 }
8223 seen.sort();
8224 seen.dedup();
8225 assert_eq!(seen.len(), 300, "a walk saw a member twice or missed one");
8226
8227 f.run(&[b"SADD", b"small", b"a", b"b", b"c"]);
8230 let (cursor, got) = split_scan(&f.run(&[b"SSCAN", b"small", b"0", b"COUNT", b"1"]));
8231 assert_eq!(cursor, "0");
8232 assert_eq!(got.len(), 3);
8233 assert_eq!(f.run(&[b"SSCAN", b"nope", b"0"]), "*2\r\n$1\r\n0\r\n*0\r\n");
8235 }
8236
8237 #[test]
8238 fn a_scan_takes_match_and_count_and_refuses_anything_else() {
8239 let mut f = Fixture::new();
8240 f.run(&[b"SADD", b"s", b"aa", b"ab", b"ba", b"12", b"13"]);
8241
8242 let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"a*"]));
8243 let mut got = got;
8244 got.sort();
8245 assert_eq!(got, ["aa", "ab"]);
8246
8247 let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"1?"]));
8250 let mut got = got;
8251 got.sort();
8252 assert_eq!(got, ["12", "13"]);
8253
8254 assert_eq!(f.run(&[b"SSCAN", b"s", b"abc"]), "-ERR invalid cursor\r\n");
8255 assert_eq!(f.run(&[b"SSCAN", b"s", b"-1"]), "-ERR invalid cursor\r\n");
8256 assert_eq!(
8257 f.run(&[b"SSCAN", b"s", b"0", b"NOPE", b"1"]),
8258 "-ERR syntax error\r\n"
8259 );
8260 assert_eq!(
8263 f.run(&[b"SSCAN", b"s", b"0", b"COUNT", b"0"]),
8264 "-ERR syntax error\r\n"
8265 );
8266 }
8267
8268 #[test]
8269 fn moving_a_member_takes_it_off_one_set_and_puts_it_on_another() {
8270 let mut f = Fixture::new();
8271 f.run(&[b"SADD", b"src", b"a", b"b"]);
8272 f.run(&[b"SADD", b"dst", b"c"]);
8273
8274 assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"a"]), ":1\r\n");
8275 assert_eq!(f.run(&[b"SISMEMBER", b"src", b"a"]), ":0\r\n");
8276 assert_eq!(f.run(&[b"SISMEMBER", b"dst", b"a"]), ":1\r\n");
8277 assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"zz"]), ":0\r\n");
8279 assert_eq!(f.run(&[b"SCARD", b"dst"]), ":2\r\n");
8280
8281 assert_eq!(f.run(&[b"SMOVE", b"src", b"fresh", b"b"]), ":1\r\n");
8284 assert_eq!(f.run(&[b"EXISTS", b"src"]), ":0\r\n");
8285 assert_eq!(f.run(&[b"SMEMBERS", b"fresh"]), "*1\r\n$1\r\nb\r\n");
8286 }
8287
8288 #[test]
8289 fn moving_checks_the_types_in_the_order_redis_checks_them() {
8290 let mut f = Fixture::new();
8294 f.run(&[b"SET", b"str", b"v"]);
8295 f.run(&[b"SADD", b"set", b"a"]);
8296
8297 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8298 assert_eq!(f.run(&[b"SMOVE", b"nope", b"str", b"a"]), ":0\r\n");
8299 assert_eq!(f.run(&[b"SMOVE", b"str", b"set", b"a"]), wrong);
8300 assert_eq!(f.run(&[b"SMOVE", b"set", b"str", b"a"]), wrong);
8301 assert_eq!(f.run(&[b"SPOP", b"str"]), wrong);
8302 assert_eq!(f.run(&[b"SRANDMEMBER", b"str"]), wrong);
8303 assert_eq!(f.run(&[b"SSCAN", b"str", b"0"]), wrong);
8304 assert_eq!(
8305 f.run(&[b"SISMEMBER", b"set", b"a"]),
8306 ":1\r\n",
8307 "and none of that moved anything"
8308 );
8309 }
8310
8311 #[test]
8312 fn a_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
8313 let mut f = Fixture::new();
8316 f.run(&[b"SADD", b"s", b"a"]);
8317 for bad in [
8318 &[b"SSCAN".as_slice(), b"s", b"abc"][..],
8319 &[b"SSCAN".as_slice(), b"s", b"0", b"COUNT", b"nope"][..],
8320 &[b"SSCAN".as_slice(), b"s", b"0", b"MATCH"][..],
8321 ] {
8322 let reply = f.run(bad);
8323 assert!(reply.starts_with("-ERR"), "got {reply}");
8324 assert!(!reply.contains('*'), "an array header went out in front");
8325 }
8326 }
8327
8328 #[test]
8329 fn a_hash_writes_reads_and_deletes_its_fields() {
8330 let mut f = Fixture::new();
8331 assert_eq!(f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]), ":2\r\n");
8332 assert_eq!(f.run(&[b"HSET", b"h", b"a", b"9"]), ":0\r\n", "a was there");
8333 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
8334 assert_eq!(f.run(&[b"HGET", b"h", b"nope"]), "$-1\r\n");
8335 assert_eq!(f.run(&[b"HGET", b"nokey", b"a"]), "$-1\r\n");
8336 assert_eq!(f.run(&[b"HLEN", b"h"]), ":2\r\n");
8337 assert_eq!(f.run(&[b"HEXISTS", b"h", b"a"]), ":1\r\n");
8338 assert_eq!(f.run(&[b"HEXISTS", b"h", b"nope"]), ":0\r\n");
8339 assert_eq!(f.run(&[b"HSTRLEN", b"h", b"a"]), ":1\r\n");
8340 assert_eq!(f.run(&[b"HSTRLEN", b"h", b"nope"]), ":0\r\n");
8341
8342 assert_eq!(f.run(&[b"HGET", b"h", b"2"]), "$-1\r\n");
8345
8346 assert_eq!(f.run(&[b"HDEL", b"h", b"a", b"nope"]), ":1\r\n");
8347 assert_eq!(f.run(&[b"HDEL", b"h", b"b"]), ":1\r\n");
8348 assert_eq!(
8349 f.run(&[b"EXISTS", b"h"]),
8350 ":0\r\n",
8351 "and losing the last field lost the key"
8352 );
8353 }
8354
8355 #[test]
8356 fn hgetall_answers_a_map_on_resp3_and_the_same_pairs_flat_on_resp2() {
8357 let mut f = Fixture::new();
8358 f.run(&[b"HSET", b"h", b"a", b"1"]);
8359 assert_eq!(f.run(&[b"HGETALL", b"h"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
8360 assert_eq!(f.run(&[b"HGETALL", b"nokey"]), "*0\r\n");
8361 assert_eq!(f.run(&[b"HKEYS", b"h"]), "*1\r\n$1\r\na\r\n");
8362 assert_eq!(f.run(&[b"HVALS", b"h"]), "*1\r\n$1\r\n1\r\n");
8363 assert_eq!(f.run(&[b"HKEYS", b"nokey"]), "*0\r\n");
8364
8365 f.run(&[b"HELLO", b"3"]);
8366 assert_eq!(f.run(&[b"HGETALL", b"h"]), "%1\r\n$1\r\na\r\n$1\r\n1\r\n");
8367 assert_eq!(
8368 f.run(&[b"HGETALL", b"nokey"]),
8369 "%0\r\n",
8370 "a missing key is the empty hash and never a nil"
8371 );
8372 assert_eq!(
8373 f.run(&[b"HKEYS", b"h"]),
8374 "*1\r\n$1\r\na\r\n",
8375 "and the two that answer one side stay arrays"
8376 );
8377 }
8378
8379 #[test]
8380 fn hmget_answers_once_per_field_and_hmset_answers_ok() {
8381 let mut f = Fixture::new();
8382 assert_eq!(f.run(&[b"HMSET", b"h", b"a", b"1", b"c", b"3"]), "+OK\r\n");
8383 assert_eq!(
8384 f.run(&[b"HMGET", b"h", b"a", b"b", b"c"]),
8385 "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n",
8386 "the reply is positional, so b is a nil and not a gap"
8387 );
8388 assert_eq!(
8389 f.run(&[b"HMGET", b"nokey", b"a", b"b"]),
8390 "*2\r\n$-1\r\n$-1\r\n",
8391 "and a missing key is all nils rather than an empty array"
8392 );
8393
8394 assert_eq!(f.run(&[b"HSETNX", b"h", b"a", b"9"]), ":0\r\n");
8395 assert_eq!(f.run(&[b"HSETNX", b"h", b"z", b"9"]), ":1\r\n");
8396 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
8397 }
8398
8399 #[test]
8400 fn a_hash_counts_up_and_says_so_when_it_cannot() {
8401 let mut f = Fixture::new();
8402 assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"5"]), ":5\r\n");
8403 assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"-7"]), ":-2\r\n");
8404 assert_eq!(f.run(&[b"HGET", b"h", b"n"]), "$2\r\n-2\r\n");
8405 assert_eq!(
8406 f.run(&[b"HINCRBYFLOAT", b"h", b"f", b"10.5"]),
8407 "$4\r\n10.5\r\n",
8408 "a bulk string and not a double, on both protocols"
8409 );
8410
8411 f.run(&[b"HSET", b"h", b"s", b"words"]);
8412 let bad = f.run(&[b"HINCRBY", b"h", b"s", b"1"]);
8413 assert!(
8414 bad.starts_with("-ERR hash value is not an integer"),
8415 "{bad}"
8416 );
8417 let bad = f.run(&[b"HINCRBY", b"h", b"n", b"nope"]);
8418 assert!(
8419 bad.starts_with("-ERR value is not an integer"),
8420 "a bad argument is not yet a hash value, {bad}"
8421 );
8422 assert_eq!(
8423 f.run(&[b"HGET", b"h", b"s"]),
8424 "$5\r\nwords\r\n",
8425 "and neither of them wrote anything"
8426 );
8427 }
8428
8429 #[test]
8430 fn a_hash_scan_walks_every_pair_once_and_novalues_drops_half_of_it() {
8431 let fields = if cfg!(miri) { 96 } else { 500 };
8436 let mut f = Fixture::new();
8437 for i in 0..fields {
8438 let field = format!("field-{i}");
8439 let value = format!("value-{i}");
8440 f.run(&[b"HSET", b"h", field.as_bytes(), value.as_bytes()]);
8441 }
8442
8443 let mut seen: Vec<String> = Vec::new();
8444 let mut cursor = "0".to_owned();
8445 loop {
8446 let reply = f.run(&[b"HSCAN", b"h", cursor.as_bytes(), b"COUNT", b"32"]);
8447 let (next, items) = scan_reply(&reply);
8448 assert_eq!(items.len() % 2, 0, "a pair went out half written");
8449 for pair in items.chunks(2) {
8450 assert_eq!(
8451 pair[0].strip_prefix("field-"),
8452 pair[1].strip_prefix("value-"),
8453 "a field came back with someone else's value"
8454 );
8455 seen.push(pair[0].clone());
8456 }
8457 cursor = next;
8458 if cursor == "0" {
8459 break;
8460 }
8461 }
8462 seen.sort();
8463 seen.dedup();
8464 assert_eq!(seen.len(), fields, "every field once and only once");
8465
8466 let (_, items) = scan_reply(&f.run(&[b"HSCAN", b"h", b"0", b"NOVALUES", b"COUNT", b"32"]));
8467 assert!(
8468 items.iter().all(|s| s.starts_with("field-")),
8469 "NOVALUES still sent the values"
8470 );
8471
8472 let last = fields - 1;
8473 let (_, one) = scan_reply(&f.run(&[
8474 b"HSCAN",
8475 b"h",
8476 b"0",
8477 b"MATCH",
8478 format!("field-{last}").as_bytes(),
8479 b"COUNT",
8480 b"1000",
8481 ]));
8482 assert_eq!(
8483 one,
8484 [format!("field-{last}"), format!("value-{last}")],
8485 "MATCH is on the field"
8486 );
8487 }
8488
8489 #[test]
8490 fn hrandfield_draws_what_it_was_asked_for_and_nests_values_on_resp3() {
8491 let mut f = Fixture::new();
8492 f.run(&[b"HSET", b"h", b"a", b"1"]);
8493 assert_eq!(f.run(&[b"HRANDFIELD", b"h"]), "$1\r\na\r\n");
8494 assert_eq!(f.run(&[b"HRANDFIELD", b"nokey"]), "$-1\r\n");
8495 assert_eq!(f.run(&[b"HRANDFIELD", b"nokey", b"3"]), "*0\r\n");
8496 assert_eq!(
8497 f.run(&[b"HRANDFIELD", b"h", b"3"]),
8498 "*1\r\n$1\r\na\r\n",
8499 "a positive count is capped at the size of the hash"
8500 );
8501 assert_eq!(
8502 f.run(&[b"HRANDFIELD", b"h", b"-3"]),
8503 "*3\r\n$1\r\na\r\n$1\r\na\r\n$1\r\na\r\n",
8504 "and a negative one repeats itself"
8505 );
8506 assert_eq!(
8507 f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
8508 "*2\r\n$1\r\na\r\n$1\r\n1\r\n",
8509 "flat on RESP2"
8510 );
8511
8512 f.run(&[b"HELLO", b"3"]);
8513 assert_eq!(
8514 f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
8515 "*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n",
8516 "and nested on RESP3, but still an array and never a map"
8517 );
8518 }
8519
8520 #[test]
8521 fn every_hash_command_says_wrongtype_and_writes_nothing() {
8522 let mut f = Fixture::new();
8523 f.run(&[b"SET", b"str", b"v"]);
8524 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8525
8526 for cmd in [
8527 &[b"HSET".as_slice(), b"str", b"f", b"v"][..],
8528 &[b"HMSET".as_slice(), b"str", b"f", b"v"][..],
8529 &[b"HSETNX".as_slice(), b"str", b"f", b"v"][..],
8530 &[b"HGET".as_slice(), b"str", b"f"][..],
8531 &[b"HMGET".as_slice(), b"str", b"f"][..],
8532 &[b"HDEL".as_slice(), b"str", b"f"][..],
8533 &[b"HLEN".as_slice(), b"str"][..],
8534 &[b"HEXISTS".as_slice(), b"str", b"f"][..],
8535 &[b"HSTRLEN".as_slice(), b"str", b"f"][..],
8536 &[b"HGETALL".as_slice(), b"str"][..],
8537 &[b"HKEYS".as_slice(), b"str"][..],
8538 &[b"HVALS".as_slice(), b"str"][..],
8539 &[b"HINCRBY".as_slice(), b"str", b"f", b"1"][..],
8540 &[b"HINCRBYFLOAT".as_slice(), b"str", b"f", b"1"][..],
8541 &[b"HRANDFIELD".as_slice(), b"str"][..],
8542 &[b"HRANDFIELD".as_slice(), b"str", b"2"][..],
8543 &[b"HSCAN".as_slice(), b"str", b"0"][..],
8544 ] {
8545 let reply = f.run(cmd);
8546 assert_eq!(reply, wrong, "{:?}", cmd[0]);
8547 }
8548 assert_eq!(
8549 f.run(&[b"GET", b"str"]),
8550 "$1\r\nv\r\n",
8551 "and none of them touched the value"
8552 );
8553 }
8554
8555 #[test]
8556 fn a_hash_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
8557 let mut f = Fixture::new();
8558 f.run(&[b"HSET", b"h", b"f", b"v"]);
8559 for bad in [
8560 &[b"HSCAN".as_slice(), b"h", b"abc"][..],
8561 &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"nope"][..],
8562 &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"0"][..],
8563 &[b"HSCAN".as_slice(), b"h", b"0", b"MATCH"][..],
8564 ] {
8565 let reply = f.run(bad);
8566 assert!(reply.starts_with("-ERR"), "got {reply}");
8567 assert!(!reply.contains('*'), "an array header went out in front");
8568 }
8569 }
8570
8571 #[test]
8572 fn a_field_deadline_goes_on_and_comes_back_in_all_four_units() {
8573 let mut f = Fixture::new();
8574 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
8575 assert_eq!(
8576 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]),
8577 "*1\r\n:1\r\n"
8578 );
8579 assert_eq!(
8580 f.run(&[b"HTTL", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
8581 "*3\r\n:100\r\n:-1\r\n:-2\r\n",
8582 "one answer per field, and the two sentinels are TTL's own"
8583 );
8584
8585 let ms = int_reply(&f.run(&[b"HPTTL", b"h", b"FIELDS", b"1", b"a"]));
8588 assert!((99_000..=100_000).contains(&ms), "got {ms}");
8589 let at = int_reply(&f.run(&[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
8590 let at_ms = int_reply(&f.run(&[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
8591 assert_eq!(at, at_ms.div_euclid(1000) + i64::from(at_ms % 1000 != 0));
8592 assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
8593
8594 assert_eq!(
8595 f.run(&[b"HPERSIST", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
8596 "*3\r\n:1\r\n:-1\r\n:-2\r\n",
8597 "one for the deadline taken off, and it does not say what it was"
8598 );
8599 assert_eq!(
8600 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8601 "*1\r\n:-1\r\n"
8602 );
8603 assert_eq!(
8604 f.run(&[b"HGET", b"h", b"a"]),
8605 "$1\r\n1\r\n",
8606 "and the field is still there with the value it had"
8607 );
8608 }
8609
8610 #[test]
8611 fn a_deadline_that_has_already_gone_deletes_the_field_now() {
8612 let mut f = Fixture::new();
8613 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
8614 assert_eq!(
8615 f.run(&[b"HEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"a"]),
8616 "*1\r\n:2\r\n",
8617 "two, and not one, because nothing was stored"
8618 );
8619 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
8620 assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
8621
8622 assert_eq!(
8623 f.run(&[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"]),
8624 "*1\r\n:2\r\n"
8625 );
8626 assert_eq!(
8627 f.run(&[b"EXISTS", b"h"]),
8628 ":0\r\n",
8629 "and the last field going took the key with it"
8630 );
8631
8632 f.run(&[b"HSET", b"h", b"a", b"1"]);
8635 assert_eq!(
8636 f.run(&[b"HEXPIRE", b"h", b"0", b"FIELDS", b"1", b"a"]),
8637 "*1\r\n:2\r\n"
8638 );
8639 }
8640
8641 #[test]
8642 fn a_field_is_gone_once_its_moment_passes() {
8643 let mut f = Fixture::new();
8644 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
8645 assert_eq!(
8646 f.run(&[b"HPEXPIRE", b"h", b"20", b"FIELDS", b"1", b"a"]),
8647 "*1\r\n:1\r\n"
8648 );
8649 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n", "not yet");
8650
8651 f.server.advance_clock_ms(60);
8655 assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
8656 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
8657 assert_eq!(
8658 f.run(&[b"HGETALL", b"h"]),
8659 "*2\r\n$1\r\nb\r\n$1\r\n2\r\n",
8660 "and the walks do not hand back a field that has expired"
8661 );
8662 }
8663
8664 #[test]
8665 fn a_missing_key_answers_the_no_field_sentinel_for_every_field() {
8666 let mut f = Fixture::new();
8667 for cmd in [
8668 &[
8669 b"HEXPIRE".as_slice(),
8670 b"nokey",
8671 b"100",
8672 b"FIELDS",
8673 b"2",
8674 b"a",
8675 b"b",
8676 ][..],
8677 &[b"HTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
8678 &[b"HPTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
8679 &[
8680 b"HEXPIRETIME".as_slice(),
8681 b"nokey",
8682 b"FIELDS",
8683 b"2",
8684 b"a",
8685 b"b",
8686 ][..],
8687 &[
8688 b"HPERSIST".as_slice(),
8689 b"nokey",
8690 b"FIELDS",
8691 b"2",
8692 b"a",
8693 b"b",
8694 ][..],
8695 ] {
8696 assert_eq!(f.run(cmd), "*2\r\n:-2\r\n:-2\r\n", "{:?}", cmd[0]);
8697 }
8698 }
8699
8700 #[test]
8701 fn writing_a_field_clears_the_deadline_that_was_on_it() {
8702 let mut f = Fixture::new();
8703 f.run(&[b"HSET", b"h", b"a", b"1"]);
8704 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]);
8705 f.run(&[b"HSET", b"h", b"a", b"2"]);
8706 assert_eq!(
8707 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8708 "*1\r\n:-1\r\n",
8709 "Redis has done this since 7.4, and it is why HGETEX exists"
8710 );
8711 }
8712
8713 #[test]
8714 fn the_four_conditions_reach_the_store_the_way_they_were_written() {
8715 let mut f = Fixture::new();
8716 f.run(&[b"HSET", b"h", b"a", b"1"]);
8717 assert_eq!(
8718 f.run(&[b"HEXPIRE", b"h", b"100", b"XX", b"FIELDS", b"1", b"a"]),
8719 "*1\r\n:0\r\n",
8720 "XX on a field with no deadline changes nothing"
8721 );
8722 assert_eq!(
8723 f.run(&[b"HEXPIRE", b"h", b"100", b"NX", b"FIELDS", b"1", b"a"]),
8724 "*1\r\n:1\r\n"
8725 );
8726 assert_eq!(
8727 f.run(&[b"HEXPIRE", b"h", b"200", b"NX", b"FIELDS", b"1", b"a"]),
8728 "*1\r\n:0\r\n",
8729 "and NX will not move one that is already there"
8730 );
8731 assert_eq!(
8732 f.run(&[b"HEXPIRE", b"h", b"50", b"GT", b"FIELDS", b"1", b"a"]),
8733 "*1\r\n:0\r\n"
8734 );
8735 assert_eq!(
8736 f.run(&[b"HEXPIRE", b"h", b"500", b"GT", b"FIELDS", b"1", b"a"]),
8737 "*1\r\n:1\r\n"
8738 );
8739 assert_eq!(
8740 f.run(&[b"HEXPIRE", b"h", b"50", b"LT", b"FIELDS", b"1", b"a"]),
8741 "*1\r\n:1\r\n"
8742 );
8743 assert_eq!(
8744 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8745 "*1\r\n:50\r\n"
8746 );
8747 }
8748
8749 #[test]
8750 fn the_field_ttl_family_leaves_nothing_half_written_on_a_bad_argument() {
8751 let mut f = Fixture::new();
8752 f.run(&[b"HSET", b"h", b"a", b"1"]);
8753 for (bad, want) in [
8754 (
8755 &[b"HEXPIRE".as_slice(), b"h", b"-1", b"FIELDS", b"1", b"a"][..],
8756 "-ERR invalid expire time, must be >= 0",
8757 ),
8758 (
8759 &[
8760 b"HEXPIRE".as_slice(),
8761 b"h",
8762 b"9999999999999999",
8763 b"FIELDS",
8764 b"1",
8765 b"a",
8766 ][..],
8767 "-ERR invalid expire time in 'hexpire' command",
8768 ),
8769 (
8770 &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELD", b"1", b"a"][..],
8771 "-ERR wrong number of arguments for 'hexpire' command",
8772 ),
8773 (
8774 &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"0", b"a"][..],
8775 "-ERR Parameter `numFields` should be greater than 0",
8776 ),
8777 (
8778 &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"2", b"a"][..],
8779 "-ERR wrong number of arguments",
8780 ),
8781 (
8782 &[b"HTTL".as_slice(), b"h", b"FIELDS", b"3", b"a", b"b"][..],
8783 "-ERR wrong number of arguments",
8784 ),
8785 ] {
8786 let reply = f.run(bad);
8787 assert!(reply.starts_with(want), "wanted {want}, got {reply}");
8788 assert!(!reply.contains('*'), "an array header went out in front");
8789 }
8790 assert_eq!(
8791 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8792 "*1\r\n:-1\r\n",
8793 "and not one of them put a deadline on anything"
8794 );
8795 }
8796
8797 #[test]
8798 fn every_field_ttl_command_says_wrongtype_and_writes_nothing() {
8799 let mut f = Fixture::new();
8800 f.run(&[b"SET", b"str", b"v"]);
8801 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8802
8803 for cmd in [
8804 &[b"HEXPIRE".as_slice(), b"str", b"100", b"FIELDS", b"1", b"f"][..],
8805 &[
8806 b"HPEXPIRE".as_slice(),
8807 b"str",
8808 b"100",
8809 b"FIELDS",
8810 b"1",
8811 b"f",
8812 ][..],
8813 &[
8814 b"HEXPIREAT".as_slice(),
8815 b"str",
8816 b"9999999999",
8817 b"FIELDS",
8818 b"1",
8819 b"f",
8820 ][..],
8821 &[
8822 b"HPEXPIREAT".as_slice(),
8823 b"str",
8824 b"9999999999999",
8825 b"FIELDS",
8826 b"1",
8827 b"f",
8828 ][..],
8829 &[b"HTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8830 &[b"HPTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8831 &[b"HEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8832 &[b"HPEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8833 &[b"HPERSIST".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8834 ] {
8835 assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
8836 }
8837 assert_eq!(
8838 f.run(&[b"GET", b"str"]),
8839 "$1\r\nv\r\n",
8840 "and none of them touched the value"
8841 );
8842 }
8843
8844 #[test]
8845 fn hgetdel_hands_the_value_out_and_then_takes_the_field() {
8846 let mut f = Fixture::new();
8847 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
8848 assert_eq!(
8849 f.run(&[b"HGETDEL", b"h", b"FIELDS", b"2", b"a", b"nope"]),
8850 "*2\r\n$1\r\n1\r\n$-1\r\n",
8851 "positional, so the field that was not there is a nil in its place"
8852 );
8853 assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
8854 assert_eq!(
8855 f.run(&[b"HGETDEL", b"nokey", b"FIELDS", b"1", b"a"]),
8856 "*1\r\n$-1\r\n"
8857 );
8858 assert_eq!(
8859 f.run(&[b"HGETDEL", b"h", b"FIELDS", b"1", b"b"]),
8860 "*1\r\n$1\r\n2\r\n"
8861 );
8862 assert_eq!(
8863 f.run(&[b"EXISTS", b"h"]),
8864 ":0\r\n",
8865 "and the last field took the key"
8866 );
8867 }
8868
8869 #[test]
8870 fn hgetex_reads_and_moves_the_deadline_in_one_command() {
8871 let mut f = Fixture::new();
8872 f.run(&[b"HSET", b"h", b"a", b"1"]);
8873 assert_eq!(
8874 f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]),
8875 "*1\r\n$1\r\n1\r\n"
8876 );
8877 assert_eq!(
8878 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8879 "*1\r\n:-1\r\n",
8880 "no option means leave it alone, which is the one place this is not GETEX"
8881 );
8882
8883 f.run(&[b"HGETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a"]);
8884 assert_eq!(
8885 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8886 "*1\r\n:100\r\n"
8887 );
8888 f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]);
8889 assert_eq!(
8890 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8891 "*1\r\n:100\r\n",
8892 "and a plain read really does leave it alone"
8893 );
8894 assert_eq!(
8895 f.run(&[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"a"]),
8896 "*1\r\n$1\r\n1\r\n"
8897 );
8898 assert_eq!(
8899 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8900 "*1\r\n:-1\r\n"
8901 );
8902
8903 assert_eq!(
8904 f.run(&[b"HGETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a"]),
8905 "*1\r\n$1\r\n1\r\n",
8906 "the value goes out before the deadline that has already gone is applied"
8907 );
8908 assert_eq!(f.run(&[b"EXISTS", b"h"]), ":0\r\n");
8909 assert_eq!(
8910 f.run(&[b"HGETEX", b"nokey", b"EX", b"100", b"FIELDS", b"1", b"a"]),
8911 "*1\r\n$-1\r\n"
8912 );
8913 }
8914
8915 #[test]
8916 fn hsetex_writes_all_of_it_or_none_of_it() {
8917 let mut f = Fixture::new();
8918 assert_eq!(
8919 f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"1"]),
8920 ":1\r\n"
8921 );
8922 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
8923 assert_eq!(
8924 f.run(&[
8925 b"HSETEX", b"h", b"FNX", b"FIELDS", b"2", b"a", b"9", b"new", b"9"
8926 ]),
8927 ":0\r\n",
8928 "FNX wants every field named to be missing"
8929 );
8930 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
8931 assert_eq!(
8932 f.run(&[b"HEXISTS", b"h", b"new"]),
8933 ":0\r\n",
8934 "and none of the list was written"
8935 );
8936 assert_eq!(
8937 f.run(&[
8938 b"HSETEX", b"h", b"FXX", b"FIELDS", b"2", b"a", b"9", b"nope", b"9"
8939 ]),
8940 ":0\r\n",
8941 "and FXX wants every one of them to be there"
8942 );
8943 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
8944 assert_eq!(
8945 f.run(&[b"HSETEX", b"h", b"FXX", b"FIELDS", b"1", b"a", b"9"]),
8946 ":1\r\n"
8947 );
8948 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
8949
8950 assert_eq!(
8951 f.run(&[b"HSETEX", b"gone", b"FXX", b"FIELDS", b"1", b"a", b"1"]),
8952 ":0\r\n"
8953 );
8954 assert_eq!(
8955 f.run(&[b"EXISTS", b"gone"]),
8956 ":0\r\n",
8957 "a key with no fields cannot meet FXX and is not created trying"
8958 );
8959 }
8960
8961 #[test]
8962 fn hsetex_clears_the_deadline_unless_it_is_told_to_keep_it() {
8963 let mut f = Fixture::new();
8964 f.run(&[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a", b"1"]);
8965 assert_eq!(
8966 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8967 "*1\r\n:100\r\n"
8968 );
8969
8970 f.run(&[b"HSETEX", b"h", b"KEEPTTL", b"FIELDS", b"1", b"a", b"2"]);
8971 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n2\r\n");
8972 assert_eq!(
8973 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8974 "*1\r\n:100\r\n",
8975 "KEEPTTL put back what the write cleared"
8976 );
8977
8978 f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"3"]);
8979 assert_eq!(
8980 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8981 "*1\r\n:-1\r\n",
8982 "and without it a write clears the deadline the way HSET does"
8983 );
8984
8985 assert_eq!(
8988 f.run(&[
8989 b"HSETEX", b"h", b"PX", b"100000", b"FXX", b"FIELDS", b"1", b"a", b"4"
8990 ]),
8991 ":1\r\n"
8992 );
8993 assert_eq!(
8994 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8995 "*1\r\n:100\r\n"
8996 );
8997
8998 assert_eq!(
8999 f.run(&[b"HSETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a", b"5"]),
9000 ":1\r\n",
9001 "written, and not the separate code the HEXPIRE family has for this"
9002 );
9003 assert_eq!(
9004 f.run(&[b"EXISTS", b"h"]),
9005 ":0\r\n",
9006 "and storing it and then removing it emptied the hash"
9007 );
9008 }
9009
9010 #[test]
9011 fn the_last_three_hash_commands_word_their_mistakes_their_own_way() {
9012 let mut f = Fixture::new();
9013 f.run(&[b"HSET", b"h", b"a", b"1"]);
9014 for (bad, want) in [
9015 (
9017 &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
9018 "-ERR Number of fields must be a positive integer",
9019 ),
9020 (
9021 &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
9022 "-ERR The `numfields` parameter must match the number of arguments",
9023 ),
9024 (
9025 &[b"HGETDEL".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
9026 "-ERR Mandatory argument FIELDS is missing or not at the right position",
9027 ),
9028 (
9030 &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
9031 "-ERR invalid number of fields",
9032 ),
9033 (
9034 &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
9035 "-ERR wrong number of arguments",
9036 ),
9037 (
9038 &[b"HGETEX".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
9039 "-ERR unknown argument: FIELD",
9040 ),
9041 (
9042 &[
9043 b"HGETEX".as_slice(),
9044 b"h",
9045 b"KEEPTTL",
9046 b"FIELDS",
9047 b"1",
9048 b"a",
9049 ][..],
9050 "-ERR unknown argument: KEEPTTL",
9051 ),
9052 (
9053 &[
9054 b"HGETEX".as_slice(),
9055 b"h",
9056 b"EX",
9057 b"100",
9058 b"PERSIST",
9059 b"FIELDS",
9060 b"1",
9061 b"a",
9062 ][..],
9063 "-ERR Only one of EX, PX, EXAT, PXAT or PERSIST arguments can be specified",
9064 ),
9065 (
9066 &[
9067 b"HSETEX".as_slice(),
9068 b"h",
9069 b"EX",
9070 b"1",
9071 b"KEEPTTL",
9072 b"FIELDS",
9073 b"1",
9074 b"a",
9075 b"1",
9076 ][..],
9077 "-ERR Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments can be specified",
9078 ),
9079 (
9080 &[
9081 b"HSETEX".as_slice(),
9082 b"h",
9083 b"FNX",
9084 b"FXX",
9085 b"FIELDS",
9086 b"1",
9087 b"a",
9088 b"1",
9089 ][..],
9090 "-ERR Only one of FXX or FNX arguments can be specified",
9091 ),
9092 (
9093 &[
9094 b"HSETEX".as_slice(),
9095 b"h",
9096 b"FIELDS",
9097 b"2",
9098 b"a",
9099 b"1",
9100 b"b",
9101 ][..],
9102 "-ERR wrong number of arguments",
9103 ),
9104 (
9105 &[
9106 b"HGETEX".as_slice(),
9107 b"h",
9108 b"EX",
9109 b"-1",
9110 b"FIELDS",
9111 b"1",
9112 b"a",
9113 ][..],
9114 "-ERR invalid expire time, must be >= 0",
9115 ),
9116 (
9117 &[
9118 b"HGETEX".as_slice(),
9119 b"h",
9120 b"PXAT",
9121 b"99999999999999",
9122 b"FIELDS",
9123 b"1",
9124 b"a",
9125 ][..],
9126 "-ERR invalid expire time in 'hgetex' command",
9127 ),
9128 (
9129 &[
9130 b"HSETEX".as_slice(),
9131 b"h",
9132 b"EX",
9133 b"abc",
9134 b"FIELDS",
9135 b"1",
9136 b"a",
9137 b"1",
9138 ][..],
9139 "-ERR value is not an integer or out of range",
9140 ),
9141 ] {
9142 let reply = f.run(bad);
9143 assert!(reply.starts_with(want), "wanted {want}, got {reply}");
9144 assert!(!reply.contains('*'), "an array header went out in front");
9145 }
9146 assert_eq!(
9147 f.run(&[b"HGET", b"h", b"a"]),
9148 "$1\r\n1\r\n",
9149 "and not one of them wrote anything"
9150 );
9151 assert_eq!(
9152 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9153 "*1\r\n:-1\r\n"
9154 );
9155 }
9156
9157 #[test]
9158 fn the_last_three_hash_commands_say_wrongtype_and_write_nothing() {
9159 let mut f = Fixture::new();
9160 f.run(&[b"SET", b"str", b"v"]);
9161 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9162 for cmd in [
9163 &[b"HGETDEL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
9164 &[b"HGETEX".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
9165 &[
9166 b"HGETEX".as_slice(),
9167 b"str",
9168 b"EX",
9169 b"100",
9170 b"FIELDS",
9171 b"1",
9172 b"f",
9173 ][..],
9174 &[b"HSETEX".as_slice(), b"str", b"FIELDS", b"1", b"f", b"v"][..],
9175 ] {
9176 assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
9177 }
9178 assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
9179 }
9180
9181 #[test]
9189 fn himport_writes_declared_values_into_sorted_fields() {
9190 let mut f = Fixture::new();
9191 assert_eq!(
9192 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"b", b"aa", b"a"]),
9193 "+OK\r\n"
9194 );
9195 assert_eq!(
9196 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2", b"3"]),
9197 "+OK\r\n"
9198 );
9199 assert_eq!(f.run(&[b"HKEYS", b"k"]), bulks(&["a", "b", "aa"]));
9200 assert_eq!(
9201 f.run(&[b"HGETALL", b"k"]),
9202 bulks(&["a", "3", "b", "1", "aa", "2"])
9203 );
9204 }
9205
9206 #[test]
9209 fn himport_set_replaces_the_whole_key() {
9210 let mut f = Fixture::new();
9211 f.run(&[b"HSET", b"k", b"gone", b"old", b"a", b"old"]);
9212 f.run(&[b"EXPIRE", b"k", b"100"]);
9213 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9214 assert_eq!(
9215 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
9216 "+OK\r\n"
9217 );
9218 assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
9219 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
9220 }
9221
9222 #[test]
9225 fn himport_fieldsets_belong_to_the_connection_and_not_to_the_keyspace() {
9226 let mut f = Fixture::new();
9227 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a"]);
9228 f.run(&[b"SELECT", b"1"]);
9229 assert_eq!(
9230 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
9231 "+OK\r\n"
9232 );
9233 f.run(&[b"SELECT", b"0"]);
9234 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
9235 assert_eq!(
9236 f.run(&[b"HIMPORT", b"SET", b"k2", b"shape", b"1"]),
9237 "-ERR no such fieldset\r\n"
9238 );
9239 }
9240
9241 #[test]
9248 fn himport_complains_in_the_order_a_real_server_does() {
9249 let mut f = Fixture::new();
9250 f.run(&[b"SET", b"str", b"v"]);
9251 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9252 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9253 assert_eq!(
9254 f.run(&[b"HIMPORT", b"SET", b"str", b"nope", b"1"]),
9255 wrong,
9256 "the type beats a missing fieldset"
9257 );
9258 assert_eq!(
9259 f.run(&[b"HIMPORT", b"SET", b"str", b"shape", b"1"]),
9260 wrong,
9261 "and it beats a value count that does not fit"
9262 );
9263 assert_eq!(
9264 f.run(&[b"HIMPORT", b"SET", b"k", b"nope", b"1"]),
9265 "-ERR no such fieldset\r\n"
9266 );
9267 for values in [&[b"1".as_slice()][..], &[b"1".as_slice(), b"2", b"3"][..]] {
9269 let mut line: Vec<&[u8]> = vec![b"HIMPORT", b"SET", b"k", b"shape"];
9270 line.extend_from_slice(values);
9271 assert_eq!(
9272 f.run(&line),
9273 "-ERR value count does not match fieldset field count\r\n",
9274 "{} values into two fields",
9275 values.len()
9276 );
9277 }
9278 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
9279 }
9280
9281 #[test]
9283 fn himport_checks_each_subcommand_count_under_its_own_name() {
9284 let mut f = Fixture::new();
9285 assert_eq!(
9286 f.run(&[b"HIMPORT"]),
9287 "-ERR wrong number of arguments for 'himport' command\r\n"
9288 );
9289 for (rest, name) in [
9290 (&["PREPARE"][..], "prepare"),
9291 (&["PREPARE", "fs"][..], "prepare"),
9292 (&["SET"][..], "set"),
9293 (&["SET", "k"][..], "set"),
9294 (&["SET", "k", "fs"][..], "set"),
9295 (&["DISCARD"][..], "discard"),
9296 (&["DISCARD", "a", "b"][..], "discard"),
9297 (&["DISCARDALL", "x"][..], "discardall"),
9298 ] {
9299 let mut line: Vec<&[u8]> = vec![b"HIMPORT"];
9300 line.extend(rest.iter().map(|a| a.as_bytes()));
9301 assert_eq!(
9302 f.run(&line),
9303 format!("-ERR wrong number of arguments for 'himport|{name}' command\r\n"),
9304 "HIMPORT {}",
9305 rest.join(" ")
9306 );
9307 }
9308 assert_eq!(
9309 f.run(&[b"HIMPORT", b"NOPE", b"x"]),
9310 "-ERR unknown subcommand 'NOPE'. Try HIMPORT HELP.\r\n"
9311 );
9312 }
9313
9314 #[test]
9317 fn a_failed_himport_prepare_leaves_the_old_fieldset_alone() {
9318 let mut f = Fixture::new();
9319 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9320 assert_eq!(
9321 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"c", b"c"]),
9322 "-ERR duplicate field name in fieldset\r\n"
9323 );
9324 assert_eq!(
9325 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
9326 "+OK\r\n"
9327 );
9328 assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
9329 }
9330
9331 #[test]
9334 fn himport_prepare_replaces_and_the_discards_count() {
9335 let mut f = Fixture::new();
9336 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9337 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"z"]);
9338 assert_eq!(
9339 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
9340 "+OK\r\n"
9341 );
9342 assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["z", "1"]));
9343
9344 assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":1\r\n");
9345 assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":0\r\n");
9346 f.run(&[b"HIMPORT", b"PREPARE", b"one", b"a"]);
9347 f.run(&[b"HIMPORT", b"PREPARE", b"two", b"a"]);
9348 assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":2\r\n");
9349 assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":0\r\n");
9350 }
9351
9352 fn int(reply: &str) -> i64 {
9358 let body = reply
9359 .strip_prefix(':')
9360 .and_then(|s| s.strip_suffix("\r\n"))
9361 .unwrap_or_else(|| panic!("wanted an integer, got {reply}"));
9362 body.parse().expect("an integer")
9363 }
9364
9365 fn int_reply(reply: &str) -> i64 {
9366 let body = reply
9367 .strip_prefix("*1\r\n:")
9368 .and_then(|s| s.strip_suffix("\r\n"))
9369 .unwrap_or_else(|| panic!("wanted one integer, got {reply}"));
9370 body.parse().expect("an integer")
9371 }
9372
9373 fn scan_reply(reply: &str) -> (String, Vec<String>) {
9375 let mut lines = reply.split("\r\n");
9376 assert_eq!(lines.next(), Some("*2"), "got {reply}");
9377 lines.next().expect("the cursor header");
9378 let cursor = lines.next().expect("a cursor").to_owned();
9379 let header = lines.next().expect("an item count");
9380 let n: usize = header[1..].parse().expect("a count");
9381 let mut items = Vec::with_capacity(n);
9382 for _ in 0..n {
9383 lines.next().expect("an item header");
9384 items.push(lines.next().expect("an item").to_owned());
9385 }
9386 (cursor, items)
9387 }
9388
9389 fn sorted(reply: &str) -> Vec<String> {
9392 let mut lines = reply.split("\r\n");
9393 let header = lines.next().expect("a header");
9394 assert!(
9395 header.starts_with('*') || header.starts_with('~'),
9396 "got {reply}"
9397 );
9398 let n: usize = header[1..].parse().expect("a member count");
9399 let mut got = Vec::with_capacity(n);
9400 for _ in 0..n {
9401 lines.next().expect("a member header");
9402 got.push(lines.next().expect("a member").to_owned());
9403 }
9404 got.sort();
9405 got
9406 }
9407
9408 #[test]
9409 fn the_algebra_answers_what_the_sets_share_and_do_not() {
9410 let mut f = Fixture::new();
9411 f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
9412 f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
9413 f.run(&[b"SADD", b"c", b"3", b"4", b"5"]);
9414
9415 assert_eq!(sorted(&f.run(&[b"SINTER", b"a", b"b", b"c"])), ["3"]);
9416 assert_eq!(
9417 sorted(&f.run(&[b"SUNION", b"a", b"b", b"c"])),
9418 ["1", "2", "3", "4", "5"]
9419 );
9420 assert_eq!(sorted(&f.run(&[b"SDIFF", b"a", b"b"])), ["1"]);
9421 assert_eq!(sorted(&f.run(&[b"SINTER", b"a"])), ["1", "2", "3"]);
9422
9423 assert_eq!(f.run(&[b"SINTER", b"a", b"nope"]), "*0\r\n");
9426 assert_eq!(sorted(&f.run(&[b"SUNION", b"a", b"nope"])), ["1", "2", "3"]);
9427 assert_eq!(f.run(&[b"SDIFF", b"nope", b"a"]), "*0\r\n");
9428 assert_eq!(f.run(&[b"DBSIZE"]), ":3\r\n", "and none of it made a key");
9429 }
9430
9431 #[test]
9432 fn the_algebra_answers_a_set_on_resp3_and_an_array_on_resp2() {
9433 let mut f = Fixture::new();
9434 f.run(&[b"SADD", b"a", b"x"]);
9435 assert_eq!(f.run(&[b"SINTER", b"a"]), "*1\r\n$1\r\nx\r\n");
9436 assert_eq!(f.run(&[b"SUNION", b"a"]), "*1\r\n$1\r\nx\r\n");
9437 assert_eq!(f.run(&[b"SDIFF", b"a"]), "*1\r\n$1\r\nx\r\n");
9438
9439 f.run(&[b"HELLO", b"3"]);
9440 assert_eq!(f.run(&[b"SINTER", b"a"]), "~1\r\n$1\r\nx\r\n");
9441 assert_eq!(f.run(&[b"SUNION", b"a"]), "~1\r\n$1\r\nx\r\n");
9442 assert_eq!(f.run(&[b"SDIFF", b"a"]), "~1\r\n$1\r\nx\r\n");
9443 assert_eq!(f.run(&[b"SINTER", b"nope"]), "~0\r\n");
9444 }
9445
9446 #[test]
9447 fn a_store_form_writes_a_key_and_answers_how_big_it_is() {
9448 let mut f = Fixture::new();
9449 f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
9450 f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
9451
9452 assert_eq!(f.run(&[b"SINTERSTORE", b"d", b"a", b"b"]), ":2\r\n");
9453 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["2", "3"]);
9454 assert_eq!(f.run(&[b"SUNIONSTORE", b"d", b"a", b"b"]), ":4\r\n");
9455 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["1", "2", "3", "4"]);
9456 assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"b"]), ":1\r\n");
9457 assert_eq!(f.run(&[b"SMEMBERS", b"d"]), "*1\r\n$1\r\n1\r\n");
9458
9459 assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"a"]), ":0\r\n");
9462 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
9463 assert_eq!(f.run(&[b"SINTERSTORE", b"a", b"a", b"b"]), ":2\r\n");
9464 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"a"])), ["2", "3"]);
9465
9466 f.run(&[b"SET", b"str", b"v"]);
9469 assert_eq!(f.run(&[b"SUNIONSTORE", b"str", b"b"]), ":3\r\n");
9470 assert_eq!(f.run(&[b"TYPE", b"str"]), "+set\r\n");
9471 }
9472
9473 #[test]
9474 fn sintercard_counts_without_building_and_stops_at_a_limit() {
9475 let mut f = Fixture::new();
9476 f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
9477 f.run(&[b"SADD", b"b", b"2", b"3", b"4", b"5"]);
9478
9479 assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"b"]), ":3\r\n");
9480 assert_eq!(
9481 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
9482 ":2\r\n"
9483 );
9484 assert_eq!(
9485 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
9486 ":3\r\n",
9487 "a limit of zero is no limit"
9488 );
9489 assert_eq!(f.run(&[b"SINTERCARD", b"1", b"a"]), ":4\r\n");
9490 assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"nope"]), ":0\r\n");
9491
9492 assert_eq!(
9494 f.run(&[b"SINTERCARD", b"0", b"a"]),
9495 "-ERR numkeys should be greater than 0\r\n"
9496 );
9497 assert_eq!(
9498 f.run(&[b"SINTERCARD", b"abc", b"a"]),
9499 "-ERR numkeys should be greater than 0\r\n"
9500 );
9501 assert_eq!(
9502 f.run(&[b"SINTERCARD", b"3", b"a", b"b"]),
9503 "-ERR Number of keys can't be greater than number of args\r\n"
9504 );
9505 assert_eq!(
9506 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"-1"]),
9507 "-ERR LIMIT can't be negative\r\n"
9508 );
9509 assert_eq!(
9510 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"NOPE", b"1"]),
9511 "-ERR syntax error\r\n"
9512 );
9513 f.run(&[b"SADD", b"LIMIT", b"2"]);
9515 assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"LIMIT"]), ":1\r\n");
9516 }
9517
9518 #[test]
9521 fn sunioncard_and_sdiffcard_count_without_building() {
9522 let mut f = Fixture::new();
9523 f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
9524 f.run(&[b"SADD", b"b", b"3", b"4", b"5", b"6"]);
9525
9526 assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"b"]), ":6\r\n");
9527 assert_eq!(
9528 f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
9529 ":2\r\n"
9530 );
9531 assert_eq!(
9532 f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
9533 ":6\r\n",
9534 "a limit of zero is no limit"
9535 );
9536 assert_eq!(f.run(&[b"SUNIONCARD", b"1", b"a"]), ":4\r\n");
9537 assert_eq!(
9538 f.run(&[b"SUNIONCARD", b"2", b"a", b"nope"]),
9539 ":4\r\n",
9540 "a missing key adds nothing to a union"
9541 );
9542
9543 assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"b"]), ":2\r\n");
9544 assert_eq!(
9545 f.run(&[b"SDIFFCARD", b"2", b"a", b"b", b"LIMIT", b"1"]),
9546 ":1\r\n"
9547 );
9548 assert_eq!(
9549 f.run(&[b"SDIFFCARD", b"2", b"b", b"a"]),
9550 ":2\r\n",
9551 "a difference is not symmetric"
9552 );
9553 assert_eq!(f.run(&[b"SDIFFCARD", b"1", b"a"]), ":4\r\n");
9554 assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"nope"]), ":4\r\n");
9555 assert_eq!(
9556 f.run(&[b"SDIFFCARD", b"2", b"nope", b"a"]),
9557 ":0\r\n",
9558 "nothing taken away from nothing"
9559 );
9560
9561 for name in [b"SUNIONCARD".as_slice(), b"SDIFFCARD".as_slice()] {
9564 assert_eq!(
9565 f.run(&[name, b"0", b"a"]),
9566 "-ERR numkeys should be greater than 0\r\n"
9567 );
9568 assert_eq!(
9569 f.run(&[name, b"abc", b"a"]),
9570 "-ERR numkeys should be greater than 0\r\n"
9571 );
9572 assert_eq!(
9573 f.run(&[name, b"-1", b"a"]),
9574 "-ERR numkeys should be greater than 0\r\n"
9575 );
9576 assert_eq!(
9577 f.run(&[name, b"3", b"a", b"b"]),
9578 "-ERR Number of keys can't be greater than number of args\r\n"
9579 );
9580 assert_eq!(
9581 f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"-1"]),
9582 "-ERR LIMIT can't be negative\r\n"
9583 );
9584 assert_eq!(
9585 f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"abc"]),
9586 "-ERR LIMIT can't be negative\r\n",
9587 "a LIMIT that is not a number gets the negative message too"
9588 );
9589 assert_eq!(
9590 f.run(&[name, b"2", b"a", b"b", b"NOPE", b"1"]),
9591 "-ERR syntax error\r\n"
9592 );
9593 assert_eq!(
9594 f.run(&[name, b"2", b"a", b"b", b"LIMIT"]),
9595 "-ERR syntax error\r\n"
9596 );
9597 assert_eq!(
9598 f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"1", b"X"]),
9599 "-ERR syntax error\r\n"
9600 );
9601 }
9602
9603 f.run(&[b"SADD", b"LIMIT", b"2"]);
9605 assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"LIMIT"]), ":4\r\n");
9606 assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"LIMIT"]), ":3\r\n");
9607 }
9608
9609 #[test]
9610 fn the_algebra_answers_wrongtype_before_it_writes_anything() {
9611 let mut f = Fixture::new();
9612 f.run(&[b"SADD", b"a", b"1"]);
9613 f.run(&[b"SADD", b"d", b"old"]);
9614 f.run(&[b"SET", b"str", b"v"]);
9615
9616 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9617 for bad in [
9618 &[b"SINTER".as_slice(), b"a", b"str"][..],
9619 &[b"SUNION".as_slice(), b"str"][..],
9620 &[b"SDIFF".as_slice(), b"a", b"str"][..],
9621 &[b"SINTERCARD".as_slice(), b"2", b"a", b"str"][..],
9622 &[b"SINTERSTORE".as_slice(), b"d", b"a", b"str"][..],
9623 &[b"SUNIONSTORE".as_slice(), b"d", b"str"][..],
9624 &[b"SDIFFSTORE".as_slice(), b"d", b"a", b"str"][..],
9625 ] {
9626 let reply = f.run(bad);
9627 assert_eq!(reply, wrong, "for {:?}", bad[0]);
9628 }
9629 assert_eq!(
9630 f.run(&[b"SMEMBERS", b"d"]),
9631 "*1\r\n$3\r\nold\r\n",
9632 "and the destination was left alone every time"
9633 );
9634 }
9635
9636 #[cfg_attr(miri, ignore = "the volume is the claim")]
9648 #[test]
9649 fn churning_sets_does_not_grow_the_server() {
9650 let mut f = Fixture::new();
9651 let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
9652 let args: Vec<&[u8]> = std::iter::once(&b"SADD"[..])
9653 .chain(std::iter::once(&b"s"[..]))
9654 .chain(members.iter().map(Vec::as_slice))
9655 .collect();
9656
9657 f.run(&args);
9658 f.run(&[b"DEL", b"s"]);
9659 f.server.compact_step();
9660 let after_first = f.server.memory_bytes();
9661
9662 for _ in 0..200 {
9663 f.run(&args);
9664 f.run(&[b"DEL", b"s"]);
9665 f.server.compact_step();
9666 }
9667 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
9668 assert!(
9669 f.server.memory_bytes() <= after_first * 2,
9670 "held {} after two hundred passes against {after_first} after one",
9671 f.server.memory_bytes()
9672 );
9673 }
9674
9675 #[test]
9683 fn a_bit_is_written_and_read_back_and_a_write_unpacks_an_int() {
9684 let mut f = Fixture::new();
9685 assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"1"]), ":0\r\n");
9686 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n\u{1}\r\n");
9687 assert_eq!(f.run(&[b"GETBIT", b"k", b"7"]), ":1\r\n");
9688 assert_eq!(f.run(&[b"GETBIT", b"k", b"6"]), ":0\r\n");
9689 assert_eq!(f.run(&[b"GETBIT", b"k", b"100"]), ":0\r\n");
9690 assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"0"]), ":1\r\n");
9691
9692 assert_eq!(f.run(&[b"SETBIT", b"nk", b"0", b"0"]), ":0\r\n");
9694 assert_eq!(f.run(&[b"STRLEN", b"nk"]), ":1\r\n");
9695 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"nk"]), "$3\r\nraw\r\n");
9696
9697 f.run(&[b"SET", b"num", b"12345"]);
9698 assert_eq!(f.run(&[b"GETBIT", b"num", b"1"]), ":0\r\n");
9699 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nint\r\n");
9700 assert_eq!(f.run(&[b"SETBIT", b"num", b"1", b"1"]), ":0\r\n");
9701 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nraw\r\n");
9702 assert_eq!(f.run(&[b"GET", b"num"]), "$5\r\nq2345\r\n");
9703 }
9704
9705 #[test]
9710 fn bits_are_counted_over_a_range_of_bytes_or_of_bits() {
9711 let mut f = Fixture::new();
9712 f.run(&[b"SET", b"mykey", b"foobar"]);
9713 assert_eq!(f.run(&[b"BITCOUNT", b"mykey"]), ":26\r\n");
9714 assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"0", b"0"]), ":4\r\n");
9715 assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"1", b"1"]), ":6\r\n");
9716 assert_eq!(
9717 f.run(&[b"BITCOUNT", b"mykey", b"1", b"1", b"BYTE"]),
9718 ":6\r\n"
9719 );
9720 assert_eq!(
9721 f.run(&[b"BITCOUNT", b"mykey", b"0", b"-5", b"BIT"]),
9722 ":25\r\n"
9723 );
9724 assert_eq!(
9725 f.run(&[b"BITCOUNT", b"mykey", b"5", b"30", b"BIT"]),
9726 ":17\r\n"
9727 );
9728 assert_eq!(f.run(&[b"BITCOUNT", b"nokey"]), ":0\r\n");
9729
9730 assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"10", b"20"]), ":0\r\n");
9733
9734 assert_eq!(
9736 f.run(&[b"BITCOUNT", b"mykey", b"0"]),
9737 "-ERR syntax error\r\n"
9738 );
9739 assert_eq!(
9740 f.run(&[b"BITCOUNT", b"mykey", b"0", b"1", b"NIB"]),
9741 "-ERR syntax error\r\n"
9742 );
9743 }
9744
9745 #[test]
9751 fn a_search_for_a_nought_past_the_end_answers_the_length_in_bits() {
9752 let mut f = Fixture::new();
9753 f.run(&[b"SET", b"ones", b"\xff\xff\xff"]);
9754 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0"]), ":24\r\n");
9755 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0"]), ":24\r\n");
9756 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"-1"]), ":-1\r\n");
9757 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"3"]), ":-1\r\n");
9758 assert_eq!(f.run(&[b"BITPOS", b"ones", b"1"]), ":0\r\n");
9759
9760 f.run(&[b"SET", b"mid", b"\x00\xff\xf0"]);
9761 assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"0"]), ":8\r\n");
9762 assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"2"]), ":16\r\n");
9763 assert_eq!(
9764 f.run(&[b"BITPOS", b"mid", b"1", b"0", b"-1", b"BIT"]),
9765 ":8\r\n"
9766 );
9767
9768 assert_eq!(f.run(&[b"BITPOS", b"gone", b"1"]), ":-1\r\n");
9771 assert_eq!(f.run(&[b"BITPOS", b"gone", b"0"]), ":0\r\n");
9772 }
9773
9774 #[test]
9776 fn the_eight_combinations_write_what_a_real_server_writes() {
9777 let mut f = Fixture::new();
9778 f.run(&[b"SET", b"a", b"abc"]);
9779 f.run(&[b"SET", b"b", b"abd"]);
9780 let cases: &[(&[u8], &str)] = &[
9781 (b"AND", "ab`"),
9782 (b"OR", "abg"),
9783 (b"XOR", "\u{0}\u{0}\u{7}"),
9784 (b"DIFF", "\u{0}\u{0}\u{3}"),
9785 (b"DIFF1", "\u{0}\u{0}\u{4}"),
9786 (b"ANDOR", "ab`"),
9787 (b"ONE", "\u{0}\u{0}\u{7}"),
9788 ];
9789 for (op, want) in cases {
9790 assert_eq!(f.run(&[b"BITOP", op, b"d", b"a", b"b"]), ":3\r\n", "{op:?}");
9791 assert_eq!(
9792 f.run(&[b"GET", b"d"]),
9793 format!("$3\r\n{want}\r\n"),
9794 "{op:?}"
9795 );
9796 }
9797 assert_eq!(f.run(&[b"BITOP", b"NOT", b"d", b"a"]), ":3\r\n");
9799 assert_eq!(f.raw(&[b"GET", b"d"]), b"$3\r\n\x9e\x9d\x9c\r\n".to_vec());
9800
9801 assert_eq!(f.run(&[b"BITOP", b"AND", b"d", b"a", b"gone"]), ":3\r\n");
9804 assert_eq!(f.run(&[b"GET", b"d"]), "$3\r\n\u{0}\u{0}\u{0}\r\n");
9805
9806 f.run(&[b"SET", b"dest", b"x"]);
9809 assert_eq!(f.run(&[b"BITOP", b"AND", b"dest", b"g1", b"g2"]), ":0\r\n");
9810 assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
9811 }
9812
9813 #[test]
9815 fn bitop_names_the_operation_in_its_own_complaints() {
9816 let mut f = Fixture::new();
9817 f.run(&[b"SET", b"a", b"abc"]);
9818 assert_eq!(
9819 f.run(&[b"BITOP", b"nope", b"d", b"a"]),
9820 "-ERR syntax error\r\n"
9821 );
9822 assert_eq!(
9823 f.run(&[b"BITOP", b"NOT", b"d", b"a", b"a"]),
9824 "-ERR BITOP NOT must be called with a single source key.\r\n"
9825 );
9826 for op in [&b"DIFF"[..], b"DIFF1", b"ANDOR"] {
9827 assert_eq!(
9828 f.run(&[b"BITOP", op, b"d", b"a"]),
9829 format!(
9830 "-ERR BITOP {} must be called with at least two source keys.\r\n",
9831 String::from_utf8_lossy(op)
9832 )
9833 );
9834 }
9835 f.run(&[b"LPUSH", b"l", b"x"]);
9836 assert_eq!(
9837 f.run(&[b"BITOP", b"AND", b"d", b"a", b"l"]),
9838 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
9839 );
9840 }
9841
9842 #[test]
9844 fn bitfield_reads_and_writes_packed_fields() {
9845 let mut f = Fixture::new();
9846 assert_eq!(f.run(&[b"BITFIELD", b"bf"]), "*0\r\n");
9847 assert_eq!(f.run(&[b"EXISTS", b"bf"]), ":0\r\n");
9848
9849 assert_eq!(
9850 f.run(&[
9851 b"BITFIELD",
9852 b"bf",
9853 b"INCRBY",
9854 b"u2",
9855 b"100",
9856 b"1",
9857 b"GET",
9858 b"u4",
9859 b"0"
9860 ]),
9861 "*2\r\n:1\r\n:0\r\n"
9862 );
9863 assert_eq!(f.run(&[b"STRLEN", b"bf"]), ":13\r\n");
9866
9867 assert_eq!(
9869 f.run(&[
9870 b"BITFIELD",
9871 b"bf",
9872 b"SET",
9873 b"u8",
9874 b"#0",
9875 b"255",
9876 b"GET",
9877 b"u8",
9878 b"#0"
9879 ]),
9880 "*2\r\n:0\r\n:255\r\n"
9881 );
9882
9883 assert_eq!(
9884 f.run(&[
9885 b"BITFIELD",
9886 b"bf",
9887 b"OVERFLOW",
9888 b"SAT",
9889 b"INCRBY",
9890 b"i8",
9891 b"0",
9892 b"120",
9893 b"INCRBY",
9894 b"i8",
9895 b"0",
9896 b"120"
9897 ]),
9898 "*2\r\n:119\r\n:127\r\n"
9899 );
9900 assert_eq!(
9901 f.run(&[
9902 b"BITFIELD",
9903 b"bf2",
9904 b"OVERFLOW",
9905 b"FAIL",
9906 b"INCRBY",
9907 b"u2",
9908 b"0",
9909 b"5"
9910 ]),
9911 "*1\r\n$-1\r\n"
9912 );
9913 assert_eq!(
9914 f.run(&[
9915 b"BITFIELD",
9916 b"bf3",
9917 b"OVERFLOW",
9918 b"WRAP",
9919 b"INCRBY",
9920 b"u2",
9921 b"0",
9922 b"5"
9923 ]),
9924 "*1\r\n:1\r\n"
9925 );
9926 assert_eq!(
9927 f.run(&[b"BITFIELD", b"bf3", b"GET", b"i64", b"0"]),
9928 "*1\r\n:4611686018427387904\r\n"
9929 );
9930 }
9931
9932 #[test]
9938 fn a_bad_bitfield_subcommand_leaves_the_key_alone() {
9939 let mut f = Fixture::new();
9940 let bad_type = "-ERR Invalid bitfield type. Use something like i16 u8. Note that u64 is not supported but i64 is.\r\n";
9941 assert_eq!(
9942 f.run(&[
9943 b"BITFIELD",
9944 b"bad",
9945 b"SET",
9946 b"u8",
9947 b"0",
9948 b"1",
9949 b"GET",
9950 b"u99",
9951 b"0"
9952 ]),
9953 bad_type
9954 );
9955 assert_eq!(f.run(&[b"EXISTS", b"bad"]), ":0\r\n");
9956 assert_eq!(
9957 f.run(&[b"BITFIELD", b"bad", b"GET", b"u64", b"0"]),
9958 bad_type
9959 );
9960 assert_eq!(
9961 f.run(&[b"BITFIELD", b"bad", b"GET"]),
9962 "-ERR syntax error\r\n"
9963 );
9964 assert_eq!(
9965 f.run(&[b"BITFIELD", b"bad", b"NOPE", b"u8", b"0"]),
9966 "-ERR syntax error\r\n"
9967 );
9968 assert_eq!(
9969 f.run(&[b"BITFIELD", b"bad", b"OVERFLOW"]),
9970 "-ERR syntax error\r\n"
9971 );
9972 assert_eq!(
9973 f.run(&[
9974 b"BITFIELD",
9975 b"bad",
9976 b"OVERFLOW",
9977 b"NOPE",
9978 b"GET",
9979 b"u8",
9980 b"0"
9981 ]),
9982 "-ERR Invalid OVERFLOW type specified\r\n"
9983 );
9984 assert_eq!(
9985 f.run(&[b"BITFIELD", b"bad", b"SET", b"u8", b"0", b"notanum"]),
9986 "-ERR value is not an integer or out of range\r\n"
9987 );
9988 for at in [&b"#-1"[..], b"abc"] {
9989 assert_eq!(
9990 f.run(&[b"BITFIELD", b"bad", b"GET", b"u8", at]),
9991 "-ERR bit offset is not an integer or out of range\r\n"
9992 );
9993 }
9994 }
9995
9996 #[test]
9998 fn bitfield_ro_answers_gets_and_refuses_the_rest() {
9999 let mut f = Fixture::new();
10000 f.run(&[b"SET", b"n", b"123"]);
10001 assert_eq!(
10002 f.run(&[b"BITFIELD_RO", b"n", b"GET", b"u8", b"0"]),
10003 "*1\r\n:49\r\n"
10004 );
10005 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"n"]), "$3\r\nint\r\n");
10007
10008 assert_eq!(
10010 f.run(&[
10011 b"BITFIELD_RO",
10012 b"n",
10013 b"OVERFLOW",
10014 b"SAT",
10015 b"GET",
10016 b"u8",
10017 b"0"
10018 ]),
10019 "*1\r\n:49\r\n"
10020 );
10021 for sub in [&b"SET"[..], b"INCRBY"] {
10022 assert_eq!(
10023 f.run(&[b"BITFIELD_RO", b"n", sub, b"u8", b"0", b"1"]),
10024 "-ERR BITFIELD_RO only supports the GET subcommand\r\n"
10025 );
10026 }
10027
10028 assert_eq!(
10029 f.run(&[b"BITFIELD_RO", b"gone", b"GET", b"u8", b"100"]),
10030 "*1\r\n:0\r\n"
10031 );
10032 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
10033 }
10034
10035 #[test]
10037 fn an_offset_off_the_end_of_the_world_is_refused() {
10038 let mut f = Fixture::new();
10039 let bad = "-ERR bit offset is not an integer or out of range\r\n";
10040 for arg in [&b"abc"[..], b"-1", b"4294967296"] {
10041 assert_eq!(f.run(&[b"SETBIT", b"k", arg, b"1"]), bad);
10042 assert_eq!(f.run(&[b"GETBIT", b"k", arg]), bad);
10043 }
10044 for arg in [&b"2"[..], b"-1"] {
10045 assert_eq!(
10046 f.run(&[b"BITPOS", b"k", arg]),
10047 "-ERR The bit argument must be 1 or 0.\r\n"
10048 );
10049 }
10050 assert_eq!(
10051 f.run(&[b"BITPOS", b"k", b"abc"]),
10052 "-ERR value is not an integer or out of range\r\n"
10053 );
10054 assert_eq!(
10055 f.run(&[b"BITPOS", b"k", b"0", b"5", b"BIT"]),
10056 "-ERR value is not an integer or out of range\r\n"
10057 );
10058 let bad_bit = "-ERR bit is not an integer or out of range\r\n";
10059 assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"2"]), bad_bit);
10060 assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"abc"]), bad_bit);
10061 }
10062
10063 #[test]
10065 fn every_bitmap_command_says_wrongtype() {
10066 let mut f = Fixture::new();
10067 f.run(&[b"LPUSH", b"l", b"x"]);
10068 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10069 let cases: &[&[&[u8]]] = &[
10070 &[b"SETBIT", b"l", b"0", b"1"],
10071 &[b"GETBIT", b"l", b"0"],
10072 &[b"BITCOUNT", b"l"],
10073 &[b"BITPOS", b"l", b"1"],
10074 &[b"BITOP", b"AND", b"d", b"l"],
10075 &[b"BITFIELD", b"l", b"GET", b"u8", b"0"],
10076 &[b"BITFIELD_RO", b"l", b"GET", b"u8", b"0"],
10077 ];
10078 for case in cases {
10079 assert_eq!(f.run(case), wrong, "{:?}", case[0]);
10080 }
10081 }
10082
10083 #[test]
10086 fn a_sketch_is_added_to_and_counted() {
10087 let mut f = Fixture::new();
10088 assert_eq!(f.run(&[b"PFADD", b"h"]), ":1\r\n");
10090 assert_eq!(f.run(&[b"PFADD", b"h"]), ":0\r\n");
10091 assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":0\r\n");
10092 assert_eq!(f.run(&[b"STRLEN", b"h"]), ":18\r\n");
10093 assert_eq!(f.run(&[b"TYPE", b"h"]), "+string\r\n");
10096 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$3\r\nraw\r\n");
10097
10098 assert_eq!(f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]), ":1\r\n");
10099 assert_eq!(f.run(&[b"PFADD", b"h", b"a"]), ":0\r\n");
10100 assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
10101 }
10102
10103 #[test]
10104 fn the_bytes_of_a_sketch_are_the_ones_a_real_server_writes() {
10105 let mut f = Fixture::new();
10106 f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
10107 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";
10109 let mut reply = b"$27\r\n".to_vec();
10110 reply.extend_from_slice(want);
10111 reply.extend_from_slice(b"\r\n");
10112 assert_eq!(f.raw(&[b"GET", b"h"]), reply);
10113 }
10114
10115 #[test]
10116 fn counting_several_keys_counts_their_union() {
10117 let mut f = Fixture::new();
10118 f.run(&[b"PFADD", b"a", b"x", b"y"]);
10119 f.run(&[b"PFADD", b"b", b"y", b"z"]);
10120 assert_eq!(f.run(&[b"PFCOUNT", b"a"]), ":2\r\n");
10121 assert_eq!(f.run(&[b"PFCOUNT", b"a", b"b"]), ":3\r\n");
10122 assert_eq!(f.run(&[b"PFCOUNT", b"gone"]), ":0\r\n");
10125 assert_eq!(f.run(&[b"PFCOUNT", b"a", b"gone"]), ":2\r\n");
10126 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
10127 }
10128
10129 #[test]
10130 fn a_merge_keeps_what_the_destination_had() {
10131 let mut f = Fixture::new();
10132 f.run(&[b"PFADD", b"a", b"x", b"y"]);
10133 f.run(&[b"PFADD", b"b", b"z"]);
10134 assert_eq!(f.run(&[b"PFMERGE", b"d", b"a", b"b"]), "+OK\r\n");
10135 assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":3\r\n");
10136 f.run(&[b"PFADD", b"c", b"w"]);
10138 assert_eq!(f.run(&[b"PFMERGE", b"d", b"c"]), "+OK\r\n");
10139 assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":4\r\n");
10140 assert_eq!(f.run(&[b"PFMERGE", b"fresh"]), "+OK\r\n");
10143 assert_eq!(f.run(&[b"PFCOUNT", b"fresh"]), ":0\r\n");
10144 }
10145
10146 #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
10155 #[test]
10156 fn the_debug_forms_answer_four_different_shapes() {
10157 let mut f = Fixture::new();
10158 f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
10159 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+sparse\r\n");
10160 assert_eq!(
10161 f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
10162 "$44\r\nZ:8436 v:1,1 Z:4274 v:2,1 Z:3068 v:1,1 Z:603\r\n"
10163 );
10164 assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":1\r\n");
10165 assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":0\r\n");
10166 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+dense\r\n");
10167 assert_eq!(f.run(&[b"STRLEN", b"h"]), ":12304\r\n");
10168 assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
10169 assert_eq!(
10171 f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
10172 "-ERR HLL encoding is not sparse\r\n"
10173 );
10174
10175 let reply = f.run(&[b"PFDEBUG", b"GETREG", b"h"]);
10177 assert!(reply.starts_with("*16384\r\n"), "{}", &reply[..16]);
10178 assert_eq!(reply.matches(":0\r\n").count(), 16381);
10179 assert_eq!(reply.matches(":1\r\n").count(), 2);
10180 assert_eq!(reply.matches(":2\r\n").count(), 1);
10181
10182 assert_eq!(f.run(&[b"PFSELFTEST"]), "+OK\r\n");
10183 }
10184
10185 #[test]
10186 fn a_string_that_is_not_a_sketch_is_refused_with_its_own_sentence() {
10187 let mut f = Fixture::new();
10188 f.run(&[b"SET", b"plain", b"not a sketch"]);
10189 let not_hll = "-WRONGTYPE Key is not a valid HyperLogLog string value.\r\n";
10190 assert_eq!(f.run(&[b"PFADD", b"plain", b"a"]), not_hll);
10191 assert_eq!(f.run(&[b"PFCOUNT", b"plain"]), not_hll);
10192 assert_eq!(f.run(&[b"PFMERGE", b"plain"]), not_hll);
10193 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"plain"]), not_hll);
10194
10195 f.run(&[b"RPUSH", b"l", b"x"]);
10198 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10199 assert_eq!(f.run(&[b"PFADD", b"l", b"a"]), wrong);
10200 assert_eq!(f.run(&[b"PFCOUNT", b"l"]), wrong);
10201 assert_eq!(f.run(&[b"PFMERGE", b"dest", b"l"]), wrong);
10202 assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
10203 assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"l"]), wrong);
10204 }
10205
10206 #[test]
10207 fn pfdebug_has_its_own_complaints() {
10208 let mut f = Fixture::new();
10209 f.run(&[b"PFADD", b"h", b"a"]);
10210 assert_eq!(
10213 f.run(&[b"PFDEBUG", b"NOPE", b"h"]),
10214 "-ERR Unknown PFDEBUG subcommand 'NOPE'\r\n"
10215 );
10216 let gone = "-ERR The specified key does not exist\r\n";
10218 assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"missing"]), gone);
10219 assert_eq!(f.run(&[b"PFDEBUG", b"DECODE", b"missing"]), gone);
10220 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"missing"]), gone);
10221 assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"missing"]), gone);
10222 assert_eq!(
10223 f.run(&[b"PFDEBUG"]),
10224 "-ERR wrong number of arguments for 'pfdebug' command\r\n"
10225 );
10226 assert_eq!(
10227 f.run(&[b"PFSELFTEST", b"x"]),
10228 "-ERR wrong number of arguments for 'pfselftest' command\r\n"
10229 );
10230 }
10231
10232 #[test]
10233 fn a_sketch_whose_opcodes_do_not_add_up_says_so() {
10234 let mut f = Fixture::new();
10235 f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
10236 let reply = f.raw(&[b"GET", b"h"]);
10239 let short = reply[5..reply.len() - 3].to_vec();
10240 f.run(&[b"SET", b"h", &short]);
10241 assert_eq!(
10242 f.run(&[b"PFCOUNT", b"h"]),
10243 "-INVALIDOBJ Corrupted HLL object detected\r\n"
10244 );
10245 }
10246
10247 #[test]
10248 fn a_sketch_survives_a_dump_and_a_restore_in_both_encodings() {
10249 let mut f = Fixture::new();
10250 f.run(&[b"PFADD", b"small", b"a", b"b", b"c"]);
10253 if cfg!(miri) {
10260 f.run(&[b"PFADD", b"big", b"a", b"b", b"c"]);
10261 f.run(&[b"PFDEBUG", b"TODENSE", b"big"]);
10262 } else {
10263 for i in 0..10_000u32 {
10264 let ele = format!("e{i}");
10265 f.run(&[b"PFADD", b"big", ele.as_bytes()]);
10266 }
10267 }
10268 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"small"]), "+sparse\r\n");
10269 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"big"]), "+dense\r\n");
10270
10271 for key in [&b"small"[..], b"big"] {
10272 let mut copy = key.to_vec();
10273 copy.push(b'2');
10274 let bytes = payload(&f.raw(&[b"DUMP", key]));
10275 assert_eq!(f.run(&[b"RESTORE", ©, b"0", &bytes]), "+OK\r\n");
10276 assert_eq!(f.raw(&[b"GET", ©]), f.raw(&[b"GET", key]));
10279 assert_eq!(
10280 f.run(&[b"PFDEBUG", b"ENCODING", ©]),
10281 f.run(&[b"PFDEBUG", b"ENCODING", key])
10282 );
10283 assert_eq!(f.run(&[b"PFCOUNT", ©]), f.run(&[b"PFCOUNT", key]));
10284 }
10285 assert_eq!(f.run(&[b"PFCOUNT", b"small2"]), ":3\r\n");
10286 assert_eq!(f.run(&[b"STRLEN", b"big2"]), ":12304\r\n");
10287 }
10288
10289 fn bulk(s: &str) -> String {
10293 format!("${}\r\n{s}\r\n", s.len())
10294 }
10295
10296 fn bulks(parts: &[&str]) -> String {
10299 let mut s = format!("*{}\r\n", parts.len());
10300 for p in parts {
10301 s.push_str(&format!("${}\r\n{p}\r\n", p.len()));
10302 }
10303 s
10304 }
10305
10306 #[test]
10307 fn a_list_is_pushed_from_both_ends_and_the_left_one_reverses() {
10308 let mut f = Fixture::new();
10309 assert_eq!(f.run(&[b"LPUSH", b"k", b"a", b"b", b"c"]), ":3\r\n");
10313 assert_eq!(
10314 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10315 bulks(&["c", "b", "a"])
10316 );
10317 assert_eq!(f.run(&[b"RPUSH", b"k", b"d"]), ":4\r\n");
10318 assert_eq!(f.run(&[b"LLEN", b"k"]), ":4\r\n");
10319 assert_eq!(f.run(&[b"LPOP", b"k"]), "$1\r\nc\r\n");
10320 assert_eq!(f.run(&[b"RPOP", b"k"]), "$1\r\nd\r\n");
10321 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "a"]));
10322 assert_eq!(f.run(&[b"TYPE", b"k"]), "+list\r\n");
10323 }
10324
10325 #[test]
10326 fn the_x_pushes_refuse_to_bring_a_list_back_to_life() {
10327 let mut f = Fixture::new();
10328 assert_eq!(f.run(&[b"LPUSHX", b"k", b"a"]), ":0\r\n");
10329 assert_eq!(f.run(&[b"RPUSHX", b"k", b"a"]), ":0\r\n");
10330 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
10331 f.run(&[b"RPUSH", b"k", b"a"]);
10332 assert_eq!(f.run(&[b"LPUSHX", b"k", b"z"]), ":2\r\n");
10333 assert_eq!(f.run(&[b"RPUSHX", b"k", b"y"]), ":3\r\n");
10334 assert_eq!(
10335 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10336 bulks(&["z", "a", "y"])
10337 );
10338 }
10339
10340 #[test]
10343 fn an_empty_pop_is_a_different_nothing_with_a_count_and_without() {
10344 let mut f = Fixture::new();
10345 assert_eq!(f.run(&[b"LPOP", b"nope"]), "$-1\r\n");
10346 assert_eq!(f.run(&[b"LPOP", b"nope", b"2"]), "*-1\r\n");
10347 assert_eq!(f.run(&[b"RPOP", b"nope"]), "$-1\r\n");
10348 assert_eq!(f.run(&[b"RPOP", b"nope", b"2"]), "*-1\r\n");
10349 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
10350 assert_eq!(f.run(&[b"LPOP", b"k", b"0"]), "*0\r\n");
10353 assert_eq!(f.run(&[b"LPOP", b"k", b"1"]), bulks(&["a"]));
10354 assert_eq!(f.run(&[b"RPOP", b"k", b"9"]), bulks(&["c", "b"]));
10356 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
10357 }
10358
10359 #[test]
10360 fn a_pop_count_has_its_own_sentence_and_a_third_argument_is_an_arity_error() {
10361 let mut f = Fixture::new();
10362 f.run(&[b"RPUSH", b"k", b"a"]);
10363 let range = "-ERR value is out of range, must be positive\r\n";
10364 assert_eq!(f.run(&[b"LPOP", b"k", b"-1"]), range);
10365 assert_eq!(f.run(&[b"LPOP", b"k", b"abc"]), range);
10366 assert_eq!(f.run(&[b"RPOP", b"k", b"-1"]), range);
10367 assert_eq!(
10370 f.run(&[b"LPOP", b"k", b"1", b"2"]),
10371 "-ERR wrong number of arguments for 'lpop' command\r\n"
10372 );
10373 assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
10374 }
10375
10376 #[test]
10377 fn a_range_takes_negative_ends_and_clamps_the_ones_that_run_off() {
10378 let mut f = Fixture::new();
10379 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
10380 assert_eq!(
10381 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10382 bulks(&["a", "b", "c"])
10383 );
10384 assert_eq!(f.run(&[b"LRANGE", b"k", b"-2", b"-1"]), bulks(&["b", "c"]));
10385 assert_eq!(f.run(&[b"LRANGE", b"k", b"1", b"1"]), bulks(&["b"]));
10386 assert_eq!(f.run(&[b"LRANGE", b"k", b"5", b"10"]), "*0\r\n");
10387 assert_eq!(f.run(&[b"LRANGE", b"k", b"2", b"1"]), "*0\r\n");
10388 assert_eq!(
10389 f.run(&[b"LRANGE", b"k", b"-100", b"100"]),
10390 bulks(&["a", "b", "c"])
10391 );
10392 assert_eq!(f.run(&[b"LRANGE", b"nope", b"0", b"-1"]), "*0\r\n");
10395 assert_eq!(
10396 f.run(&[b"LRANGE", b"k", b"a", b"b"]),
10397 "-ERR value is not an integer or out of range\r\n"
10398 );
10399 }
10400
10401 #[test]
10402 fn an_index_reads_and_writes_from_whichever_end_is_nearer() {
10403 let mut f = Fixture::new();
10404 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
10405 assert_eq!(f.run(&[b"LINDEX", b"k", b"0"]), "$1\r\na\r\n");
10406 assert_eq!(f.run(&[b"LINDEX", b"k", b"-1"]), "$1\r\nc\r\n");
10407 assert_eq!(f.run(&[b"LINDEX", b"k", b"99"]), "$-1\r\n");
10408 assert_eq!(f.run(&[b"LINDEX", b"nope", b"0"]), "$-1\r\n");
10409 assert_eq!(f.run(&[b"LSET", b"k", b"-1", b"z"]), "+OK\r\n");
10410 assert_eq!(
10411 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10412 bulks(&["a", "b", "z"])
10413 );
10414 assert_eq!(
10417 f.run(&[b"LSET", b"k", b"99", b"z"]),
10418 "-ERR index out of range\r\n"
10419 );
10420 assert_eq!(
10421 f.run(&[b"LSET", b"nope", b"0", b"z"]),
10422 "-ERR no such key\r\n"
10423 );
10424 }
10425
10426 #[test]
10427 fn linsert_says_three_things_with_one_signed_number() {
10428 let mut f = Fixture::new();
10429 assert_eq!(
10432 f.run(&[b"LINSERT", b"nope", b"BEFORE", b"a", b"x"]),
10433 ":0\r\n"
10434 );
10435 f.run(&[b"RPUSH", b"k", b"a", b"b"]);
10436 assert_eq!(f.run(&[b"LINSERT", b"k", b"before", b"a", b"X"]), ":3\r\n");
10437 assert_eq!(f.run(&[b"LINSERT", b"k", b"AFTER", b"b", b"Y"]), ":4\r\n");
10438 assert_eq!(
10439 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10440 bulks(&["X", "a", "b", "Y"])
10441 );
10442 assert_eq!(
10443 f.run(&[b"LINSERT", b"k", b"BEFORE", b"zz", b"x"]),
10444 ":-1\r\n"
10445 );
10446 assert_eq!(
10447 f.run(&[b"LINSERT", b"k", b"SIDEWAYS", b"a", b"x"]),
10448 "-ERR syntax error\r\n"
10449 );
10450 }
10451
10452 #[test]
10453 fn lrem_counts_in_three_directions_and_takes_the_key_when_it_empties() {
10454 let mut f = Fixture::new();
10455 f.run(&[b"RPUSH", b"k", b"a", b"b", b"a", b"c", b"a"]);
10456 assert_eq!(f.run(&[b"LREM", b"k", b"2", b"a"]), ":2\r\n");
10457 assert_eq!(
10458 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10459 bulks(&["b", "c", "a"])
10460 );
10461 assert_eq!(f.run(&[b"LREM", b"k", b"-1", b"a"]), ":1\r\n");
10462 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
10463 assert_eq!(f.run(&[b"LREM", b"k", b"0", b"b"]), ":1\r\n");
10464 assert_eq!(f.run(&[b"LREM", b"k", b"0", b"c"]), ":1\r\n");
10465 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
10466 assert_eq!(f.run(&[b"LREM", b"nope", b"0", b"a"]), ":0\r\n");
10467 }
10468
10469 #[test]
10470 fn ltrim_keeps_a_window_and_an_empty_one_deletes_the_key() {
10471 let mut f = Fixture::new();
10472 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c", b"d"]);
10473 assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"-2"]), "+OK\r\n");
10474 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
10475 assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"0"]), "+OK\r\n");
10478 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
10479 assert_eq!(f.run(&[b"LTRIM", b"nope", b"0", b"-1"]), "+OK\r\n");
10480 }
10481
10482 #[test]
10483 fn lpos_walks_from_either_end_and_stops_where_it_is_told() {
10484 let mut f = Fixture::new();
10485 f.run(&[b"RPUSH", b"p", b"a", b"b", b"c", b"a", b"b", b"c", b"a"]);
10486 assert_eq!(f.run(&[b"LPOS", b"p", b"a"]), ":0\r\n");
10487 assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1"]), ":6\r\n");
10488 assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"2"]), ":3\r\n");
10489 assert_eq!(
10490 f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"2"]),
10491 "*2\r\n:0\r\n:3\r\n"
10492 );
10493 assert_eq!(
10494 f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1", b"COUNT", b"0"]),
10495 "*3\r\n:6\r\n:3\r\n:0\r\n"
10496 );
10497 assert_eq!(
10500 f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"0", b"MAXLEN", b"3"]),
10501 "*1\r\n:0\r\n"
10502 );
10503 assert_eq!(f.run(&[b"LPOS", b"p", b"zz"]), "$-1\r\n");
10506 assert_eq!(f.run(&[b"LPOS", b"p", b"zz", b"COUNT", b"0"]), "*0\r\n");
10507 assert_eq!(f.run(&[b"LPOS", b"nope", b"a"]), "$-1\r\n");
10508 assert_eq!(f.run(&[b"LPOS", b"nope", b"a", b"COUNT", b"2"]), "*0\r\n");
10509 }
10510
10511 #[test]
10512 fn lpos_words_its_three_mistakes_the_way_redis_does() {
10513 let mut f = Fixture::new();
10514 f.run(&[b"RPUSH", b"p", b"a"]);
10515 assert_eq!(
10518 f.run(&[b"LPOS", b"p", b"a", b"RANK", b"0"]),
10519 "-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"
10520 );
10521 assert_eq!(
10522 f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"-1"]),
10523 "-ERR COUNT can't be negative\r\n"
10524 );
10525 assert_eq!(
10526 f.run(&[b"LPOS", b"p", b"a", b"MAXLEN", b"-1"]),
10527 "-ERR MAXLEN can't be negative\r\n"
10528 );
10529 assert_eq!(
10530 f.run(&[b"LPOS", b"p", b"a", b"RANK"]),
10531 "-ERR syntax error\r\n"
10532 );
10533 assert_eq!(
10534 f.run(&[b"LPOS", b"p", b"a", b"FOO", b"1"]),
10535 "-ERR syntax error\r\n"
10536 );
10537 }
10538
10539 #[test]
10540 fn a_move_takes_from_one_end_and_gives_to_another_even_on_one_key() {
10541 let mut f = Fixture::new();
10542 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
10543 assert_eq!(f.run(&[b"RPOPLPUSH", b"k", b"d"]), "$1\r\nc\r\n");
10544 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
10545 assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c"]));
10546 assert_eq!(
10547 f.run(&[b"LMOVE", b"k", b"d", b"LEFT", b"RIGHT"]),
10548 "$1\r\na\r\n"
10549 );
10550 assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c", "a"]));
10551 f.run(&[b"DEL", b"r"]);
10554 f.run(&[b"RPUSH", b"r", b"1", b"2", b"3"]);
10555 assert_eq!(f.run(&[b"RPOPLPUSH", b"r", b"r"]), "$1\r\n3\r\n");
10556 assert_eq!(
10557 f.run(&[b"LRANGE", b"r", b"0", b"-1"]),
10558 bulks(&["3", "1", "2"])
10559 );
10560 assert_eq!(
10561 f.run(&[b"LMOVE", b"nope", b"d", b"LEFT", b"LEFT"]),
10562 "$-1\r\n"
10563 );
10564 assert_eq!(
10565 f.run(&[b"LMOVE", b"r", b"d", b"LEFT", b"SIDEWAYS"]),
10566 "-ERR syntax error\r\n"
10567 );
10568 }
10569
10570 #[test]
10571 fn a_move_checks_the_destination_before_it_takes_anything() {
10572 let mut f = Fixture::new();
10573 f.run(&[b"RPUSH", b"k", b"a", b"b"]);
10574 f.run(&[b"SET", b"str", b"v"]);
10575 assert_eq!(
10576 f.run(&[b"LMOVE", b"k", b"str", b"LEFT", b"LEFT"]),
10577 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
10578 );
10579 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
10581 }
10582
10583 #[test]
10584 fn a_block_move_orders_the_block_by_the_ends_and_the_ordering_word() {
10585 for (from, to, order, want) in [
10589 ("LEFT", "RIGHT", "OBO", ["a", "b"]),
10590 ("LEFT", "RIGHT", "BULK", ["a", "b"]),
10591 ("LEFT", "LEFT", "OBO", ["b", "a"]),
10592 ("LEFT", "LEFT", "BULK", ["a", "b"]),
10593 ("RIGHT", "LEFT", "OBO", ["d", "e"]),
10594 ("RIGHT", "LEFT", "BULK", ["d", "e"]),
10595 ("RIGHT", "RIGHT", "OBO", ["e", "d"]),
10596 ("RIGHT", "RIGHT", "BULK", ["d", "e"]),
10597 ] {
10598 let mut f = Fixture::new();
10599 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c", b"d", b"e"]);
10600 let how = format!("{from} {to} {order}");
10601 let reply = f.run(&[
10602 b"LMOVEM",
10603 b"s",
10604 b"d",
10605 from.as_bytes(),
10606 to.as_bytes(),
10607 b"COUNT",
10608 b"2",
10609 order.as_bytes(),
10610 ]);
10611 assert_eq!(reply, bulks(&want), "the reply for {how}");
10612 assert_eq!(
10613 f.run(&[b"LRANGE", b"d", b"0", b"-1"]),
10614 bulks(&want),
10615 "the destination for {how}"
10616 );
10617 }
10618 }
10619
10620 #[test]
10621 fn a_block_move_of_one_needs_no_count_at_all() {
10622 let mut f = Fixture::new();
10623 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
10624 assert_eq!(
10625 f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT"]),
10626 bulks(&["a"])
10627 );
10628 assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["b", "c"]));
10629 assert_eq!(
10632 f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT"]),
10633 "-ERR syntax error\r\n"
10634 );
10635 assert_eq!(
10636 f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"2"]),
10637 "-ERR syntax error\r\n"
10638 );
10639 }
10640
10641 #[test]
10642 fn a_block_move_with_exactly_takes_all_of_them_or_none() {
10643 let mut f = Fixture::new();
10644 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
10645 assert_eq!(
10649 f.run(&[
10650 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"EXACTLY", b"99", b"BULK"
10651 ]),
10652 "*-1\r\n"
10653 );
10654 assert_eq!(
10655 f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
10656 bulks(&["a", "b", "c"])
10657 );
10658 assert_eq!(
10660 f.run(&[
10661 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"99", b"BULK"
10662 ]),
10663 bulks(&["a", "b", "c"])
10664 );
10665 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
10666 assert_eq!(
10667 f.run(&[
10668 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
10669 ]),
10670 "*-1\r\n"
10671 );
10672 }
10673
10674 #[test]
10675 fn a_block_move_onto_itself_rotates_by_the_count() {
10676 let mut f = Fixture::new();
10677 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
10678 assert_eq!(
10679 f.run(&[
10680 b"LMOVEM", b"s", b"s", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
10681 ]),
10682 bulks(&["a", "b"])
10683 );
10684 assert_eq!(
10685 f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
10686 bulks(&["c", "a", "b"])
10687 );
10688 }
10689
10690 #[test]
10691 fn a_block_move_reads_the_count_before_the_ordering_word() {
10692 let mut f = Fixture::new();
10693 f.run(&[b"RPUSH", b"s", b"a", b"b"]);
10694 f.run(&[b"SET", b"str", b"v"]);
10695 let count = "-ERR count should be greater than 0\r\n";
10696 assert_eq!(
10697 f.run(&[
10698 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"NOPE"
10699 ]),
10700 count
10701 );
10702 assert_eq!(
10703 f.run(&[
10704 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"0", b"BULK"
10705 ]),
10706 count
10707 );
10708 assert_eq!(
10709 f.run(&[
10710 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"NOPE"
10711 ]),
10712 "-ERR syntax error\r\n"
10713 );
10714 assert_eq!(
10715 f.run(&[
10716 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"NOPE", b"abc", b"BULK"
10717 ]),
10718 "-ERR syntax error\r\n"
10719 );
10720 assert_eq!(
10723 f.run(&[
10724 b"LMOVEM", b"str", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"BULK"
10725 ]),
10726 count
10727 );
10728 assert_eq!(
10729 f.run(&[
10730 b"LMOVEM", b"s", b"str", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
10731 ]),
10732 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
10733 );
10734 assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["a", "b"]));
10735 }
10736
10737 #[test]
10738 fn lmpop_answers_from_the_first_key_that_has_anything() {
10739 let mut f = Fixture::new();
10740 f.run(&[b"RPUSH", b"b", b"1", b"2", b"3"]);
10741 assert_eq!(
10744 f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT", b"COUNT", b"2"]),
10745 "*2\r\n$1\r\nb\r\n*2\r\n$1\r\n1\r\n$1\r\n2\r\n"
10746 );
10747 assert_eq!(
10748 f.run(&[b"LMPOP", b"2", b"a", b"b", b"RIGHT"]),
10749 "*2\r\n$1\r\nb\r\n*1\r\n$1\r\n3\r\n"
10750 );
10751 assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
10752 assert_eq!(f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT"]), "*-1\r\n");
10755 }
10756
10757 #[test]
10758 fn lmpop_has_its_own_words_for_a_count_and_for_a_key_count() {
10759 let mut f = Fixture::new();
10760 f.run(&[b"RPUSH", b"k", b"a"]);
10761 assert_eq!(
10762 f.run(&[b"LMPOP", b"0", b"k", b"LEFT"]),
10763 "-ERR numkeys should be greater than 0\r\n"
10764 );
10765 assert_eq!(
10766 f.run(&[b"LMPOP", b"-1", b"k", b"LEFT"]),
10767 "-ERR numkeys should be greater than 0\r\n"
10768 );
10769 assert_eq!(
10770 f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"0"]),
10771 "-ERR count should be greater than 0\r\n"
10772 );
10773 assert_eq!(
10776 f.run(&[b"LMPOP", b"3", b"k", b"LEFT"]),
10777 "-ERR syntax error\r\n"
10778 );
10779 assert_eq!(
10780 f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"1", b"x"]),
10781 "-ERR syntax error\r\n"
10782 );
10783 assert_eq!(
10784 f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"FOO", b"1"]),
10785 "-ERR syntax error\r\n"
10786 );
10787 assert_eq!(
10788 f.run(&[b"LMPOP", b"1", b"k", b"SIDEWAYS"]),
10789 "-ERR syntax error\r\n"
10790 );
10791 assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
10792 }
10793
10794 #[test]
10795 fn every_list_command_says_wrongtype_and_writes_nothing() {
10796 let mut f = Fixture::new();
10797 f.run(&[b"SET", b"str", b"v"]);
10798 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10799 for cmd in [
10800 &[b"LPUSH".as_slice(), b"str", b"a"][..],
10801 &[b"RPUSH", b"str", b"a"],
10802 &[b"LPUSHX", b"str", b"a"],
10803 &[b"RPUSHX", b"str", b"a"],
10804 &[b"LPOP", b"str"],
10805 &[b"LPOP", b"str", b"2"],
10806 &[b"RPOP", b"str"],
10807 &[b"LLEN", b"str"],
10808 &[b"LRANGE", b"str", b"0", b"-1"],
10809 &[b"LINDEX", b"str", b"0"],
10810 &[b"LSET", b"str", b"0", b"a"],
10811 &[b"LINSERT", b"str", b"BEFORE", b"a", b"b"],
10812 &[b"LREM", b"str", b"0", b"a"],
10813 &[b"LTRIM", b"str", b"0", b"-1"],
10814 &[b"LPOS", b"str", b"a"],
10815 &[b"LPOS", b"str", b"a", b"COUNT", b"0"],
10816 &[b"RPOPLPUSH", b"str", b"d"],
10817 &[b"LMOVE", b"str", b"d", b"LEFT", b"LEFT"],
10818 &[b"LMPOP", b"1", b"str", b"LEFT"],
10819 ] {
10820 assert_eq!(f.run(cmd), wrong, "{:?}", String::from_utf8_lossy(cmd[0]));
10821 }
10822 assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
10823 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
10824 }
10825
10826 #[test]
10830 fn a_timeout_has_three_ways_of_being_wrong() {
10831 let mut f = Fixture::new();
10832 let not_float = "-ERR timeout is not a float or out of range\r\n";
10833 let range = "-ERR timeout is out of range\r\n";
10834 for (bad, want) in [
10835 (&[b"BLPOP".as_slice(), b"k", b"abc"][..], not_float),
10836 (&[b"BLPOP", b"k", b"nan"], not_float),
10837 (&[b"BLPOP", b"k", b""], not_float),
10838 (&[b"BLPOP", b"k", b" 1"], not_float),
10841 (&[b"BLPOP", b"k", b"1 "], not_float),
10842 (&[b"BLPOP", b"k", b"-1"], "-ERR timeout is negative\r\n"),
10843 (&[b"BLPOP", b"k", b"-0.1"], "-ERR timeout is negative\r\n"),
10844 (&[b"BLPOP", b"k", b"1e400"], range),
10847 (&[b"BLPOP", b"k", b"inf"], range),
10848 (&[b"BLPOP", b"k", b"9999999999999999"], range),
10849 (&[b"BRPOP", b"k", b"abc"], not_float),
10850 (
10851 &[b"BLMOVE", b"a", b"b", b"LEFT", b"RIGHT", b"abc"],
10852 not_float,
10853 ),
10854 (
10855 &[b"BRPOPLPUSH", b"a", b"b", b"-1"],
10856 "-ERR timeout is negative\r\n",
10857 ),
10858 (&[b"BLMPOP", b"abc", b"1", b"k", b"LEFT"], not_float),
10859 ] {
10860 assert_eq!(f.run(bad), want, "for {bad:?}");
10861 }
10862 }
10863
10864 #[test]
10867 fn a_zero_timeout_waits_and_the_smallest_positive_one_does_not() {
10868 let mut f = Fixture::new();
10869 for timeout in [b"0".as_slice(), b"0.0", b"-0.0"] {
10870 let (flow, out) = f.flow(&[b"BLPOP", b"k", timeout]);
10871 assert_eq!(flow, Flow::Block, "for {timeout:?}");
10872 assert!(out.is_empty(), "for {timeout:?}");
10873 }
10874 let (flow, out) = f.flow(&[b"BLPOP", b"k", b"0.0000001"]);
10878 assert_eq!(flow, Flow::Block);
10879 assert!(out.is_empty());
10880 }
10881
10882 #[test]
10883 fn a_blocking_command_that_can_be_answered_answers_like_the_one_it_wraps() {
10884 let mut f = Fixture::new();
10885 f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
10886
10887 assert_eq!(
10890 f.flow(&[b"BLPOP", b"nope", b"L", b"0"]),
10891 (Flow::Continue, "*2\r\n$1\r\nL\r\n$1\r\na\r\n".to_owned())
10892 );
10893 assert_eq!(
10894 f.run(&[b"BRPOP", b"L", b"0"]),
10895 "*2\r\n$1\r\nL\r\n$1\r\ne\r\n"
10896 );
10897 assert_eq!(
10898 f.run(&[
10899 b"BLMPOP", b"0", b"2", b"nope", b"L", b"LEFT", b"COUNT", b"2"
10900 ]),
10901 "*2\r\n$1\r\nL\r\n*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
10902 );
10903 assert_eq!(
10904 f.run(&[b"BLMOVE", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
10905 "$1\r\nd\r\n"
10906 );
10907 assert_eq!(
10908 f.run(&[b"EXISTS", b"L"]),
10909 ":0\r\n",
10910 "and the key went with it"
10911 );
10912 assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nd\r\n");
10913 f.run(&[b"RPUSH", b"D", b"x"]);
10916 assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"D", b"0"]), "$1\r\nx\r\n");
10917 assert_eq!(
10918 f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
10919 "*2\r\n$1\r\nx\r\n$1\r\nd\r\n"
10920 );
10921 }
10922
10923 #[test]
10924 fn blmpop_reads_its_count_and_its_key_count_the_way_lmpop_does() {
10925 let mut f = Fixture::new();
10926 f.run(&[b"RPUSH", b"k", b"a"]);
10927 for (bad, want) in [
10928 (
10929 &[b"BLMPOP".as_slice(), b"0", b"0", b"k", b"LEFT"][..],
10930 "-ERR numkeys should be greater than 0\r\n",
10931 ),
10932 (
10933 &[b"BLMPOP", b"0", b"-1", b"k", b"LEFT"],
10934 "-ERR numkeys should be greater than 0\r\n",
10935 ),
10936 (
10939 &[b"BLMPOP", b"0", b"2", b"k", b"LEFT"],
10940 "-ERR syntax error\r\n",
10941 ),
10942 (
10943 &[b"BLMPOP", b"0", b"1", b"k", b"SIDEWAYS"],
10944 "-ERR syntax error\r\n",
10945 ),
10946 (
10947 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT"],
10948 "-ERR syntax error\r\n",
10949 ),
10950 (
10951 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"2", b"x"],
10952 "-ERR syntax error\r\n",
10953 ),
10954 (
10957 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"0"],
10958 "-ERR count should be greater than 0\r\n",
10959 ),
10960 (
10961 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"abc"],
10962 "-ERR count should be greater than 0\r\n",
10963 ),
10964 ] {
10965 assert_eq!(f.run(bad), want, "for {bad:?}");
10966 }
10967 assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n", "and none of them popped");
10968 }
10969
10970 #[test]
10971 fn a_blocking_move_reads_its_directions_before_its_timeout() {
10972 let mut f = Fixture::new();
10973 assert_eq!(
10976 f.run(&[b"BLMOVE", b"a", b"b", b"UP", b"DOWN", b"abc"]),
10977 "-ERR syntax error\r\n"
10978 );
10979 assert_eq!(
10980 f.run(&[b"BLMOVE", b"a", b"b", b"LEFT", b"DOWN", b"0.05"]),
10981 "-ERR syntax error\r\n"
10982 );
10983 }
10984
10985 #[test]
10989 fn a_blocking_block_move_that_can_be_answered_answers_like_lmovem() {
10990 let mut f = Fixture::new();
10991 f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
10992 assert_eq!(
10993 f.flow(&[b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
10994 (Flow::Continue, "*1\r\n$1\r\na\r\n".to_owned())
10995 );
10996 assert_eq!(
10997 f.run(&[
10998 b"BLMOVEM", b"L", b"D", b"RIGHT", b"RIGHT", b"0", b"COUNT", b"2", b"OBO"
10999 ]),
11000 bulks(&["e", "d"])
11001 );
11002 assert_eq!(
11003 f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
11004 bulks(&["a", "e", "d"])
11005 );
11006 assert_eq!(
11008 f.run(&[
11009 b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"2", b"BULK"
11010 ]),
11011 bulks(&["b", "c"])
11012 );
11013 assert_eq!(f.run(&[b"EXISTS", b"L"]), ":0\r\n", "and the key went");
11014 }
11015
11016 #[test]
11020 fn a_blocking_block_move_waits_for_the_whole_block_only_under_exactly() {
11021 let mut f = Fixture::new();
11022 f.run(&[b"RPUSH", b"L", b"a", b"b"]);
11023 assert_eq!(
11025 f.flow(&[
11026 b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"COUNT", b"3", b"BULK"
11027 ]),
11028 (Flow::Continue, bulks(&["a", "b"]))
11029 );
11030
11031 f.run(&[b"RPUSH", b"L", b"a", b"b"]);
11032 assert_eq!(
11035 f.flow(&[
11036 b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"3", b"BULK"
11037 ])
11038 .0,
11039 Flow::Block
11040 );
11041 assert_eq!(f.run(&[b"LRANGE", b"L", b"0", b"-1"]), bulks(&["a", "b"]));
11042 }
11043
11044 #[test]
11045 fn a_blocking_block_move_reads_its_directions_then_its_timeout_then_its_count() {
11046 let mut f = Fixture::new();
11047 let syntax = "-ERR syntax error\r\n";
11048 assert_eq!(
11050 f.run(&[
11051 b"BLMOVEM", b"a", b"b", b"UP", b"DOWN", b"abc", b"NOPE", b"x", b"y"
11052 ]),
11053 syntax
11054 );
11055 assert_eq!(
11057 f.run(&[
11058 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"abc", b"COUNT", b"abc", b"BULK"
11059 ]),
11060 "-ERR timeout is not a float or out of range\r\n"
11061 );
11062 assert_eq!(
11063 f.run(&[
11064 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"-1", b"COUNT", b"1", b"BULK"
11065 ]),
11066 "-ERR timeout is negative\r\n"
11067 );
11068 assert_eq!(
11070 f.run(&[
11071 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"abc", b"NOPE"
11072 ]),
11073 "-ERR count should be greater than 0\r\n"
11074 );
11075 assert_eq!(
11076 f.run(&[
11077 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"1", b"NOPE"
11078 ]),
11079 syntax
11080 );
11081 assert_eq!(
11084 f.run(&[b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT"]),
11085 syntax
11086 );
11087 assert_eq!(
11088 f.run(&[
11089 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"2"
11090 ]),
11091 syntax
11092 );
11093 }
11094
11095 #[test]
11098 fn a_blocking_command_errors_on_a_wrong_type_rather_than_waiting_on_it() {
11099 let mut f = Fixture::new();
11100 f.run(&[b"SET", b"S", b"v"]);
11101 f.run(&[b"RPUSH", b"D", b"x"]);
11102 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
11103
11104 assert_eq!(f.run(&[b"BLPOP", b"S", b"0"]), wrong);
11105 assert_eq!(f.run(&[b"BLPOP", b"E", b"S", b"0"]), wrong);
11108 assert_eq!(f.run(&[b"BRPOP", b"S", b"0"]), wrong);
11109 assert_eq!(f.run(&[b"BLMPOP", b"0", b"1", b"S", b"LEFT"]), wrong);
11110 assert_eq!(f.run(&[b"BRPOPLPUSH", b"S", b"D", b"0"]), wrong);
11111 assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"S", b"0"]), wrong);
11114 assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nx\r\n");
11115 assert_eq!(
11116 f.run(&[b"BLMOVEM", b"S", b"D", b"LEFT", b"RIGHT", b"0"]),
11117 wrong
11118 );
11119 assert_eq!(
11120 f.run(&[b"BLMOVEM", b"D", b"S", b"LEFT", b"RIGHT", b"0"]),
11121 wrong
11122 );
11123
11124 assert_eq!(
11128 f.flow(&[b"BLMOVE", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
11129 .0,
11130 Flow::Block
11131 );
11132 assert_eq!(
11136 f.flow(&[b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
11137 .0,
11138 Flow::Block
11139 );
11140 f.run(&[b"RPUSH", b"E", b"1", b"2"]);
11141 assert_eq!(
11142 f.flow(&[
11143 b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1", b"EXACTLY", b"3", b"BULK"
11144 ])
11145 .0,
11146 Flow::Block
11147 );
11148 }
11149
11150 #[cfg_attr(miri, ignore = "the volume is the claim")]
11155 #[test]
11156 fn churning_lists_does_not_grow_the_server() {
11157 let mut f = Fixture::new();
11158 let vals: Vec<Vec<u8>> = (0..200).map(|i| format!("v{i}").into_bytes()).collect();
11159 let args: Vec<&[u8]> = [&b"RPUSH"[..], &b"k"[..]]
11160 .into_iter()
11161 .chain(vals.iter().map(Vec::as_slice))
11162 .collect();
11163
11164 f.run(&args);
11165 f.run(&[b"DEL", b"k"]);
11166 f.server.compact_step();
11167 let after_first = f.server.memory_bytes();
11168
11169 for _ in 0..200 {
11170 f.run(&args);
11171 f.run(&[b"LTRIM", b"k", b"1", b"0"]);
11172 f.server.compact_step();
11173 }
11174 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
11175 assert!(
11176 f.server.memory_bytes() <= after_first * 2,
11177 "held {} after two hundred passes against {after_first} after one",
11178 f.server.memory_bytes()
11179 );
11180 }
11181
11182 #[test]
11185 fn a_sorted_set_takes_scores_and_gives_them_back() {
11186 let mut f = Fixture::new();
11187 assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]), ":2\r\n");
11188 assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]), ":1\r\n");
11189 assert_eq!(f.run(&[b"ZCARD", b"z"]), ":3\r\n");
11190 assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$1\r\n2\r\n");
11191 assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "$-1\r\n");
11192 assert_eq!(f.run(&[b"ZSCORE", b"nokey", b"b"]), "$-1\r\n");
11193 assert_eq!(
11194 f.run(&[b"ZMSCORE", b"z", b"a", b"nope", b"c"]),
11195 "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n"
11196 );
11197 assert_eq!(f.run(&[b"ZREM", b"z", b"a", b"nope"]), ":1\r\n");
11198 assert_eq!(f.run(&[b"ZCARD", b"z"]), ":2\r\n");
11199 assert_eq!(f.run(&[b"ZREM", b"z", b"b", b"c"]), ":2\r\n");
11201 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
11202 }
11203
11204 #[test]
11205 fn a_score_is_a_double_on_resp3_and_digits_on_resp2() {
11206 let mut f = Fixture::new();
11207 f.run(&[b"ZADD", b"z", b"1.5", b"a", b"inf", b"b", b"-inf", b"c"]);
11208 assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$3\r\n1.5\r\n");
11209 assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$3\r\ninf\r\n");
11210 assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), "$4\r\n-inf\r\n");
11211
11212 f.out = Out::new(Proto::Resp3);
11213 assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), ",1.5\r\n");
11214 assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), ",inf\r\n");
11215 assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), ",-inf\r\n");
11216 assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "_\r\n");
11217 }
11218
11219 #[test]
11220 fn the_zadd_options_gate_what_gets_written() {
11221 let mut f = Fixture::new();
11222 f.run(&[b"ZADD", b"z", b"5", b"a"]);
11223 assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"9", b"a"]), ":0\r\n");
11225 assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$1\r\n5\r\n");
11226 assert_eq!(f.run(&[b"ZADD", b"z", b"XX", b"9", b"new"]), ":0\r\n");
11227 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":1\r\n");
11228 assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"3", b"a"]), ":0\r\n");
11230 assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"7", b"a"]), ":1\r\n");
11231 assert_eq!(f.run(&[b"ZADD", b"z", b"LT", b"CH", b"9", b"a"]), ":0\r\n");
11232 assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"1", b"b"]), ":1\r\n");
11234 assert_eq!(
11235 f.run(&[b"ZADD", b"z", b"CH", b"2", b"a", b"2", b"c"]),
11236 ":2\r\n"
11237 );
11238 }
11239
11240 #[test]
11241 fn zadd_incr_answers_a_score_or_nothing_at_all() {
11242 let mut f = Fixture::new();
11243 assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"5", b"m"]), "$1\r\n5\r\n");
11244 assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"2", b"m"]), "$1\r\n7\r\n");
11245 assert_eq!(
11248 f.run(&[b"ZADD", b"z", b"NX", b"INCR", b"2", b"m"]),
11249 "$-1\r\n"
11250 );
11251 assert_eq!(
11252 f.run(&[b"ZADD", b"z", b"XX", b"INCR", b"2", b"gone"]),
11253 "$-1\r\n"
11254 );
11255 assert_eq!(
11256 f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"-1", b"m"]),
11257 "$-1\r\n"
11258 );
11259 assert_eq!(
11260 f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"1", b"m"]),
11261 "$1\r\n8\r\n"
11262 );
11263 assert_eq!(f.run(&[b"ZINCRBY", b"z", b"2", b"m"]), "$2\r\n10\r\n");
11264 assert_eq!(f.run(&[b"ZINCRBY", b"z", b"1", b"fresh"]), "$1\r\n1\r\n");
11265 }
11266
11267 #[test]
11268 fn the_two_infinities_will_not_be_added_together() {
11269 let mut f = Fixture::new();
11270 f.run(&[b"ZADD", b"z", b"inf", b"m"]);
11271 let nan = "-ERR resulting score is not a number (NaN)\r\n";
11272 assert_eq!(f.run(&[b"ZINCRBY", b"z", b"-inf", b"m"]), nan);
11273 assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"-inf", b"m"]), nan);
11274 assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\ninf\r\n");
11275 assert_eq!(f.run(&[b"ZINCRBY", b"gone", b"1", b"m"]), "$1\r\n1\r\n");
11277 }
11278
11279 #[test]
11280 fn zadd_says_its_mistakes_the_way_redis_says_them() {
11281 let mut f = Fixture::new();
11282 assert_eq!(
11285 f.run(&[b"ZADD", b"z", b"NX", b"XX"]),
11286 "-ERR syntax error\r\n"
11287 );
11288 assert_eq!(
11289 f.run(&[b"ZADD", b"z", b"NX", b"XX", b"1", b"a"]),
11290 "-ERR XX and NX options at the same time are not compatible\r\n"
11291 );
11292 let gtlt = "-ERR GT, LT, and/or NX options at the same time are not compatible\r\n";
11293 assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"GT", b"1", b"a"]), gtlt);
11294 assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"LT", b"1", b"a"]), gtlt);
11295 assert_eq!(
11296 f.run(&[b"ZADD", b"z", b"INCR", b"1", b"a", b"2", b"b"]),
11297 "-ERR INCR option supports a single increment-element pair\r\n"
11298 );
11299 assert_eq!(
11301 f.run(&[b"ZADD", b"z", b"1", b"a", b"2"]),
11302 "-ERR syntax error\r\n"
11303 );
11304 assert_eq!(
11306 f.run(&[b"ZADD", b"z", b"1", b"a", b"nonsense", b"b"]),
11307 "-ERR value is not a valid float\r\n"
11308 );
11309 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
11310 }
11311
11312 #[test]
11313 fn a_rank_says_where_a_member_sits_from_either_end() {
11314 let mut f = Fixture::new();
11315 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11316 assert_eq!(f.run(&[b"ZRANK", b"z", b"a"]), ":0\r\n");
11317 assert_eq!(f.run(&[b"ZRANK", b"z", b"c"]), ":2\r\n");
11318 assert_eq!(f.run(&[b"ZREVRANK", b"z", b"c"]), ":0\r\n");
11319 assert_eq!(f.run(&[b"ZREVRANK", b"z", b"a"]), ":2\r\n");
11320 assert_eq!(
11322 f.run(&[b"ZRANK", b"z", b"b", b"WITHSCORE"]),
11323 "*2\r\n:1\r\n$1\r\n2\r\n"
11324 );
11325 assert_eq!(f.run(&[b"ZRANK", b"z", b"nope"]), "$-1\r\n");
11326 assert_eq!(f.run(&[b"ZRANK", b"z", b"nope", b"WITHSCORE"]), "*-1\r\n");
11327 assert_eq!(f.run(&[b"ZRANK", b"nokey", b"a", b"WITHSCORE"]), "*-1\r\n");
11328 assert_eq!(
11331 f.run(&[b"ZRANK", b"z", b"b", b"bogus"]),
11332 "-ERR syntax error\r\n"
11333 );
11334 assert_eq!(
11335 f.run(&[b"ZREVRANK", b"z", b"b", b"WITHSCORE", b"more"]),
11336 "-ERR wrong number of arguments for 'zrevrank' command\r\n"
11337 );
11338 }
11339
11340 #[test]
11341 fn the_two_counts_read_their_two_kinds_of_bound() {
11342 let mut f = Fixture::new();
11343 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11344 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"-inf", b"+inf"]), ":3\r\n");
11345 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"2", b"3"]), ":2\r\n");
11346 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"3"]), ":2\r\n");
11347 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"(3"]), ":1\r\n");
11348 assert_eq!(f.run(&[b"ZCOUNT", b"nokey", b"-inf", b"+inf"]), ":0\r\n");
11349 assert_eq!(
11350 f.run(&[b"ZCOUNT", b"z", b"bogus", b"3"]),
11351 "-ERR min or max is not a float\r\n"
11352 );
11353
11354 f.run(&[b"ZADD", b"l", b"0", b"a", b"0", b"b", b"0", b"c"]);
11355 assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"-", b"+"]), ":3\r\n");
11356 assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"[a", b"(c"]), ":2\r\n");
11357 assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"(a", b"+"]), ":2\r\n");
11358 assert_eq!(
11361 f.run(&[b"ZLEXCOUNT", b"l", b"a", b"c"]),
11362 "-ERR min or max not valid string range item\r\n"
11363 );
11364 }
11365
11366 #[test]
11372 fn one_range_command_selects_by_rank_or_score_or_name() {
11373 let mut f = Fixture::new();
11374 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11375 assert_eq!(
11376 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
11377 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
11378 );
11379 assert_eq!(
11380 f.run(&[b"ZRANGE", b"z", b"-2", b"-1"]),
11381 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11382 );
11383 assert_eq!(f.run(&[b"ZRANGE", b"z", b"5", b"9"]), "*0\r\n");
11384 assert_eq!(f.run(&[b"ZRANGE", b"nokey", b"0", b"-1"]), "*0\r\n");
11385 assert_eq!(
11388 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"REV"]),
11389 "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
11390 );
11391 assert_eq!(
11392 f.run(&[b"ZRANGE", b"z", b"(1", b"+inf", b"BYSCORE"]),
11393 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11394 );
11395 assert_eq!(
11398 f.run(&[b"ZRANGE", b"z", b"+inf", b"(1", b"BYSCORE", b"REV"]),
11399 "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
11400 );
11401 assert_eq!(
11402 f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX"]),
11403 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
11404 );
11405 assert_eq!(
11406 f.run(&[b"ZRANGE", b"z", b"+", b"-", b"BYLEX", b"REV"]),
11407 "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
11408 );
11409 }
11410
11411 #[test]
11414 fn the_older_range_spellings_name_their_high_end_first() {
11415 let mut f = Fixture::new();
11416 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11417 assert_eq!(
11418 f.run(&[b"ZREVRANGE", b"z", b"0", b"-1"]),
11419 "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
11420 );
11421 assert_eq!(
11422 f.run(&[b"ZREVRANGE", b"z", b"0", b"0", b"WITHSCORES"]),
11423 "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
11424 );
11425 assert_eq!(
11426 f.run(&[b"ZRANGEBYSCORE", b"z", b"(1", b"3"]),
11427 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11428 );
11429 assert_eq!(
11430 f.run(&[b"ZREVRANGEBYSCORE", b"z", b"3", b"(1"]),
11431 "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
11432 );
11433 assert_eq!(f.run(&[b"ZREVRANGEBYSCORE", b"z", b"(1", b"3"]), "*0\r\n");
11437 assert_eq!(
11438 f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"(c"]),
11439 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
11440 );
11441 assert_eq!(
11442 f.run(&[b"ZREVRANGEBYLEX", b"z", b"(c", b"[a"]),
11443 "*2\r\n$1\r\nb\r\n$1\r\na\r\n"
11444 );
11445 for cmd in [
11448 &[b"ZREVRANGE".as_slice(), b"z", b"0", b"-1", b"BYSCORE"][..],
11449 &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"REV"],
11450 &[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"BYLEX"],
11451 ] {
11452 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{:?}", cmd[0]);
11453 }
11454 }
11455
11456 #[test]
11459 fn limit_and_withscores_are_read_by_all_of_them_and_refused_afterwards() {
11460 let mut f = Fixture::new();
11461 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11462 assert_eq!(
11463 f.run(&[
11464 b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"1", b"1"
11465 ]),
11466 "*1\r\n$1\r\nb\r\n"
11467 );
11468 assert_eq!(
11470 f.run(&[
11471 b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"-1", b"2"
11472 ]),
11473 "*0\r\n"
11474 );
11475 assert_eq!(
11476 f.run(&[
11477 b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"0", b"-1"
11478 ]),
11479 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
11480 );
11481 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";
11483 assert_eq!(
11484 f.run(&[
11485 b"ZRANGEBYSCORE",
11486 b"z",
11487 b"1",
11488 b"3",
11489 b"WITHSCORES",
11490 b"LIMIT",
11491 b"0",
11492 b"2"
11493 ]),
11494 both
11495 );
11496 assert_eq!(
11497 f.run(&[
11498 b"ZRANGEBYSCORE",
11499 b"z",
11500 b"1",
11501 b"3",
11502 b"LIMIT",
11503 b"0",
11504 b"2",
11505 b"WITHSCORES"
11506 ]),
11507 both
11508 );
11509 let needs_by = "-ERR syntax error, LIMIT is only supported in combination with either BYSCORE or BYLEX\r\n";
11512 assert_eq!(
11513 f.run(&[
11514 b"ZREVRANGE",
11515 b"z",
11516 b"0",
11517 b"-1",
11518 b"WITHSCORES",
11519 b"LIMIT",
11520 b"0",
11521 b"1"
11522 ]),
11523 needs_by
11524 );
11525 assert_eq!(
11526 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"LIMIT", b"0", b"1"]),
11527 needs_by
11528 );
11529 let not_bylex = "-ERR syntax error, WITHSCORES not supported in combination with BYLEX\r\n";
11530 assert_eq!(
11531 f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX", b"WITHSCORES"]),
11532 not_bylex
11533 );
11534 assert_eq!(
11535 f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"WITHSCORES"]),
11536 not_bylex
11537 );
11538 for cmd in [
11541 &[
11542 b"ZRANGE".as_slice(),
11543 b"z",
11544 b"0",
11545 b"-1",
11546 b"BYSCORE",
11547 b"BYLEX",
11548 ][..],
11549 &[b"ZRANGE", b"z", b"0", b"-1", b"junk"],
11550 &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"0"],
11551 ] {
11552 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
11553 }
11554 assert_eq!(
11555 f.run(&[b"ZRANGEBYSCORE", b"z", b"bad", b"3"]),
11556 "-ERR min or max is not a float\r\n"
11557 );
11558 assert_eq!(
11559 f.run(&[b"ZRANGEBYLEX", b"z", b"a", b"[c"]),
11560 "-ERR min or max not valid string range item\r\n"
11561 );
11562 assert_eq!(
11563 f.run(&[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"a", b"2"]),
11564 "-ERR value is not an integer or out of range\r\n"
11565 );
11566 }
11567
11568 #[test]
11571 fn withscores_nests_on_resp3_and_flattens_on_resp2() {
11572 let mut f = Fixture::new();
11573 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11574 assert_eq!(
11575 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
11576 "*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"
11577 );
11578 f.out = Out::new(Proto::Resp3);
11579 assert_eq!(
11580 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
11581 "*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"
11582 );
11583 assert_eq!(
11584 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
11585 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
11586 );
11587 }
11588
11589 #[test]
11591 fn a_range_store_writes_the_window_into_another_key() {
11592 let mut f = Fixture::new();
11593 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11594 assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1"]), ":3\r\n");
11595 assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"5", b"9"]), ":0\r\n");
11598 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
11599 assert_eq!(
11600 f.run(&[b"ZRANGESTORE", b"d", b"z", b"(1", b"+inf", b"BYSCORE"]),
11601 ":2\r\n"
11602 );
11603 assert_eq!(
11604 f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
11605 "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
11606 );
11607 assert_eq!(f.run(&[b"ZRANGESTORE", b"z", b"z", b"1", b"2"]), ":2\r\n");
11610 assert_eq!(
11611 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
11612 "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
11613 );
11614 assert_eq!(
11617 f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1", b"WITHSCORES"]),
11618 "-ERR syntax error\r\n"
11619 );
11620 }
11621
11622 #[test]
11625 fn the_three_removals_share_their_window_with_the_reads() {
11626 let mut f = Fixture::new();
11627 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11628 assert_eq!(f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"0"]), ":1\r\n");
11629 assert_eq!(
11630 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
11631 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11632 );
11633 assert_eq!(
11634 f.run(&[b"ZREMRANGEBYSCORE", b"z", b"(2", b"+inf"]),
11635 ":1\r\n"
11636 );
11637 assert_eq!(f.run(&[b"ZRANGE", b"z", b"0", b"-1"]), "*1\r\n$1\r\nb\r\n");
11638 assert_eq!(f.run(&[b"ZREMRANGEBYLEX", b"z", b"-", b"+"]), ":1\r\n");
11640 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
11641 assert_eq!(
11642 f.run(&[b"ZREMRANGEBYRANK", b"nokey", b"0", b"-1"]),
11643 ":0\r\n"
11644 );
11645 assert_eq!(
11646 f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"x"]),
11647 "-ERR value is not an integer or out of range\r\n"
11648 );
11649 }
11650
11651 #[test]
11653 fn the_three_algebra_commands_combine_scores_and_order_the_answer_once() {
11654 let mut f = Fixture::new();
11655 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11656 f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
11657 assert_eq!(
11658 f.run(&[b"ZUNION", b"2", b"z", b"y"]),
11659 "*4\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\nd\r\n"
11660 );
11661 assert_eq!(
11664 f.run(&[b"ZUNION", b"2", b"z", b"y", b"WITHSCORES"]),
11665 "*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"
11666 );
11667 assert_eq!(
11668 f.run(&[
11669 b"ZUNION",
11670 b"2",
11671 b"z",
11672 b"y",
11673 b"WEIGHTS",
11674 b"2",
11675 b"3",
11676 b"WITHSCORES"
11677 ]),
11678 "*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"
11679 );
11680 assert_eq!(
11681 f.run(&[
11682 b"ZUNION",
11683 b"2",
11684 b"z",
11685 b"y",
11686 b"AGGREGATE",
11687 b"MIN",
11688 b"WITHSCORES"
11689 ]),
11690 "*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"
11691 );
11692 assert_eq!(
11693 f.run(&[
11694 b"ZUNION",
11695 b"2",
11696 b"z",
11697 b"y",
11698 b"AGGREGATE",
11699 b"MAX",
11700 b"WITHSCORES"
11701 ]),
11702 "*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"
11703 );
11704 assert_eq!(
11705 f.run(&[b"ZINTER", b"2", b"z", b"y", b"WITHSCORES"]),
11706 "*2\r\n$1\r\nb\r\n$2\r\n12\r\n"
11707 );
11708 assert_eq!(
11709 f.run(&[b"ZDIFF", b"2", b"z", b"y", b"WITHSCORES"]),
11710 "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nc\r\n$1\r\n3\r\n"
11711 );
11712 assert_eq!(f.run(&[b"ZUNION", b"1", b"nokey"]), "*0\r\n");
11713 f.run(&[b"SADD", b"p", b"a", b"d"]);
11716 assert_eq!(
11717 f.run(&[b"ZUNION", b"2", b"z", b"p", b"WITHSCORES"]),
11718 "*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"
11719 );
11720 for cmd in [
11723 &[
11724 b"ZDIFF".as_slice(),
11725 b"2",
11726 b"z",
11727 b"y",
11728 b"WEIGHTS",
11729 b"1",
11730 b"1",
11731 ][..],
11732 &[b"ZDIFF", b"2", b"z", b"y", b"AGGREGATE", b"MIN"],
11733 ] {
11734 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
11735 }
11736 }
11737
11738 #[test]
11740 fn the_algebra_counts_its_keys_and_says_so_when_the_count_is_wrong() {
11741 let mut f = Fixture::new();
11742 f.run(&[b"ZADD", b"z", b"1", b"a"]);
11743 f.run(&[b"ZADD", b"y", b"2", b"b"]);
11744 assert_eq!(
11746 f.run(&[b"ZUNION", b"0", b"z"]),
11747 "-ERR at least 1 input key is needed for 'zunion' command\r\n"
11748 );
11749 assert_eq!(
11750 f.run(&[b"ZUNION", b"-1", b"z"]),
11751 "-ERR at least 1 input key is needed for 'zunion' command\r\n"
11752 );
11753 assert_eq!(
11754 f.run(&[b"ZINTERCARD", b"0", b"z"]),
11755 "-ERR at least 1 input key is needed for 'zintercard' command\r\n"
11756 );
11757 assert_eq!(
11760 f.run(&[b"ZUNION", b"3", b"z", b"y"]),
11761 "-ERR syntax error\r\n"
11762 );
11763 assert_eq!(
11764 f.run(&[b"ZUNION", b"x", b"z"]),
11765 "-ERR value is not an integer or out of range\r\n"
11766 );
11767 assert_eq!(
11770 f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"1"]),
11771 "-ERR syntax error\r\n"
11772 );
11773 assert_eq!(
11774 f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"a", b"b"]),
11775 "-ERR weight value is not a float\r\n"
11776 );
11777 assert_eq!(
11778 f.run(&[b"ZUNION", b"2", b"z", b"y", b"AGGREGATE", b"NOPE"]),
11779 "-ERR syntax error\r\n"
11780 );
11781 }
11782
11783 #[test]
11785 fn the_algebra_stores_answer_a_count_and_delete_an_empty_destination() {
11786 let mut f = Fixture::new();
11787 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11788 f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
11789 assert_eq!(f.run(&[b"ZUNIONSTORE", b"d", b"2", b"z", b"y"]), ":4\r\n");
11790 assert_eq!(
11791 f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
11792 "*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"
11793 );
11794 assert_eq!(f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"y"]), ":1\r\n");
11795 assert_eq!(f.run(&[b"ZDIFFSTORE", b"d", b"2", b"z", b"y"]), ":2\r\n");
11796 assert_eq!(
11799 f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"nokey"]),
11800 ":0\r\n"
11801 );
11802 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
11803 assert_eq!(f.run(&[b"ZUNIONSTORE", b"z", b"2", b"z", b"y"]), ":4\r\n");
11805 assert_eq!(f.run(&[b"ZCARD", b"z"]), ":4\r\n");
11806 for cmd in [
11807 &[
11808 b"ZUNIONSTORE".as_slice(),
11809 b"d",
11810 b"2",
11811 b"z",
11812 b"y",
11813 b"WITHSCORES",
11814 ][..],
11815 &[
11816 b"ZDIFFSTORE",
11817 b"d",
11818 b"2",
11819 b"z",
11820 b"y",
11821 b"WEIGHTS",
11822 b"1",
11823 b"1",
11824 ],
11825 ] {
11826 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
11827 }
11828 }
11829
11830 #[test]
11832 fn intercard_counts_and_stops_at_its_limit() {
11833 let mut f = Fixture::new();
11834 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11835 f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"c", b"30", b"d"]);
11836 assert_eq!(f.run(&[b"ZINTERCARD", b"2", b"z", b"y"]), ":2\r\n");
11837 assert_eq!(
11839 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"0"]),
11840 ":2\r\n"
11841 );
11842 assert_eq!(
11843 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"1"]),
11844 ":1\r\n"
11845 );
11846 let bad = "-ERR LIMIT can't be negative\r\n";
11849 assert_eq!(
11850 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"-1"]),
11851 bad
11852 );
11853 assert_eq!(
11854 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"x"]),
11855 bad
11856 );
11857 for cmd in [
11858 &[b"ZINTERCARD".as_slice(), b"3", b"z", b"y"][..],
11859 &[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT"],
11860 &[b"ZINTERCARD", b"2", b"z", b"y", b"junk", b"1"],
11861 ] {
11862 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
11863 }
11864 }
11865
11866 #[test]
11868 fn a_draw_answers_one_member_or_an_array_of_them() {
11869 let mut f = Fixture::new();
11870 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11871 assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "$-1\r\n");
11874 assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
11875 assert_eq!(f.run(&[b"ZRANDMEMBER", b"z", b"0"]), "*0\r\n");
11876 assert!(f.run(&[b"ZRANDMEMBER", b"z"]).starts_with("$1\r\n"));
11877 let all = f.run(&[b"ZRANDMEMBER", b"z", b"10"]);
11880 assert!(all.starts_with("*3\r\n"), "{all}");
11881 for m in ["a", "b", "c"] {
11882 assert!(all.contains(m), "{all}");
11883 }
11884 assert!(
11887 f.run(&[b"ZRANDMEMBER", b"z", b"-5"]).starts_with("*5\r\n"),
11888 "five draws with replacement"
11889 );
11890 assert!(
11891 f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"])
11892 .starts_with("*4\r\n"),
11893 "two pairs, flat on RESP2"
11894 );
11895 f.out = Out::new(Proto::Resp3);
11896 let got = f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"]);
11897 assert!(got.starts_with("*2\r\n*2\r\n"), "{got}");
11898 assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "_\r\n");
11899 f.out = Out::new(Proto::Resp2);
11900 assert_eq!(
11901 f.run(&[b"ZRANDMEMBER", b"z", b"2", b"junk"]),
11902 "-ERR syntax error\r\n"
11903 );
11904 assert_eq!(
11905 f.run(&[b"ZRANDMEMBER", b"z", b"x"]),
11906 "-ERR value is not an integer or out of range\r\n"
11907 );
11908 }
11909
11910 #[test]
11912 fn a_sorted_set_scan_answers_pairs_of_strings_on_both_protocols() {
11913 let mut f = Fixture::new();
11914 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11915 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";
11916 assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
11917 assert_eq!(f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"10"]), all);
11918 assert_eq!(
11919 f.run(&[b"ZSCAN", b"z", b"0", b"MATCH", b"a*"]),
11920 "*2\r\n$1\r\n0\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
11921 );
11922 assert_eq!(
11923 f.run(&[b"ZSCAN", b"nokey", b"0"]),
11924 "*2\r\n$1\r\n0\r\n*0\r\n"
11925 );
11926 f.out = Out::new(Proto::Resp3);
11929 assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
11930 f.out = Out::new(Proto::Resp2);
11931 assert_eq!(
11932 f.run(&[b"ZSCAN", b"z", b"0", b"NOVALUES"]),
11933 "-ERR NOVALUES option can only be used in HSCAN\r\n"
11934 );
11935 assert_eq!(f.run(&[b"ZSCAN", b"z", b"-1"]), "-ERR invalid cursor\r\n");
11936 assert_eq!(
11937 f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"0"]),
11938 "-ERR syntax error\r\n"
11939 );
11940 }
11941
11942 #[test]
11944 fn a_sorted_set_pop_changes_shape_when_it_is_given_a_count() {
11945 let mut f = Fixture::new();
11946 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11947 assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
11949 assert_eq!(f.run(&[b"ZPOPMAX", b"z"]), "*2\r\n$1\r\nc\r\n$1\r\n3\r\n");
11950 f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
11951 assert_eq!(
11953 f.run(&[b"ZPOPMIN", b"z", b"2"]),
11954 "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
11955 );
11956 assert_eq!(f.run(&[b"ZPOPMIN", b"nokey"]), "*0\r\n");
11959 assert_eq!(f.run(&[b"ZPOPMIN", b"nokey", b"2"]), "*0\r\n");
11960 assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"0"]), "*0\r\n");
11961 assert_eq!(
11963 f.run(&[b"ZPOPMIN", b"z", b"9"]),
11964 "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
11965 );
11966 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
11967
11968 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]);
11969 f.out = Out::new(Proto::Resp3);
11970 assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n,1\r\n");
11971 assert_eq!(
11972 f.run(&[b"ZPOPMIN", b"z", b"1"]),
11973 "*1\r\n*2\r\n$1\r\nb\r\n,2\r\n"
11974 );
11975 f.out = Out::new(Proto::Resp2);
11976 let bad = "-ERR value is out of range, must be positive\r\n";
11979 assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"x"]), bad);
11980 assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"-1"]), bad);
11981 assert_eq!(
11982 f.run(&[b"ZPOPMIN", b"z", b"1", b"2"]),
11983 "-ERR syntax error\r\n"
11984 );
11985 }
11986
11987 #[test]
11989 fn a_multi_key_pop_names_the_key_that_answered_and_nests_its_pairs() {
11990 let mut f = Fixture::new();
11991 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11992 assert_eq!(
11993 f.run(&[b"ZMPOP", b"2", b"nokey", b"z", b"MIN"]),
11994 "*2\r\n$1\r\nz\r\n*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
11995 );
11996 assert_eq!(
11999 f.run(&[b"ZMPOP", b"1", b"z", b"MAX", b"COUNT", b"2"]),
12000 "*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"
12001 );
12002 assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "*-1\r\n");
12004 f.out = Out::new(Proto::Resp3);
12005 assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "_\r\n");
12006 f.out = Out::new(Proto::Resp2);
12007 let numkeys = "-ERR numkeys should be greater than 0\r\n";
12008 for bad in [
12009 &[b"ZMPOP".as_slice(), b"0", b"z", b"MIN"][..],
12010 &[b"ZMPOP", b"-1", b"z", b"MIN"],
12011 &[b"ZMPOP", b"x", b"z", b"MIN"],
12012 ] {
12013 assert_eq!(f.run(bad), numkeys, "{:?}", bad[1]);
12014 }
12015 let count = "-ERR count should be greater than 0\r\n";
12016 for bad in [
12017 &[b"ZMPOP".as_slice(), b"1", b"z", b"MIN", b"COUNT", b"0"][..],
12018 &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"-1"],
12019 &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"x"],
12020 ] {
12021 assert_eq!(f.run(bad), count, "{:?}", bad[5]);
12022 }
12023 let syntax = "-ERR syntax error\r\n";
12024 for bad in [
12025 &[b"ZMPOP".as_slice(), b"2", b"z", b"MIN"][..],
12028 &[b"ZMPOP", b"1", b"z", b"SIDEWAYS"],
12029 &[b"ZMPOP", b"1", b"z", b"MIN", b"junk"],
12030 &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"1", b"junk"],
12031 ] {
12032 assert_eq!(f.run(bad), syntax, "{bad:?}");
12033 }
12034 }
12035
12036 #[test]
12039 fn the_sorted_set_pops_that_wait_answer_like_the_ones_they_wrap() {
12040 let mut f = Fixture::new();
12041 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12042 assert_eq!(
12043 f.flow(&[b"BZPOPMIN", b"nokey", b"z", b"0"]),
12044 (
12045 Flow::Continue,
12046 "*3\r\n$1\r\nz\r\n$1\r\na\r\n$1\r\n1\r\n".to_owned()
12047 )
12048 );
12049 assert_eq!(
12050 f.run(&[b"BZPOPMAX", b"z", b"0"]),
12051 "*3\r\n$1\r\nz\r\n$1\r\nc\r\n$1\r\n3\r\n"
12052 );
12053 f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
12054 assert_eq!(
12055 f.run(&[
12056 b"BZMPOP", b"0", b"2", b"nokey", b"z", b"MIN", b"COUNT", b"2"
12057 ]),
12058 "*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"
12059 );
12060 f.out = Out::new(Proto::Resp3);
12061 assert_eq!(
12062 f.run(&[b"BZPOPMIN", b"z", b"0"]),
12063 "*3\r\n$1\r\nz\r\n$1\r\nc\r\n,3\r\n"
12064 );
12065 f.out = Out::new(Proto::Resp2);
12066 assert_eq!(
12068 f.flow(&[b"BZPOPMIN", b"z", b"0"]),
12069 (Flow::Block, String::new())
12070 );
12071 assert_eq!(
12072 f.flow(&[b"BZMPOP", b"0", b"1", b"z", b"MIN"]),
12073 (Flow::Block, String::new())
12074 );
12075 assert_eq!(
12078 f.run(&[b"BZMPOP", b"abc", b"0", b"z", b"MIN"]),
12079 "-ERR timeout is not a float or out of range\r\n"
12080 );
12081 assert_eq!(
12082 f.run(&[b"BZMPOP", b"0", b"0", b"z", b"MIN"]),
12083 "-ERR numkeys should be greater than 0\r\n"
12084 );
12085 assert_eq!(
12086 f.run(&[b"BZPOPMIN", b"z", b"-1"]),
12087 "-ERR timeout is negative\r\n"
12088 );
12089 }
12090
12091 #[test]
12095 fn a_parked_sorted_set_client_waits_for_a_member_and_not_for_a_key() {
12096 let mut f = Fixture::new();
12097 assert_eq!(f.flow(&[b"BZPOPMIN", b"z", b"0"]).0, Flow::Block);
12098 assert_eq!(f.server.parked(), 1);
12099 f.run(&[b"SET", b"z", b"v"]);
12102 let mut out = Out::new(Proto::Resp2);
12103 assert!(!f.server.serve_waiter(7, 0, &mut out));
12104 assert!(out.as_slice().is_empty());
12105 f.run(&[b"DEL", b"z"]);
12106 f.run(&[b"ZADD", b"z", b"5", b"m"]);
12107 assert!(f.server.serve_waiter(7, 0, &mut out));
12108 assert_eq!(
12109 core::str::from_utf8(out.as_slice()).expect("ascii"),
12110 "*3\r\n$1\r\nz\r\n$1\r\nm\r\n$1\r\n5\r\n"
12111 );
12112 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
12115 }
12116
12117 #[test]
12118 fn every_sorted_set_command_says_wrongtype_and_writes_nothing() {
12119 let mut f = Fixture::new();
12120 f.run(&[b"SET", b"s", b"v"]);
12121 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12122 for cmd in [
12123 &[b"ZADD".as_slice(), b"s", b"1", b"a"][..],
12124 &[b"ZINCRBY", b"s", b"1", b"a"],
12125 &[b"ZCARD", b"s"],
12126 &[b"ZSCORE", b"s", b"a"],
12127 &[b"ZMSCORE", b"s", b"a"],
12128 &[b"ZREM", b"s", b"a"],
12129 &[b"ZRANK", b"s", b"a"],
12130 &[b"ZREVRANK", b"s", b"a"],
12131 &[b"ZCOUNT", b"s", b"1", b"2"],
12132 &[b"ZLEXCOUNT", b"s", b"-", b"+"],
12133 &[b"ZRANGE", b"s", b"0", b"-1"],
12134 &[b"ZREVRANGE", b"s", b"0", b"-1"],
12135 &[b"ZRANGEBYSCORE", b"s", b"1", b"2"],
12136 &[b"ZREVRANGEBYSCORE", b"s", b"2", b"1"],
12137 &[b"ZRANGEBYLEX", b"s", b"-", b"+"],
12138 &[b"ZREVRANGEBYLEX", b"s", b"+", b"-"],
12139 &[b"ZRANGESTORE", b"d", b"s", b"0", b"-1"],
12140 &[b"ZREMRANGEBYRANK", b"s", b"0", b"-1"],
12141 &[b"ZREMRANGEBYSCORE", b"s", b"1", b"2"],
12142 &[b"ZREMRANGEBYLEX", b"s", b"-", b"+"],
12143 &[b"ZUNION", b"1", b"s"],
12144 &[b"ZINTER", b"1", b"s"],
12145 &[b"ZDIFF", b"1", b"s"],
12146 &[b"ZUNIONSTORE", b"d", b"1", b"s"],
12147 &[b"ZINTERSTORE", b"d", b"1", b"s"],
12148 &[b"ZDIFFSTORE", b"d", b"1", b"s"],
12149 &[b"ZINTERCARD", b"1", b"s"],
12150 &[b"ZRANDMEMBER", b"s"],
12151 &[b"ZSCAN", b"s", b"0"],
12152 &[b"ZPOPMIN", b"s"],
12153 &[b"ZPOPMAX", b"s", b"2"],
12154 &[b"ZMPOP", b"1", b"s", b"MIN"],
12155 &[b"BZPOPMIN", b"s", b"0"],
12156 &[b"BZPOPMAX", b"s", b"0"],
12157 &[b"BZMPOP", b"0", b"1", b"s", b"MIN"],
12158 ] {
12159 assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
12160 }
12161 assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nv\r\n");
12162 }
12163
12164 #[cfg_attr(miri, ignore = "the volume is the claim")]
12169 #[test]
12170 fn churning_sorted_sets_does_not_grow_the_server() {
12171 let mut f = Fixture::new();
12172 let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
12173 let scores: Vec<Vec<u8>> = (0..200).map(|i| format!("{i}").into_bytes()).collect();
12174 let mut args: Vec<&[u8]> = vec![b"ZADD", b"z"];
12175 for i in 0..200 {
12176 args.push(&scores[i]);
12177 args.push(&members[i]);
12178 }
12179
12180 f.run(&args);
12181 f.run(&[b"DEL", b"z"]);
12182 f.server.compact_step();
12183 let after_first = f.server.memory_bytes();
12184
12185 for _ in 0..200 {
12186 f.run(&args);
12187 f.run(&[b"DEL", b"z"]);
12188 f.server.compact_step();
12189 }
12190 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
12191 assert!(
12192 f.server.memory_bytes() <= after_first * 2,
12193 "held {} after two hundred passes against {after_first} after one",
12194 f.server.memory_bytes()
12195 );
12196 }
12197
12198 fn sicily(f: &mut Fixture) {
12206 f.run(&[
12207 b"GEOADD",
12208 b"Sicily",
12209 b"13.361389",
12210 b"38.115556",
12211 b"Palermo",
12212 b"15.087269",
12213 b"37.502669",
12214 b"Catania",
12215 ]);
12216 f.run(&[
12217 b"GEOADD",
12218 b"Sicily",
12219 b"13.583333",
12220 b"37.316667",
12221 b"Agrigento",
12222 ]);
12223 }
12224
12225 #[test]
12226 fn places_go_in_as_scores_and_come_back_as_positions() {
12227 let mut f = Fixture::new();
12228 assert_eq!(
12229 f.run(&[
12230 b"GEOADD",
12231 b"Sicily",
12232 b"13.361389",
12233 b"38.115556",
12234 b"Palermo",
12235 b"15.087269",
12236 b"37.502669",
12237 b"Catania"
12238 ]),
12239 ":2\r\n"
12240 );
12241 assert_eq!(f.run(&[b"TYPE", b"Sicily"]), "+zset\r\n");
12245 assert_eq!(
12246 f.run(&[b"ZSCORE", b"Sicily", b"Palermo"]),
12247 "$16\r\n3479099956230698\r\n"
12248 );
12249 assert_eq!(
12250 f.run(&[b"GEOPOS", b"Sicily", b"Palermo", b"NonExisting"]),
12251 "*2\r\n*2\r\n$18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n*-1\r\n"
12252 );
12253 assert_eq!(
12254 f.run(&[
12255 b"GEOHASH",
12256 b"Sicily",
12257 b"Palermo",
12258 b"Catania",
12259 b"NonExisting"
12260 ]),
12261 "*3\r\n$11\r\nsqc8b49rny0\r\n$11\r\nsqdtr74hyu0\r\n$-1\r\n"
12262 );
12263 assert_eq!(f.run(&[b"GEOPOS", b"nokey", b"a"]), "*1\r\n*-1\r\n");
12267 assert_eq!(f.run(&[b"GEOHASH", b"nokey", b"a"]), "*1\r\n$-1\r\n");
12268 }
12269
12270 #[test]
12271 fn a_distance_comes_back_with_four_places_in_whatever_unit_was_asked_for() {
12272 let mut f = Fixture::new();
12273 sicily(&mut f);
12274 assert_eq!(
12275 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania"]),
12276 "$11\r\n166274.1516\r\n"
12277 );
12278 assert_eq!(
12279 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"km"]),
12280 "$8\r\n166.2742\r\n"
12281 );
12282 assert_eq!(
12283 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"mi"]),
12284 "$8\r\n103.3182\r\n"
12285 );
12286 assert_eq!(
12290 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Foo"]),
12291 "$-1\r\n"
12292 );
12293 assert_eq!(f.run(&[b"GEODIST", b"nokey", b"a", b"b"]), "$-1\r\n");
12294 assert_eq!(
12295 f.run(&[b"GEODIST", b"nokey", b"a", b"b", b"parsecs"]),
12296 "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n"
12297 );
12298 assert_eq!(
12299 f.run(&[b"GEODIST", b"Sicily", b"a", b"b", b"km", b"extra"]),
12300 "-ERR syntax error\r\n"
12301 );
12302 }
12303
12304 #[test]
12305 fn a_search_finds_what_is_inside_it_nearest_first() {
12306 let mut f = Fixture::new();
12307 sicily(&mut f);
12308 let all = "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n";
12309 assert_eq!(
12310 f.run(&[
12311 b"GEOSEARCH",
12312 b"Sicily",
12313 b"FROMLONLAT",
12314 b"15",
12315 b"37",
12316 b"BYRADIUS",
12317 b"200",
12318 b"km",
12319 b"ASC"
12320 ]),
12321 all
12322 );
12323 assert_eq!(
12326 f.run(&[b"GEORADIUS", b"Sicily", b"15", b"37", b"200", b"km", b"ASC"]),
12327 all
12328 );
12329 assert_eq!(
12330 f.run(&[
12331 b"GEORADIUS_RO",
12332 b"Sicily",
12333 b"15",
12334 b"37",
12335 b"200",
12336 b"km",
12337 b"ASC"
12338 ]),
12339 all
12340 );
12341 assert_eq!(
12344 f.run(&[
12345 b"GEORADIUS",
12346 b"Sicily",
12347 b"15",
12348 b"37",
12349 b"200",
12350 b"km",
12351 b"DESC",
12352 b"COUNT",
12353 b"1"
12354 ]),
12355 "*1\r\n$7\r\nPalermo\r\n"
12356 );
12357 assert_eq!(
12358 f.run(&[
12359 b"GEORADIUS",
12360 b"Sicily",
12361 b"15",
12362 b"37",
12363 b"200",
12364 b"km",
12365 b"COUNT",
12366 b"1"
12367 ]),
12368 "*1\r\n$7\r\nCatania\r\n"
12369 );
12370 let empty = "*0\r\n";
12373 assert_eq!(
12374 f.run(&[
12375 b"GEOSEARCH",
12376 b"Sicily",
12377 b"FROMLONLAT",
12378 b"15",
12379 b"37",
12380 b"BYRADIUS",
12381 b"1",
12382 b"km"
12383 ]),
12384 empty
12385 );
12386 assert_eq!(
12387 f.run(&[
12388 b"GEOSEARCH",
12389 b"nokey",
12390 b"FROMLONLAT",
12391 b"15",
12392 b"37",
12393 b"BYRADIUS",
12394 b"1",
12395 b"km"
12396 ]),
12397 empty
12398 );
12399 assert_eq!(
12400 f.run(&[b"GEORADIUSBYMEMBER", b"nokey", b"m", b"1", b"km"]),
12401 empty
12402 );
12403 }
12404
12405 #[test]
12406 fn a_search_centred_on_a_member_starts_from_where_that_member_is() {
12407 let mut f = Fixture::new();
12408 sicily(&mut f);
12409 assert_eq!(
12410 f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Agrigento", b"100", b"km"]),
12411 "*2\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
12412 );
12413 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";
12416 assert_eq!(
12417 f.run(&[
12418 b"GEORADIUSBYMEMBER_RO",
12419 b"Sicily",
12420 b"Agrigento",
12421 b"100",
12422 b"km",
12423 b"WITHDIST"
12424 ]),
12425 with_dist
12426 );
12427 assert_eq!(
12428 f.run(&[
12429 b"GEOSEARCH",
12430 b"Sicily",
12431 b"FROMMEMBER",
12432 b"Agrigento",
12433 b"BYRADIUS",
12434 b"100",
12435 b"km",
12436 b"ASC",
12437 b"WITHDIST"
12438 ]),
12439 with_dist
12440 );
12441 assert_eq!(
12442 f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Nowhere", b"100", b"km"]),
12443 "-ERR could not decode requested zset member\r\n"
12444 );
12445 }
12446
12447 #[test]
12448 fn a_box_search_reports_the_distance_the_hash_and_the_coordinates() {
12449 let mut f = Fixture::new();
12450 sicily(&mut f);
12451 assert_eq!(
12455 f.run(&[
12456 b"GEOSEARCH",
12457 b"Sicily",
12458 b"FROMLONLAT",
12459 b"15",
12460 b"37",
12461 b"BYBOX",
12462 b"400",
12463 b"400",
12464 b"km",
12465 b"ASC",
12466 b"WITHCOORD",
12467 b"WITHDIST",
12468 b"WITHHASH"
12469 ]),
12470 "*3\r\n*4\r\n$7\r\nCatania\r\n$7\r\n56.4413\r\n:3479447370796909\r\n*2\r\n\
12471 $18\r\n15.087267458438873\r\n$17\r\n37.50266842333162\r\n\
12472 *4\r\n$9\r\nAgrigento\r\n$8\r\n130.4235\r\n:3479030013248308\r\n*2\r\n\
12473 $18\r\n13.583331406116486\r\n$18\r\n37.316668049938166\r\n\
12474 *4\r\n$7\r\nPalermo\r\n$8\r\n190.4424\r\n:3479099956230698\r\n*2\r\n\
12475 $18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n"
12476 );
12477 }
12478
12479 #[test]
12480 fn a_store_writes_the_hashes_and_a_storedist_writes_the_distances() {
12481 let mut f = Fixture::new();
12482 sicily(&mut f);
12483 let hashes = "*6\r\n$9\r\nAgrigento\r\n$16\r\n3479030013248308\r\n\
12484 $7\r\nPalermo\r\n$16\r\n3479099956230698\r\n\
12485 $7\r\nCatania\r\n$16\r\n3479447370796909\r\n";
12486 assert_eq!(
12487 f.run(&[
12488 b"GEOSEARCHSTORE",
12489 b"dst",
12490 b"Sicily",
12491 b"FROMLONLAT",
12492 b"15",
12493 b"37",
12494 b"BYRADIUS",
12495 b"200",
12496 b"km",
12497 b"ASC"
12498 ]),
12499 ":3\r\n"
12500 );
12501 assert_eq!(
12502 f.run(&[b"ZRANGE", b"dst", b"0", b"-1", b"WITHSCORES"]),
12503 hashes
12504 );
12505 assert_eq!(
12508 f.run(&[
12509 b"GEORADIUS",
12510 b"Sicily",
12511 b"15",
12512 b"37",
12513 b"200",
12514 b"km",
12515 b"STORE",
12516 b"dst3"
12517 ]),
12518 ":3\r\n"
12519 );
12520 assert_eq!(
12521 f.run(&[b"ZRANGE", b"dst3", b"0", b"-1", b"WITHSCORES"]),
12522 hashes
12523 );
12524 assert_eq!(
12533 f.run(&[
12534 b"GEOSEARCHSTORE",
12535 b"dst2",
12536 b"Sicily",
12537 b"FROMLONLAT",
12538 b"15",
12539 b"37",
12540 b"BYRADIUS",
12541 b"200",
12542 b"km",
12543 b"ASC",
12544 b"STOREDIST"
12545 ]),
12546 ":3\r\n"
12547 );
12548 for (member, want) in [
12549 ("Catania", 56.441_257_870_158_19),
12550 ("Agrigento", 130.423_487_067_147_14),
12551 ("Palermo", 190.442_429_847_757_92),
12552 ] {
12553 let reply = f.run(&[b"ZSCORE", b"dst2", member.as_bytes()]);
12554 let got: f64 = reply
12555 .trim_start_matches(|c: char| c != '\n')
12556 .trim()
12557 .parse()
12558 .unwrap_or_else(|_| panic!("{member} scored {reply:?}"));
12559 assert!(
12560 (got - want).abs() < 1e-9,
12561 "{member} scored {got} not {want}"
12562 );
12563 }
12564 assert_eq!(
12567 f.run(&[b"ZRANGE", b"dst2", b"0", b"-1"]),
12568 "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
12569 );
12570 assert_eq!(
12574 f.run(&[
12575 b"GEOSEARCHSTORE",
12576 b"dst",
12577 b"nokey",
12578 b"FROMLONLAT",
12579 b"15",
12580 b"37",
12581 b"BYRADIUS",
12582 b"200",
12583 b"km"
12584 ]),
12585 ":0\r\n"
12586 );
12587 assert_eq!(f.run(&[b"EXISTS", b"dst"]), ":0\r\n");
12588 }
12589
12590 #[test]
12591 fn the_gates_on_geoadd_are_the_ones_zadd_has() {
12592 let mut f = Fixture::new();
12593 sicily(&mut f);
12594 assert_eq!(
12597 f.run(&[
12598 b"GEOADD",
12599 b"Sicily",
12600 b"XX",
12601 b"CH",
12602 b"13.361389",
12603 b"38.115556",
12604 b"Palermo"
12605 ]),
12606 ":0\r\n"
12607 );
12608 assert_eq!(
12609 f.run(&[
12610 b"GEOADD",
12611 b"Sicily",
12612 b"NX",
12613 b"13.361389",
12614 b"38.9",
12615 b"Palermo"
12616 ]),
12617 ":0\r\n"
12618 );
12619 assert_eq!(
12620 f.run(&[
12621 b"GEOADD",
12622 b"Sicily",
12623 b"CH",
12624 b"13.361389",
12625 b"38.9",
12626 b"Palermo"
12627 ]),
12628 ":1\r\n"
12629 );
12630 assert_eq!(
12633 f.run(&[
12634 b"GEOADD",
12635 b"new",
12636 b"13.361389",
12637 b"38.115556",
12638 b"here",
12639 b"181",
12640 b"38",
12641 b"there"
12642 ]),
12643 "-ERR invalid longitude,latitude pair 181.000000,38.000000\r\n"
12644 );
12645 assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
12646 assert_eq!(
12647 f.run(&[b"GEOADD", b"new", b"x", b"38", b"here"]),
12648 "-ERR value is not a valid float\r\n"
12649 );
12650 assert_eq!(
12653 f.run(&[b"GEOADD", b"new", b"13", b"38", b"here", b"and"]),
12654 "-ERR syntax error\r\n"
12655 );
12656 assert_eq!(
12657 f.run(&[b"GEOADD", b"new", b"NX", b"XX", b"CH"]),
12658 "-ERR syntax error\r\n"
12659 );
12660 assert_eq!(
12661 f.run(&[b"GEOADD", b"new", b"CH", b"CH", b"CH", b"CH"]),
12662 "-ERR syntax error\r\n"
12663 );
12664 assert_eq!(
12665 f.run(&[b"GEOADD", b"new", b"NX", b"CH"]),
12666 "-ERR wrong number of arguments for 'geoadd' command\r\n"
12667 );
12668 }
12669
12670 #[test]
12673 fn every_way_a_search_can_be_written_wrong_has_its_own_sentence() {
12674 let mut f = Fixture::new();
12675 sicily(&mut f);
12676 let cases: &[(&[&[u8]], &str)] = &[
12677 (
12678 &[b"GEORADIUS", b"Sicily", b"15", b"37", b"x", b"km"],
12679 "-ERR need numeric radius\r\n",
12680 ),
12681 (
12682 &[b"GEORADIUS", b"Sicily", b"15", b"37", b"-1", b"km"],
12683 "-ERR radius cannot be negative\r\n",
12684 ),
12685 (
12686 &[b"GEORADIUS", b"Sicily", b"15", b"37", b"1", b"parsecs"],
12687 "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n",
12688 ),
12689 (
12690 &[b"GEORADIUS", b"Sicily", b"181", b"37", b"1", b"km"],
12691 "-ERR invalid longitude,latitude pair 181.000000,37.000000\r\n",
12692 ),
12693 (
12694 &[
12695 b"GEOSEARCH",
12696 b"Sicily",
12697 b"FROMLONLAT",
12698 b"15",
12699 b"37",
12700 b"BYBOX",
12701 b"x",
12702 b"1",
12703 b"km",
12704 ],
12705 "-ERR need numeric width\r\n",
12706 ),
12707 (
12708 &[
12709 b"GEOSEARCH",
12710 b"Sicily",
12711 b"FROMLONLAT",
12712 b"15",
12713 b"37",
12714 b"BYBOX",
12715 b"1",
12716 b"y",
12717 b"km",
12718 ],
12719 "-ERR need numeric height\r\n",
12720 ),
12721 (
12722 &[
12723 b"GEOSEARCH",
12724 b"Sicily",
12725 b"FROMLONLAT",
12726 b"15",
12727 b"37",
12728 b"BYBOX",
12729 b"-1",
12730 b"1",
12731 b"km",
12732 ],
12733 "-ERR height or width cannot be negative\r\n",
12734 ),
12735 (
12736 &[
12737 b"GEOSEARCH",
12738 b"Sicily",
12739 b"FROMLONLAT",
12740 b"15",
12741 b"37",
12742 b"BYRADIUS",
12743 b"1",
12744 b"km",
12745 b"ANY",
12746 ],
12747 "-ERR the ANY argument requires COUNT argument\r\n",
12748 ),
12749 (
12750 &[
12751 b"GEOSEARCH",
12752 b"Sicily",
12753 b"FROMLONLAT",
12754 b"15",
12755 b"37",
12756 b"BYRADIUS",
12757 b"1",
12758 b"km",
12759 b"COUNT",
12760 b"0",
12761 ],
12762 "-ERR COUNT must be > 0\r\n",
12763 ),
12764 (
12765 &[
12766 b"GEOSEARCH",
12767 b"Sicily",
12768 b"BYRADIUS",
12769 b"1",
12770 b"km",
12771 b"BYBOX",
12772 b"1",
12773 b"1",
12774 b"km",
12775 ],
12776 "-ERR syntax error\r\n",
12777 ),
12778 (
12779 &[
12780 b"GEOSEARCH",
12781 b"Sicily",
12782 b"FROMMEMBER",
12783 b"Palermo",
12784 b"FROMLONLAT",
12785 b"1",
12786 b"2",
12787 b"BYRADIUS",
12788 b"1",
12789 b"km",
12790 ],
12791 "-ERR syntax error\r\n",
12792 ),
12793 (
12796 &[
12797 b"geosearch",
12798 b"Sicily",
12799 b"BYRADIUS",
12800 b"1",
12801 b"km",
12802 b"ASC",
12803 b"WITHDIST",
12804 ],
12805 "-ERR exactly one of FROMMEMBER or FROMLONLAT can be specified for geosearch\r\n",
12806 ),
12807 (
12808 &[
12809 b"GEOSEARCH",
12810 b"Sicily",
12811 b"FROMLONLAT",
12812 b"15",
12813 b"37",
12814 b"ASC",
12815 b"WITHDIST",
12816 ],
12817 "-ERR exactly one of BYRADIUS and BYBOX can be specified for GEOSEARCH\r\n",
12818 ),
12819 (
12822 &[
12823 b"GEOSEARCHSTORE",
12824 b"d",
12825 b"Sicily",
12826 b"FROMLONLAT",
12827 b"15",
12828 b"37",
12829 b"BYRADIUS",
12830 b"1",
12831 b"km",
12832 b"WITHCOORD",
12833 ],
12834 "-ERR GEOSEARCHSTORE is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
12835 ),
12836 (
12837 &[
12838 b"GEORADIUS",
12839 b"Sicily",
12840 b"15",
12841 b"37",
12842 b"1",
12843 b"km",
12844 b"WITHDIST",
12845 b"STORE",
12846 b"d",
12847 ],
12848 "-ERR STORE option in GEORADIUS is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
12849 ),
12850 (
12853 &[
12854 b"GEORADIUS_RO",
12855 b"Sicily",
12856 b"15",
12857 b"37",
12858 b"1",
12859 b"km",
12860 b"STORE",
12861 b"d",
12862 ],
12863 "-ERR syntax error\r\n",
12864 ),
12865 (
12866 &[
12867 b"GEOSEARCH",
12868 b"Sicily",
12869 b"FROMLONLAT",
12870 b"15",
12871 b"37",
12872 b"BYRADIUS",
12873 b"1",
12874 b"km",
12875 b"STOREDIST",
12876 ],
12877 "-ERR syntax error\r\n",
12878 ),
12879 ];
12880 for (parts, want) in cases {
12881 assert_eq!(&f.run(parts), want, "{:?}", parts[0]);
12882 }
12883 }
12884
12885 #[test]
12888 fn every_geo_command_says_wrongtype() {
12889 let mut f = Fixture::new();
12890 f.run(&[b"SET", b"s", b"v"]);
12891 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12892 let cases: &[&[&[u8]]] = &[
12893 &[b"GEOADD", b"s", b"13", b"38", b"m"],
12894 &[b"GEOPOS", b"s", b"m"],
12895 &[b"GEOHASH", b"s", b"m"],
12896 &[b"GEODIST", b"s", b"a", b"b"],
12897 &[
12898 b"GEOSEARCH",
12899 b"s",
12900 b"FROMLONLAT",
12901 b"15",
12902 b"37",
12903 b"BYRADIUS",
12904 b"1",
12905 b"km",
12906 ],
12907 &[
12908 b"GEOSEARCHSTORE",
12909 b"d",
12910 b"s",
12911 b"FROMLONLAT",
12912 b"15",
12913 b"37",
12914 b"BYRADIUS",
12915 b"1",
12916 b"km",
12917 ],
12918 &[b"GEORADIUS", b"s", b"15", b"37", b"1", b"km"],
12919 &[b"GEORADIUS_RO", b"s", b"15", b"37", b"1", b"km"],
12920 &[b"GEORADIUSBYMEMBER", b"s", b"m", b"1", b"km"],
12921 &[b"GEORADIUSBYMEMBER_RO", b"s", b"m", b"1", b"km"],
12922 ];
12923 for case in cases {
12924 assert_eq!(f.run(case), wrong, "{:?}", case[0]);
12925 }
12926 assert_eq!(
12929 f.run(&[b"GEORADIUS", b"s", b"15", b"37", b"x", b"km"]),
12930 wrong
12931 );
12932 }
12933
12934 #[test]
12937 fn an_array_writes_at_any_index_and_reads_back_what_it_sent() {
12938 let mut f = Fixture::new();
12939 assert_eq!(
12942 f.run(&[b"ARSET", b"a", b"1000", b"x", b"y", b"z"]),
12943 ":3\r\n"
12944 );
12945 assert_eq!(f.run(&[b"ARSET", b"a", b"1000", b"X", b"Y"]), ":0\r\n");
12946 assert_eq!(f.run(&[b"ARGET", b"a", b"1000"]), "$1\r\nX\r\n");
12947 assert_eq!(f.run(&[b"ARGET", b"a", b"1002"]), "$1\r\nz\r\n");
12948 assert_eq!(f.run(&[b"ARGET", b"a", b"999"]), "$-1\r\n");
12950 assert_eq!(f.run(&[b"ARGET", b"nope", b"0"]), "$-1\r\n");
12951 assert_eq!(
12952 f.run(&[b"ARMGET", b"a", b"1002", b"999", b"1000"]),
12953 "*3\r\n$1\r\nz\r\n$-1\r\n$1\r\nX\r\n"
12954 );
12955 assert_eq!(f.run(&[b"ARMSET", b"a", b"5", b"p", b"5", b"q"]), ":1\r\n");
12957 assert_eq!(f.run(&[b"ARGET", b"a", b"5"]), "$1\r\nq\r\n");
12958 }
12959
12960 #[test]
12963 fn the_length_is_the_high_water_mark_and_the_count_is_the_population() {
12964 let mut f = Fixture::new();
12965 assert_eq!(f.run(&[b"ARLEN", b"nope"]), ":0\r\n");
12966 assert_eq!(f.run(&[b"ARCOUNT", b"nope"]), ":0\r\n");
12967 f.run(&[b"ARMSET", b"a", b"0", b"x", b"9", b"y"]);
12968 assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
12969 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
12970 assert_eq!(f.run(&[b"ARDEL", b"a", b"0"]), ":1\r\n");
12972 assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
12973 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":1\r\n");
12974
12975 f.run(&[b"ARSET", b"top", b"18446744073709551614", b"z"]);
12979 assert_eq!(f.run(&[b"ARLEN", b"top"]), ":18446744073709551615\r\n");
12980 assert_eq!(f.run(&[b"ARCOUNT", b"top"]), ":1\r\n");
12981 assert_eq!(
12984 f.run(&[b"ARSET", b"over", b"18446744073709551614", b"a", b"b"]),
12985 "-ERR array index overflow\r\n"
12986 );
12987 assert_eq!(f.run(&[b"EXISTS", b"over"]), ":0\r\n");
12988 }
12989
12990 #[test]
12993 fn a_range_read_answers_for_the_holes_too_and_is_capped_at_a_million() {
12994 let mut f = Fixture::new();
12995 f.run(&[b"ARSET", b"a", b"1", b"x"]);
12996 assert_eq!(
12997 f.run(&[b"ARGETRANGE", b"a", b"0", b"3"]),
12998 "*4\r\n$-1\r\n$1\r\nx\r\n$-1\r\n$-1\r\n"
12999 );
13000 assert_eq!(
13003 f.run(&[b"ARGETRANGE", b"a", b"3", b"0"]),
13004 "*4\r\n$-1\r\n$-1\r\n$1\r\nx\r\n$-1\r\n"
13005 );
13006 assert_eq!(
13008 f.run(&[b"ARGETRANGE", b"nope", b"0", b"1"]),
13009 "*2\r\n$-1\r\n$-1\r\n"
13010 );
13011 assert_eq!(
13015 f.run(&[b"ARGETRANGE", b"nope", b"0", b"18446744073709551614"]),
13016 "-ERR range exceeds maximum of 1000000 items\r\n"
13017 );
13018 }
13019
13020 #[test]
13023 fn a_bad_index_late_in_the_line_writes_none_of_the_earlier_ones() {
13024 let mut f = Fixture::new();
13025 assert_eq!(
13026 f.run(&[b"ARMSET", b"a", b"0", b"x", b"-1", b"y"]),
13027 "-ERR invalid array index\r\n"
13028 );
13029 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
13030 f.run(&[b"ARSET", b"a", b"0", b"x", b"y", b"z"]);
13031 assert_eq!(
13032 f.run(&[b"ARDEL", b"a", b"0", b"01"]),
13033 "-ERR invalid array index\r\n"
13034 );
13035 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":3\r\n");
13036 assert_eq!(
13039 f.run(&[b"ARGET", b"a", b"-1"]),
13040 "-ERR invalid array index\r\n"
13041 );
13042 assert_eq!(
13045 f.run(&[b"ARMSET", b"a", b"0", b"x", b"1"]),
13046 "-ERR wrong number of arguments for 'armset' command\r\n"
13047 );
13048 assert_eq!(
13049 f.run(&[b"ARDELRANGE", b"a", b"0", b"1", b"2"]),
13050 "-ERR wrong number of arguments for 'ardelrange' command\r\n"
13051 );
13052 }
13053
13054 #[test]
13055 fn a_range_delete_costs_the_elements_and_takes_the_key_when_it_empties() {
13056 let mut f = Fixture::new();
13057 f.run(&[b"ARSET", b"a", b"0", b"0", b"1", b"2", b"3", b"4"]);
13058 assert_eq!(f.run(&[b"ARDELRANGE", b"a", b"3", b"1"]), ":3\r\n");
13059 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
13060 assert_eq!(
13063 f.run(&[
13064 b"ARDELRANGE",
13065 b"a",
13066 b"100",
13067 b"200",
13068 b"0",
13069 b"18446744073709551614"
13070 ]),
13071 ":2\r\n"
13072 );
13073 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
13074 assert_eq!(f.run(&[b"ARDELRANGE", b"nope", b"0", b"1"]), ":0\r\n");
13075 assert_eq!(f.run(&[b"ARDEL", b"nope", b"0"]), ":0\r\n");
13076 }
13077
13078 #[test]
13081 fn a_value_comes_back_byte_for_byte_however_it_was_packed() {
13082 let mut f = Fixture::new();
13083 let long = vec![b'v'; 200];
13084 f.run(&[
13085 b"ARMSET", b"a", b"0", b"42", b"1", b"007", b"2", b"3.5", b"3", b"3.14", b"4",
13086 b"short", b"5", &long, b"6", b"-0",
13087 ]);
13088 assert_eq!(
13092 f.run(&[b"ARGETRANGE", b"a", b"0", b"6"]),
13093 format!(
13094 "*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",
13095 String::from_utf8_lossy(&long)
13096 )
13097 );
13098 }
13099
13100 #[test]
13101 fn an_array_is_a_type_and_an_encoding_a_client_can_see() {
13102 let mut f = Fixture::new();
13103 f.run(&[b"ARSET", b"a", b"0", b"x"]);
13104 assert_eq!(f.run(&[b"TYPE", b"a"]), "+array\r\n");
13105 assert_eq!(
13106 f.run(&[b"OBJECT", b"ENCODING", b"a"]),
13107 "$12\r\nsliced-array\r\n"
13108 );
13109 assert_eq!(f.run(&[b"EXPIRE", b"a", b"100"]), ":1\r\n");
13111 assert_eq!(f.run(&[b"PERSIST", b"a"]), ":1\r\n");
13112 assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":1\r\n");
13113 assert_eq!(f.run(&[b"ARGET", b"b", b"0"]), "$1\r\nx\r\n");
13114 assert_eq!(f.run(&[b"RENAME", b"a", b"c"]), "+OK\r\n");
13115 assert_eq!(f.run(&[b"ARCOUNT", b"c"]), ":1\r\n");
13116 }
13117
13118 #[test]
13119 fn every_array_command_refuses_a_key_holding_something_else() {
13120 let mut f = Fixture::new();
13121 f.run(&[b"SET", b"s", b"v"]);
13122 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13123 for cmd in [
13124 &[b"ARSET".as_ref(), b"s", b"0", b"x"][..],
13125 &[b"ARMSET".as_ref(), b"s", b"0", b"x"][..],
13126 &[b"ARGET".as_ref(), b"s", b"0"][..],
13127 &[b"ARMGET".as_ref(), b"s", b"0"][..],
13128 &[b"ARGETRANGE".as_ref(), b"s", b"0", b"1"][..],
13129 &[b"ARLEN".as_ref(), b"s"][..],
13130 &[b"ARCOUNT".as_ref(), b"s"][..],
13131 &[b"ARDEL".as_ref(), b"s", b"0"][..],
13132 &[b"ARDELRANGE".as_ref(), b"s", b"0", b"1"][..],
13133 &[b"ARINSERT".as_ref(), b"s", b"x"][..],
13134 &[b"ARRING".as_ref(), b"s", b"4", b"x"][..],
13135 &[b"ARNEXT".as_ref(), b"s"][..],
13136 &[b"ARSEEK".as_ref(), b"s", b"1"][..],
13137 &[b"ARLASTITEMS".as_ref(), b"s", b"1"][..],
13138 &[b"ARSCAN".as_ref(), b"s", b"0", b"1"][..],
13139 &[b"ARGREP".as_ref(), b"s", b"0", b"1", b"EXACT", b"v"][..],
13140 &[b"AROP".as_ref(), b"s", b"0", b"1", b"SUM"][..],
13141 &[b"ARINFO".as_ref(), b"s"][..],
13142 ] {
13143 assert_eq!(f.run(cmd), wrong, "{}", String::from_utf8_lossy(cmd[0]));
13144 }
13145 }
13146
13147 #[test]
13151 fn a_bad_index_reports_the_type_only_where_redis_reports_it() {
13152 let mut f = Fixture::new();
13153 f.run(&[b"SET", b"s", b"v"]);
13154 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13155 let bad = "-ERR invalid array index\r\n";
13156 assert_eq!(f.run(&[b"ARGET", b"s", b"-1"]), wrong);
13157 assert_eq!(f.run(&[b"ARMGET", b"s", b"0", b"-1"]), wrong);
13158 assert_eq!(f.run(&[b"ARSET", b"s", b"-1", b"x"]), bad);
13159 assert_eq!(f.run(&[b"ARDEL", b"s", b"-1"]), bad);
13160 assert_eq!(f.run(&[b"ARSCAN", b"s", b"-1", b"0"]), bad);
13161 assert_eq!(f.run(&[b"ARGREP", b"s", b"-1", b"0", b"EXACT", b"v"]), bad);
13162 f.run(&[b"ARSET", b"a", b"0", b"x"]);
13164 assert_eq!(f.run(&[b"ARGET", b"a", b"-1"]), bad);
13165 assert_eq!(f.run(&[b"ARGET", b"nope", b"-1"]), bad);
13166 }
13167
13168 #[test]
13169 fn an_append_follows_a_cursor_the_client_can_move() {
13170 let mut f = Fixture::new();
13171 assert_eq!(f.run(&[b"ARNEXT", b"nope"]), ":0\r\n");
13172 assert_eq!(f.run(&[b"ARINSERT", b"a", b"x", b"y"]), ":1\r\n");
13173 assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":2\r\n");
13174 assert_eq!(f.run(&[b"ARINSERT", b"a", b"z"]), ":2\r\n");
13175 assert_eq!(f.run(&[b"ARGET", b"a", b"2"]), "$1\r\nz\r\n");
13176
13177 assert_eq!(f.run(&[b"ARSEEK", b"nope", b"5"]), ":0\r\n");
13180 assert_eq!(f.run(&[b"EXISTS", b"nope"]), ":0\r\n");
13181 assert_eq!(f.run(&[b"ARSEEK", b"a", b"100"]), ":1\r\n");
13182 assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":100\r\n");
13183 assert_eq!(f.run(&[b"ARINSERT", b"a", b"far"]), ":100\r\n");
13184 assert_eq!(f.run(&[b"ARSEEK", b"a", b"0"]), ":1\r\n");
13185 assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":0\r\n");
13186
13187 assert_eq!(f.run(&[b"ARSEEK", b"a", b"18446744073709551615"]), ":1\r\n");
13190 assert_eq!(f.run(&[b"ARNEXT", b"a"]), "$-1\r\n");
13191 assert_eq!(
13192 f.run(&[b"ARINSERT", b"a", b"x"]),
13193 "-ERR insert index overflow\r\n"
13194 );
13195 assert_eq!(
13196 f.run(&[b"ARSET", b"a", b"18446744073709551615", b"x"]),
13197 "-ERR invalid array index\r\n"
13198 );
13199 }
13200
13201 #[test]
13202 fn a_ring_keeps_the_newest_and_renumbers_them_when_it_is_resized() {
13203 let mut f = Fixture::new();
13204 assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"a", b"b", b"c"]), ":2\r\n");
13205 assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"d", b"e"]), ":1\r\n");
13206 assert_eq!(f.run(&[b"ARLEN", b"r"]), ":3\r\n");
13207 assert_eq!(
13208 f.run(&[b"ARGETRANGE", b"r", b"0", b"2"]),
13209 "*3\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nc\r\n"
13210 );
13211 assert_eq!(f.run(&[b"ARRING", b"r", b"5", b"f"]), ":3\r\n");
13214 assert_eq!(
13215 f.run(&[b"ARGETRANGE", b"r", b"0", b"3"]),
13216 "*4\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nf\r\n"
13217 );
13218 assert_eq!(
13221 f.run(&[b"ARRING", b"r", b"0", b"x"]),
13222 "-ERR size must be positive\r\n"
13223 );
13224 assert_eq!(
13225 f.run(&[b"ARRING", b"r", b"big", b"x"]),
13226 "-ERR invalid size\r\n"
13227 );
13228 }
13229
13230 #[test]
13231 fn the_last_items_walk_back_from_the_cursor_and_report_the_holes() {
13232 let mut f = Fixture::new();
13233 assert_eq!(f.run(&[b"ARLASTITEMS", b"nope", b"5"]), "*0\r\n");
13234 f.run(&[b"ARRING", b"r", b"4", b"a", b"b", b"c", b"d", b"e"]);
13235 assert_eq!(
13236 f.run(&[b"ARLASTITEMS", b"r", b"3"]),
13237 "*3\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n"
13238 );
13239 assert_eq!(
13240 f.run(&[b"ARLASTITEMS", b"r", b"3", b"rev"]),
13241 "*3\r\n$1\r\ne\r\n$1\r\nd\r\n$1\r\nc\r\n"
13242 );
13243 assert_eq!(
13244 f.run(&[b"ARLASTITEMS", b"r", b"99"]),
13245 "*4\r\n$1\r\nb\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n",
13246 "more than there is gets what there is"
13247 );
13248 assert_eq!(f.run(&[b"ARLASTITEMS", b"r", b"0", b"junk"]), "*0\r\n");
13251 assert_eq!(
13252 f.run(&[b"ARLASTITEMS", b"r", b"1", b"junk"]),
13253 "-ERR syntax error\r\n"
13254 );
13255 assert_eq!(
13256 f.run(&[b"ARLASTITEMS", b"r", b"nine"]),
13257 "-ERR invalid COUNT\r\n"
13258 );
13259
13260 f.run(&[b"ARMSET", b"h", b"0", b"x", b"2", b"z"]);
13263 assert_eq!(
13264 f.run(&[b"ARLASTITEMS", b"h", b"5"]),
13265 "*2\r\n$-1\r\n$1\r\nz\r\n"
13266 );
13267 }
13268
13269 #[test]
13270 fn a_scan_answers_pairs_for_what_is_there_and_skips_what_is_not() {
13271 let mut f = Fixture::new();
13272 assert_eq!(f.run(&[b"ARSCAN", b"nope", b"0", b"10"]), "*0\r\n");
13273 f.run(&[b"ARMSET", b"a", b"0", b"x", b"7", b"y", b"1000000", b"z"]);
13274 assert_eq!(
13277 f.run(&[b"ARSCAN", b"a", b"0", b"18446744073709551614"]),
13278 "*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"
13279 );
13280 assert_eq!(
13281 f.run(&[
13282 b"ARSCAN",
13283 b"a",
13284 b"18446744073709551614",
13285 b"0",
13286 b"LIMIT",
13287 b"1"
13288 ]),
13289 "*1\r\n*2\r\n:1000000\r\n$1\r\nz\r\n"
13290 );
13291 assert_eq!(f.run(&[b"ARSCAN", b"a", b"1", b"6"]), "*0\r\n");
13292 assert_eq!(
13293 f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT", b"0"]),
13294 "-ERR LIMIT must be positive\r\n"
13295 );
13296 assert_eq!(
13297 f.run(&[b"ARSCAN", b"a", b"0", b"10", b"NOPE", b"1"]),
13298 "-ERR syntax error\r\n"
13299 );
13300 assert_eq!(
13301 f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT"]),
13302 "-ERR wrong number of arguments for 'arscan' command\r\n"
13303 );
13304 }
13305
13306 #[test]
13307 fn a_grep_answers_the_indexes_whose_elements_match() {
13308 let mut f = Fixture::new();
13309 assert_eq!(
13310 f.run(&[b"ARGREP", b"nope", b"0", b"10", b"EXACT", b"x"]),
13311 "*0\r\n"
13312 );
13313 f.run(&[b"ARSET", b"a", b"0", b"alpha", b"beta", b"gamma", b"ALPHA"]);
13314
13315 assert_eq!(
13318 f.run(&[b"ARGREP", b"a", b"-", b"+", b"GLOB", b"*a"]),
13319 "*3\r\n:0\r\n:1\r\n:2\r\n"
13320 );
13321 assert_eq!(
13322 f.run(&[b"ARGREP", b"a", b"+", b"-", b"GLOB", b"*a"]),
13323 "*3\r\n:2\r\n:1\r\n:0\r\n"
13324 );
13325 assert_eq!(
13326 f.run(&[b"ARGREP", b"a", b"1", b"2", b"GLOB", b"*a"]),
13327 "*2\r\n:1\r\n:2\r\n"
13328 );
13329
13330 assert_eq!(
13333 f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha"]),
13334 "*1\r\n:0\r\n"
13335 );
13336 assert_eq!(
13337 f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha", b"NOCASE"]),
13338 "*2\r\n:0\r\n:3\r\n"
13339 );
13340 assert_eq!(
13341 f.run(&[b"ARGREP", b"a", b"-", b"+", b"MATCH", b"mm"]),
13342 "*1\r\n:2\r\n"
13343 );
13344 assert_eq!(
13345 f.run(&[b"ARGREP", b"a", b"-", b"+", b"RE", b"^[bg]"]),
13346 "*2\r\n:1\r\n:2\r\n"
13347 );
13348
13349 let both: &[&[u8]] = &[
13352 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al",
13353 ];
13354 assert_eq!(f.run(both), "*2\r\n:0\r\n:1\r\n");
13355 assert_eq!(
13356 f.run(&[
13357 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND"
13358 ]),
13359 "*0\r\n"
13360 );
13361 assert_eq!(
13362 f.run(&[
13363 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND", b"OR"
13364 ]),
13365 "*2\r\n:0\r\n:1\r\n"
13366 );
13367
13368 assert_eq!(
13371 f.run(&[
13372 b"ARGREP",
13373 b"a",
13374 b"-",
13375 b"+",
13376 b"MATCH",
13377 b"a",
13378 b"WITHVALUES",
13379 b"LIMIT",
13380 b"2"
13381 ]),
13382 "*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"
13383 );
13384 assert_eq!(
13385 f.run(&[
13386 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"ALPHA", b"LIMIT", b"1"
13387 ]),
13388 "*1\r\n:3\r\n"
13389 );
13390 }
13391
13392 #[test]
13394 fn a_grep_reports_a_broken_command_the_way_redis_does() {
13395 let mut f = Fixture::new();
13396 f.run(&[b"ARSET", b"a", b"0", b"alpha"]);
13397 let syntax = "-ERR syntax error\r\n";
13398
13399 assert_eq!(
13402 f.run(&[b"ARGREP", b"a", b"-1", b"0", b"NOPE", b"x"]),
13403 "-ERR invalid array index\r\n"
13404 );
13405 assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOPE", b"x"]), syntax);
13406 assert_eq!(
13408 f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"EXACT"]),
13409 syntax
13410 );
13411 assert_eq!(
13412 f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT"]),
13413 syntax
13414 );
13415 assert_eq!(
13416 f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"WITHVALUES"]),
13417 syntax,
13418 "a command with no predicate in it at all"
13419 );
13420 assert_eq!(
13421 f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"0"]),
13422 "-ERR LIMIT must be positive\r\n"
13423 );
13424 assert_eq!(
13425 f.run(&[
13426 b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"nine"
13427 ]),
13428 "-ERR value is not an integer or out of range\r\n"
13429 );
13430 assert_eq!(
13431 f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b""]),
13432 "-ERR regular expression is empty\r\n"
13433 );
13434 assert_eq!(
13435 f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b"(a"]),
13436 "-ERR invalid regular expression: Missing ')'\r\n"
13437 );
13438 assert_eq!(
13439 f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", br"(a)\1"]),
13440 "-ERR regular expression backreferences are not supported\r\n"
13441 );
13442 let arity = "-ERR wrong number of arguments for 'argrep' command\r\n";
13445 assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT"]), arity);
13446 assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1"]), arity);
13447 }
13448
13449 #[test]
13450 fn an_op_reduces_a_range_to_one_number() {
13451 let mut f = Fixture::new();
13452 f.run(&[b"ARSET", b"a", b"0", b"1", b"2.5", b"word", b"-4"]);
13453 assert_eq!(
13454 f.run(&[b"AROP", b"a", b"0", b"10", b"SUM"]),
13455 "$4\r\n-0.5\r\n"
13456 );
13457 assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"min"]), "$2\r\n-4\r\n");
13458 assert_eq!(
13459 f.run(&[b"AROP", b"a", b"0", b"10", b"MAX"]),
13460 "$3\r\n2.5\r\n"
13461 );
13462 assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"USED"]), ":4\r\n");
13463 assert_eq!(
13464 f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH", b"word"]),
13465 ":1\r\n"
13466 );
13467 f.run(&[b"ARSET", b"t", b"0", b"0.1", b"0.2"]);
13470 assert_eq!(
13471 f.run(&[b"AROP", b"t", b"0", b"10", b"SUM"]),
13472 "$19\r\n0.30000000000000004\r\n"
13473 );
13474 assert_eq!(f.run(&[b"ZADD", b"z", b"0.3", b"m"]), ":1\r\n");
13475 assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\n0.3\r\n");
13476
13477 f.run(&[b"ARSET", b"w", b"0", b"word"]);
13480 assert_eq!(f.run(&[b"AROP", b"w", b"0", b"10", b"SUM"]), "$-1\r\n");
13481 assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"SUM"]), "$-1\r\n");
13482 assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"USED"]), ":0\r\n");
13483
13484 assert_eq!(
13485 f.run(&[b"AROP", b"a", b"0", b"10", b"NOPE"]),
13486 "-ERR unknown operation\r\n"
13487 );
13488 assert_eq!(
13489 f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH"]),
13490 "-ERR MATCH requires a value argument\r\n"
13491 );
13492 assert_eq!(
13493 f.run(&[b"AROP", b"a", b"0", b"10", b"SUM", b"extra"]),
13494 "-ERR wrong number of arguments for 'arop' command\r\n"
13495 );
13496 }
13497
13498 #[test]
13499 fn the_info_is_a_map_and_a_missing_key_is_an_error() {
13500 let mut f = Fixture::new();
13501 assert_eq!(f.run(&[b"ARINFO", b"nope"]), "-ERR no such key\r\n");
13502 f.run(&[b"ARINSERT", b"a", b"x", b"y"]);
13503 let short = f.run(&[b"ARINFO", b"a"]);
13504 assert!(
13505 short.starts_with("*14\r\n"),
13506 "seven pairs on RESP2: {short}"
13507 );
13508 assert!(short.contains("$5\r\ncount\r\n:2\r\n"), "{short}");
13509 assert!(
13510 short.contains("$17\r\nnext-insert-index\r\n:2\r\n"),
13511 "{short}"
13512 );
13513 assert!(short.contains("$10\r\nslice-size\r\n:4096\r\n"), "{short}");
13514 let full = f.run(&[b"ARINFO", b"a", b"full"]);
13515 assert!(full.starts_with("*24\r\n"), "twelve pairs: {full}");
13516 assert!(full.contains("$12\r\ndense-slices\r\n:0\r\n"), "{full}");
13519 assert!(full.contains("$13\r\nsparse-slices\r\n:1\r\n"), "{full}");
13520 assert!(
13521 full.contains("$14\r\navg-dense-size\r\n$1\r\n0\r\n"),
13522 "{full}"
13523 );
13524 assert_eq!(f.run(&[b"ARINFO", b"a", b"nope"]), "-ERR syntax error\r\n");
13525
13526 let mut g = Fixture::new();
13528 g.run(&[b"HELLO", b"3"]);
13529 g.run(&[b"ARINSERT", b"a", b"x"]);
13530 let map = g.run(&[b"ARINFO", b"a", b"FULL"]);
13531 assert!(map.starts_with("%12\r\n"), "{map}");
13532 assert!(map.contains("$5\r\ncount\r\n:1\r\n"), "{map}");
13533 assert!(map.contains("$14\r\navg-dense-size\r\n,0\r\n"), "{map}");
13534 }
13535
13536 #[test]
13537 fn a_double_on_the_wire_is_written_the_way_redis_writes_one() {
13538 let mut f = Fixture::new();
13539 for (score, want) in [
13542 ("3", "3"),
13543 ("3.5", "3.5"),
13544 ("0.3", "0.3"),
13545 ("1e30", "1e+30"),
13546 ("1e19", "1e+19"),
13547 ("1e-7", "1e-7"),
13548 ("0.000001", "0.000001"),
13549 ("4611686018427387904", "4611686018427387904"),
13550 ("-0", "-0"),
13551 ] {
13552 f.run(&[b"ZADD", b"z", score.as_bytes(), b"m"]);
13553 assert_eq!(
13554 f.run(&[b"ZSCORE", b"z", b"m"]),
13555 format!("${}\r\n{want}\r\n", want.len()),
13556 "score {score}"
13557 );
13558 }
13559
13560 let mut g = Fixture::new();
13563 g.run(&[b"HELLO", b"3"]);
13564 g.run(&[b"ZADD", b"z", b"1e30", b"m"]);
13565 assert_eq!(g.run(&[b"ZSCORE", b"z", b"m"]), ",1e+30\r\n");
13566 assert_eq!(
13571 g.run(&[b"INCRBYFLOAT", b"s", b"1e30"]),
13572 "$31\r\n1000000000000000000000000000000\r\n"
13573 );
13574 assert_eq!(g.run(&[b"INCRBYFLOAT", b"t", b"0.1"]), "$3\r\n0.1\r\n");
13575 assert_eq!(
13576 g.run(&[b"HINCRBYFLOAT", b"h", b"f", b"1e19"]),
13577 "$20\r\n10000000000000000000\r\n"
13578 );
13579 }
13580
13581 #[test]
13584 fn a_node_comes_back_with_the_fields_it_went_in_with() {
13585 let mut f = Fixture::new();
13586 assert_eq!(
13587 f.run(&[
13588 b"G.NADD", b"social", b"ada", b"name", b"Ada", b"born", b"1815"
13589 ]),
13590 ":1\r\n"
13591 );
13592 assert_eq!(
13598 f.run(&[b"G.NGET", b"social", b"ada"]),
13599 "*4\r\n$4\r\nborn\r\n$4\r\n1815\r\n$4\r\nname\r\n$3\r\nAda\r\n"
13600 );
13601 assert_eq!(
13604 f.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada Lovelace"]),
13605 ":0\r\n"
13606 );
13607 assert_eq!(
13608 f.run(&[b"G.NGET", b"social", b"ada"]),
13609 "*2\r\n$4\r\nname\r\n$12\r\nAda Lovelace\r\n"
13610 );
13611 assert_eq!(f.run(&[b"G.NADD", b"social", b"grace"]), ":1\r\n");
13614 assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
13615 assert_eq!(f.run(&[b"G.NGET", b"social", b"nobody"]), "$-1\r\n");
13616 assert_eq!(f.run(&[b"G.NGET", b"nokey", b"ada"]), "$-1\r\n");
13617
13618 assert_eq!(
13621 f.run(&[b"G.NADD", b"fresh", b"n", b"lonely"]),
13622 "-ERR syntax error\r\n"
13623 );
13624 assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
13625
13626 let mut g = Fixture::new();
13628 g.run(&[b"HELLO", b"3"]);
13629 g.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada"]);
13630 assert_eq!(
13631 g.run(&[b"G.NGET", b"social", b"ada"]),
13632 "%1\r\n$4\r\nname\r\n$3\r\nAda\r\n"
13633 );
13634 }
13635
13636 #[test]
13637 fn an_edge_creates_the_ends_it_needs() {
13638 let mut f = Fixture::new();
13639 assert_eq!(
13640 f.run(&[
13641 b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1843"
13642 ]),
13643 ":1\r\n"
13644 );
13645 assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "*0\r\n");
13647 assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
13648 assert_eq!(
13649 f.run(&[b"G.OUT", b"social", b"ada", b"FOLLOWS"]),
13650 "*2\r\n$1\r\n0\r\n*1\r\n$5\r\ngrace\r\n"
13651 );
13652 assert_eq!(
13653 f.run(&[b"G.IN", b"social", b"grace", b"FOLLOWS"]),
13654 "*2\r\n$1\r\n0\r\n*1\r\n$3\r\nada\r\n"
13655 );
13656 assert_eq!(
13659 f.run(&[
13660 b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1844"
13661 ]),
13662 ":0\r\n"
13663 );
13664 assert_eq!(f.run(&[b"G.DEG", b"social", b"ada", b"FOLLOWS"]), ":1\r\n");
13665 assert_eq!(
13667 f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"WORKS_WITH"]),
13668 ":1\r\n"
13669 );
13670 assert_eq!(
13671 f.run(&[b"G.DEG", b"social", b"ada", b"WORKS_WITH"]),
13672 ":1\r\n"
13673 );
13674
13675 assert_eq!(
13676 f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
13677 ":1\r\n"
13678 );
13679 assert_eq!(
13680 f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
13681 ":0\r\n"
13682 );
13683 assert_eq!(
13686 f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"NEVER"]),
13687 ":0\r\n"
13688 );
13689 assert_eq!(
13690 f.run(&[b"G.EDEL", b"social", b"ada", b"nobody", b"FOLLOWS"]),
13691 ":0\r\n"
13692 );
13693 assert_eq!(
13694 f.run(&[b"G.EDEL", b"nokey", b"ada", b"grace", b"FOLLOWS"]),
13695 ":0\r\n"
13696 );
13697 }
13698
13699 #[test]
13702 fn a_hop_answers_a_cursor_and_a_page() {
13703 let mut f = Fixture::new();
13704 for i in 0..25u32 {
13705 let dst = format!("n{i}");
13706 f.run(&[b"G.EADD", b"social", b"hub", dst.as_bytes(), b"FOLLOWS"]);
13707 }
13708 let first = f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS"]);
13710 assert!(first.starts_with("*2\r\n$2\r\n10\r\n*10\r\n"), "{first}");
13711
13712 let mut seen = 0;
13713 let mut cursor = String::from("0");
13714 loop {
13715 let page = f.run(&[
13716 b"G.OUT",
13717 b"social",
13718 b"hub",
13719 b"FOLLOWS",
13720 b"COUNT",
13721 b"7",
13722 b"CURSOR",
13723 cursor.as_bytes(),
13724 ]);
13725 let (head, rest) = page.split_once("\r\n*").expect("a cursor and a page");
13726 cursor = head
13727 .rsplit("\r\n")
13728 .next()
13729 .expect("the cursor line")
13730 .to_string();
13731 seen += rest
13732 .split_once("\r\n")
13733 .expect("the page length")
13734 .0
13735 .parse::<usize>()
13736 .expect("a length");
13737 if cursor == "0" {
13738 break;
13739 }
13740 }
13741 assert_eq!(seen, 25, "every neighbour once across the pages");
13742
13743 assert_eq!(
13746 f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"CURSOR", b"900"]),
13747 "*2\r\n$1\r\n0\r\n*0\r\n"
13748 );
13749 assert_eq!(
13750 f.run(&[b"G.OUT", b"social", b"hub", b"NEVER"]),
13751 "*2\r\n$1\r\n0\r\n*0\r\n"
13752 );
13753 assert_eq!(
13754 f.run(&[b"G.OUT", b"nokey", b"hub", b"FOLLOWS"]),
13755 "*2\r\n$1\r\n0\r\n*0\r\n"
13756 );
13757 assert_eq!(
13758 f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"COUNT", b"0"]),
13759 "-ERR COUNT must be a positive integer\r\n"
13760 );
13761 assert_eq!(
13762 f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"NOPE", b"1"]),
13763 "-ERR syntax error\r\n"
13764 );
13765 }
13766
13767 #[test]
13768 fn a_degree_counts_one_way_or_both() {
13769 let mut f = Fixture::new();
13770 f.run(&[b"G.EADD", b"social", b"a", b"b", b"F"]);
13771 f.run(&[b"G.EADD", b"social", b"a", b"c", b"F"]);
13772 f.run(&[b"G.EADD", b"social", b"d", b"a", b"F"]);
13773 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F"]), ":2\r\n");
13774 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"OUT"]), ":2\r\n");
13775 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"IN"]), ":1\r\n");
13776 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"BOTH"]), ":3\r\n");
13777 assert_eq!(f.run(&[b"G.DEG", b"social", b"nobody", b"F"]), ":0\r\n");
13778 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"NEVER"]), ":0\r\n");
13779 assert_eq!(f.run(&[b"G.DEG", b"nokey", b"a", b"F"]), ":0\r\n");
13780 assert_eq!(
13781 f.run(&[b"G.DEG", b"social", b"a", b"F", b"SIDEWAYS"]),
13782 "-ERR syntax error\r\n"
13783 );
13784 }
13785
13786 #[test]
13789 fn a_walk_reaches_each_node_once_however_many_ways_there_are() {
13790 let mut f = Fixture::new();
13791 for (src, dst) in [
13792 ("ada", "grace"),
13793 ("ada", "alan"),
13794 ("grace", "edsger"),
13795 ("alan", "edsger"),
13796 ("edsger", "barbara"),
13797 ] {
13798 f.run(&[b"G.EADD", b"social", src.as_bytes(), dst.as_bytes(), b"F"]);
13799 }
13800 assert_eq!(
13803 f.run(&[b"G.NEIGH", b"social", b"ada", b"F"]),
13804 "*3\r\n$5\r\ngrace\r\n$4\r\nalan\r\n$6\r\nedsger\r\n"
13805 );
13806 assert_eq!(
13807 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"1"]),
13808 "*2\r\n$5\r\ngrace\r\n$4\r\nalan\r\n"
13809 );
13810 let deep = f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"9"]);
13811 assert!(deep.starts_with("*4\r\n"), "the whole component: {deep}");
13812 assert!(deep.contains("$7\r\nbarbara\r\n"), "{deep}");
13813 assert_eq!(
13815 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"COUNT", b"1"]),
13816 "*1\r\n$5\r\ngrace\r\n"
13817 );
13818 assert_eq!(f.run(&[b"G.NEIGH", b"social", b"barbara", b"F"]), "*0\r\n");
13820 assert_eq!(f.run(&[b"G.NEIGH", b"social", b"ada", b"NEVER"]), "*0\r\n");
13821 assert_eq!(f.run(&[b"G.NEIGH", b"nokey", b"ada", b"F"]), "*0\r\n");
13822 assert_eq!(
13823 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"0"]),
13824 "-ERR DEPTH must be a positive integer\r\n"
13825 );
13826 assert_eq!(
13827 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"NOPE", b"1"]),
13828 "-ERR syntax error\r\n"
13829 );
13830 }
13831
13832 #[test]
13835 fn a_path_is_the_shortest_one_and_goes_over_any_label() {
13836 let mut f = Fixture::new();
13837 for i in 0..6u32 {
13840 let src = format!("n{i}");
13841 let dst = format!("n{}", i + 1);
13842 f.run(&[b"G.EADD", b"road", src.as_bytes(), dst.as_bytes(), b"STEP"]);
13843 }
13844 assert_eq!(
13845 f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
13846 "*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"
13847 );
13848 f.run(&[b"G.EADD", b"road", b"n0", b"n5", b"JUMP"]);
13849 assert_eq!(
13850 f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
13851 "*3\r\n$2\r\nn0\r\n$2\r\nn5\r\n$2\r\nn6\r\n"
13852 );
13853 assert_eq!(
13856 f.run(&[b"G.PATH", b"road", b"n2", b"n2"]),
13857 "*1\r\n$2\r\nn2\r\n"
13858 );
13859 assert_eq!(
13860 f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"MAXDEPTH", b"1"]),
13861 "*0\r\n"
13862 );
13863 assert_eq!(f.run(&[b"G.PATH", b"road", b"n6", b"n0"]), "*0\r\n");
13865 f.run(&[b"G.NADD", b"road", b"island"]);
13868 assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"island"]), "*0\r\n");
13869 assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"nobody"]), "*0\r\n");
13870 assert_eq!(f.run(&[b"G.PATH", b"nokey", b"n0", b"n6"]), "*0\r\n");
13871 assert_eq!(
13872 f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"NOPE", b"3"]),
13873 "-ERR syntax error\r\n"
13874 );
13875 }
13876
13877 #[test]
13881 fn the_keyspace_sees_a_graph_key_like_any_other() {
13882 let mut f = Fixture::new();
13883 f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"F"]);
13884 assert_eq!(f.run(&[b"TYPE", b"social"]), "+graph\r\n");
13885 assert_eq!(
13886 f.run(&[b"OBJECT", b"ENCODING", b"social"]),
13887 "$9\r\nadjacency\r\n"
13888 );
13889 assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
13890 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
13891 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$6\r\nsocial\r\n");
13892 let held = f.server.memory_bytes();
13896 for i in 0..200u32 {
13897 let dst = format!("n{i}");
13898 f.run(&[b"G.EADD", b"big", b"hub", dst.as_bytes(), b"F"]);
13899 }
13900 assert!(
13901 f.server.memory_bytes() > held,
13902 "two hundred edges cost something: {held} then {}",
13903 f.server.memory_bytes()
13904 );
13905 f.run(&[b"DEL", b"big"]);
13906
13907 assert_eq!(f.run(&[b"EXPIRE", b"social", b"100"]), ":1\r\n");
13910 assert_eq!(f.run(&[b"PERSIST", b"social"]), ":1\r\n");
13911 assert_eq!(f.run(&[b"RENAME", b"social", b"net"]), "+OK\r\n");
13912 assert_eq!(f.run(&[b"MOVE", b"net", b"1"]), ":1\r\n");
13913 assert_eq!(f.run(&[b"EXISTS", b"net"]), ":0\r\n");
13914 f.run(&[b"SELECT", b"1"]);
13915 assert_eq!(f.run(&[b"G.DEG", b"net", b"ada", b"F"]), ":1\r\n");
13916
13917 assert_eq!(f.run(&[b"DEL", b"net"]), ":1\r\n");
13918 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
13919 f.run(&[b"G.NADD", b"g", b"n"]);
13920 assert_eq!(f.run(&[b"FLUSHDB"]), "+OK\r\n");
13921 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
13922 }
13923
13924 #[test]
13927 fn a_graph_cannot_be_copied_or_dumped() {
13928 let mut f = Fixture::new();
13929 f.run(&[b"G.NADD", b"social", b"ada"]);
13930 assert_eq!(
13931 f.run(&[b"COPY", b"social", b"other"]),
13932 "-ERR COPY is not supported for a graph\r\n"
13933 );
13934 assert_eq!(
13935 f.run(&[b"COPY", b"social", b"other", b"DB", b"1"]),
13936 "-ERR COPY is not supported for a graph\r\n"
13937 );
13938 assert_eq!(
13939 f.run(&[b"DUMP", b"social"]),
13940 "-ERR DUMP is not supported for a graph\r\n"
13941 );
13942 assert_eq!(f.run(&[b"EXISTS", b"social", b"other"]), ":1\r\n");
13944 }
13945
13946 #[test]
13949 fn a_graph_and_a_string_are_the_wrong_type_for_each_other() {
13950 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13951 let mut f = Fixture::new();
13952 f.run(&[b"G.NADD", b"social", b"ada"]);
13953 assert_eq!(f.run(&[b"GET", b"social"]), wrong);
13954 assert_eq!(f.run(&[b"LPUSH", b"social", b"x"]), wrong);
13955 assert_eq!(f.run(&[b"SADD", b"social", b"x"]), wrong);
13956
13957 f.run(&[b"SET", b"str", b"v"]);
13958 for cmd in [
13959 vec![b"G.NADD".as_ref(), b"str", b"n"],
13960 vec![b"G.NGET".as_ref(), b"str", b"n"],
13961 vec![b"G.NDEL".as_ref(), b"str", b"n"],
13962 vec![b"G.EADD".as_ref(), b"str", b"a", b"b", b"F"],
13963 vec![b"G.EDEL".as_ref(), b"str", b"a", b"b", b"F"],
13964 vec![b"G.OUT".as_ref(), b"str", b"a", b"F"],
13965 vec![b"G.IN".as_ref(), b"str", b"a", b"F"],
13966 vec![b"G.DEG".as_ref(), b"str", b"a", b"F"],
13967 vec![b"G.NEIGH".as_ref(), b"str", b"a", b"F"],
13968 vec![b"G.PATH".as_ref(), b"str", b"a", b"b"],
13969 ] {
13970 assert_eq!(f.run(&cmd), wrong, "{:?}", cmd[0]);
13971 }
13972 }
13973
13974 #[test]
13977 fn a_graph_goes_when_its_last_node_does() {
13978 let mut f = Fixture::new();
13979 f.run(&[
13980 b"G.EADD", b"social", b"ada", b"grace", b"F", b"since", b"1843",
13981 ]);
13982 assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":1\r\n");
13983 assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "$-1\r\n");
13985 assert_eq!(
13986 f.run(&[b"G.DEG", b"social", b"grace", b"F", b"IN"]),
13987 ":0\r\n"
13988 );
13989 assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":0\r\n");
13990 assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
13991
13992 assert_eq!(f.run(&[b"G.NDEL", b"social", b"grace"]), ":1\r\n");
13993 assert_eq!(f.run(&[b"EXISTS", b"social"]), ":0\r\n");
13994 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
13995 assert_eq!(f.run(&[b"G.NDEL", b"nokey", b"ada"]), ":0\r\n");
13996
13997 f.run(&[b"G.NADD", b"social", b"first"]);
14000 f.run(&[b"G.NADD", b"social", b"second"]);
14001 f.run(&[b"G.NDEL", b"social", b"first"]);
14002 f.run(&[b"G.EADD", b"social", b"third", b"second", b"F"]);
14003 assert_eq!(
14004 f.run(&[b"G.OUT", b"social", b"third", b"F"]),
14005 "*2\r\n$1\r\n0\r\n*1\r\n$6\r\nsecond\r\n"
14006 );
14007 }
14008
14009 #[test]
14014 fn a_json_path_answers_a_set_and_a_legacy_path_answers_a_value() {
14015 let mut f = Fixture::new();
14016 let doc = br#"{"a":1,"b":{"c":true}}"#;
14017 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$", doc]), "+OK\r\n");
14018 assert_eq!(
14021 f.run(&[b"JSON.GET", b"doc"]),
14022 bulk(r#"{"a":1,"b":{"c":true}}"#)
14023 );
14024 assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.a"]), bulk("[1]"));
14025 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("1"));
14026 assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$..c"]), bulk("[true]"));
14027 assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.nope"]), bulk("[]"));
14030 assert_eq!(
14031 f.run(&[b"JSON.GET", b"doc", b".nope"]),
14032 "-ERR Path does not exist\r\n"
14033 );
14034 assert_eq!(f.run(&[b"JSON.GET", b"nokey"]), "$-1\r\n");
14035 assert_eq!(f.run(&[b"TYPE", b"doc"]), "+ReJSON-RL\r\n");
14038 assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":1\r\n");
14039 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"doc"]), bulk("raw"));
14040 assert_eq!(f.run(&[b"DEL", b"doc"]), ":1\r\n");
14041 assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
14042 }
14043
14044 #[test]
14050 fn the_two_json_errors_that_carry_no_prefix() {
14051 let mut f = Fixture::new();
14052 f.run(&[b"SET", b"plain", b"x"]);
14053 let wrong = "-Existing key has wrong Redis type\r\n";
14054 assert_eq!(f.run(&[b"JSON.GET", b"plain"]), wrong);
14055 assert_eq!(f.run(&[b"JSON.SET", b"plain", b"$", b"1"]), wrong);
14056 assert_eq!(f.run(&[b"JSON.DEL", b"plain"]), wrong);
14057 assert_eq!(f.run(&[b"JSON.TYPE", b"plain"]), wrong);
14058 assert_eq!(f.run(&[b"JSON.CLEAR", b"plain"]), wrong);
14059
14060 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"z":1},"b":{"z":2}}"#]);
14061 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.*.z", b"9"]), "+OK\r\n");
14065 assert_eq!(
14066 f.run(&[b"JSON.GET", b"doc"]),
14067 bulk(r#"{"a":{"z":9},"b":{"z":9}}"#)
14068 );
14069 assert_eq!(
14070 f.run(&[b"JSON.SET", b"doc", b"$.*.y", b"9"]),
14071 "-Err wrong static path\r\n"
14072 );
14073 }
14074
14075 #[test]
14077 fn json_set_creates_one_field_and_refuses_to_invent_the_rest() {
14078 let mut f = Fixture::new();
14079 assert_eq!(
14081 f.run(&[b"JSON.SET", b"new", b".a", b"1"]),
14082 "-ERR new objects must be created at the root\r\n"
14083 );
14084 assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
14085 assert_eq!(
14088 f.run(&[b"JSON.SET", b"new", b".a", b"1", b"NX"]),
14089 "-ERR new objects must be created at the root\r\n"
14090 );
14091 assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"XX"]), "$-1\r\n");
14092 assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"NX"]), "+OK\r\n");
14093
14094 f.run(&[
14095 b"JSON.SET",
14096 b"doc",
14097 b"$",
14098 br#"{"o":{},"arr":[1,2],"s":"x"}"#,
14099 ]);
14100 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"1"]), "+OK\r\n");
14102 assert_eq!(
14105 f.run(&[b"JSON.SET", b"doc", b"$.nope.made", b"1"]),
14106 "$-1\r\n"
14107 );
14108 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.s.made", b"1"]), "$-1\r\n");
14109 assert_eq!(
14111 f.run(&[b"JSON.SET", b"doc", b"$.arr[5]", b"9"]),
14112 "-ERR array index out of range\r\n"
14113 );
14114 assert_eq!(
14115 f.run(&[b"JSON.SET", b"doc", b"$.arr[2]", b"9"]),
14116 "-ERR array index out of range\r\n"
14117 );
14118 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.arr[1]", b"9"]), "+OK\r\n");
14119 assert_eq!(
14122 f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"2", b"NX"]),
14123 "$-1\r\n"
14124 );
14125 assert_eq!(
14126 f.run(&[b"JSON.SET", b"doc", b"$.gone", b"2", b"XX"]),
14127 "$-1\r\n"
14128 );
14129 assert_eq!(
14130 f.run(&[b"JSON.GET", b"doc"]),
14131 bulk(r#"{"o":{"made":1},"s":"x","arr":[1,9]}"#)
14132 );
14133 assert!(
14137 f.run(&[b"JSON.SET", b"doc", b"$.s", b"nope"])
14138 .starts_with("-this is not the start of a value")
14139 );
14140 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".s"]), bulk("\"x\""));
14141 }
14142
14143 #[test]
14146 fn the_json_commands_that_do_not_answer_text() {
14147 let mut f = Fixture::new();
14148 let doc = br#"{"a":1,"t":true,"o":{"x":1},"arr":[1,2],"f":1.5,"s":"x","n":null}"#;
14149 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14150
14151 assert_eq!(f.run(&[b"JSON.TYPE", b"doc"]), bulk("object"));
14152 assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".a"]), bulk("integer"));
14153 assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".f"]), bulk("number"));
14154 assert_eq!(
14155 f.run(&[b"JSON.TYPE", b"doc", b"$.a"]),
14156 format!("*1\r\n{}", bulk("integer"))
14157 );
14158 assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".nope"]), "$-1\r\n");
14161 assert_eq!(f.run(&[b"JSON.TYPE", b"nokey"]), "$-1\r\n");
14162
14163 assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.t"]), "*1\r\n:0\r\n");
14166 assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b".t"]), bulk("true"));
14167 assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.a"]), "*1\r\n$-1\r\n");
14170 assert_eq!(
14171 f.run(&[b"JSON.TOGGLE", b"doc", b".a"]),
14172 "-ERR Path does not exist or not a bool\r\n"
14173 );
14174 assert_eq!(
14175 f.run(&[b"JSON.TOGGLE", b"doc", b".nope"]),
14176 "-ERR Path does not exist or not a bool\r\n"
14177 );
14178 assert_eq!(
14179 f.run(&[b"JSON.TOGGLE", b"nokey", b"$.a"]),
14180 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14181 );
14182
14183 assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.s"]), ":0\r\n");
14186 assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":4\r\n");
14187 assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":0\r\n");
14188 assert_eq!(
14189 f.run(&[b"JSON.GET", b"doc"]),
14190 bulk(r#"{"a":0,"f":0,"n":null,"o":{},"s":"x","t":true,"arr":[]}"#)
14191 );
14192
14193 assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.nope"]), ":0\r\n");
14196 assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.a"]), ":1\r\n");
14197 assert_eq!(f.run(&[b"JSON.FORGET", b"doc", b"$.*"]), ":6\r\n");
14202 assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":0\r\n");
14203 assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
14204 assert_eq!(f.run(&[b"JSON.DEL", b"doc"]), ":0\r\n");
14205 assert_eq!(f.run(&[b"JSON.SET", b"empty", b"$", b"{}"]), "+OK\r\n");
14206 assert_eq!(f.run(&[b"EXISTS", b"empty"]), ":1\r\n");
14207 assert_eq!(f.run(&[b"JSON.GET", b"empty"]), bulk("{}"));
14208 assert_eq!(f.run(&[b"JSON.DEL", b"nokey"]), ":0\r\n");
14209 }
14210
14211 #[test]
14218 fn json_get_lays_out_the_wrapper_it_builds() {
14219 let mut f = Fixture::new();
14220 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[1,{"c":2}]}"#]);
14221
14222 assert_eq!(
14223 f.run(&[b"JSON.GET", b"doc", b"$.a", b"$.b"]),
14224 bulk(r#"{"$.a":[1],"$.b":[[1,{"c":2}]]}"#)
14225 );
14226 assert_eq!(
14228 f.run(&[b"JSON.GET", b"doc", b".a", b".b"]),
14229 bulk(r#"{".a":1,".b":[1,{"c":2}]}"#)
14230 );
14231 let fmt: &[&[u8]] = &[b"INDENT", b" ", b"NEWLINE", b"\n", b"SPACE", b" "];
14232 let mut one = vec![b"JSON.GET".as_slice(), b"doc"];
14233 one.extend_from_slice(fmt);
14234 one.push(b"$.b");
14235 assert_eq!(
14236 f.run(&one),
14237 bulk("[\n [\n 1,\n {\n \"c\": 2\n }\n ]\n]")
14238 );
14239 let mut two = vec![b"JSON.GET".as_slice(), b"doc"];
14240 two.extend_from_slice(fmt);
14241 two.push(b"$.a");
14242 two.push(b"$.nope");
14243 assert_eq!(
14244 f.run(&two),
14245 bulk("{\n \"$.a\": [\n 1\n ],\n \"$.nope\": []\n}")
14246 );
14247 let mut root = vec![b"JSON.GET".as_slice(), b"doc", b"SPACE", b" "];
14250 root.push(b".a");
14251 assert_eq!(f.run(&root), bulk("1"));
14252 }
14253
14254 #[test]
14257 fn json_mget_answers_once_per_key_whatever_is_under_them() {
14258 let mut f = Fixture::new();
14259 f.run(&[b"JSON.SET", b"one", b"$", br#"{"a":1}"#]);
14260 f.run(&[b"JSON.SET", b"two", b"$", br#"{"a":2}"#]);
14261 f.run(&[b"SET", b"plain", b"x"]);
14262 assert_eq!(
14263 f.run(&[b"JSON.MGET", b"one", b"two", b"$.a"]),
14264 format!("*2\r\n{}{}", bulk("[1]"), bulk("[2]"))
14265 );
14266 assert_eq!(
14269 f.run(&[b"JSON.MGET", b"one", b"nokey", b"plain", b".a"]),
14270 format!("*3\r\n{}$-1\r\n$-1\r\n", bulk("1"))
14271 );
14272 assert_eq!(f.run(&[b"JSON.MGET", b"one", b".nope"]), "*1\r\n$-1\r\n");
14275 }
14276
14277 #[test]
14285 fn the_json_commands_that_answer_a_size_disagree_about_every_failure() {
14286 let mut f = Fixture::new();
14287 let doc = br#"{"a":[1,2,3],"o":{"x":1,"y":2},"s":"hello","n":7}"#;
14288 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14289
14290 assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b".a"]), ":3\r\n");
14291 assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b"$.a"]), "*1\r\n:3\r\n");
14292 assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".o"]), ":2\r\n");
14293 assert_eq!(f.run(&[b"JSON.STRLEN", b"doc", b".s"]), ":5\r\n");
14294 assert_eq!(
14295 f.run(&[b"JSON.OBJKEYS", b"doc", b".o"]),
14296 format!("*2\r\n{}{}", bulk("x"), bulk("y"))
14297 );
14298 assert_eq!(
14301 f.run(&[b"JSON.ARRLEN", b"doc", b"$.*"]),
14302 "*4\r\n:3\r\n$-1\r\n$-1\r\n$-1\r\n"
14303 );
14304
14305 assert_eq!(
14308 f.run(&[b"JSON.ARRLEN", b"doc", b".nope"]),
14309 "-ERR Path does not exist\r\n"
14310 );
14311 assert_eq!(
14312 f.run(&[b"JSON.STRLEN", b"doc", b".nope"]),
14313 "-ERR Path does not exist\r\n"
14314 );
14315 assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".nope"]), "$-1\r\n");
14316 assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b".nope"]), "$-1\r\n");
14319 assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b"$.nope"]), "*0\r\n");
14322
14323 assert_eq!(
14327 f.run(&[b"JSON.ARRLEN", b"doc", b".n"]),
14328 "-ERR Path does not exist or not an array\r\n"
14329 );
14330 assert_eq!(
14331 f.run(&[b"JSON.OBJKEYS", b"doc", b".n"]),
14332 "-ERR Path does not exist or not an object\r\n"
14333 );
14334 assert_eq!(
14335 f.run(&[b"JSON.OBJLEN", b"doc", b".n"]),
14336 "-WRONGTYPE wrong type of path value - expected object\r\n"
14337 );
14338 assert_eq!(
14339 f.run(&[b"JSON.STRLEN", b"doc", b".n"]),
14340 "-WRONGTYPE wrong type of path value - expected string\r\n"
14341 );
14342
14343 assert_eq!(f.run(&[b"JSON.ARRLEN", b"nokey", b".a"]), "$-1\r\n");
14346 assert_eq!(f.run(&[b"JSON.OBJLEN", b"nokey", b".a"]), "$-1\r\n");
14347 assert_eq!(f.run(&[b"JSON.STRLEN", b"nokey", b".a"]), "$-1\r\n");
14348 assert_eq!(f.run(&[b"JSON.OBJKEYS", b"nokey", b".a"]), "$-1\r\n");
14349 assert_eq!(
14350 f.run(&[b"JSON.ARRLEN", b"nokey", b"$.a"]),
14351 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14352 );
14353 assert_eq!(
14355 f.run(&[b"JSON.OBJLEN", b"nokey", b"$.a"]),
14356 "-ERR Path does not exist or not an object\r\n"
14357 );
14358 }
14359
14360 #[test]
14366 fn the_json_array_writes_agree_on_the_errors_and_not_on_the_indexes() {
14367 let mut f = Fixture::new();
14368 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3],"n":7}"#]);
14369
14370 assert_eq!(f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"4"]), ":4\r\n");
14371 assert_eq!(
14372 f.run(&[b"JSON.ARRAPPEND", b"doc", b"$.a", b"5", b"6"]),
14373 "*1\r\n:6\r\n"
14374 );
14375 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[1,2,3,4,5,6]"));
14376
14377 assert_eq!(
14380 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-1", b"0"]),
14381 ":7\r\n"
14382 );
14383 assert_eq!(
14384 f.run(&[b"JSON.GET", b"doc", b".a"]),
14385 bulk("[1,2,3,4,5,0,6]")
14386 );
14387 assert_eq!(
14388 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"7", b"9"]),
14389 ":8\r\n"
14390 );
14391 assert_eq!(
14393 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"9", b"9"]),
14394 "-ERR index out of bounds\r\n"
14395 );
14396 assert_eq!(
14397 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-9", b"9"]),
14398 "-ERR index out of bounds\r\n"
14399 );
14400
14401 f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3,4,5]"]);
14404 assert_eq!(
14405 f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"1", b"3"]),
14406 ":3\r\n"
14407 );
14408 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[2,3,4]"));
14409 assert_eq!(
14410 f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"-2", b"99"]),
14411 ":2\r\n"
14412 );
14413 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[3,4]"));
14414 assert_eq!(
14415 f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"9", b"9"]),
14416 ":0\r\n"
14417 );
14418 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
14419
14420 f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3]"]);
14423 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), bulk("3"));
14424 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"0"]), bulk("1"));
14425 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"99"]), bulk("2"));
14426 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), "$-1\r\n");
14427
14428 for call in [
14431 &[&b"JSON.ARRAPPEND"[..], b"doc", b"PATH", b"1"][..],
14432 &[&b"JSON.ARRTRIM"[..], b"doc", b"PATH", b"1", b"1"][..],
14433 &[&b"JSON.ARRPOP"[..], b"doc", b"PATH", b"1"][..],
14434 &[&b"JSON.ARRINSERT"[..], b"doc", b"PATH", b"0", b"1"][..],
14435 ] {
14436 for path in [&b".n"[..], &b".nope"[..]] {
14437 let args: Vec<&[u8]> = call
14438 .iter()
14439 .map(|a| if *a == b"PATH" { path } else { *a })
14440 .collect();
14441 assert_eq!(
14442 f.run(&args),
14443 "-ERR Path does not exist or not an array\r\n",
14444 "{} {}",
14445 String::from_utf8_lossy(call[0]),
14446 String::from_utf8_lossy(path)
14447 );
14448 }
14449 }
14450
14451 assert_eq!(
14454 f.run(&[b"JSON.ARRAPPEND", b"nokey", b".a", b"1"]),
14455 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14456 );
14457 assert_eq!(
14458 f.run(&[b"JSON.ARRPOP", b"nokey", b"$.a"]),
14459 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14460 );
14461
14462 assert!(
14467 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"nope"])
14468 .starts_with("-this is not the start of a value")
14469 );
14470 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
14471 }
14472
14473 #[test]
14481 fn json_arrinsert_is_all_or_nothing_across_the_matches() {
14482 let mut f = Fixture::new();
14483 let doc = br#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#;
14484 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14485 assert_eq!(
14486 f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"-2", b"0"]),
14487 "-ERR index out of bounds\r\n"
14488 );
14489 assert_eq!(
14490 f.run(&[b"JSON.GET", b"doc"]),
14491 bulk(r#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#)
14492 );
14493 assert_eq!(
14495 f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"0", b"0"]),
14496 "*3\r\n:4\r\n:3\r\n:2\r\n"
14497 );
14498 assert_eq!(
14499 f.run(&[b"JSON.GET", b"doc"]),
14500 bulk(r#"{"a":[0,1,2,3],"n":{"a":[0,9,8],"in":{"a":[0,1]}}}"#)
14501 );
14502 }
14503
14504 #[test]
14512 fn json_arrindex_has_an_exclusive_stop_and_a_start_that_cannot_run_off_the_end() {
14513 let mut f = Fixture::new();
14514 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3,1],"n":7}"#]);
14515
14516 assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"2"]), ":1\r\n");
14517 assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"9"]), ":-1\r\n");
14518 assert_eq!(
14519 f.run(&[b"JSON.ARRINDEX", b"doc", b"$.a", b"2"]),
14520 "*1\r\n:1\r\n"
14521 );
14522
14523 assert_eq!(
14526 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"0"]),
14527 ":3\r\n"
14528 );
14529 assert_eq!(
14532 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"3"]),
14533 ":-1\r\n"
14534 );
14535
14536 for start in [&b"4"[..], &b"5"[..], &b"-1"[..]] {
14539 assert_eq!(
14540 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", start]),
14541 ":3\r\n",
14542 "{}",
14543 String::from_utf8_lossy(start)
14544 );
14545 }
14546 assert_eq!(
14547 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"-100"]),
14548 ":0\r\n"
14549 );
14550 f.run(&[b"JSON.SET", b"doc", b"$.a", b"[]"]);
14553 assert_eq!(
14554 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1"]),
14555 ":-1\r\n"
14556 );
14557
14558 f.run(&[b"JSON.SET", b"doc", b"$.a", br#"[{"k":1},[1,2],"s"]"#]);
14562 assert_eq!(
14563 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", br#"{"k":1}"#]),
14564 ":0\r\n"
14565 );
14566 assert_eq!(
14567 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[1,2]"]),
14568 ":1\r\n"
14569 );
14570 assert_eq!(
14571 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[2,1]"]),
14572 ":-1\r\n"
14573 );
14574
14575 assert_eq!(
14579 f.run(&[b"JSON.ARRINDEX", b"doc", b".nope", b"1"]),
14580 "-ERR Path does not exist\r\n"
14581 );
14582 assert_eq!(
14583 f.run(&[b"JSON.ARRINDEX", b"doc", b".n", b"1"]),
14584 "-WRONGTYPE wrong type of path value - expected array\r\n"
14585 );
14586 assert_eq!(
14587 f.run(&[b"JSON.ARRINDEX", b"nokey", b".a", b"1"]),
14588 "-ERR Path does not exist\r\n"
14589 );
14590 assert_eq!(
14591 f.run(&[b"JSON.ARRINDEX", b"nokey", b"$.a", b"1"]),
14592 "-ERR Path does not exist\r\n"
14593 );
14594 }
14595
14596 #[test]
14599 fn the_json_number_family_answers_json_text_and_keeps_its_integers() {
14600 let mut f = Fixture::new();
14601 let doc = br#"{"i":7,"f":1.5,"neg":-2,"s":"ab"}"#;
14602 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14603
14604 assert_eq!(
14607 f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2"]),
14608 bulk("9").as_str()
14609 );
14610 assert_eq!(
14612 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.i", b"2"]),
14613 bulk("[11]").as_str()
14614 );
14615 assert_eq!(
14618 f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2.0"]),
14619 bulk("13.0").as_str()
14620 );
14621 assert_eq!(
14622 f.run(&[b"JSON.TYPE", b"doc", b".i"]),
14623 bulk("number").as_str()
14624 );
14625 assert_eq!(
14626 f.run(&[b"JSON.NUMMULTBY", b"doc", b".f", b"2"]),
14627 bulk("3.0").as_str()
14628 );
14629 assert_eq!(
14630 f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"3"]),
14631 bulk("-8").as_str()
14632 );
14633 f.run(&[b"JSON.SET", b"doc", b"$.f", b"1.5"]);
14636 assert_eq!(
14637 f.run(&[b"JSON.NUMPOWBY", b"doc", b".f", b"0.5"]),
14638 bulk("1.224744871391589").as_str()
14639 );
14640 assert_eq!(
14641 f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"0.5"]),
14642 "-ERR result is not a number\r\n"
14643 );
14644 f.run(&[b"JSON.SET", b"doc", b"$.big", b"9223372036854775807"]);
14648 assert_eq!(
14649 f.run(&[b"JSON.NUMINCRBY", b"doc", b".big", b"1"]),
14650 "-ERR numeric overflow\r\n"
14651 );
14652 f.run(&[b"JSON.SET", b"doc", b"$.p", b"2"]);
14653 assert_eq!(
14654 f.run(&[b"JSON.NUMPOWBY", b"doc", b".p", b"-1"]),
14655 "-ERR numeric overflow\r\n"
14656 );
14657 f.run(&[b"JSON.SET", b"doc", b"$.huge", b"1e308"]);
14659 assert_eq!(
14660 f.run(&[b"JSON.NUMMULTBY", b"doc", b".huge", b"1e10"]),
14661 "-ERR result is not a number\r\n"
14662 );
14663
14664 assert_eq!(
14668 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"1"]),
14669 bulk("[null]").as_str()
14670 );
14671 assert_eq!(
14672 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.nope", b"1"]),
14673 bulk("[]").as_str()
14674 );
14675 assert_eq!(
14676 f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", b"1"]),
14677 "-ERR Path does not exist or does not contains a number\r\n"
14678 );
14679 assert_eq!(
14680 f.run(&[b"JSON.NUMINCRBY", b"doc", b".nope", b"1"]),
14681 "-ERR Path does not exist or does not contains a number\r\n"
14682 );
14683 assert_eq!(
14686 f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"true"]),
14687 "-bad input number\r\n"
14688 );
14689 assert_eq!(
14690 f.run(&[b"JSON.NUMINCRBY", b"nokey", b".i", b"1"]),
14691 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14692 );
14693 assert_eq!(
14694 f.run(&[b"JSON.NUMINCRBY", b"nokey", b"$.i", b"1"]),
14695 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14696 );
14697 }
14698
14699 #[test]
14702 fn json_strappend_reads_its_shape_off_the_argument_count() {
14703 let mut f = Fixture::new();
14704 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"s":"ab","n":1}"#]);
14705
14706 assert_eq!(
14707 f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""c""#]),
14708 ":3\r\n"
14709 );
14710 assert_eq!(
14711 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", br#""d""#]),
14712 "*1\r\n:4\r\n"
14713 );
14714 assert_eq!(
14717 f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""\u00e9""#]),
14718 ":6\r\n"
14719 );
14720 f.run(&[b"JSON.SET", b"str", b"$", br#""ab""#]);
14723 assert_eq!(f.run(&[b"JSON.STRAPPEND", b"str", br#""c""#]), ":3\r\n");
14724 assert_eq!(f.run(&[b"JSON.GET", b"str"]), bulk("\"abc\"").as_str());
14725
14726 assert_eq!(
14730 f.run(&[b"JSON.STRAPPEND", b"doc", b".s", b"5"]),
14731 "-WRONGTYPE wrong type of path value - expected string\r\n"
14732 );
14733 assert_eq!(
14734 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", br#""c""#]),
14735 "*1\r\n$-1\r\n"
14736 );
14737 assert_eq!(
14738 f.run(&[b"JSON.STRAPPEND", b"doc", b".n", br#""c""#]),
14739 "-ERR Path does not exist or not a string\r\n"
14740 );
14741 assert_eq!(
14742 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.nope", br#""c""#]),
14743 "*0\r\n"
14744 );
14745 assert_eq!(
14746 f.run(&[b"JSON.STRAPPEND", b"nokey", br#""c""#]),
14747 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14748 );
14749 }
14750
14751 #[test]
14754 fn a_legacy_wildcard_write_touches_every_match_and_answers_only_one() {
14755 let mut f = Fixture::new();
14756 let three = br#"{"a":[[7],[7,7],[7,7,7]]}"#;
14759
14760 f.run(&[b"JSON.SET", b"doc", b"$", three]);
14761 assert_eq!(
14762 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
14763 ":4\r\n"
14764 );
14765 f.run(&[b"JSON.SET", b"doc", b"$", three]);
14766 assert_eq!(
14767 f.run(&[b"JSON.ARRINSERT", b"doc", b".a[*]", b"0", b"9"]),
14768 ":2\r\n"
14769 );
14770 f.run(&[b"JSON.SET", b"doc", b"$", three]);
14771 assert_eq!(
14772 f.run(&[b"JSON.ARRTRIM", b"doc", b".a[*]", b"0", b"1"]),
14773 ":1\r\n"
14774 );
14775 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[1,2,3],[4,5,6]]}"#]);
14776 assert_eq!(
14777 f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]", b"0"]),
14778 bulk("1").as_str()
14779 );
14780 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3]}"#]);
14781 assert_eq!(
14782 f.run(&[b"JSON.NUMINCRBY", b"doc", b".a[*]", b"10"]),
14783 bulk("13").as_str()
14784 );
14785 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["p","qq","rrr"]}"#]);
14786 assert_eq!(
14787 f.run(&[b"JSON.STRAPPEND", b"doc", b".a[*]", br#""z""#]),
14788 ":4\r\n"
14789 );
14790 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[true,false,true]}"#]);
14791 assert_eq!(
14792 f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
14793 bulk("false").as_str()
14794 );
14795 assert_eq!(
14798 f.run(&[b"JSON.GET", b"doc", b".a"]),
14799 bulk("[false,true,false]").as_str()
14800 );
14801
14802 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x",[1],[1,2]]}"#]);
14805 assert_eq!(
14806 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
14807 ":3\r\n"
14808 );
14809 assert_eq!(
14810 f.run(&[b"JSON.GET", b"doc", b".a"]),
14811 bulk(r#"["x",[1,9],[1,2,9]]"#).as_str()
14812 );
14813 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x","y"]}"#]);
14816 assert_eq!(
14817 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
14818 "-ERR Path does not exist or not an array\r\n"
14819 );
14820 assert_eq!(
14821 f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
14822 "-ERR Path does not exist or not a bool\r\n"
14823 );
14824 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[],[2,3]]}"#]);
14828 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]"]), "$-1\r\n");
14829 assert_eq!(
14830 f.run(&[b"JSON.GET", b"doc", b".a"]),
14831 bulk("[[],[2]]").as_str()
14832 );
14833 }
14834
14835 #[test]
14838 fn a_write_reaches_a_match_that_sits_inside_another_match() {
14839 let mut f = Fixture::new();
14840 let nested = br#"{"a":[{"a":[7]},{"a":[7,7]}]}"#;
14841
14842 f.run(&[b"JSON.SET", b"doc", b"$", nested]);
14843 assert_eq!(
14844 f.run(&[b"JSON.ARRAPPEND", b"doc", b"$..a", b"9"]),
14845 "*3\r\n:3\r\n:2\r\n:3\r\n"
14846 );
14847 assert_eq!(
14848 f.run(&[b"JSON.GET", b"doc", b"$"]),
14849 bulk(r#"[{"a":[{"a":[7,9]},{"a":[7,7,9]},9]}]"#).as_str()
14850 );
14851
14852 f.run(&[b"JSON.SET", b"doc", b"$", nested]);
14855 assert_eq!(
14856 f.run(&[b"JSON.ARRTRIM", b"doc", b"$..a", b"0", b"0"]),
14857 "*3\r\n:1\r\n:1\r\n:1\r\n"
14858 );
14859 assert_eq!(
14860 f.run(&[b"JSON.GET", b"doc", b"$"]),
14861 bulk(r#"[{"a":[{"a":[7]}]}]"#).as_str()
14862 );
14863
14864 f.run(&[b"JSON.SET", b"doc", b"$", nested]);
14867 assert_eq!(
14868 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$..a[0]", b"1"]),
14869 bulk("[null,8,8]").as_str()
14870 );
14871 }
14872
14873 #[test]
14876 fn a_bad_operand_is_not_the_answer_when_the_path_found_nothing_to_use_it_on() {
14877 let mut f = Fixture::new();
14878 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"n":7,"s":"t"}"#]);
14879
14880 assert_eq!(
14883 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", br#""x""#]),
14884 bulk("[null]").as_str()
14885 );
14886 assert_eq!(
14887 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"notjson"]),
14888 bulk("[null]").as_str()
14889 );
14890 assert_eq!(
14891 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.missing", b"notjson"]),
14892 bulk("[]").as_str()
14893 );
14894 assert_eq!(
14895 f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", br#""x""#]),
14896 "-ERR Path does not exist or does not contains a number\r\n"
14897 );
14898 assert_eq!(
14900 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.n", br#""x""#]),
14901 "-bad input number\r\n"
14902 );
14903
14904 assert_eq!(
14906 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", b"1"]),
14907 "*1\r\n$-1\r\n"
14908 );
14909 assert_eq!(
14910 f.run(&[b"JSON.STRAPPEND", b"doc", b".n", b"1"]),
14911 "-ERR Path does not exist or not a string\r\n"
14912 );
14913 assert_eq!(
14914 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", b"1"]),
14915 "-WRONGTYPE wrong type of path value - expected string\r\n"
14916 );
14917
14918 assert_eq!(
14920 f.run(&[b"JSON.NUMINCRBY", b"nope", b"$.a", br#""x""#]),
14921 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14922 );
14923 assert_eq!(
14924 f.run(&[b"JSON.STRAPPEND", b"nope", b"$.a", b"1"]),
14925 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14926 );
14927 }
14928
14929 #[test]
14932 fn a_merge_patch_adds_replaces_and_deletes_in_one_write() {
14933 let mut f = Fixture::new();
14934
14935 assert_eq!(
14939 f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"x":null,"y":1}"#]),
14940 "+OK\r\n"
14941 );
14942 assert_eq!(
14943 f.run(&[b"JSON.GET", b"doc", b"$"]),
14944 bulk(r#"[{"x":null,"y":1}]"#).as_str()
14945 );
14946
14947 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1,"c":2},"d":3}"#]);
14950 assert_eq!(
14951 f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"a":{"b":null,"e":4}}"#]),
14952 "+OK\r\n"
14953 );
14954 assert_eq!(
14955 f.run(&[b"JSON.GET", b"doc", b"$"]),
14956 bulk(r#"[{"a":{"c":2,"e":4},"d":3}]"#).as_str()
14957 );
14958
14959 assert_eq!(f.run(&[b"JSON.MERGE", b"doc", b"$.a", b"[1,2]"]), "+OK\r\n");
14961 assert_eq!(
14962 f.run(&[b"JSON.GET", b"doc", b"$"]),
14963 bulk(r#"[{"a":[1,2],"d":3}]"#).as_str()
14964 );
14965
14966 assert_eq!(
14970 f.run(&[b"JSON.MERGE", b"doc", b"$.d", br#"{"p":null,"q":9}"#]),
14971 "+OK\r\n"
14972 );
14973 assert_eq!(
14974 f.run(&[b"JSON.GET", b"doc", b"$"]),
14975 bulk(r#"[{"a":[1,2],"d":{"q":9}}]"#).as_str()
14976 );
14977
14978 assert_eq!(
14982 f.run(&[b"JSON.MERGE", b"doc", b"$.new", br#"{"z":null}"#]),
14983 "+OK\r\n"
14984 );
14985 assert_eq!(
14986 f.run(&[b"JSON.GET", b"doc", b"$.new"]),
14987 bulk(r#"[{"z":null}]"#).as_str()
14988 );
14989 assert_eq!(
14990 f.run(&[b"JSON.MERGE", b"doc", b"$.no.deep", b"1"]),
14991 "$-1\r\n"
14992 );
14993 assert_eq!(
14994 f.run(&[b"JSON.MERGE", b"doc", b"$.no.*", b"1"]),
14995 "-Err wrong static path\r\n"
14996 );
14997
14998 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"n":1},"b":{"n":2}}"#]);
15000 assert_eq!(
15001 f.run(&[b"JSON.MERGE", b"doc", b"$.*", br#"{"m":0}"#]),
15002 "+OK\r\n"
15003 );
15004 assert_eq!(
15005 f.run(&[b"JSON.GET", b"doc", b"$"]),
15006 bulk(r#"[{"a":{"m":0,"n":1},"b":{"m":0,"n":2}}]"#).as_str()
15007 );
15008
15009 assert_eq!(
15011 f.run(&[b"JSON.MERGE", b"doc", b"$", b"{}", b"more"]),
15012 "-ERR syntax error\r\n"
15013 );
15014 assert_eq!(
15015 f.run(&[b"JSON.MERGE", b"gone", b"$.a", b"1"]),
15016 "-ERR new objects must be created at the root\r\n"
15017 );
15018 f.run(&[b"SET", b"str", b"x"]);
15019 assert_eq!(
15020 f.run(&[b"JSON.MERGE", b"str", b"$", b"1"]),
15021 "-Existing key has wrong Redis type\r\n"
15022 );
15023 }
15024
15025 #[test]
15028 fn a_merge_down_a_descent_keeps_what_the_inner_match_did() {
15029 let mut f = Fixture::new();
15030 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
15031 assert_eq!(
15032 f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"m":1}"#]),
15033 "+OK\r\n"
15034 );
15035 assert_eq!(
15039 f.run(&[b"JSON.GET", b"doc", b"$"]),
15040 bulk(r#"[{"a":{"b":{"m":1},"m":1},"c":{"m":1}}]"#).as_str()
15041 );
15042
15043 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
15046 assert_eq!(
15047 f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"a":null}"#]),
15048 "+OK\r\n"
15049 );
15050 assert_eq!(
15051 f.run(&[b"JSON.GET", b"doc", b"$"]),
15052 bulk(r#"[{"a":{"b":{}},"c":{}}]"#).as_str()
15053 );
15054 }
15055
15056 #[test]
15059 fn a_filter_path_reads_and_writes_the_members_it_keeps() {
15060 let mut f = Fixture::new();
15061 let doc = br#"{"book":[{"t":"a","p":8},{"t":"b","p":13},{"t":"c","p":9}],"cap":10}"#;
15062 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
15063
15064 assert_eq!(
15065 f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < 10)].t"]),
15066 bulk(r#"["a","c"]"#).as_str()
15067 );
15068 assert_eq!(
15071 f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < $.cap)].t"]),
15072 bulk(r#"["a","c"]"#).as_str()
15073 );
15074 assert_eq!(
15076 f.run(&[b"JSON.GET", b"doc", b"book[?(@.p < 10)].t"]),
15077 bulk(r#""a""#).as_str()
15078 );
15079 assert_eq!(
15080 f.run(&[b"JSON.TYPE", b"doc", b"$.book[?(@.p > 10)]"]),
15081 "*1\r\n$6\r\nobject\r\n"
15082 );
15083
15084 assert_eq!(
15088 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.book[?(@.p < 10)].p", b"1"]),
15089 bulk("[9,10]").as_str()
15090 );
15091 assert_eq!(
15092 f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].t", br#""B""#]),
15093 "+OK\r\n"
15094 );
15095 assert_eq!(
15096 f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].n", b"1"]),
15097 "-Err wrong static path\r\n"
15098 );
15099 assert_eq!(
15100 f.run(&[b"JSON.DEL", b"doc", b"$.book[?(@.p > 9)]"]),
15101 ":2\r\n"
15102 );
15103 assert_eq!(
15104 f.run(&[b"JSON.GET", b"doc", b"$"]),
15105 bulk(r#"[{"cap":10,"book":[{"p":9,"t":"a"}]}]"#).as_str()
15106 );
15107
15108 assert!(
15111 f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p <)]"])
15112 .starts_with("-ERR")
15113 );
15114 assert!(
15115 f.run(&[b"JSON.GET", b"nokey", b"$.book[?(@.p <)]"])
15116 .starts_with("-ERR")
15117 );
15118 }
15119
15120 #[test]
15123 fn a_filter_takes_the_membership_operators_and_the_methods_too() {
15124 let mut f = Fixture::new();
15125 let doc = br#"{"box":[{"t":"a","n":[1,2],"g":"x"},{"t":"b","n":[9],"g":"y"}]}"#;
15126 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
15127
15128 for (path, want) in [
15129 (&b"$.box[?(@.g in [\"x\"])].t"[..], r#"["a"]"#),
15130 (b"$.box[?(@.g nin [\"x\"])].t", r#"["b"]"#),
15131 (b"$.box[?(@.n anyof [2,3])].t", r#"["a"]"#),
15132 (b"$.box[?(@.n subsetof [1,2,3])].t", r#"["a"]"#),
15133 (b"$.box[?(@.n size 2)].t", r#"["a"]"#),
15134 (b"$.box[?(@.n empty false)].t", r#"["a","b"]"#),
15135 (b"$.box[?(@.n.length() == 1)].t", r#"["b"]"#),
15136 (b"$.box[?(@.n.sum() > 5)].t", r#"["b"]"#),
15137 (b"$.box[?(@.n[0] + 1 == 2)].t", r#"["a"]"#),
15138 (b"$.box[?(@~ size 3)].t", r#"["a","b"]"#),
15139 (b"$.box[?(@.n~)].t", "[]"),
15140 (b"$.box[?(@.n sizeof 2)].t", r#"["a"]"#),
15141 (b"$.box[?(-@.n[0] == -9)].t", r#"["b"]"#),
15142 (b"$.box[?(1 in @.n)].t", r#"["a"]"#),
15143 (b"$.box[?(\"g\" in @~)].t", r#"["a","b"]"#),
15144 ] {
15145 assert_eq!(f.run(&[b"JSON.GET", b"doc", path]), bulk(want).as_str());
15146 }
15147
15148 assert_eq!(
15151 f.run(&[b"JSON.SET", b"doc", b"$.box[?(@.n size 1)].g", br#""z""#]),
15152 "+OK\r\n"
15153 );
15154 assert_eq!(
15155 f.run(&[b"JSON.GET", b"doc", b"$.box[?(@.g == \"z\")].t"]),
15156 bulk(r#"["b"]"#).as_str()
15157 );
15158 }
15159
15160 #[test]
15163 fn a_merge_onto_a_number_inside_an_array_is_a_merge_and_not_an_error() {
15164 let mut f = Fixture::new();
15165 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2]}"#]);
15166 assert_eq!(
15167 f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
15168 "+OK\r\n"
15169 );
15170 assert_eq!(
15171 f.run(&[b"JSON.GET", b"doc", b"$"]),
15172 bulk(r#"[{"a":[{"x":1},2]}]"#).as_str()
15173 );
15174 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,"s"]}"#]);
15177 assert_eq!(
15178 f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
15179 "+OK\r\n"
15180 );
15181 assert_eq!(
15182 f.run(&[b"JSON.GET", b"doc", b"$"]),
15183 bulk(r#"[{"a":[{"x":1},"s"]}]"#).as_str()
15184 );
15185 }
15186
15187 #[test]
15190 fn an_mset_writes_every_triple_it_can_and_checks_the_rest_up_front() {
15191 let mut f = Fixture::new();
15192 assert_eq!(
15193 f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b", b"$", b"2"]),
15194 "+OK\r\n"
15195 );
15196 assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[1]").as_str());
15197 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[2]").as_str());
15198
15199 assert_eq!(
15201 f.run(&[b"JSON.MSET", b"a", b"$", b"3", b"a", b"$", b"4"]),
15202 "+OK\r\n"
15203 );
15204 assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[4]").as_str());
15205
15206 f.run(&[b"JSON.SET", b"a", b"$", br#"{"n":1}"#]);
15211 assert_eq!(
15212 f.run(&[b"JSON.MSET", b"a", b"$.no.deep", b"9", b"b", b"$", b"5"]),
15213 "$-1\r\n"
15214 );
15215 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[5]").as_str());
15216 assert_eq!(
15217 f.run(&[b"JSON.MSET", b"b", b"$", b"6", b"a", b"$.no.deep", b"9"]),
15218 "$-1\r\n"
15219 );
15220 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
15221
15222 f.run(&[b"SET", b"str", b"x"]);
15227 assert_eq!(
15228 f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"b", b"$", b"notjson"]),
15229 "-this is not the start of a value, at byte 0 of the JSON text\r\n"
15230 );
15231 assert_eq!(
15232 f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"str", b"$", b"1"]),
15233 "-Existing key has wrong Redis type\r\n"
15234 );
15235 assert_eq!(
15236 f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"gone", b"$.x", b"1"]),
15237 "-ERR new objects must be created at the root\r\n"
15238 );
15239 assert_eq!(f.run(&[b"JSON.GET", b"a", b"$.n"]), bulk("[1]").as_str());
15240
15241 assert_eq!(
15246 f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$.no.*", b"9"]),
15247 "-Err wrong static path\r\n"
15248 );
15249 assert_eq!(
15250 f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$[0]", b"9"]),
15251 "-ERR array index out of range\r\n"
15252 );
15253 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
15254
15255 f.run(&[b"JSON.SET", b"c", b"$", br#"{"n":1}"#]);
15259 assert_eq!(
15260 f.run(&[b"JSON.MSET", b"c", b"$", br#"{"n":2}"#, b"c", b"$.n", b"3"]),
15261 "+OK\r\n"
15262 );
15263 assert_eq!(
15264 f.run(&[b"JSON.GET", b"c", b"$"]),
15265 bulk(r#"[{"n":3}]"#).as_str()
15266 );
15267
15268 assert_eq!(
15271 f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b"]),
15272 "-ERR wrong number of arguments for 'json.mset' command\r\n"
15273 );
15274 }
15275
15276 #[test]
15279 fn json_resp_answers_the_document_as_resp_types() {
15280 let mut f = Fixture::new();
15281 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[2,"c"]}"#]);
15282 assert_eq!(
15283 f.run(&[b"JSON.RESP", b"doc"]),
15284 "*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"
15285 );
15286 assert_eq!(
15288 f.run(&[b"JSON.RESP", b"doc", b"$.b"]),
15289 "*1\r\n*3\r\n+[\r\n:2\r\n$1\r\nc\r\n"
15290 );
15291
15292 f.run(&[
15293 b"JSON.SET",
15294 b"doc",
15295 b"$",
15296 br#"{"f":2.5,"t":true,"z":null,"e":[],"o":{}}"#,
15297 ]);
15298 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".e"]), "*1\r\n+[\r\n");
15299 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".o"]), "*1\r\n+{\r\n");
15300 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".f"]), bulk("2.5").as_str());
15303 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".t"]), "+true\r\n");
15304 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".z"]), "$-1\r\n");
15305
15306 assert_eq!(
15309 f.run(&[b"JSON.RESP", b"doc", b".nope"]),
15310 "-ERR Path does not exist\r\n"
15311 );
15312 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b"$.nope"]), "*0\r\n");
15313 assert_eq!(f.run(&[b"JSON.RESP", b"gone"]), "$-1\r\n");
15314 assert_eq!(f.run(&[b"JSON.RESP", b"gone", b"$"]), "$-1\r\n");
15315 }
15316
15317 #[test]
15321 fn json_debug_answers_a_byte_count_and_its_own_help() {
15322 let mut f = Fixture::new();
15323 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2],"s":"hello"}"#]);
15324 let one = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".s"]);
15325 assert!(one.starts_with(':'), "{one}");
15326 assert_eq!(
15327 f.run(&[b"JSON.DEBUG", b"memory", b"doc", b"$.s"]),
15328 format!("*1\r\n{one}")
15329 );
15330 let whole = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc"]);
15331 assert!(whole.starts_with(':') && whole.len() > one.len(), "{whole}");
15332
15333 assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone"]), ":0\r\n");
15337 assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone", b"$"]), "*0\r\n");
15338 assert_eq!(
15339 f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".nope"]),
15340 "-ERR Path does not exist\r\n"
15341 );
15342 assert_eq!(
15343 f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b"$.nope"]),
15344 "*0\r\n"
15345 );
15346
15347 assert_eq!(
15348 f.run(&[b"JSON.DEBUG", b"HELP"]),
15349 "*2\r\n$42\r\nMEMORY <key> [path] - reports memory usage\r\n\
15350 $34\r\nHELP - this message\r\n"
15351 );
15352 assert_eq!(
15353 f.run(&[b"JSON.DEBUG", b"NOPE"]),
15354 "-ERR unknown subcommand - try `JSON.DEBUG HELP`\r\n"
15355 );
15356 assert_eq!(
15357 f.run(&[b"JSON.DEBUG", b"MEMORY"]),
15358 "-ERR wrong number of arguments for 'json.debug' command\r\n"
15359 );
15360 }
15361
15362 #[test]
15367 fn the_first_vadd_decides_how_wide_the_set_is() {
15368 let mut f = Fixture::new();
15369 assert_eq!(
15370 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]),
15371 ":1\r\n"
15372 );
15373 assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
15374 assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
15375 assert_eq!(
15378 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"east"]),
15379 ":0\r\n"
15380 );
15381 assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
15382 assert_eq!(
15385 f.run(&[b"VADD", b"v", b"VALUES", b"3", b"1", b"0", b"0", b"up"]),
15386 "-ERR Vector dimension mismatch - got 3 but set has 2\r\n"
15387 );
15388 assert_eq!(
15391 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"0", b"nowhere"]),
15392 ":1\r\n"
15393 );
15394 assert_eq!(
15395 f.run(&[b"VEMB", b"v", b"nowhere"]),
15396 "*2\r\n$1\r\n0\r\n$1\r\n0\r\n"
15397 );
15398 assert_eq!(
15402 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"other", b"BIN"]),
15403 "-ERR asked quantization mismatch with existing vector set\r\n"
15404 );
15405 assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
15408 assert_eq!(f.run(&[b"VDIM", b"fresh"]), "-ERR key does not exist\r\n");
15409 assert_eq!(f.run(&[b"VCARD", b"fresh"]), ":0\r\n");
15410 }
15411
15412 #[test]
15415 fn vemb_gives_back_the_vector_and_vsim_gives_back_a_similarity() {
15416 let mut f = Fixture::new();
15417 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"4", b"a"]);
15418 assert_eq!(
15424 f.run(&[b"VEMB", b"v", b"a"]),
15425 "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
15426 );
15427 f.run(&[b"VADD", b"n", b"VALUES", b"2", b"3", b"4", b"a", b"NOQUANT"]);
15429 assert_eq!(
15430 f.run(&[b"VEMB", b"n", b"a"]),
15431 "*2\r\n$1\r\n3\r\n$1\r\n4\r\n"
15432 );
15433 f.run(&[b"VADD", b"b", b"VALUES", b"2", b"3", b"-4", b"a", b"BIN"]);
15436 assert_eq!(
15437 f.run(&[b"VEMB", b"b", b"a"]),
15438 "*2\r\n$1\r\n1\r\n$2\r\n-1\r\n"
15439 );
15440 assert_eq!(f.run(&[b"VEMB", b"v", b"nobody"]), "*-1\r\n");
15441 assert_eq!(f.run(&[b"VEMB", b"nokey", b"a"]), "*-1\r\n");
15442
15443 let mut f = Fixture::new();
15447 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"0", b"a"]);
15448 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"-1", b"0", b"opposite"]);
15449 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"7", b"across"]);
15450 assert_eq!(
15451 f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"2", b"0", b"WITHSCORES"]),
15452 "*6\r\n$1\r\na\r\n$1\r\n1\r\n$6\r\nacross\r\n$3\r\n0.5\r\n\
15453 $8\r\nopposite\r\n$1\r\n0\r\n"
15454 );
15455 assert_eq!(
15458 f.run(&[b"VSIM", b"v", b"ELE", b"a"]),
15459 "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
15460 );
15461 assert_eq!(f.run(&[b"VSIM", b"v", b"ELE", b"nobody"]), "*0\r\n");
15464 assert_eq!(f.run(&[b"VSIM", b"nokey", b"ELE", b"a"]), "*0\r\n");
15465 assert_eq!(
15468 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1"]),
15469 "*1\r\n$6\r\nacross\r\n"
15470 );
15471 assert_eq!(
15472 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"TRUTH"]),
15473 "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
15474 );
15475 assert_eq!(
15479 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1", b"EF", b"500"]),
15480 "*1\r\n$6\r\nacross\r\n"
15481 );
15482
15483 let mut g = Fixture::new();
15486 g.run(&[b"HELLO", b"3"]);
15487 g.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15488 assert_eq!(
15489 g.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHSCORES"]),
15490 "%1\r\n$4\r\neast\r\n,1\r\n"
15491 );
15492 }
15493
15494 #[test]
15496 fn an_attribute_is_bytes_and_an_empty_one_takes_it_off() {
15497 let mut f = Fixture::new();
15498 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15499 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
15500 assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]), ":1\r\n");
15501 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$7\r\n{\"k\":1}\r\n");
15502 assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"not json"]), ":1\r\n");
15505 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$8\r\nnot json\r\n");
15506 assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b""]), ":1\r\n");
15508 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
15509 assert_eq!(f.run(&[b"VSETATTR", b"v", b"nobody", b"{}"]), ":0\r\n");
15512 assert_eq!(f.run(&[b"VSETATTR", b"nokey", b"east", b"{}"]), ":0\r\n");
15513 assert_eq!(f.run(&[b"EXISTS", b"nokey"]), ":0\r\n");
15514 assert_eq!(f.run(&[b"VGETATTR", b"v", b"nobody"]), "$-1\r\n");
15517 assert_eq!(f.run(&[b"VISMEMBER", b"v", b"east"]), ":1\r\n");
15518 assert_eq!(f.run(&[b"VISMEMBER", b"v", b"nobody"]), ":0\r\n");
15519 assert_eq!(f.run(&[b"VISMEMBER", b"nokey", b"east"]), ":0\r\n");
15520
15521 f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
15523 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
15524 assert_eq!(
15525 f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHATTRIBS"]),
15526 "*4\r\n$4\r\neast\r\n$7\r\n{\"k\":1}\r\n$5\r\nnorth\r\n$-1\r\n"
15527 );
15528 }
15529
15530 #[test]
15533 fn vrem_takes_the_attribute_with_it() {
15534 let mut f = Fixture::new();
15535 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15536 f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
15537 assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":1\r\n");
15538 assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":0\r\n");
15539 assert_eq!(f.run(&[b"VREM", b"nokey", b"east"]), ":0\r\n");
15540 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
15543
15544 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15547 f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
15548 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
15549 f.run(&[b"VREM", b"v", b"east"]);
15550 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"between"]);
15551 assert_eq!(f.run(&[b"VGETATTR", b"v", b"between"]), "$-1\r\n");
15552 }
15553
15554 #[test]
15557 fn vinfo_says_partition_first() {
15558 let mut f = Fixture::new();
15559 f.run(&[
15560 b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east", b"M", b"32",
15561 ]);
15562 f.run(&[b"VSETATTR", b"v", b"east", b"{}"]);
15563 let info = f.run(&[b"VINFO", b"v"]);
15564 assert!(info.starts_with("*24\r\n$10\r\nindex-type\r\n$9\r\npartition\r\n"));
15565 assert!(info.contains("$6\r\nhnsw-m\r\n:32\r\n"), "{info}");
15568 assert!(info.contains("$10\r\nvector-dim\r\n:2\r\n"), "{info}");
15569 assert!(info.contains("$16\r\nattributes-count\r\n:1\r\n"), "{info}");
15570 assert!(
15573 info.contains("$10\r\nquant-type\r\n$4\r\nint8\r\n"),
15574 "{info}"
15575 );
15576 let mut f = Fixture::new();
15577 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north", b"BIN"]);
15578 assert!(
15579 f.run(&[b"VINFO", b"v"])
15580 .contains("$10\r\nquant-type\r\n$3\r\nbin\r\n")
15581 );
15582 assert_eq!(f.run(&[b"VINFO", b"nokey"]), "$-1\r\n");
15583 }
15584
15585 fn named() -> Fixture {
15587 let mut f = Fixture::new();
15588 for (i, name) in ["alpha", "beta", "gamma", "delta", "epsilon"]
15589 .iter()
15590 .enumerate()
15591 {
15592 let x = (i + 1).to_string();
15593 f.run(&[
15594 b"VADD",
15595 b"r",
15596 b"VALUES",
15597 b"2",
15598 x.as_bytes(),
15599 b"1",
15600 name.as_bytes(),
15601 ]);
15602 }
15603 f
15604 }
15605
15606 #[test]
15609 fn vrange_walks_the_names_and_not_the_vectors() {
15610 let mut f = named();
15611 assert_eq!(
15612 f.run(&[b"VRANGE", b"r", b"-", b"+"]),
15613 "*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"
15614 );
15615 assert_eq!(
15616 f.run(&[b"VRANGE", b"r", b"[a", b"[d"]),
15617 "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n",
15618 "the high end is a name and not a prefix, so delta is past it"
15619 );
15620 assert_eq!(
15621 f.run(&[b"VRANGE", b"r", b"(alpha", b"(gamma"]),
15622 "*3\r\n$4\r\nbeta\r\n$5\r\ndelta\r\n$7\r\nepsilon\r\n"
15623 );
15624 assert_eq!(
15625 f.run(&[b"VRANGE", b"r", b"[beta", b"[beta"]),
15626 "*1\r\n$4\r\nbeta\r\n"
15627 );
15628 assert_eq!(f.run(&[b"VRANGE", b"r", b"[z", b"+"]), "*0\r\n");
15629 f.run(&[b"VADD", b"r", b"VALUES", b"2", b"1", b"1", b"Beta"]);
15632 assert_eq!(
15633 f.run(&[b"VRANGE", b"r", b"-", b"[beta"]),
15634 "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
15635 );
15636 assert_eq!(f.run(&[b"VRANGE", b"nokey", b"-", b"+"]), "*0\r\n");
15637 }
15638
15639 #[test]
15642 fn a_vrange_count_of_zero_asks_for_nothing() {
15643 let mut f = named();
15644 assert_eq!(
15645 f.run(&[b"VRANGE", b"r", b"-", b"+", b"2"]),
15646 "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
15647 );
15648 assert_eq!(f.run(&[b"VRANGE", b"r", b"-", b"+", b"0"]), "*0\r\n");
15649 assert!(
15650 f.run(&[b"VRANGE", b"r", b"-", b"+", b"-1"])
15651 .starts_with("*5\r\n"),
15652 "a negative count is no limit at all"
15653 );
15654 }
15655
15656 #[test]
15659 fn vrange_says_which_end_it_could_not_read() {
15660 let mut f = named();
15661 assert_eq!(
15662 f.run(&[b"VRANGE", b"r", b"x", b"y"]),
15663 "-ERR invalid start range format\r\n"
15664 );
15665 assert_eq!(
15666 f.run(&[b"VRANGE", b"r", b"+", b"x"]),
15667 "-ERR invalid end range format\r\n",
15668 "the high end is spelled wrong, which is worth saying before the \
15669 low end being on the wrong side"
15670 );
15671 assert_eq!(
15672 f.run(&[b"VRANGE", b"r", b"+", b"-"]),
15673 "-ERR '-' can only be used as first argument, '+' only as second\r\n"
15674 );
15675 assert_eq!(
15678 f.run(&[b"VRANGE", b"r", b"[", b"+"]),
15679 "-ERR invalid start range format\r\n"
15680 );
15681 assert_eq!(
15682 f.run(&[b"VRANGE", b"r", b"x", b"+", b"z"]),
15683 "-ERR invalid COUNT value\r\n"
15684 );
15685 assert_eq!(
15686 f.run(&[b"VRANGE", b"r", b"-", b"+", b"2", b"extra"]),
15687 "-ERR wrong number of arguments for 'VRANGE' command\r\n"
15688 );
15689 f.run(&[b"SET", b"s", b"x"]);
15690 assert!(
15691 f.run(&[b"VRANGE", b"s", b"-", b"+"])
15692 .starts_with("-WRONGTYPE")
15693 );
15694 }
15695
15696 #[test]
15699 fn reduce_is_refused_and_not_ignored() {
15700 let mut f = Fixture::new();
15701 let reduce = f.run(&[
15702 b"VADD", b"v", b"REDUCE", b"1", b"VALUES", b"2", b"1", b"0", b"east",
15703 ]);
15704 assert!(
15705 reduce.starts_with("-ERR REDUCE is not supported."),
15706 "{reduce}"
15707 );
15708 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
15709 }
15710
15711 #[test]
15714 fn vsim_filter_reads_the_attributes() {
15715 let mut f = Fixture::new();
15716 for (name, x, y, attr) in [
15717 ("a", "1", "0", r#"{"lang":"en","year":1999}"#),
15718 ("b", "9", "1", r#"{"lang":"fr","year":2005}"#),
15719 ("c", "8", "2", r#"{"lang":"en","year":1970}"#),
15720 ("d", "7", "3", r#"{"lang":"en","year":2020}"#),
15721 ] {
15722 f.run(&[
15723 b"VADD",
15724 b"v",
15725 b"VALUES",
15726 b"2",
15727 x.as_bytes(),
15728 y.as_bytes(),
15729 name.as_bytes(),
15730 b"SETATTR",
15731 attr.as_bytes(),
15732 ]);
15733 }
15734 assert_eq!(
15737 f.run(&[
15738 b"VSIM",
15739 b"v",
15740 b"VALUES",
15741 b"2",
15742 b"9",
15743 b"1",
15744 b"COUNT",
15745 b"2",
15746 b"FILTER",
15747 b".lang == \"en\"",
15748 ]),
15749 "*2\r\n$1\r\na\r\n$1\r\nc\r\n"
15750 );
15751 assert_eq!(
15754 f.run(&[
15755 b"VSIM",
15756 b"v",
15757 b"VALUES",
15758 b"2",
15759 b"9",
15760 b"1",
15761 b"FILTER",
15762 b".lang == 'en' and .year > 1980",
15763 ]),
15764 "*2\r\n$1\r\na\r\n$1\r\nd\r\n"
15765 );
15766 assert_eq!(
15768 f.run(&[
15769 b"VSIM",
15770 b"v",
15771 b"VALUES",
15772 b"2",
15773 b"9",
15774 b"1",
15775 b"FILTER",
15776 b".lang in ['fr', 'de']",
15777 ]),
15778 "*1\r\n$1\r\nb\r\n"
15779 );
15780 assert_eq!(
15781 f.run(&[
15782 b"VSIM",
15783 b"v",
15784 b"VALUES",
15785 b"2",
15786 b"9",
15787 b"1",
15788 b"FILTER",
15789 b".rating > 3"
15790 ]),
15791 "*0\r\n"
15792 );
15793 assert_eq!(
15796 f.run(&[
15797 b"VSIM",
15798 b"v",
15799 b"VALUES",
15800 b"2",
15801 b"9",
15802 b"1",
15803 b"TRUTH",
15804 b"FILTER",
15805 b".year < 1980",
15806 ]),
15807 "*1\r\n$1\r\nc\r\n"
15808 );
15809 f.run(&[b"VSETATTR", b"v", b"b", r#"{"lang":"en"}"#.as_bytes()]);
15812 assert_eq!(
15813 f.run(&[
15814 b"VSIM",
15815 b"v",
15816 b"VALUES",
15817 b"2",
15818 b"9",
15819 b"1",
15820 b"COUNT",
15821 b"1",
15822 b"FILTER",
15823 b".lang == \"en\"",
15824 ]),
15825 "*1\r\n$1\r\nb\r\n"
15826 );
15827 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"9", b"2", b"b"]);
15830 assert_eq!(
15831 f.run(&[
15832 b"VSIM",
15833 b"v",
15834 b"VALUES",
15835 b"2",
15836 b"9",
15837 b"1",
15838 b"COUNT",
15839 b"1",
15840 b"FILTER",
15841 b".lang == \"en\"",
15842 ]),
15843 "*1\r\n$1\r\nb\r\n"
15844 );
15845
15846 let bad = f.run(&[b"VSIM", b"nokey", b"ELE", b"e", b"FILTER", b".k =="]);
15849 assert_eq!(bad, "-ERR invalid FILTER expression\r\n");
15850 assert_eq!(
15851 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"FILTER", b"junk"]),
15852 "-ERR invalid FILTER expression\r\n"
15853 );
15854 assert_eq!(
15857 f.run(&[
15858 b"VSIM",
15859 b"v",
15860 b"VALUES",
15861 b"2",
15862 b"9",
15863 b"1",
15864 b"COUNT",
15865 b"1",
15866 b"FILTER-EF",
15867 b"500",
15868 b"FILTER",
15869 b".lang == 'en'",
15870 ]),
15871 "*1\r\n$1\r\nb\r\n"
15872 );
15873 assert_eq!(
15874 f.run(&[
15875 b"VSIM",
15876 b"v",
15877 b"VALUES",
15878 b"2",
15879 b"9",
15880 b"1",
15881 b"COUNT",
15882 b"1",
15883 b"FILTER-EF",
15884 b"0"
15885 ]),
15886 "*1\r\n$1\r\nb\r\n"
15887 );
15888 assert_eq!(
15889 f.run(&[
15890 b"VSIM",
15891 b"v",
15892 b"VALUES",
15893 b"2",
15894 b"9",
15895 b"1",
15896 b"FILTER-EF",
15897 b"lots"
15898 ]),
15899 "-ERR EF must be a positive integer\r\n"
15900 );
15901 }
15902
15903 #[test]
15906 fn the_keyspace_sees_a_vector_set_key_like_any_other() {
15907 let mut f = Fixture::new();
15908 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15909 assert_eq!(f.run(&[b"TYPE", b"v"]), "+vectorset\r\n");
15910 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":1\r\n");
15911 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"v"]), "$6\r\nrabitq\r\n");
15912 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\nv\r\n");
15913 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
15914 assert_eq!(f.run(&[b"EXPIRE", b"v", b"100"]), ":1\r\n");
15915 assert_eq!(f.run(&[b"TTL", b"v"]), ":100\r\n");
15916 assert_eq!(f.run(&[b"PERSIST", b"v"]), ":1\r\n");
15917 assert_eq!(f.run(&[b"DEL", b"v"]), ":1\r\n");
15918 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
15919
15920 f.run(&[b"SET", b"s", b"1"]);
15922 assert_eq!(
15923 f.run(&[b"VADD", b"s", b"VALUES", b"2", b"1", b"0", b"east"]),
15924 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15925 );
15926 assert_eq!(
15927 f.run(&[b"VCARD", b"s"]),
15928 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15929 );
15930 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15931 assert_eq!(
15932 f.run(&[b"GET", b"v"]),
15933 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15934 );
15935 f.run(&[b"G.NADD", b"social", b"ada"]);
15939 assert_eq!(
15940 f.run(&[b"VCARD", b"social"]),
15941 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15942 );
15943 assert_eq!(
15944 f.run(&[b"G.NGET", b"v", b"ada"]),
15945 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15946 );
15947 }
15948
15949 #[test]
15952 fn vrandmember_has_the_two_shapes_srandmember_has() {
15953 let mut f = Fixture::new();
15954 for (i, name) in [&b"a"[..], b"b", b"c"].iter().enumerate() {
15955 let x = (i + 1).to_string();
15956 f.run(&[b"VADD", b"v", b"VALUES", b"2", x.as_bytes(), b"1", name]);
15957 }
15958 let one = f.run(&[b"VRANDMEMBER", b"v"]);
15960 assert!(one.starts_with("$1\r\n"), "{one}");
15961 let mut all = f.run(&[b"VRANDMEMBER", b"v", b"9"]);
15963 assert!(all.starts_with("*3\r\n"), "{all}");
15964 for name in ["a", "b", "c"] {
15965 assert!(all.contains(name), "{all} is missing {name}");
15966 }
15967 all = f.run(&[b"VRANDMEMBER", b"v", b"2"]);
15968 assert!(all.starts_with("*2\r\n"), "{all}");
15969 let many = f.run(&[b"VRANDMEMBER", b"v", b"-5"]);
15971 assert!(many.starts_with("*5\r\n"), "{many}");
15972 assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey"]), "$-1\r\n");
15974 assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
15975 }
15976
15977 #[test]
15980 fn vlinks_reports_one_layer_of_partition_neighbours() {
15981 let mut f = Fixture::new();
15982 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15983 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
15984 assert_eq!(
15987 f.run(&[b"VLINKS", b"v", b"east"]),
15988 "*1\r\n*1\r\n$5\r\nnorth\r\n"
15989 );
15990 assert_eq!(
15991 f.run(&[b"VLINKS", b"v", b"east", b"WITHSCORES"]),
15992 "*1\r\n*2\r\n$5\r\nnorth\r\n$3\r\n0.5\r\n"
15993 );
15994 assert_eq!(f.run(&[b"VLINKS", b"v", b"nobody"]), "*-1\r\n");
15995 assert_eq!(f.run(&[b"VLINKS", b"nokey", b"east"]), "*-1\r\n");
15996 }
15997
15998 #[test]
16001 fn fp32_and_values_are_the_same_vector() {
16002 let mut f = Fixture::new();
16003 let mut blob = Vec::new();
16004 for x in [3.0f32, 4.0] {
16005 blob.extend_from_slice(&x.to_le_bytes());
16006 }
16007 assert_eq!(f.run(&[b"VADD", b"v", b"FP32", &blob, b"a"]), ":1\r\n");
16008 assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
16009 assert_eq!(
16010 f.run(&[b"VEMB", b"v", b"a"]),
16011 "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
16012 );
16013 assert_eq!(
16019 f.run(&[b"VEMB", b"v", b"a", b"RAW"]),
16020 "*4\r\n+int8\r\n$2\r\n_\x7f\r\n$1\r\n5\r\n$17\r\n0.800000011920929\r\n"
16021 );
16022 assert_eq!(
16024 f.run(&[b"VADD", b"w", b"FP32", b"abc", b"a"]),
16025 "-ERR invalid vector specification\r\n"
16026 );
16027 assert_eq!(
16029 f.run(&[b"VADD", b"w", b"VALUES", b"4", b"1", b"0", b"a"]),
16030 "-ERR syntax error\r\n"
16031 );
16032 assert_eq!(f.run(&[b"EXISTS", b"w"]), ":0\r\n");
16033 }
16034
16035 #[test]
16040 fn bf_add_makes_the_filter_and_says_whether_it_was_new() {
16041 let mut f = Fixture::new();
16042 assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":1\r\n");
16043 assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":0\r\n");
16044 assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"hello"]), ":1\r\n");
16045 assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"never"]), ":0\r\n");
16046 assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":1\r\n");
16047 assert_eq!(
16050 f.run(&[b"BF.INFO", b"b"]),
16051 "*10\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
16052 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
16053 +Expansion rate\r\n:2\r\n"
16054 );
16055 assert_eq!(f.run(&[b"TYPE", b"b"]), "+MBbloom--\r\n");
16056 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"b"]), "$3\r\nraw\r\n");
16057 assert_eq!(f.run(&[b"BF.CARD", b"gone"]), ":0\r\n");
16060 assert_eq!(f.run(&[b"BF.INFO", b"gone"]), "-ERR not found\r\n");
16061 }
16062
16063 #[test]
16069 fn a_wrong_type_is_a_miss_to_the_two_that_only_read_bits() {
16070 let mut f = Fixture::new();
16071 f.run(&[b"SET", b"s", b"text"]);
16072 assert_eq!(f.run(&[b"BF.EXISTS", b"s", b"x"]), ":0\r\n");
16073 assert_eq!(f.run(&[b"BF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
16074 for cmd in [
16075 vec![&b"BF.ADD"[..], b"s", b"x"],
16076 vec![&b"BF.MADD"[..], b"s", b"x"],
16077 vec![&b"BF.CARD"[..], b"s"],
16078 vec![&b"BF.INFO"[..], b"s"],
16079 vec![&b"BF.DEBUG"[..], b"s"],
16080 vec![&b"BF.SCANDUMP"[..], b"s", b"0"],
16081 ] {
16082 let name = String::from_utf8_lossy(cmd[0]).into_owned();
16083 assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
16084 }
16085 assert_eq!(
16088 f.run(&[b"BF.RESERVE", b"s", b"abc", b"10"]),
16089 "-ERR bad error rate\r\n"
16090 );
16091 assert!(
16092 f.run(&[b"BF.RESERVE", b"s", b"0.01", b"10"])
16093 .starts_with("-WRONGTYPE")
16094 );
16095 }
16096
16097 #[test]
16100 fn a_full_filter_grows_a_link_and_a_fixed_one_says_no() {
16101 let mut f = Fixture::new();
16102 assert_eq!(f.run(&[b"BF.RESERVE", b"g", b"0.01", b"10"]), "+OK\r\n");
16103 for i in 0..10u32 {
16104 assert_eq!(
16105 f.run(&[b"BF.ADD", b"g", i.to_string().as_bytes()]),
16106 ":1\r\n"
16107 );
16108 }
16109 assert_eq!(f.run(&[b"BF.INFO", b"g", b"FILTERS"]), "*1\r\n:1\r\n");
16110 assert_eq!(f.run(&[b"BF.ADD", b"g", b"11"]), ":1\r\n");
16111 assert_eq!(f.run(&[b"BF.INFO", b"g", b"filters"]), "*1\r\n:2\r\n");
16112 assert_eq!(f.run(&[b"BF.INFO", b"g", b"CAPACITY"]), "*1\r\n:30\r\n");
16115 assert_eq!(
16116 f.run(&[b"BF.DEBUG", b"g"]),
16117 "*3\r\n$7\r\nsize:11\r\n\
16118 $71\r\nbytes:16 bits:128 hashes:8 hashwidth:64 capacity:10 size:10 ratio:0.005\r\n\
16119 $71\r\nbytes:32 bits:256 hashes:9 hashwidth:64 capacity:20 size:1 ratio:0.0025\r\n"
16120 );
16121
16122 assert_eq!(
16124 f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]),
16125 "+OK\r\n"
16126 );
16127 assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":1\r\n");
16128 assert_eq!(f.run(&[b"BF.ADD", b"n", b"b"]), ":1\r\n");
16129 assert_eq!(
16130 f.run(&[b"BF.ADD", b"n", b"c"]),
16131 "-ERR non scaling filter is full\r\n"
16132 );
16133 assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":0\r\n");
16136 assert_eq!(f.run(&[b"BF.INFO", b"n", b"EXPANSION"]), "*1\r\n$-1\r\n");
16139 f.run(&[b"BF.RESERVE", b"z", b"0.01", b"2", b"EXPANSION", b"0"]);
16140 assert_eq!(f.run(&[b"BF.INFO", b"z", b"EXPANSION"]), "*1\r\n$-1\r\n");
16141 assert_eq!(
16144 f.run(&[
16145 b"BF.RESERVE",
16146 b"q",
16147 b"0.01",
16148 b"2",
16149 b"NONSCALING",
16150 b"EXPANSION",
16151 b"2"
16152 ]),
16153 "-Nonscaling filters cannot expand\r\n"
16154 );
16155 }
16156
16157 #[test]
16160 fn madd_truncates_its_reply_at_the_item_that_did_not_fit() {
16161 let mut f = Fixture::new();
16162 f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]);
16163 assert_eq!(
16164 f.run(&[b"BF.MADD", b"n", b"a", b"b", b"c", b"d"]),
16165 "*3\r\n:1\r\n:1\r\n-ERR non scaling filter is full\r\n"
16166 );
16167 assert_eq!(
16168 f.run(&[b"BF.MEXISTS", b"n", b"a", b"c"]),
16169 "*2\r\n:1\r\n:0\r\n"
16170 );
16171 }
16172
16173 #[test]
16176 fn insert_is_a_reserve_and_a_madd_with_different_errors() {
16177 let mut f = Fixture::new();
16178 assert_eq!(
16179 f.run(&[
16180 b"BF.INSERT",
16181 b"i",
16182 b"CAPACITY",
16183 b"50",
16184 b"ERROR",
16185 b"0.001",
16186 b"ITEMS",
16187 b"a",
16188 b"b"
16189 ]),
16190 "*2\r\n:1\r\n:1\r\n"
16191 );
16192 assert_eq!(f.run(&[b"BF.INFO", b"i", b"CAPACITY"]), "*1\r\n:50\r\n");
16193 assert_eq!(
16195 f.run(&[b"BF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
16196 "-ERR not found\r\n"
16197 );
16198 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
16199 assert_eq!(
16202 f.run(&[b"BF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
16203 "-Bad capacity\r\n"
16204 );
16205 assert_eq!(
16206 f.run(&[b"BF.INSERT", b"i", b"ERROR", b"2", b"ITEMS", b"a"]),
16207 "-Bad error rate\r\n"
16208 );
16209 assert_eq!(
16210 f.run(&[b"BF.INSERT", b"i", b"EXPANSION", b"99999", b"ITEMS", b"a"]),
16211 "-Bad expansion\r\n"
16212 );
16213 assert_eq!(
16218 f.run(&[b"BF.INSERT", b"ns", b"NOSUCHTHING", b"ITEMS", b"a"]),
16219 "*1\r\n:1\r\n"
16220 );
16221 assert_eq!(f.run(&[b"BF.INFO", b"ns", b"EXPANSION"]), "*1\r\n$-1\r\n");
16222 assert_eq!(
16226 f.run(&[b"BF.INSERT", b"e1", b"E", b"4", b"ITEMS", b"a"]),
16227 "*1\r\n:1\r\n"
16228 );
16229 assert_eq!(f.run(&[b"BF.INFO", b"e1", b"EXPANSION"]), "*1\r\n:4\r\n");
16230 assert_eq!(
16231 f.run(&[b"BF.INSERT", b"e2", b"ER", b"0.5", b"ITEMS", b"a"]),
16232 "*1\r\n:1\r\n"
16233 );
16234 assert_eq!(
16235 f.run(&[b"BF.INSERT", b"gone", b"NOC", b"ITEMS", b"a"]),
16236 "-ERR not found\r\n"
16237 );
16238 assert_eq!(
16240 f.run(&[b"BF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
16241 "-Unknown argument received\r\n"
16242 );
16243 assert_eq!(
16245 f.run(&[b"BF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
16246 "*1\r\n:1\r\n"
16247 );
16248 assert!(
16250 f.run(&[b"BF.INSERT", b"i", b"ITEMS"])
16251 .contains("wrong number of arguments")
16252 );
16253 }
16254
16255 #[test]
16258 fn a_dump_replays_into_a_filter_that_answers_the_same() {
16259 let mut f = Fixture::new();
16260 f.run(&[b"BF.RESERVE", b"src", b"0.01", b"10"]);
16261 for i in 0..25u32 {
16262 f.run(&[b"BF.ADD", b"src", i.to_string().as_bytes()]);
16263 }
16264 assert_eq!(f.run(&[b"BF.INFO", b"src", b"FILTERS"]), "*1\r\n:2\r\n");
16265
16266 let mut iter = b"0".to_vec();
16269 let mut chunks = 0;
16270 loop {
16271 let raw = f.raw(&[b"BF.SCANDUMP", b"src", &iter]);
16272 let text = String::from_utf8_lossy(&raw).into_owned();
16273 let next = text
16274 .split("\r\n")
16275 .nth(1)
16276 .and_then(|n| n.strip_prefix(':'))
16277 .expect("a two element reply of an iterator and a chunk")
16278 .to_owned();
16279 let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
16280 let data = &body[body
16281 .windows(2)
16282 .position(|w| w == b"\r\n")
16283 .expect("a length line")
16284 + 2..body.len() - 2];
16285 if next == "0" {
16286 assert!(data.is_empty(), "the last chunk is empty");
16287 break;
16288 }
16289 let put = f.run(&[b"BF.LOADCHUNK", b"dst", next.as_bytes(), data]);
16290 assert_eq!(put, "+OK\r\n", "loading chunk {chunks}");
16291 iter = next.into_bytes();
16292 chunks += 1;
16293 }
16294 assert_eq!(chunks, 3, "a header and one chunk per link");
16295
16296 assert_eq!(f.run(&[b"BF.INFO", b"dst"]), f.run(&[b"BF.INFO", b"src"]));
16297 assert_eq!(f.run(&[b"BF.DEBUG", b"dst"]), f.run(&[b"BF.DEBUG", b"src"]));
16298 for i in 0..25u32 {
16299 assert_eq!(
16300 f.run(&[b"BF.EXISTS", b"dst", i.to_string().as_bytes()]),
16301 ":1\r\n"
16302 );
16303 }
16304
16305 assert_eq!(
16308 f.run(&[b"BF.LOADCHUNK", b"dst", b"1", b"anything"]),
16309 "-ERR received bad data\r\n"
16310 );
16311 assert_eq!(
16312 f.run(&[b"BF.LOADCHUNK", b"fresh", b"1", b"anything"]),
16313 "-ERR received bad data\r\n"
16314 );
16315 assert_eq!(
16317 f.run(&[b"BF.LOADCHUNK", b"dst", b"99999", b"x"]),
16318 "-ERR invalid offset - no link found\r\n"
16319 );
16320 assert_eq!(
16321 f.run(&[b"BF.LOADCHUNK", b"dst", b"nope", b"x"]),
16322 "-ERR Second argument must be numeric\r\n"
16323 );
16324 assert_eq!(
16327 f.run(&[b"BF.SCANDUMP", b"src", b"nope"]),
16328 "-Second argument must be numeric\r\n"
16329 );
16330 }
16331
16332 #[test]
16335 fn reserve_reads_its_numbers_the_way_string2ll_does() {
16336 let mut f = Fixture::new();
16337 for (args, want) in [
16338 (vec![&b"abc"[..], b"10"], "-ERR bad error rate\r\n"),
16339 (vec![&b"nan"[..], b"10"], "-ERR bad error rate\r\n"),
16340 (
16341 vec![&b"0"[..], b"10"],
16342 "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
16343 ),
16344 (
16345 vec![&b"1"[..], b"10"],
16346 "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
16347 ),
16348 (
16349 vec![&b"inf"[..], b"10"],
16350 "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
16351 ),
16352 (vec![&b"0.01"[..], b"+10"], "-ERR bad capacity\r\n"),
16353 (vec![&b"0.01"[..], b"1e2"], "-ERR bad capacity\r\n"),
16354 (vec![&b"0.01"[..], b"007"], "-ERR bad capacity\r\n"),
16355 (
16356 vec![&b"0.01"[..], b"0"],
16357 "-ERR capacity must be in the range [1, 1073741824]\r\n",
16358 ),
16359 (
16360 vec![&b"0.01"[..], b"1073741825"],
16361 "-ERR capacity must be in the range [1, 1073741824]\r\n",
16362 ),
16363 ] {
16364 let mut cmd = vec![&b"BF.RESERVE"[..], b"k"];
16365 cmd.extend(args.iter().copied());
16366 assert_eq!(f.run(&cmd), want, "{}", String::from_utf8_lossy(args[0]));
16367 }
16368 assert_eq!(
16369 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION"]),
16370 "-ERR no expansion\r\n"
16371 );
16372 assert_eq!(
16373 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"abc"]),
16374 "-ERR bad expansion\r\n"
16375 );
16376 assert_eq!(
16377 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"32769"]),
16378 "-ERR expansion must be in the range [0, 32768]\r\n"
16379 );
16380 assert_eq!(
16382 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"junk"]),
16383 "+OK\r\n"
16384 );
16385 assert_eq!(
16386 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10"]),
16387 "-ERR item exists\r\n"
16388 );
16389 assert_eq!(
16390 f.run(&[b"BF.INFO", b"k", b"nosuchfield"]),
16391 "-Invalid information value\r\n"
16392 );
16393 assert!(
16394 f.run(&[b"BF.INFO", b"k", b"CAPACITY", b"SIZE"])
16395 .contains("wrong number of arguments")
16396 );
16397 }
16398
16399 #[test]
16401 fn the_bloom_family_answers_in_resp3_spelling_too() {
16402 let mut f = Fixture::new();
16403 f.out.set_proto(Proto::Resp3);
16404 assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#t\r\n");
16405 assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#f\r\n");
16406 assert_eq!(f.run(&[b"BF.MADD", b"b", b"a", b"c"]), "*2\r\n#f\r\n#t\r\n");
16407 assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"a"]), "#t\r\n");
16408 assert_eq!(
16409 f.run(&[b"BF.MEXISTS", b"b", b"a", b"z"]),
16410 "*2\r\n#t\r\n#f\r\n"
16411 );
16412 assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":2\r\n");
16414 assert_eq!(
16415 f.run(&[b"BF.INFO", b"b"]),
16416 "%5\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
16417 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:2\r\n\
16418 +Expansion rate\r\n:2\r\n"
16419 );
16420 assert_eq!(
16423 f.run(&[b"BF.INFO", b"b", b"CAPACITY"]),
16424 "%1\r\n+Capacity\r\n:100\r\n"
16425 );
16426 }
16427
16428 fn cf_header(
16437 items: u64,
16438 buckets: u64,
16439 deletes: u64,
16440 filters: u64,
16441 geometry: [u16; 3],
16442 ) -> Vec<u8> {
16443 let mut out = Vec::with_capacity(38);
16444 for n in [items, buckets, deletes, filters] {
16445 out.extend_from_slice(&n.to_le_bytes());
16446 }
16447 for n in geometry {
16448 out.extend_from_slice(&n.to_le_bytes());
16449 }
16450 out
16451 }
16452
16453 #[test]
16457 fn cf_add_makes_the_filter_and_counts_the_copies() {
16458 let mut f = Fixture::new();
16459 assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
16460 assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
16461 assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":2\r\n");
16462 assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"hello"]), ":0\r\n");
16465 assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"other"]), ":1\r\n");
16466 assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"hello"]), ":1\r\n");
16467 assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"no"]), ":0\r\n");
16468 assert_eq!(
16469 f.run(&[b"CF.MEXISTS", b"d", b"hello", b"no"]),
16470 "*2\r\n:1\r\n:0\r\n"
16471 );
16472 assert_eq!(
16475 f.run(&[b"CF.INFO", b"d"]),
16476 "*16\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
16477 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:3\r\n\
16478 +Number of items deleted\r\n:0\r\n+Bucket size\r\n:2\r\n\
16479 +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
16480 );
16481 assert_eq!(
16482 f.run(&[b"CF.DEBUG", b"d"]),
16483 "$79\r\nbktsize:2 buckets:512 items:3 deletes:0 filters:1 \
16484 max_iterations:20 expansion:1\r\n"
16485 );
16486 assert_eq!(f.run(&[b"TYPE", b"d"]), "+MBbloomCF\r\n");
16487 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
16488
16489 assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
16491 assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":1\r\n");
16492 assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
16493 assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":0\r\n");
16494 assert_eq!(f.run(&[b"CF.COMPACT", b"d"]), "+OK\r\n");
16495
16496 assert_eq!(f.run(&[b"CF.INFO", b"gone"]), "-ERR not found\r\n");
16499 assert_eq!(f.run(&[b"CF.DEL", b"gone", b"x"]), "-Not found\r\n");
16500 assert_eq!(
16501 f.run(&[b"CF.COMPACT", b"gone"]),
16502 "-Cuckoo filter was not found\r\n"
16503 );
16504 assert_eq!(f.run(&[b"CF.EXISTS", b"gone", b"x"]), ":0\r\n");
16505 assert!(
16509 f.run(&[b"CF.COMPACT", b"a", b"b"])
16510 .contains("wrong number of arguments")
16511 );
16512 }
16513
16514 #[test]
16517 fn a_wrong_type_is_a_miss_to_the_four_that_only_read_fingerprints() {
16518 let mut f = Fixture::new();
16519 f.run(&[b"SET", b"s", b"text"]);
16520 assert_eq!(f.run(&[b"CF.EXISTS", b"s", b"x"]), ":0\r\n");
16521 assert_eq!(f.run(&[b"CF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
16522 assert_eq!(f.run(&[b"CF.COUNT", b"s", b"x"]), ":0\r\n");
16523 assert_eq!(f.run(&[b"CF.DEL", b"s", b"x"]), "-Not found\r\n");
16527 assert_eq!(
16528 f.run(&[b"CF.COMPACT", b"s"]),
16529 "-Cuckoo filter was not found\r\n"
16530 );
16531 for cmd in [
16532 vec![&b"CF.ADD"[..], b"s", b"x"],
16533 vec![&b"CF.ADDNX"[..], b"s", b"x"],
16534 vec![&b"CF.INSERT"[..], b"s", b"ITEMS", b"x"],
16535 vec![&b"CF.INSERTNX"[..], b"s", b"ITEMS", b"x"],
16536 vec![&b"CF.INFO"[..], b"s"],
16537 vec![&b"CF.DEBUG"[..], b"s"],
16538 vec![&b"CF.SCANDUMP"[..], b"s", b"0"],
16539 vec![&b"CF.LOADCHUNK"[..], b"s", b"2", b"x"],
16540 vec![&b"CF.RESERVE"[..], b"s", b"64"],
16541 ] {
16542 let name = String::from_utf8_lossy(cmd[0]).into_owned();
16543 assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
16544 }
16545 }
16546
16547 #[test]
16550 fn reserve_complains_about_its_options_in_the_order_it_looks_for_them() {
16551 let mut f = Fixture::new();
16552 assert_eq!(
16553 f.run(&[
16554 b"CF.RESERVE",
16555 b"r",
16556 b"64",
16557 b"BUCKETSIZE",
16558 b"1",
16559 b"MAXITERATIONS",
16560 b"7",
16561 b"EXPANSION",
16562 b"4"
16563 ]),
16564 "+OK\r\n"
16565 );
16566 assert_eq!(
16567 f.run(&[b"CF.DEBUG", b"r"]),
16568 "$77\r\nbktsize:1 buckets:64 items:0 deletes:0 filters:1 \
16569 max_iterations:7 expansion:4\r\n"
16570 );
16571 assert_eq!(f.run(&[b"CF.RESERVE", b"r", b"64"]), "-ERR item exists\r\n");
16572
16573 assert_eq!(f.run(&[b"CF.RESERVE", b"q", b"abc"]), "-Bad capacity\r\n");
16574 assert_eq!(
16575 f.run(&[b"CF.RESERVE", b"q", b"1"]),
16576 "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
16577 );
16578 assert_eq!(
16581 f.run(&[b"CF.RESERVE", b"q", b"7", b"BUCKETSIZE", b"4"]),
16582 "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
16583 );
16584 assert_eq!(
16585 f.run(&[b"CF.RESERVE", b"q", b"8", b"BUCKETSIZE", b"4"]),
16586 "+OK\r\n"
16587 );
16588
16589 assert_eq!(
16594 f.run(&[b"CF.RESERVE", b"q2", b"64", b"BUCKETSIZE", b"0"]),
16595 "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
16596 );
16597 assert_eq!(
16598 f.run(&[
16599 b"CF.RESERVE",
16600 b"q2",
16601 b"64",
16602 b"EXPANSION",
16603 b"xx",
16604 b"BUCKETSIZE",
16605 b"0"
16606 ]),
16607 "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
16608 );
16609 assert_eq!(
16610 f.run(&[
16611 b"CF.RESERVE",
16612 b"q2",
16613 b"64",
16614 b"MAXITERATIONS",
16615 b"0",
16616 b"BUCKETSIZE",
16617 b"0"
16618 ]),
16619 "-MAXITERATIONS: value must be in the range [1, 65535]\r\n"
16620 );
16621 assert_eq!(
16625 f.run(&[
16626 b"CF.RESERVE",
16627 b"q3",
16628 b"64",
16629 b"BUCKETSIZE",
16630 b"1",
16631 b"BUCKETSIZE",
16632 b"0"
16633 ]),
16634 "+OK\r\n"
16635 );
16636 assert_eq!(
16639 f.run(&[b"CF.RESERVE", b"q4", b"64", b"NOSUCH", b"9"]),
16640 "+OK\r\n"
16641 );
16642 assert_eq!(
16643 f.run(&[b"CF.DEBUG", b"q4"]),
16644 "$78\r\nbktsize:2 buckets:32 items:0 deletes:0 filters:1 \
16645 max_iterations:20 expansion:1\r\n"
16646 );
16647 assert!(
16650 f.run(&[b"CF.RESERVE", b"q5", b"64", b"BUCKETSIZE"])
16651 .contains("wrong number of arguments")
16652 );
16653 }
16654
16655 #[test]
16658 fn insert_checks_every_occurrence_and_matches_on_the_first_letter() {
16659 let mut f = Fixture::new();
16660 assert_eq!(
16661 f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"64", b"ITEMS", b"a", b"b"]),
16662 "*2\r\n:1\r\n:1\r\n"
16663 );
16664 assert_eq!(
16665 f.run(&[b"CF.DEBUG", b"i"]),
16666 "$78\r\nbktsize:2 buckets:32 items:2 deletes:0 filters:1 \
16667 max_iterations:20 expansion:1\r\n"
16668 );
16669 assert_eq!(
16672 f.run(&[b"CF.INSERTNX", b"i", b"ITEMS", b"a", b"c"]),
16673 "*2\r\n:0\r\n:1\r\n"
16674 );
16675 assert_eq!(
16676 f.run(&[b"CF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
16677 "-ERR not found\r\n"
16678 );
16679 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
16680
16681 assert_eq!(
16682 f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
16683 "-Bad capacity\r\n"
16684 );
16685 assert_eq!(
16688 f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"2", b"ITEMS", b"a"]),
16689 "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
16690 );
16691 assert_eq!(
16695 f.run(&[
16696 b"CF.INSERT",
16697 b"i",
16698 b"CAPACITY",
16699 b"8",
16700 b"CAPACITY",
16701 b"2",
16702 b"ITEMS",
16703 b"a"
16704 ]),
16705 "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
16706 );
16707 assert_eq!(
16711 f.run(&[b"CF.INSERT", b"i", b"NOSUCH", b"ITEMS", b"a"]),
16712 "*1\r\n:1\r\n"
16713 );
16714 assert_eq!(
16715 f.run(&[b"CF.INSERT", b"i", b"ITEMSXYZ", b"a"]),
16716 "*1\r\n:1\r\n"
16717 );
16718 assert_eq!(
16719 f.run(&[b"CF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
16720 "-Unknown argument received\r\n"
16721 );
16722 assert_eq!(
16724 f.run(&[b"CF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
16725 "*1\r\n:1\r\n"
16726 );
16727 assert!(
16729 f.run(&[b"CF.INSERT", b"i", b"ITEMS"])
16730 .contains("wrong number of arguments")
16731 );
16732 assert!(
16733 f.run(&[b"CF.INSERT", b"i", b"CAPACITY"])
16734 .contains("wrong number of arguments")
16735 );
16736 }
16737
16738 #[test]
16741 fn a_full_filter_and_one_that_ran_out_of_filters_answer_differently() {
16742 let mut f = Fixture::new();
16743 f.run(&[
16744 b"CF.RESERVE",
16745 b"s",
16746 b"4",
16747 b"BUCKETSIZE",
16748 b"1",
16749 b"EXPANSION",
16750 b"0",
16751 ]);
16752 for i in 0..4u32 {
16753 assert_eq!(
16754 f.run(&[b"CF.ADD", b"s", i.to_string().as_bytes()]),
16755 ":1\r\n"
16756 );
16757 }
16758 assert_eq!(f.run(&[b"CF.ADD", b"s", b"4"]), "-Filter is full\r\n");
16759 assert_eq!(f.run(&[b"CF.ADDNX", b"s", b"zz"]), "-Filter is full\r\n");
16760 assert_eq!(
16763 f.run(&[b"CF.INSERT", b"s", b"ITEMS", b"p", b"q"]),
16764 "*2\r\n:-1\r\n:-1\r\n"
16765 );
16766 assert_eq!(
16767 f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"0", b"q"]),
16768 "*2\r\n:0\r\n:-1\r\n"
16769 );
16770
16771 let full = cf_header(0, 4, 0, 32, [1, 20, 1]);
16777 assert_eq!(f.run(&[b"CF.LOADCHUNK", b"g", b"1", &full]), "+OK\r\n");
16778 assert_eq!(
16779 f.run(&[b"CF.ADD", b"g", b"q"]),
16780 "-Maximum expansions reached\r\n"
16781 );
16782 assert_eq!(
16783 f.run(&[b"CF.INFO", b"g"]),
16784 "*16\r\n+Size\r\n:680\r\n+Number of buckets\r\n:4\r\n\
16785 +Number of filters\r\n:32\r\n+Number of items inserted\r\n:0\r\n\
16786 +Number of items deleted\r\n:0\r\n+Bucket size\r\n:1\r\n\
16787 +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
16788 );
16789 }
16790
16791 #[test]
16795 fn a_cuckoo_dump_replays_into_a_filter_that_answers_the_same() {
16796 let mut f = Fixture::new();
16797 f.run(&[
16798 b"CF.RESERVE",
16799 b"src",
16800 b"8",
16801 b"BUCKETSIZE",
16802 b"2",
16803 b"EXPANSION",
16804 b"2",
16805 ]);
16806 for i in 0..40u32 {
16807 f.run(&[b"CF.ADD", b"src", i.to_string().as_bytes()]);
16808 }
16809 let mut pos = b"0".to_vec();
16813 let mut chunks = 0;
16814 loop {
16815 let raw = f.raw(&[b"CF.SCANDUMP", b"src", &pos]);
16816 let head = String::from_utf8_lossy(&raw[..raw.len().min(24)]).into_owned();
16817 let next = head
16818 .split("\r\n")
16819 .nth(1)
16820 .and_then(|n| n.strip_prefix(':'))
16821 .expect("a two element reply of a position and a chunk")
16822 .to_owned();
16823 if next == "0" {
16824 assert!(raw.ends_with(b"$-1\r\n"), "the walk ends on a nil");
16825 break;
16826 }
16827 let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
16828 let at = body
16829 .windows(2)
16830 .position(|w| w == b"\r\n")
16831 .expect("a length line")
16832 + 2;
16833 let data = &body[at..body.len() - 2];
16834 assert_eq!(
16835 f.run(&[b"CF.LOADCHUNK", b"dst", next.as_bytes(), data]),
16836 "+OK\r\n",
16837 "loading chunk {chunks}"
16838 );
16839 pos = next.into_bytes();
16840 chunks += 1;
16841 }
16842 assert!(chunks >= 2, "a header and at least one chunk");
16843
16844 assert_eq!(f.run(&[b"CF.INFO", b"dst"]), f.run(&[b"CF.INFO", b"src"]));
16845 assert_eq!(f.run(&[b"CF.DEBUG", b"dst"]), f.run(&[b"CF.DEBUG", b"src"]));
16846 for i in 0..40u32 {
16847 assert_eq!(
16848 f.run(&[b"CF.EXISTS", b"dst", i.to_string().as_bytes()]),
16849 ":1\r\n"
16850 );
16851 }
16852
16853 f.run(&[b"CF.RESERVE", b"empty", b"4", b"BUCKETSIZE", b"1"]);
16856 assert_eq!(
16857 f.run(&[b"CF.SCANDUMP", b"empty", b"0"]),
16858 "*2\r\n:0\r\n$-1\r\n"
16859 );
16860
16861 assert_eq!(
16865 f.run(&[b"CF.SCANDUMP", b"src", b"nope"]),
16866 "-Invalid position\r\n"
16867 );
16868 assert_eq!(
16869 f.run(&[b"CF.SCANDUMP", b"src", b"-1"]),
16870 "-Invalid position\r\n"
16871 );
16872 assert_eq!(
16873 f.run(&[b"CF.LOADCHUNK", b"dst", b"0", b"x"]),
16874 "-Invalid position\r\n"
16875 );
16876 assert_eq!(
16877 f.run(&[b"CF.LOADCHUNK", b"dst", b"99999", b"x"]),
16878 "-Couldn't load chunk!\r\n"
16879 );
16880 let good = cf_header(0, 8, 0, 1, [2, 20, 1]);
16882 assert_eq!(
16883 f.run(&[b"CF.LOADCHUNK", b"dst", b"1", &good]),
16884 "-ERR item exists\r\n"
16885 );
16886 assert_eq!(
16890 f.run(&[b"CF.LOADCHUNK", b"n1", b"1", b"short"]),
16891 "-Invalid header\r\n"
16892 );
16893 for (why, bad) in [
16894 ("no filters at all", cf_header(0, 8, 0, 0, [2, 20, 1])),
16895 ("no buckets", cf_header(0, 0, 0, 1, [2, 20, 1])),
16896 (
16897 "a bucket count that is not a power of two",
16898 cf_header(0, 3, 0, 1, [2, 20, 1]),
16899 ),
16900 ("an empty bucket", cf_header(0, 8, 0, 1, [0, 20, 1])),
16901 ("no kicks", cf_header(0, 8, 0, 1, [2, 0, 1])),
16902 (
16903 "a growth nobody could reach",
16904 cf_header(0, 8, 0, 1, [2, 20, 32769]),
16905 ),
16906 (
16907 "a chain that cannot grow and did",
16908 cf_header(0, 8, 0, 2, [2, 20, 0]),
16909 ),
16910 (
16913 "a filter count that wraps",
16914 cf_header(0, 8, 0, 65_536, [2, 20, 1]),
16915 ),
16916 ] {
16917 assert_eq!(
16918 f.run(&[b"CF.LOADCHUNK", b"bad", b"1", &bad]),
16919 "-Couldn't create filter!\r\n",
16920 "{why}"
16921 );
16922 }
16923 }
16924
16925 #[test]
16928 fn the_cuckoo_family_answers_in_resp3_spelling_too() {
16929 let mut f = Fixture::new();
16930 f.out.set_proto(Proto::Resp3);
16931 assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
16932 assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
16933 assert_eq!(f.run(&[b"CF.ADDNX", b"c", b"a"]), "#f\r\n");
16934 assert_eq!(f.run(&[b"CF.EXISTS", b"c", b"a"]), "#t\r\n");
16935 assert_eq!(
16936 f.run(&[b"CF.MEXISTS", b"c", b"a", b"z"]),
16937 "*2\r\n#t\r\n#f\r\n"
16938 );
16939 assert_eq!(f.run(&[b"CF.DEL", b"c", b"a"]), "#t\r\n");
16940 assert_eq!(f.run(&[b"CF.DEL", b"c", b"z"]), "#f\r\n");
16941 assert_eq!(f.run(&[b"CF.COUNT", b"c", b"a"]), ":1\r\n");
16943 assert_eq!(
16944 f.run(&[b"CF.INFO", b"c"]),
16945 "%8\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
16946 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
16947 +Number of items deleted\r\n:1\r\n+Bucket size\r\n:2\r\n\
16948 +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
16949 );
16950
16951 f.run(&[
16956 b"CF.RESERVE",
16957 b"s",
16958 b"4",
16959 b"BUCKETSIZE",
16960 b"1",
16961 b"EXPANSION",
16962 b"0",
16963 ]);
16964 assert_eq!(
16965 f.run(&[
16966 b"CF.INSERT",
16967 b"s",
16968 b"ITEMS",
16969 b"a",
16970 b"b",
16971 b"c",
16972 b"d",
16973 b"e",
16974 b"f"
16975 ]),
16976 "*6\r\n#t\r\n#t\r\n#t\r\n#f\r\n#f\r\n#f\r\n"
16977 );
16978 assert_eq!(
16979 f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"a", b"zz"]),
16980 "*2\r\n:0\r\n:-1\r\n"
16981 );
16982 assert_eq!(f.run(&[b"CF.ADD", b"s", b"zzz"]), "-Filter is full\r\n");
16983 assert_eq!(f.run(&[b"CF.SCANDUMP", b"c", b"9999"]), "*2\r\n:0\r\n_\r\n");
16986 }
16987
16988 #[test]
16993 fn a_sketch_is_made_from_a_size_or_from_an_error_rate() {
16994 let mut f = Fixture::new();
16995 assert_eq!(f.run(&[b"CMS.INITBYDIM", b"d", b"100", b"5"]), "+OK\r\n");
16996 assert_eq!(
16997 f.run(&[b"CMS.INFO", b"d"]),
16998 "*6\r\n+width\r\n:100\r\n+depth\r\n:5\r\n+count\r\n:0\r\n"
16999 );
17000 assert_eq!(f.run(&[b"TYPE", b"d"]), "+CMSk-TYPE\r\n");
17001 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
17002 assert_eq!(
17005 f.run(&[b"CMS.INITBYPROB", b"p", b"0.01", b"0.03"]),
17006 "+OK\r\n"
17007 );
17008 assert_eq!(
17009 f.run(&[b"CMS.INFO", b"p"]),
17010 "*6\r\n+width\r\n:200\r\n+depth\r\n:6\r\n+count\r\n:0\r\n"
17011 );
17012 assert_eq!(
17015 f.run(&[b"CMS.INITBYDIM", b"d", b"0", b"2"]),
17016 "-CMS: key already exists\r\n"
17017 );
17018 assert_eq!(
17019 f.run(&[b"CMS.INITBYDIM", b"new", b"0", b"2"]),
17020 "-CMS: invalid width\r\n"
17021 );
17022 assert_eq!(
17023 f.run(&[b"CMS.INITBYDIM", b"new", b"2", b"0"]),
17024 "-CMS: invalid depth\r\n"
17025 );
17026 assert_eq!(
17027 f.run(&[b"CMS.INITBYPROB", b"new", b"0", b"0.5"]),
17028 "-CMS: invalid overestimation value\r\n"
17029 );
17030 assert_eq!(
17031 f.run(&[b"CMS.INITBYPROB", b"new", b"0.1", b"1"]),
17032 "-CMS: invalid prob value\r\n"
17033 );
17034 assert_eq!(
17038 f.run(&[b"CMS.INITBYPROB", b"new", b"0.5", b"1e-46"]),
17039 "-CMS: invalid init arguments\r\n"
17040 );
17041 assert_eq!(
17045 f.run(&[b"CMS.INITBYDIM", b"new", b"268435457", b"1"]),
17046 "-CMS: Insufficient memory to create the key\r\n"
17047 );
17048 assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
17049 }
17050
17051 #[test]
17054 fn increments_are_parsed_whole_and_the_counters_saturate() {
17055 let mut f = Fixture::new();
17056 f.run(&[b"CMS.INITBYDIM", b"c", b"100", b"4"]);
17057 assert_eq!(
17058 f.run(&[b"CMS.INCRBY", b"c", b"a", b"3", b"b", b"4"]),
17059 "*2\r\n:3\r\n:4\r\n"
17060 );
17061 assert_eq!(
17064 f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"a", b"1"]),
17065 "*2\r\n:4\r\n:5\r\n"
17066 );
17067 assert_eq!(
17069 f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"x"]),
17070 "-CMS: Cannot parse number\r\n"
17071 );
17072 assert_eq!(
17073 f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"-1"]),
17074 "-CMS: Number cannot be negative\r\n"
17075 );
17076 assert_eq!(
17077 f.run(&[b"CMS.QUERY", b"c", b"a", b"b"]),
17078 "*2\r\n:5\r\n:4\r\n"
17079 );
17080 f.run(&[b"CMS.INCRBY", b"c", b"a", b"4294967295"]);
17083 assert_eq!(
17084 f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b", b"1"]),
17085 "*2\r\n-CMS: INCRBY overflow\r\n:5\r\n"
17086 );
17087 assert_eq!(f.run(&[b"CMS.QUERY", b"c", b"a"]), "*1\r\n:4294967295\r\n");
17088 f.run(&[b"CMS.INITBYDIM", b"w", b"4", b"1"]);
17091 f.run(&[b"CMS.INCRBY", b"w", b"x", b"9223372036854775807"]);
17092 f.run(&[b"CMS.INCRBY", b"w", b"x", b"1"]);
17093 assert_eq!(
17094 f.run(&[b"CMS.INFO", b"w"]),
17095 "*6\r\n+width\r\n:4\r\n+depth\r\n:1\r\n+count\r\n:-9223372036854775808\r\n"
17096 );
17097 assert!(
17100 f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b"])
17101 .contains("wrong number of arguments")
17102 );
17103 assert_eq!(
17104 f.run(&[b"CMS.INCRBY", b"nope", b"a", b"1"]),
17105 "-CMS: key does not exist\r\n"
17106 );
17107 assert_eq!(
17108 f.run(&[b"CMS.QUERY", b"nope", b"a"]),
17109 "-CMS: key does not exist\r\n"
17110 );
17111 }
17112
17113 #[test]
17116 fn a_merge_lands_whole_or_not_at_all() {
17117 let mut f = Fixture::new();
17118 for name in [&b"m1"[..], b"m2", b"dst"] {
17119 f.run(&[b"CMS.INITBYDIM", name, b"64", b"3"]);
17120 }
17121 f.run(&[b"CMS.INCRBY", b"m1", b"a", b"5"]);
17122 f.run(&[b"CMS.INCRBY", b"m2", b"a", b"7"]);
17123 assert_eq!(
17124 f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
17125 "+OK\r\n"
17126 );
17127 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
17128 assert_eq!(
17131 f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
17132 "+OK\r\n"
17133 );
17134 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
17135 assert_eq!(
17136 f.run(&[
17137 b"CMS.MERGE",
17138 b"dst",
17139 b"2",
17140 b"m1",
17141 b"m2",
17142 b"WEIGHTS",
17143 b"2",
17144 b"3"
17145 ]),
17146 "+OK\r\n"
17147 );
17148 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
17149 assert_eq!(
17152 f.run(&[
17153 b"CMS.MERGE",
17154 b"dst",
17155 b"1",
17156 b"m1",
17157 b"WEIGHTS",
17158 b"4611686018427387904"
17159 ]),
17160 "-CMS: MERGE overflow\r\n"
17161 );
17162 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
17163 f.run(&[b"CMS.INITBYDIM", b"wide", b"128", b"3"]);
17166 assert_eq!(
17167 f.run(&[b"CMS.MERGE", b"gone", b"1", b"m1"]),
17168 "-CMS: key does not exist\r\n"
17169 );
17170 assert_eq!(
17171 f.run(&[b"CMS.MERGE", b"dst", b"0", b"m1"]),
17172 "-CMS: Number of keys must be positive\r\n"
17173 );
17174 assert_eq!(
17175 f.run(&[b"CMS.MERGE", b"dst", b"3", b"m1"]),
17176 "-CMS: wrong number of keys\r\n"
17177 );
17178 assert_eq!(
17179 f.run(&[b"CMS.MERGE", b"dst", b"1", b"m1", b"WEIGHTS", b"1", b"2"]),
17180 "-CMS: wrong number of keys/weights\r\n"
17181 );
17182 assert_eq!(
17183 f.run(&[b"CMS.MERGE", b"dst", b"1", b"wide"]),
17184 "-CMS: width/depth is not equal\r\n"
17185 );
17186 assert_eq!(
17187 f.run(&[b"CMS.MERGE", b"dst", b"1", b"gone"]),
17188 "-CMS: key does not exist\r\n"
17189 );
17190 }
17191
17192 #[test]
17195 fn a_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
17196 let mut f = Fixture::new();
17197 f.run(&[b"SET", b"s", b"text"]);
17198 for cmd in [
17199 vec![&b"CMS.INITBYDIM"[..], b"s", b"8", b"2"],
17200 vec![&b"CMS.INCRBY"[..], b"s", b"a", b"1"],
17201 vec![&b"CMS.QUERY"[..], b"s", b"a"],
17202 vec![&b"CMS.INFO"[..], b"s"],
17203 vec![&b"CMS.MERGE"[..], b"s", b"1", b"s"],
17204 ] {
17205 let name = String::from_utf8_lossy(cmd[0]).into_owned();
17206 let reply = f.run(&cmd);
17207 assert!(
17210 reply.starts_with("-WRONGTYPE") || reply == "-CMS: key already exists\r\n",
17211 "{name}: {reply}"
17212 );
17213 }
17214 f.run(&[b"CMS.INITBYDIM", b"c", b"64", b"2"]);
17215 assert_eq!(
17219 f.run(&[b"COPY", b"c", b"c2"]),
17220 "-ERR not supported for this module key\r\n"
17221 );
17222 assert_eq!(
17223 f.run(&[b"DUMP", b"c"]),
17224 "-ERR DUMP is not supported for this module key\r\n"
17225 );
17226 f.run(&[b"G.NADD", b"g", b"a"]);
17228 assert_eq!(
17229 f.run(&[b"COPY", b"g", b"g2"]),
17230 "-ERR COPY is not supported for a graph\r\n"
17231 );
17232 assert_eq!(
17233 f.run(&[b"DUMP", b"g"]),
17234 "-ERR DUMP is not supported for a graph\r\n"
17235 );
17236 assert_eq!(f.run(&[b"EXPIRE", b"c", b"100"]), ":1\r\n");
17239 assert_eq!(f.run(&[b"PERSIST", b"c"]), ":1\r\n");
17240 assert_eq!(f.run(&[b"RENAME", b"c", b"c3"]), "+OK\r\n");
17241 assert_eq!(f.run(&[b"TYPE", b"c3"]), "+CMSk-TYPE\r\n");
17242 assert_eq!(f.run(&[b"DEL", b"c3"]), ":1\r\n");
17243 }
17244
17245 #[test]
17250 fn a_reserve_takes_three_arguments_or_six() {
17251 let mut f = Fixture::new();
17252 assert_eq!(f.run(&[b"TOPK.RESERVE", b"t", b"5"]), "+OK\r\n");
17253 assert_eq!(
17254 f.run(&[b"TOPK.INFO", b"t"]),
17255 "*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"
17256 );
17257 for cmd in [
17260 vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8"],
17261 vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8", b"7"],
17262 ] {
17263 assert!(f.run(&cmd).contains("wrong number of arguments"));
17264 }
17265 assert_eq!(
17266 f.run(&[b"TOPK.RESERVE", b"u", b"5", b"8", b"7", b"0.5"]),
17267 "+OK\r\n"
17268 );
17269 assert_eq!(
17272 f.run(&[b"TOPK.RESERVE", b"u", b"0", b"0", b"0", b"9"]),
17273 "-TopK: key already exists\r\n"
17274 );
17275 assert_eq!(
17276 f.run(&[b"TOPK.RESERVE", b"v", b"0"]),
17277 "-TopK: invalid k\r\n"
17278 );
17279 assert_eq!(
17280 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"0", b"7", b"0.9"]),
17281 "-TopK: invalid width\r\n"
17282 );
17283 assert_eq!(
17284 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"x", b"0.9"]),
17285 "-TopK: invalid depth\r\n"
17286 );
17287 assert_eq!(
17289 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"0"]),
17290 "-TopK: invalid decay value. must be '<= 1' & '> 0'\r\n"
17291 );
17292 assert_eq!(
17293 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"1"]),
17294 "+OK\r\n"
17295 );
17296 assert_eq!(
17298 f.run(&[
17299 b"TOPK.RESERVE",
17300 b"w",
17301 b"1",
17302 b"4294967295",
17303 b"4294967295",
17304 b"0.9"
17305 ]),
17306 "-ERR Insufficient memory to create topk data structure\r\n"
17307 );
17308 }
17309
17310 #[test]
17312 fn the_kept_set_is_what_query_and_list_answer_from() {
17313 let mut f = Fixture::new();
17314 f.run(&[b"TOPK.RESERVE", b"t", b"2", b"1000", b"5", b"0.9"]);
17315 assert_eq!(
17318 f.run(&[b"TOPK.ADD", b"t", b"a", b"b"]),
17319 "*2\r\n$-1\r\n$-1\r\n"
17320 );
17321 assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"a", b"10"]), "*1\r\n$-1\r\n");
17322 assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"c"]), "*1\r\n$1\r\nb\r\n");
17325 assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"c", b"5"]), "*1\r\n$-1\r\n");
17326 assert_eq!(
17327 f.run(&[b"TOPK.QUERY", b"t", b"a", b"b", b"c"]),
17328 "*3\r\n:1\r\n:0\r\n:1\r\n"
17329 );
17330 assert_eq!(
17332 f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
17333 "*3\r\n:11\r\n:1\r\n:6\r\n"
17334 );
17335 assert_eq!(f.run(&[b"TOPK.LIST", b"t"]), "*2\r\n$1\r\na\r\n$1\r\nc\r\n");
17336 assert_eq!(
17337 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"]),
17338 "*4\r\n$1\r\na\r\n:11\r\n$1\r\nc\r\n:6\r\n"
17339 );
17340 assert_eq!(
17343 f.run(&[b"TOPK.LIST", b"t", b"w"]),
17344 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
17345 );
17346 assert_eq!(
17347 f.run(&[b"TOPK.LIST", b"t", b""]),
17348 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
17349 );
17350 assert_eq!(
17351 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNTS"]),
17352 "-WITHCOUNT keyword expected\r\n"
17353 );
17354 assert_eq!(
17357 f.run(&[b"TOPK.LIST", b"missing", b"nope"]),
17358 "-WITHCOUNT keyword expected\r\n"
17359 );
17360 assert_eq!(
17361 f.run(&[b"TOPK.LIST", b"missing"]),
17362 "-TopK: key does not exist\r\n"
17363 );
17364 f.run(&[b"TOPK.RESERVE", b"z", b"3"]);
17366 assert_eq!(
17367 f.run(&[b"TOPK.INCRBY", b"z", b"nothing", b"0"]),
17368 "*1\r\n$-1\r\n"
17369 );
17370 assert_eq!(f.run(&[b"TOPK.QUERY", b"z", b"nothing"]), "*1\r\n:1\r\n");
17371 assert_eq!(f.run(&[b"TOPK.LIST", b"z"]), "*0\r\n");
17372 }
17373
17374 #[test]
17377 fn an_increment_is_applied_as_it_goes_and_stops_at_a_bad_one() {
17378 let mut f = Fixture::new();
17379 f.run(&[b"TOPK.RESERVE", b"t", b"5", b"1000", b"5", b"0.9"]);
17380 assert_eq!(
17384 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"3", b"b", b"-1", b"c", b"4"]),
17385 format!(
17386 "*2\r\n$-1\r\n-{}\r\n",
17387 "TopK: increment must be an integer greater or equal to 0 and smaller or equal to 100,000"
17388 )
17389 );
17390 assert_eq!(
17391 f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
17392 "*3\r\n:3\r\n:0\r\n:0\r\n"
17393 );
17394 assert_eq!(
17396 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100000"]),
17397 "*1\r\n$-1\r\n"
17398 );
17399 assert!(
17400 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100001"])
17401 .contains("smaller or equal to 100,000")
17402 );
17403 assert!(
17405 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"1", b"b"])
17406 .contains("wrong number of arguments")
17407 );
17408 assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:100003\r\n");
17409 }
17410
17411 #[test]
17413 fn a_query_is_a_bool_and_info_is_a_map_on_resp3() {
17414 let mut f = Fixture::new();
17415 f.run(&[b"HELLO", b"3"]);
17416 f.run(&[b"TOPK.RESERVE", b"t", b"2", b"8", b"7", b"0.5"]);
17417 f.run(&[b"TOPK.ADD", b"t", b"a"]);
17418 assert_eq!(
17419 f.run(&[b"TOPK.QUERY", b"t", b"a", b"b"]),
17420 "*2\r\n#t\r\n#f\r\n"
17421 );
17422 assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:1\r\n");
17424 assert_eq!(
17425 f.run(&[b"TOPK.INFO", b"t"]),
17426 "%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"
17427 );
17428 assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"a"]), "*1\r\n_\r\n");
17429 }
17430
17431 #[test]
17434 fn a_top_k_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
17435 let mut f = Fixture::new();
17436 f.run(&[b"SET", b"s", b"text"]);
17437 for cmd in [
17438 vec![&b"TOPK.RESERVE"[..], b"s", b"5"],
17439 vec![&b"TOPK.ADD"[..], b"s", b"a"],
17440 vec![&b"TOPK.INCRBY"[..], b"s", b"a", b"1"],
17441 vec![&b"TOPK.QUERY"[..], b"s", b"a"],
17442 vec![&b"TOPK.COUNT"[..], b"s", b"a"],
17443 vec![&b"TOPK.LIST"[..], b"s"],
17444 vec![&b"TOPK.INFO"[..], b"s"],
17445 ] {
17446 let name = String::from_utf8_lossy(cmd[0]).into_owned();
17447 let reply = f.run(&cmd);
17448 assert!(
17449 reply.starts_with("-WRONGTYPE") || reply == "-TopK: key already exists\r\n",
17450 "{name}: {reply}"
17451 );
17452 }
17453 f.run(&[b"TOPK.RESERVE", b"t", b"5"]);
17454 assert_eq!(
17455 f.run(&[b"COPY", b"t", b"t2"]),
17456 "-ERR not supported for this module key\r\n"
17457 );
17458 assert_eq!(
17459 f.run(&[b"DUMP", b"t"]),
17460 "-ERR DUMP is not supported for this module key\r\n"
17461 );
17462 assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
17463 assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
17464 assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
17465 assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TopK-TYPE\r\n");
17466 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
17467 assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
17468 assert_eq!(
17471 f.run(&[b"TOPK.INFO", b"t3"]),
17472 "-TopK: key does not exist\r\n"
17473 );
17474 }
17475
17476 #[test]
17481 fn a_create_takes_two_arguments_or_four_and_reads_the_last_one() {
17482 let mut f = Fixture::new();
17483 assert_eq!(f.run(&[b"TDIGEST.CREATE", b"t"]), "+OK\r\n");
17484 assert_eq!(
17486 f.run(&[b"TDIGEST.INFO", b"t"]),
17487 "*18\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:0\r\n\
17488 +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:0\r\n+Unmerged weight\r\n:0\r\n\
17489 +Observations\r\n:0\r\n+Total compressions\r\n:0\r\n+Memory usage\r\n:9840\r\n"
17490 );
17491 assert_eq!(
17492 f.run(&[b"TDIGEST.CREATE", b"t"]),
17493 "-ERR T-Digest: key already exists\r\n"
17494 );
17495 assert!(
17497 f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION"])
17498 .contains("wrong number of arguments")
17499 );
17500 assert_eq!(
17501 f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION", b"1000"]),
17502 "+OK\r\n"
17503 );
17504 assert_eq!(
17505 f.run(&[b"TDIGEST.CREATE", b"v", b"compression", b"1"]),
17506 "+OK\r\n"
17507 );
17508 assert_eq!(
17512 f.run(&[b"TDIGEST.CREATE", b"w", b"100", b"COMPRESSION"]),
17513 "-ERR T-Digest: error parsing compression parameter\r\n"
17514 );
17515 assert_eq!(
17516 f.run(&[b"TDIGEST.CREATE", b"w", b"NOPE", b"100"]),
17517 "-ERR T-Digest: wrong keyword\r\n"
17518 );
17519 assert_eq!(
17520 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"1.5"]),
17521 "-ERR T-Digest: error parsing compression parameter\r\n"
17522 );
17523 assert_eq!(
17524 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"0"]),
17525 "-ERR T-Digest: compression parameter needs to be a positive integer\r\n"
17526 );
17527 assert_eq!(
17530 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"357913942"]),
17531 "-ERR T-Digest: allocation failed\r\n"
17532 );
17533 assert_eq!(
17535 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"100000000"]),
17536 "-ERR T-Digest: allocation failed\r\n"
17537 );
17538 assert_eq!(
17541 f.run(&[b"TDIGEST.CREATE", b"t", b"COMPRESSION", b"0"]),
17542 "-ERR T-Digest: key already exists\r\n"
17543 );
17544 }
17545
17546 #[test]
17549 fn the_quantile_family_answers_what_the_module_answers() {
17550 let mut f = Fixture::new();
17551 f.run(&[b"TDIGEST.CREATE", b"s"]);
17552 assert_eq!(
17553 f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]),
17554 "+OK\r\n"
17555 );
17556 assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), "$1\r\n1\r\n");
17557 assert_eq!(f.run(&[b"TDIGEST.MAX", b"s"]), "$1\r\n4\r\n");
17558 assert_eq!(
17560 f.run(&[b"TDIGEST.CDF", b"s", b"1", b"2", b"3", b"4"]),
17561 "*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"
17562 );
17563 assert_eq!(
17564 f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"0.5", b"1"]),
17565 "*3\r\n$1\r\n1\r\n$1\r\n3\r\n$1\r\n4\r\n"
17566 );
17567 assert_eq!(
17570 f.run(&[b"TDIGEST.QUANTILE", b"s", b"0.5", b"0.1", b"0.9"]),
17571 "*3\r\n$1\r\n3\r\n$1\r\n1\r\n$1\r\n4\r\n"
17572 );
17573 assert_eq!(
17574 f.run(&[b"TDIGEST.RANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
17575 "*5\r\n:-1\r\n:0\r\n:2\r\n:3\r\n:4\r\n"
17576 );
17577 assert_eq!(
17578 f.run(&[b"TDIGEST.REVRANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
17579 "*5\r\n:4\r\n:3\r\n:1\r\n:0\r\n:-1\r\n"
17580 );
17581 assert_eq!(
17582 f.run(&[b"TDIGEST.BYRANK", b"s", b"0", b"1", b"3", b"4"]),
17583 "*4\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n4\r\n$3\r\ninf\r\n"
17584 );
17585 assert_eq!(
17586 f.run(&[b"TDIGEST.BYREVRANK", b"s", b"0", b"1", b"3", b"4"]),
17587 "*4\r\n$1\r\n4\r\n$1\r\n3\r\n$1\r\n1\r\n$4\r\n-inf\r\n"
17588 );
17589 assert_eq!(
17590 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0", b"1"]),
17591 "$3\r\n2.5\r\n"
17592 );
17593 assert_eq!(
17594 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.25", b"0.75"]),
17595 "$3\r\n2.5\r\n"
17596 );
17597 assert_eq!(
17599 f.run(&[b"TDIGEST.QUANTILE", b"s", b"1.1"]),
17600 "-ERR T-Digest: quantile should be in [0,1]\r\n"
17601 );
17602 assert_eq!(
17603 f.run(&[b"TDIGEST.QUANTILE", b"s", b"zzz"]),
17604 "-ERR T-Digest: error parsing quantile\r\n"
17605 );
17606 assert_eq!(
17607 f.run(&[b"TDIGEST.CDF", b"s", b"zzz"]),
17608 "-ERR T-Digest: error parsing cdf\r\n"
17609 );
17610 assert_eq!(
17611 f.run(&[b"TDIGEST.RANK", b"s", b"zzz"]),
17612 "-ERR T-Digest: error parsing value\r\n"
17613 );
17614 assert_eq!(
17615 f.run(&[b"TDIGEST.BYRANK", b"s", b"-1"]),
17616 "-ERR T-Digest: rank needs to be non negative\r\n"
17617 );
17618 assert_eq!(
17619 f.run(&[b"TDIGEST.BYRANK", b"s", b"1.5"]),
17620 "-ERR T-Digest: error parsing rank\r\n"
17621 );
17622 assert_eq!(
17625 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"zzz", b"0.9"]),
17626 "-ERR T-Digest: error parsing low_cut_percentile\r\n"
17627 );
17628 assert_eq!(
17629 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"zzz"]),
17630 "-ERR T-Digest: error parsing high_cut_percentile\r\n"
17631 );
17632 assert_eq!(
17633 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"1.1"]),
17634 "-ERR T-Digest: low_cut_percentile and high_cut_percentile should be in [0,1]\r\n"
17635 );
17636 assert_eq!(
17637 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.5", b"0.5"]),
17638 "-ERR T-Digest: low_cut_percentile should be lower than high_cut_percentile\r\n"
17639 );
17640 }
17641
17642 #[test]
17645 fn an_empty_digest_has_an_answer_for_everything() {
17646 let mut f = Fixture::new();
17647 f.run(&[b"TDIGEST.CREATE", b"e"]);
17648 assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
17649 assert_eq!(f.run(&[b"TDIGEST.MAX", b"e"]), "$3\r\nnan\r\n");
17650 assert_eq!(
17651 f.run(&[b"TDIGEST.QUANTILE", b"e", b"0", b"1"]),
17652 "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
17653 );
17654 assert_eq!(f.run(&[b"TDIGEST.CDF", b"e", b"0"]), "*1\r\n$3\r\nnan\r\n");
17655 assert_eq!(
17656 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"e", b"0.1", b"0.9"]),
17657 "$3\r\nnan\r\n"
17658 );
17659 assert_eq!(
17662 f.run(&[b"TDIGEST.RANK", b"e", b"0", b"1"]),
17663 "*2\r\n:-2\r\n:-2\r\n"
17664 );
17665 assert_eq!(
17666 f.run(&[b"TDIGEST.REVRANK", b"e", b"0", b"1"]),
17667 "*2\r\n:-2\r\n:-2\r\n"
17668 );
17669 assert_eq!(
17670 f.run(&[b"TDIGEST.BYRANK", b"e", b"0", b"5"]),
17671 "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
17672 );
17673 f.run(&[b"TDIGEST.ADD", b"e", b"1", b"2", b"3"]);
17675 assert_eq!(f.run(&[b"TDIGEST.RESET", b"e"]), "+OK\r\n");
17676 assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
17677 f.run(&[b"TDIGEST.CREATE", b"e2"]);
17680 assert_eq!(
17681 f.run(&[b"TDIGEST.INFO", b"e"]),
17682 f.run(&[b"TDIGEST.INFO", b"e2"])
17683 );
17684 }
17685
17686 #[test]
17689 fn a_sample_is_read_the_way_redis_reads_a_double() {
17690 let mut f = Fixture::new();
17691 f.run(&[b"TDIGEST.CREATE", b"a"]);
17692 for bad in [
17695 &b"nan"[..],
17696 b"1e400",
17697 b"-1e400",
17698 b"1e309",
17699 b"1e-400",
17700 b"",
17701 b" 1",
17702 b"1 ",
17703 b"1e",
17704 b"--1",
17705 ] {
17706 assert_eq!(
17707 f.run(&[b"TDIGEST.ADD", b"a", bad]),
17708 "-ERR T-Digest: error parsing val parameter\r\n",
17709 "{}",
17710 String::from_utf8_lossy(bad)
17711 );
17712 }
17713 for word in [&b"inf"[..], b"-inf", b"+INF", b"Infinity"] {
17716 assert_eq!(
17717 f.run(&[b"TDIGEST.ADD", b"a", word]),
17718 "-ERR T-Digest: val parameter needs to be a finite number\r\n",
17719 "{}",
17720 String::from_utf8_lossy(word)
17721 );
17722 }
17723 for good in [&b"0x10"[..], b".5", b"1.", b"1e-320", b"-0", b"0"] {
17726 assert_eq!(
17727 f.run(&[b"TDIGEST.ADD", b"a", good]),
17728 "+OK\r\n",
17729 "{}",
17730 String::from_utf8_lossy(good)
17731 );
17732 }
17733 assert!(
17735 f.run(&[b"TDIGEST.INFO", b"a"])
17736 .contains("Observations\r\n:6\r\n")
17737 );
17738 assert_eq!(
17741 f.run(&[b"TDIGEST.ADD", b"a", b"1", b"zzz"]),
17742 "-ERR T-Digest: error parsing val parameter\r\n"
17743 );
17744 assert!(
17745 f.run(&[b"TDIGEST.INFO", b"a"])
17746 .contains("Observations\r\n:6\r\n")
17747 );
17748 }
17749
17750 #[test]
17753 fn a_merge_sweeps_the_destination_between_its_inputs() {
17754 let mut f = Fixture::new();
17755 f.run(&[b"TDIGEST.CREATE", b"m1", b"COMPRESSION", b"100"]);
17756 f.run(&[b"TDIGEST.ADD", b"m1", b"1", b"2", b"3"]);
17757 f.run(&[b"TDIGEST.CREATE", b"m2", b"COMPRESSION", b"200"]);
17758 f.run(&[b"TDIGEST.ADD", b"m2", b"4", b"5", b"6"]);
17759 assert_eq!(
17760 f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"m2"]),
17761 "+OK\r\n"
17762 );
17763 let info = f.run(&[b"TDIGEST.INFO", b"d"]);
17768 assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
17769 assert!(info.contains("Merged nodes\r\n:3\r\n"), "{info}");
17770 assert!(info.contains("Unmerged nodes\r\n:3\r\n"), "{info}");
17771 assert!(info.contains("Total compressions\r\n:1\r\n"), "{info}");
17772 assert_eq!(f.run(&[b"TDIGEST.MIN", b"d"]), "$1\r\n1\r\n");
17773 assert_eq!(f.run(&[b"TDIGEST.MAX", b"d"]), "$1\r\n6\r\n");
17774 assert!(
17777 f.run(&[b"TDIGEST.INFO", b"m1"])
17778 .contains("Merged nodes\r\n:3\r\n")
17779 );
17780 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1"]);
17783 let info = f.run(&[b"TDIGEST.INFO", b"d"]);
17784 assert!(info.contains("Observations\r\n:9\r\n"), "{info}");
17785 assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
17786 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"OVERRIDE"]);
17789 let info = f.run(&[b"TDIGEST.INFO", b"d"]);
17790 assert!(info.contains("Observations\r\n:3\r\n"), "{info}");
17791 assert!(info.contains("Compression\r\n:100\r\n"), "{info}");
17792 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION", b"500"]);
17794 assert!(
17795 f.run(&[b"TDIGEST.INFO", b"d"])
17796 .contains("Compression\r\n:500\r\n")
17797 );
17798 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"d"]);
17800 assert!(
17801 f.run(&[b"TDIGEST.INFO", b"d"])
17802 .contains("Observations\r\n:12\r\n")
17803 );
17804 assert_eq!(
17806 f.run(&[b"TDIGEST.MERGE", b"d", b"zzz", b"m1"]),
17807 "-ERR T-Digest: error parsing numkeys\r\n"
17808 );
17809 assert_eq!(
17810 f.run(&[b"TDIGEST.MERGE", b"d", b"0", b"m1"]),
17811 "-ERR T-Digest: numkeys needs to be a positive integer\r\n"
17812 );
17813 assert!(
17814 f.run(&[b"TDIGEST.MERGE", b"d", b"3", b"m1", b"m2"])
17815 .contains("wrong number of arguments")
17816 );
17817 assert!(
17818 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION"])
17819 .contains("wrong number of arguments")
17820 );
17821 assert_eq!(
17822 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"NOPE"]),
17823 "-ERR T-Digest: wrong keyword\r\n"
17824 );
17825 assert_eq!(
17828 f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"gone"]),
17829 "-ERR T-Digest: key does not exist\r\n"
17830 );
17831 assert!(
17832 f.run(&[b"TDIGEST.INFO", b"d"])
17833 .contains("Observations\r\n:12\r\n")
17834 );
17835 assert_eq!(
17838 f.run(&[b"TDIGEST.MERGE", b"gone", b"1", b"gone"]),
17839 "-ERR T-Digest: key does not exist\r\n"
17840 );
17841 }
17842
17843 #[test]
17845 fn a_digest_answers_doubles_and_a_map_on_resp3() {
17846 let mut f = Fixture::new();
17847 f.run(&[b"HELLO", b"3"]);
17848 f.run(&[b"TDIGEST.CREATE", b"s"]);
17849 f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]);
17850 assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), ",1\r\n");
17851 assert_eq!(
17852 f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"1"]),
17853 "*2\r\n,1\r\n,4\r\n"
17854 );
17855 assert_eq!(f.run(&[b"TDIGEST.CDF", b"s", b"1"]), "*1\r\n,0.125\r\n");
17856 assert_eq!(f.run(&[b"TDIGEST.BYRANK", b"s", b"4"]), "*1\r\n,inf\r\n");
17858 assert_eq!(
17859 f.run(&[b"TDIGEST.BYREVRANK", b"s", b"4"]),
17860 "*1\r\n,-inf\r\n"
17861 );
17862 f.run(&[b"TDIGEST.CREATE", b"e"]);
17863 assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), ",nan\r\n");
17864 assert_eq!(f.run(&[b"TDIGEST.RANK", b"s", b"1"]), "*1\r\n:0\r\n");
17866 assert_eq!(
17869 f.run(&[b"TDIGEST.INFO", b"s"]),
17870 "%9\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:4\r\n\
17871 +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:4\r\n+Unmerged weight\r\n:0\r\n\
17872 +Observations\r\n:4\r\n+Total compressions\r\n:1\r\n+Memory usage\r\n:9840\r\n"
17873 );
17874 }
17875
17876 #[test]
17879 fn a_t_digest_is_a_module_key_to_the_rest_of_the_keyspace() {
17880 let mut f = Fixture::new();
17881 f.run(&[b"SET", b"s", b"text"]);
17882 for cmd in [
17883 vec![&b"TDIGEST.CREATE"[..], b"s"],
17884 vec![&b"TDIGEST.RESET"[..], b"s"],
17885 vec![&b"TDIGEST.ADD"[..], b"s", b"1"],
17886 vec![&b"TDIGEST.MIN"[..], b"s"],
17887 vec![&b"TDIGEST.MAX"[..], b"s"],
17888 vec![&b"TDIGEST.QUANTILE"[..], b"s", b"0.5"],
17889 vec![&b"TDIGEST.CDF"[..], b"s", b"1"],
17890 vec![&b"TDIGEST.TRIMMED_MEAN"[..], b"s", b"0.1", b"0.9"],
17891 vec![&b"TDIGEST.RANK"[..], b"s", b"1"],
17892 vec![&b"TDIGEST.REVRANK"[..], b"s", b"1"],
17893 vec![&b"TDIGEST.BYRANK"[..], b"s", b"0"],
17894 vec![&b"TDIGEST.BYREVRANK"[..], b"s", b"0"],
17895 vec![&b"TDIGEST.INFO"[..], b"s"],
17896 ] {
17897 let name = String::from_utf8_lossy(cmd[0]).into_owned();
17898 let reply = f.run(&cmd);
17899 assert!(reply.starts_with("-WRONGTYPE"), "{name}: {reply}");
17900 }
17901 f.run(&[b"TDIGEST.CREATE", b"t"]);
17903 assert!(
17904 f.run(&[b"TDIGEST.MERGE", b"s", b"1", b"t"])
17905 .starts_with("-WRONGTYPE")
17906 );
17907 assert!(
17908 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"s"])
17909 .starts_with("-WRONGTYPE")
17910 );
17911 assert_eq!(
17912 f.run(&[b"COPY", b"t", b"t2"]),
17913 "-ERR not supported for this module key\r\n"
17914 );
17915 assert_eq!(
17916 f.run(&[b"DUMP", b"t"]),
17917 "-ERR DUMP is not supported for this module key\r\n"
17918 );
17919 assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
17920 assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
17921 assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
17922 assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TDIS-TYPE\r\n");
17923 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
17924 assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
17925 assert_eq!(
17928 f.run(&[b"TDIGEST.INFO", b"t3"]),
17929 "-ERR T-Digest: key does not exist\r\n"
17930 );
17931 assert_eq!(
17934 f.run(&[b"TDIGEST.QUANTILE", b"t3", b"zzz"]),
17935 "-ERR T-Digest: key does not exist\r\n"
17936 );
17937 }
17938
17939 fn without_memory(reply: &str) -> String {
17949 let head = "+memoryUsage\r\n:";
17950 let at = reply.find(head).expect("every TS.INFO reports memory");
17951 let rest = &reply[at + head.len()..];
17952 let end = rest.find("\r\n").expect("and it is a whole number");
17953 format!("{}{}", &reply[..at + head.len()], &rest[end..])
17954 }
17955
17956 #[test]
17959 fn a_series_is_made_empty_and_reports_on_itself() {
17960 let mut f = Fixture::new();
17961 assert_eq!(f.run(&[b"TS.CREATE", b"t"]), "+OK\r\n");
17962 assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
17963 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t"]), "$3\r\nraw\r\n");
17964 assert_eq!(
17968 without_memory(&f.run(&[b"TS.INFO", b"t"])),
17969 "*28\r\n\
17970 +totalSamples\r\n:0\r\n\
17971 +memoryUsage\r\n:\r\n\
17972 +firstTimestamp\r\n:0\r\n\
17973 +lastTimestamp\r\n:0\r\n\
17974 +retentionTime\r\n:0\r\n\
17975 +chunkCount\r\n:1\r\n\
17976 +chunkSize\r\n:4096\r\n\
17977 +chunkType\r\n+compressed\r\n\
17978 +duplicatePolicy\r\n+block\r\n\
17979 +labels\r\n*0\r\n\
17980 +sourceKey\r\n$-1\r\n\
17981 +rules\r\n*0\r\n\
17982 +ignoreMaxTimeDiff\r\n:0\r\n\
17983 +ignoreMaxValDiff\r\n$1\r\n0\r\n"
17984 );
17985 assert_eq!(
17988 f.run(&[b"TS.CREATE", b"t"]),
17989 "-ERR TSDB: key already exists\r\n"
17990 );
17991 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
17992 assert_eq!(
17993 f.run(&[b"TS.CREATE", b"str"]),
17994 "-ERR TSDB: key already exists\r\n"
17995 );
17996 assert_eq!(
17999 f.run(&[b"TS.CREATE", b"t", b"RETENTION", b"abc"]),
18000 "-ERR TSDB: Couldn't parse RETENTION\r\n"
18001 );
18002 let wrong = "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
18008 assert_eq!(f.run(&[b"TS.INFO", b"str"]), wrong);
18009 assert_eq!(f.run(&[b"TS.GET", b"str"]), wrong);
18010 assert_eq!(f.run(&[b"TS.ALTER", b"str"]), wrong);
18011 assert_eq!(f.run(&[b"TS.DEL", b"str", b"0", b"1"]), wrong);
18012 assert_eq!(f.run(&[b"TS.INCRBY", b"str", b"1"]), wrong);
18013 assert_eq!(
18014 f.run(&[b"TS.ADD", b"str", b"1", b"1"]),
18015 "-ERR TSDB: the key is not a TSDB key\r\n"
18016 );
18017 assert_eq!(
18019 f.run(&[b"TS.INFO", b"nope"]),
18020 "-ERR TSDB: the key does not exist\r\n"
18021 );
18022 assert_eq!(
18023 f.run(&[b"TS.GET", b"nope"]),
18024 "-ERR TSDB: the key does not exist\r\n"
18025 );
18026 assert_eq!(
18027 f.run(&[b"TS.ALTER", b"nope"]),
18028 "-ERR TSDB: the key does not exist\r\n"
18029 );
18030 assert_eq!(
18031 f.run(&[b"TS.DEL", b"nope", b"1", b"2"]),
18032 "-ERR TSDB: the key does not exist\r\n"
18033 );
18034 }
18035
18036 #[test]
18039 fn the_options_are_a_keyword_scan_and_not_a_grammar() {
18040 let mut f = Fixture::new();
18041 assert_eq!(
18042 f.run(&[
18043 b"TS.CREATE",
18044 b"t",
18045 b"RETENTION",
18046 b"5000",
18047 b"ENCODING",
18048 b"UNCOMPRESSED",
18049 b"CHUNK_SIZE",
18050 b"128",
18051 b"DUPLICATE_POLICY",
18052 b"LAST",
18053 b"IGNORE",
18054 b"10",
18055 b"0.5",
18056 b"LABELS",
18057 b"room",
18058 b"kitchen"
18059 ]),
18060 "+OK\r\n"
18061 );
18062 let info = f.run(&[b"TS.INFO", b"t"]);
18063 assert!(info.contains("+retentionTime\r\n:5000\r\n"), "{info}");
18064 assert!(info.contains("+chunkSize\r\n:128\r\n"), "{info}");
18065 assert!(info.contains("+chunkType\r\n+uncompressed\r\n"), "{info}");
18066 assert!(info.contains("+duplicatePolicy\r\n+last\r\n"), "{info}");
18067 assert!(info.contains("+ignoreMaxTimeDiff\r\n:10\r\n"), "{info}");
18068 assert!(
18071 info.contains("+ignoreMaxValDiff\r\n$3\r\n0.5\r\n"),
18072 "{info}"
18073 );
18074 assert!(
18075 info.contains("+labels\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n"),
18076 "{info}"
18077 );
18078
18079 assert_eq!(f.run(&[b"TS.CREATE", b"junk", b"FOO"]), "+OK\r\n");
18081 assert_eq!(
18085 f.run(&[
18086 b"TS.CREATE",
18087 b"g",
18088 b"LABELS",
18089 b"a",
18090 b"b",
18091 b"RETENTION",
18092 b"5"
18093 ]),
18094 "+OK\r\n"
18095 );
18096 let greedy = f.run(&[b"TS.INFO", b"g"]);
18097 assert!(greedy.contains("+retentionTime\r\n:5\r\n"), "{greedy}");
18098 assert!(
18099 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"),
18100 "{greedy}"
18101 );
18102
18103 assert_eq!(
18105 f.run(&[b"TS.CREATE", b"e", b"LABELS", b"a", b"b(c"]),
18106 "-ERR TSDB: Couldn't parse LABELS\r\n"
18107 );
18108 assert_eq!(
18109 f.run(&[b"TS.CREATE", b"e", b"LABELS", b"", b"b"]),
18110 "-ERR TSDB: Couldn't parse LABELS\r\n"
18111 );
18112 assert_eq!(
18113 f.run(&[b"TS.CREATE", b"e", b"RETENTION"]),
18114 "-ERR TSDB: Couldn't parse RETENTION\r\n"
18115 );
18116 assert_eq!(
18119 f.run(&[b"TS.CREATE", b"e", b"RETENTION", b"-1"]),
18120 "-TSDB: Couldn't parse RETENTION\r\n"
18121 );
18122 assert_eq!(
18123 f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"abc"]),
18124 "-ERR TSDB: Couldn't parse CHUNK_SIZE\r\n"
18125 );
18126 assert_eq!(
18127 f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"100"]),
18128 "-ERR TSDB: CHUNK_SIZE value must be a multiple of 8 in the range [48 .. 1048576]\r\n"
18129 );
18130 assert_eq!(
18131 f.run(&[b"TS.CREATE", b"e", b"ENCODING", b"nope"]),
18132 "-ERR TSDB: unknown ENCODING parameter\r\n"
18133 );
18134 assert!(
18137 f.run(&[b"TS.CREATE", b"e", b"ENCODING"])
18138 .contains("wrong number of arguments for 'ts.create' command")
18139 );
18140 assert_eq!(
18141 f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY"]),
18142 "-ERR TSDB: Couldn't parse DUPLICATE_POLICY\r\n"
18143 );
18144 assert_eq!(
18145 f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY", b"nope"]),
18146 "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
18147 );
18148 assert_eq!(
18149 f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"10"]),
18150 "-ERR TSDB: Couldn't parse IGNORE\r\n"
18151 );
18152 assert_eq!(
18153 f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"-1", b"1"]),
18154 "-ERR TSDB: IGNORE arguments cannot be negative\r\n"
18155 );
18156 assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
18157
18158 assert_eq!(f.run(&[b"TS.ALTER", b"t", b"RETENTION", b"9"]), "+OK\r\n");
18161 let after = f.run(&[b"TS.INFO", b"t"]);
18162 assert!(after.contains("+retentionTime\r\n:9\r\n"), "{after}");
18163 assert!(after.contains("+chunkSize\r\n:128\r\n"), "{after}");
18164 assert!(after.contains("+duplicatePolicy\r\n+last\r\n"), "{after}");
18165 assert_eq!(
18166 f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"nope"]),
18167 "-ERR TSDB: unknown ENCODING parameter\r\n"
18168 );
18169 assert_eq!(
18171 f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"COMPRESSED"]),
18172 "+OK\r\n"
18173 );
18174 assert!(
18175 f.run(&[b"TS.INFO", b"t"])
18176 .contains("+chunkType\r\n+uncompressed\r\n")
18177 );
18178 }
18179
18180 #[test]
18183 fn samples_land_where_they_are_put_and_the_newest_comes_back() {
18184 let mut f = Fixture::new();
18185 assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1.5"]), ":100\r\n");
18186 assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
18188 assert_eq!(f.run(&[b"TS.ADD", b"t", b"200", b"2"]), ":200\r\n");
18189 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+2\r\n");
18192 assert_eq!(f.run(&[b"TS.ADD", b"t", b"300", b"1e300"]), ":300\r\n");
18193 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+1E300\r\n");
18194 assert_eq!(f.run(&[b"TS.CREATE", b"empty"]), "+OK\r\n");
18197 assert_eq!(f.run(&[b"TS.GET", b"empty"]), "*0\r\n");
18198
18199 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
18202 assert_eq!(
18203 f.run(&[b"TS.ADD", b"str", b"1", b".5"]),
18204 "-ERR TSDB: invalid value\r\n"
18205 );
18206 for bad in [
18210 &b".5"[..],
18211 b"1.",
18212 b"+1",
18213 b" 1",
18214 b"0x10",
18215 b"inf",
18216 b"1e400",
18217 b"--1",
18218 b"1e",
18219 ] {
18220 assert_eq!(
18221 f.run(&[b"TS.ADD", b"v", b"1", bad]),
18222 "-ERR TSDB: invalid value\r\n",
18223 "{}",
18224 String::from_utf8_lossy(bad)
18225 );
18226 }
18227 assert_eq!(f.run(&[b"TS.ADD", b"v", b"1", b"NaN"]), ":1\r\n");
18229
18230 assert_eq!(
18233 f.run(&[b"TS.ADD", b"t", b"abc", b"1"]),
18234 "-ERR TSDB: invalid timestamp\r\n"
18235 );
18236 assert_eq!(
18237 f.run(&[b"TS.ADD", b"t", b"-1", b"1"]),
18238 "-ERR TSDB: invalid timestamp, must be a nonnegative integer\r\n"
18239 );
18240
18241 assert_eq!(
18244 f.run(&[b"TS.ADD", b"t", b"300", b"7"]),
18245 "-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"
18246 );
18247 assert_eq!(
18248 f.run(&[b"TS.ADD", b"t", b"300", b"7", b"ON_DUPLICATE", b"LAST"]),
18249 ":300\r\n"
18250 );
18251 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+7\r\n");
18252 assert_eq!(
18255 f.run(&[b"TS.ADD", b"fresh", b"1", b"1", b"ON_DUPLICATE", b"nope"]),
18256 ":1\r\n"
18257 );
18258 assert_eq!(
18259 f.run(&[b"TS.ADD", b"fresh", b"2", b"1", b"ON_DUPLICATE", b"nope"]),
18260 "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
18261 );
18262
18263 assert_eq!(f.run(&[b"TS.CREATE", b"r", b"RETENTION", b"50"]), "+OK\r\n");
18266 assert_eq!(f.run(&[b"TS.ADD", b"r", b"1000", b"1"]), ":1000\r\n");
18267 assert_eq!(f.run(&[b"TS.ADD", b"r", b"960", b"1"]), ":960\r\n");
18268 assert_eq!(
18269 f.run(&[b"TS.ADD", b"r", b"940", b"1"]),
18270 "-ERR TSDB: Timestamp is older than retention\r\n"
18271 );
18272 assert_eq!(f.run(&[b"TS.ADD", b"r", b"1100", b"1"]), ":1100\r\n");
18274 assert!(
18275 f.run(&[b"TS.INFO", b"r"])
18276 .contains("+totalSamples\r\n:1\r\n")
18277 );
18278
18279 assert_eq!(
18282 f.run(&[
18283 b"TS.CREATE",
18284 b"i",
18285 b"DUPLICATE_POLICY",
18286 b"LAST",
18287 b"IGNORE",
18288 b"10",
18289 b"0.5"
18290 ]),
18291 "+OK\r\n"
18292 );
18293 assert_eq!(f.run(&[b"TS.ADD", b"i", b"1000", b"1"]), ":1000\r\n");
18294 assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"1.2"]), ":1000\r\n");
18295 assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"9"]), ":1005\r\n");
18296 }
18297
18298 #[test]
18301 fn a_madd_answers_each_triple_and_creates_nothing() {
18302 let mut f = Fixture::new();
18303 assert_eq!(f.run(&[b"TS.CREATE", b"a"]), "+OK\r\n");
18304 assert_eq!(f.run(&[b"TS.CREATE", b"b"]), "+OK\r\n");
18305 assert_eq!(
18306 f.run(&[
18307 b"TS.MADD", b"a", b"100", b"1", b"b", b"100", b"2", b"a", b"200", b"3"
18308 ]),
18309 "*3\r\n:100\r\n:100\r\n:200\r\n"
18310 );
18311 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
18314 assert_eq!(
18315 f.run(&[
18316 b"TS.MADD", b"gone", b"1", b"1", b"str", b"1", b"1", b"a", b"300", b"4"
18317 ]),
18318 "*3\r\n\
18319 -ERR TSDB: the key is not a TSDB key\r\n\
18320 -ERR TSDB: the key is not a TSDB key\r\n\
18321 :300\r\n"
18322 );
18323 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
18324 assert_eq!(
18326 f.run(&[b"TS.MADD", b"a", b"400", b"zzz", b"a", b"abc", b"1"]),
18327 "*2\r\n-ERR TSDB: invalid value\r\n-ERR TSDB: invalid timestamp\r\n"
18328 );
18329 assert!(
18331 f.run(&[b"TS.MADD", b"a", b"1", b"1", b"a"])
18332 .contains("wrong number of arguments for 'ts.madd' command")
18333 );
18334 }
18335
18336 #[test]
18338 fn an_increment_walks_the_newest_value_up_and_down() {
18339 let mut f = Fixture::new();
18340 assert_eq!(
18341 f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
18342 ":100\r\n"
18343 );
18344 assert_eq!(
18345 f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
18346 ":100\r\n"
18347 );
18348 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n+10\r\n");
18351 assert_eq!(
18352 f.run(&[b"TS.DECRBY", b"t", b"3", b"TIMESTAMP", b"200"]),
18353 ":200\r\n"
18354 );
18355 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+7\r\n");
18356 assert_eq!(
18359 f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP", b"150"]),
18360 "-TSDB: timestamp must be equal to or higher than the maximum existing timestamp\r\n"
18361 );
18362 assert_eq!(
18366 f.run(&[b"TS.INCRBY", b"p", b"+5", b"TIMESTAMP", b"1"]),
18367 ":1\r\n"
18368 );
18369 assert_eq!(
18370 f.run(&[b"TS.INCRBY", b"q", b".5", b"TIMESTAMP", b"1"]),
18371 ":1\r\n"
18372 );
18373 assert_eq!(
18374 f.run(&[b"TS.INCRBY", b"t", b"nan"]),
18375 "-ERR TSDB: invalid increase/decrease value\r\n"
18376 );
18377 assert_eq!(
18378 f.run(&[b"TS.INCRBY", b"t", b"zzz"]),
18379 "-ERR TSDB: invalid increase/decrease value\r\n"
18380 );
18381 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
18384 assert_eq!(
18385 f.run(&[b"TS.INCRBY", b"str", b"zzz"]),
18386 "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
18387 );
18388 assert_eq!(
18393 f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP"]),
18394 "-ERR TSDB: invalid timestamp\r\n"
18395 );
18396 assert_eq!(
18399 f.run(&[b"TS.INCRBY", b"lab", b"1", b"LABELS", b"TIMESTAMP", b"5"]),
18400 format!(":{}\r\n", f.server.now_ms())
18401 );
18402 assert_eq!(f.run(&[b"TS.ADD", b"n", b"1", b"nan"]), ":1\r\n");
18404 assert_eq!(
18405 f.run(&[b"TS.INCRBY", b"n", b"1", b"TIMESTAMP", b"2"]),
18406 "-ERR TSDB: cannot increment/decrement NaN value\r\n"
18407 );
18408 }
18409
18410 #[test]
18412 fn deleting_takes_out_a_span_and_answers_how_many_went() {
18413 let mut f = Fixture::new();
18414 for at in [b"100".as_slice(), b"200", b"300", b"400"] {
18415 f.run(&[b"TS.ADD", b"t", at, b"1"]);
18416 }
18417 assert_eq!(f.run(&[b"TS.DEL", b"t", b"200", b"300"]), ":2\r\n");
18418 assert!(
18419 f.run(&[b"TS.INFO", b"t"])
18420 .contains("+totalSamples\r\n:2\r\n")
18421 );
18422 assert_eq!(f.run(&[b"TS.DEL", b"t", b"400", b"100"]), ":0\r\n");
18424 assert_eq!(f.run(&[b"TS.DEL", b"t", b"-", b"+"]), ":2\r\n");
18426 let empty = f.run(&[b"TS.INFO", b"t"]);
18429 assert!(empty.contains("+totalSamples\r\n:0\r\n"), "{empty}");
18430 assert!(empty.contains("+chunkCount\r\n:1\r\n"), "{empty}");
18431 assert!(empty.contains("+firstTimestamp\r\n:0\r\n"), "{empty}");
18432 assert!(empty.contains("+lastTimestamp\r\n:0\r\n"), "{empty}");
18433 assert_eq!(f.run(&[b"TS.DEL", b"t", b"0", b"1000"]), ":0\r\n");
18434 assert_eq!(
18436 f.run(&[b"TS.DEL", b"t", b"abc", b"5"]),
18437 "-ERR TSDB: wrong fromTimestamp\r\n"
18438 );
18439 assert_eq!(
18440 f.run(&[b"TS.DEL", b"t", b"5", b"abc"]),
18441 "-ERR TSDB: wrong toTimestamp\r\n"
18442 );
18443 assert_eq!(
18444 f.run(&[b"TS.DEL", b"t", b"-5", b"5"]),
18445 "-ERR TSDB: wrong fromTimestamp\r\n"
18446 );
18447 }
18448
18449 #[test]
18452 fn resp3_writes_a_sample_as_a_double_and_the_info_as_a_map() {
18453 let mut f = Fixture::new();
18454 f.out = Out::new(Proto::Resp3);
18455 assert_eq!(
18456 f.run(&[b"TS.CREATE", b"t", b"LABELS", b"room", b"kitchen"]),
18457 "+OK\r\n"
18458 );
18459 assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1e300"]), ":100\r\n");
18460 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n,1e+300\r\n");
18462 assert_eq!(
18463 without_memory(&f.run(&[b"TS.INFO", b"t"])),
18464 "%14\r\n\
18465 +totalSamples\r\n:1\r\n\
18466 +memoryUsage\r\n:\r\n\
18467 +firstTimestamp\r\n:100\r\n\
18468 +lastTimestamp\r\n:100\r\n\
18469 +retentionTime\r\n:0\r\n\
18470 +chunkCount\r\n:1\r\n\
18471 +chunkSize\r\n:4096\r\n\
18472 +chunkType\r\n+compressed\r\n\
18473 +duplicatePolicy\r\n+block\r\n\
18474 +labels\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
18475 +sourceKey\r\n_\r\n\
18476 +rules\r\n%0\r\n\
18477 +ignoreMaxTimeDiff\r\n:0\r\n\
18478 +ignoreMaxValDiff\r\n,0\r\n"
18479 );
18480 }
18481
18482 #[test]
18485 fn a_range_walks_a_span_and_a_revrange_walks_it_backwards() {
18486 let mut f = Fixture::new();
18487 for (at, v) in [
18488 (b"100".as_slice(), b"1".as_slice()),
18489 (b"200", b"2"),
18490 (b"300", b"3"),
18491 (b"400", b"4"),
18492 ] {
18493 f.run(&[b"TS.ADD", b"t", at, v]);
18494 }
18495 assert_eq!(
18496 f.run(&[b"TS.RANGE", b"t", b"-", b"+"]),
18497 "*4\r\n*2\r\n:100\r\n+1\r\n*2\r\n:200\r\n+2\r\n\
18498 *2\r\n:300\r\n+3\r\n*2\r\n:400\r\n+4\r\n"
18499 );
18500 assert_eq!(
18502 f.run(&[b"TS.RANGE", b"t", b"150", b"350"]),
18503 "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
18504 );
18505 assert_eq!(
18508 f.run(&[b"TS.REVRANGE", b"t", b"-", b"+", b"COUNT", b"2"]),
18509 "*2\r\n*2\r\n:400\r\n+4\r\n*2\r\n:300\r\n+3\r\n"
18510 );
18511 assert_eq!(f.run(&[b"TS.RANGE", b"t", b"400", b"100"]), "*0\r\n");
18513 assert_eq!(
18515 f.run(&[
18516 b"TS.RANGE",
18517 b"t",
18518 b"-",
18519 b"+",
18520 b"FILTER_BY_VALUE",
18521 b"2",
18522 b"3"
18523 ]),
18524 "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
18525 );
18526 assert_eq!(
18527 f.run(&[
18528 b"TS.RANGE",
18529 b"t",
18530 b"-",
18531 b"+",
18532 b"FILTER_BY_TS",
18533 b"100",
18534 b"400"
18535 ]),
18536 "*2\r\n*2\r\n:100\r\n+1\r\n*2\r\n:400\r\n+4\r\n"
18537 );
18538 assert_eq!(
18540 f.run(&[
18541 b"TS.RANGE",
18542 b"t",
18543 b"-",
18544 b"+",
18545 b"ZZZ",
18546 b"FILTER_BY_TS",
18547 b"400"
18548 ]),
18549 "*1\r\n*2\r\n:400\r\n+4\r\n"
18550 );
18551 assert_eq!(
18553 f.run(&[b"TS.RANGE", b"t", b"-", b"+", b"LATEST", b"COUNT", b"1"]),
18554 "*1\r\n*2\r\n:100\r\n+1\r\n"
18555 );
18556 }
18557
18558 #[test]
18560 fn aggregation_puts_one_column_a_reduction_in_a_flat_row() {
18561 let mut f = Fixture::new();
18562 for (at, v) in [
18563 (b"100".as_slice(), b"1".as_slice()),
18564 (b"200", b"2"),
18565 (b"300", b"3"),
18566 (b"400", b"4"),
18567 ] {
18568 f.run(&[b"TS.ADD", b"t", at, v]);
18569 }
18570 assert_eq!(
18571 f.run(&[
18572 b"TS.RANGE",
18573 b"t",
18574 b"-",
18575 b"+",
18576 b"AGGREGATION",
18577 b"avg",
18578 b"200"
18579 ]),
18580 "*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"
18581 );
18582 assert_eq!(
18585 f.run(&[
18586 b"TS.RANGE",
18587 b"t",
18588 b"-",
18589 b"+",
18590 b"AGGREGATION",
18591 b"min,max,count",
18592 b"200"
18593 ]),
18594 "*3\r\n\
18595 *4\r\n:0\r\n+1\r\n+1\r\n+1\r\n\
18596 *4\r\n:200\r\n+2\r\n+3\r\n+2\r\n\
18597 *4\r\n:400\r\n+4\r\n+4\r\n+1\r\n"
18598 );
18599 assert_eq!(
18601 f.run(&[
18602 b"TS.RANGE",
18603 b"t",
18604 b"-",
18605 b"+",
18606 b"AGGREGATION",
18607 b"avg",
18608 b"200",
18609 b"BUCKETTIMESTAMP",
18610 b"+"
18611 ]),
18612 "*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"
18613 );
18614 assert_eq!(
18616 f.run(&[
18617 b"TS.RANGE",
18618 b"t",
18619 b"100",
18620 b"400",
18621 b"ALIGN",
18622 b"100",
18623 b"AGGREGATION",
18624 b"sum",
18625 b"200"
18626 ]),
18627 "*2\r\n*2\r\n:100\r\n+3\r\n*2\r\n:300\r\n+7\r\n"
18628 );
18629 assert_eq!(
18632 f.run(&[
18633 b"TS.RANGE",
18634 b"t",
18635 b"-",
18636 b"+",
18637 b"AGGREGATION",
18638 b"count",
18639 b"200"
18640 ]),
18641 "*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"
18642 );
18643 assert_eq!(
18644 f.run(&[
18645 b"TS.RANGE",
18646 b"t",
18647 b"-",
18648 b"+",
18649 b"AGGREGATION",
18650 b"count",
18651 b"200",
18652 b"COUNT",
18653 b"1"
18654 ]),
18655 "*1\r\n*2\r\n:0\r\n+1\r\n"
18656 );
18657 }
18658
18659 #[test]
18662 fn empty_fills_a_gap_and_last_carries_the_reading_before_it() {
18663 let mut f = Fixture::new();
18664 for (at, v) in [
18665 (b"0".as_slice(), b"1".as_slice()),
18666 (b"100", b"2"),
18667 (b"500", b"nan"),
18668 (b"600", b"3"),
18669 ] {
18670 f.run(&[b"TS.ADD", b"g", at, v]);
18671 }
18672 assert_eq!(
18675 f.run(&[
18676 b"TS.RANGE",
18677 b"g",
18678 b"-",
18679 b"+",
18680 b"AGGREGATION",
18681 b"avg",
18682 b"100"
18683 ]),
18684 "*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"
18685 );
18686 assert_eq!(
18688 f.run(&[
18689 b"TS.RANGE",
18690 b"g",
18691 b"-",
18692 b"+",
18693 b"AGGREGATION",
18694 b"sum",
18695 b"100",
18696 b"EMPTY"
18697 ]),
18698 "*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\
18699 *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\
18700 *2\r\n:600\r\n+3\r\n"
18701 );
18702 assert_eq!(
18707 f.run(&[
18708 b"TS.RANGE",
18709 b"g",
18710 b"-",
18711 b"+",
18712 b"AGGREGATION",
18713 b"last",
18714 b"100",
18715 b"EMPTY"
18716 ]),
18717 "*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\
18718 *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\
18719 *2\r\n:600\r\n+3\r\n"
18720 );
18721 assert_eq!(
18722 f.run(&[
18723 b"TS.REVRANGE",
18724 b"g",
18725 b"-",
18726 b"+",
18727 b"AGGREGATION",
18728 b"last",
18729 b"100",
18730 b"EMPTY"
18731 ]),
18732 "*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\
18733 *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\
18734 *2\r\n:0\r\n+1\r\n"
18735 );
18736 assert_eq!(
18739 f.run(&[
18740 b"TS.RANGE",
18741 b"g",
18742 b"500",
18743 b"600",
18744 b"AGGREGATION",
18745 b"last",
18746 b"100",
18747 b"EMPTY"
18748 ]),
18749 "*2\r\n*2\r\n:500\r\n+NaN\r\n*2\r\n:600\r\n+3\r\n"
18750 );
18751 }
18752
18753 #[test]
18756 fn a_range_says_what_the_module_says_when_the_options_do_not_add_up() {
18757 let mut f = Fixture::new();
18758 f.run(&[b"TS.ADD", b"t", b"100", b"1"]);
18759 f.run(&[b"SET", b"str", b"x"]);
18760 let cases: &[(&[&[u8]], &str)] = &[
18761 (
18762 &[b"TS.RANGE", b"t"],
18763 "-ERR wrong number of arguments for 'ts.range' command\r\n",
18764 ),
18765 (
18767 &[b"TS.RANGE", b"gone", b"-", b"+", b"COUNT", b"x"],
18768 "-ERR TSDB: the key does not exist\r\n",
18769 ),
18770 (
18771 &[b"TS.RANGE", b"str", b"-", b"+"],
18772 "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n",
18773 ),
18774 (
18775 &[b"TS.RANGE", b"t", b"abc", b"+"],
18776 "-ERR TSDB: wrong fromTimestamp\r\n",
18777 ),
18778 (
18779 &[b"TS.RANGE", b"t", b"-", b"abc"],
18780 "-ERR TSDB: wrong toTimestamp\r\n",
18781 ),
18782 (
18783 &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT"],
18784 "-ERR TSDB: COUNT argument is missing\r\n",
18785 ),
18786 (
18787 &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"x"],
18788 "-ERR TSDB: Couldn't parse COUNT\r\n",
18789 ),
18790 (
18791 &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"0"],
18792 "-ERR TSDB: Invalid COUNT value\r\n",
18793 ),
18794 (
18795 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg"],
18796 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
18797 ),
18798 (
18799 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"x"],
18800 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
18801 ),
18802 (
18803 &[
18804 b"TS.RANGE",
18805 b"t",
18806 b"-",
18807 b"+",
18808 b"AGGREGATION",
18809 b"nope",
18810 b"100",
18811 ],
18812 "-ERR TSDB: Unknown aggregation type\r\n",
18813 ),
18814 (
18815 &[
18816 b"TS.RANGE",
18817 b"t",
18818 b"-",
18819 b"+",
18820 b"AGGREGATION",
18821 b"avg,,min",
18822 b"100",
18823 ],
18824 "-ERR TSDB: Empty aggregation type in list\r\n",
18825 ),
18826 (
18828 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"nope", b"0"],
18829 "-ERR TSDB: Unknown aggregation type\r\n",
18830 ),
18831 (
18832 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"0"],
18833 "-ERR TSDB: bucketDuration must be greater than zero\r\n",
18834 ),
18835 (
18836 &[
18837 b"TS.RANGE",
18838 b"t",
18839 b"-",
18840 b"+",
18841 b"AGGREGATION",
18842 b"avg",
18843 b"100",
18844 b"X",
18845 b"EMPTY",
18846 ],
18847 "-ERR TSDB: EMPTY flag should be the 3rd or 5th flag after AGGREGATION flag\r\n",
18848 ),
18849 (
18850 &[
18851 b"TS.RANGE",
18852 b"t",
18853 b"-",
18854 b"+",
18855 b"AGGREGATION",
18856 b"avg",
18857 b"100",
18858 b"BUCKETTIMESTAMP",
18859 b"z",
18860 ],
18861 "-ERR TSDB: unknown BUCKETTIMESTAMP parameter\r\n",
18862 ),
18863 (
18864 &[
18865 b"TS.RANGE",
18866 b"t",
18867 b"-",
18868 b"+",
18869 b"AGGREGATION",
18870 b"avg",
18871 b"100",
18872 b"X",
18873 b"Y",
18874 b"BUCKETTIMESTAMP",
18875 b"-",
18876 ],
18877 "-ERR TSDB: BUCKETTIMESTAMP flag should be the 3rd or 4th flag after \
18878 AGGREGATION flag\r\n",
18879 ),
18880 (
18881 &[
18882 b"TS.RANGE",
18883 b"t",
18884 b"-",
18885 b"+",
18886 b"ALIGN",
18887 b"z",
18888 b"AGGREGATION",
18889 b"avg",
18890 b"100",
18891 ],
18892 "-ERR TSDB: unknown ALIGN parameter\r\n",
18893 ),
18894 (
18895 &[b"TS.RANGE", b"t", b"-", b"+", b"ALIGN", b"5"],
18896 "-ERR TSDB: ALIGN parameter can only be used with AGGREGATION\r\n",
18897 ),
18898 (
18899 &[
18900 b"TS.RANGE",
18901 b"t",
18902 b"-",
18903 b"+",
18904 b"ALIGN",
18905 b"-",
18906 b"AGGREGATION",
18907 b"avg",
18908 b"100",
18909 ],
18910 "-ERR TSDB: start alignment can only be used with explicit start timestamp\r\n",
18911 ),
18912 (
18913 &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_VALUE", b"1"],
18914 "-ERR TSDB: FILTER_BY_VALUE one or more arguments are missing\r\n",
18915 ),
18916 (
18917 &[
18918 b"TS.RANGE",
18919 b"t",
18920 b"-",
18921 b"+",
18922 b"FILTER_BY_VALUE",
18923 b"x",
18924 b"2",
18925 ],
18926 "-ERR TSDB: Couldn't parse MIN\r\n",
18927 ),
18928 (
18929 &[
18930 b"TS.RANGE",
18931 b"t",
18932 b"-",
18933 b"+",
18934 b"FILTER_BY_VALUE",
18935 b"1",
18936 b"y",
18937 ],
18938 "-ERR TSDB: Couldn't parse MAX\r\n",
18939 ),
18940 (
18941 &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_TS"],
18942 "-ERR TSDB: FILTER_BY_TS one or more arguments are missing\r\n",
18943 ),
18944 ];
18945 for (argv, want) in cases {
18946 let got = f.run(argv);
18947 assert_eq!(&got, want, "{:?}", argv.last());
18948 }
18949 f.run(&[b"TS.ADD", b"wide", b"0", b"1"]);
18953 f.run(&[b"TS.ADD", b"wide", b"1000000000000", b"2"]);
18954 assert_eq!(
18955 f.run(&[
18956 b"TS.RANGE",
18957 b"wide",
18958 b"-",
18959 b"+",
18960 b"AGGREGATION",
18961 b"avg",
18962 b"1",
18963 b"EMPTY"
18964 ]),
18965 "-ERR TSDB: the requested range holds too many empty buckets\r\n"
18966 );
18967 }
18968
18969 #[test]
18971 fn resp3_writes_a_read_value_as_a_double() {
18972 let mut f = Fixture::new();
18973 f.out = Out::new(Proto::Resp3);
18974 for (at, v) in [
18975 (b"0".as_slice(), b"1".as_slice()),
18976 (b"100", b"2"),
18977 (b"500", b"nan"),
18978 (b"600", b"3"),
18979 ] {
18980 f.run(&[b"TS.ADD", b"g", at, v]);
18981 }
18982 assert_eq!(
18983 f.run(&[
18984 b"TS.RANGE",
18985 b"g",
18986 b"0",
18987 b"100",
18988 b"AGGREGATION",
18989 b"avg,min",
18990 b"200"
18991 ]),
18992 "*1\r\n*3\r\n:0\r\n,1.5\r\n,1\r\n"
18993 );
18994 assert_eq!(
18995 f.run(&[
18996 b"TS.RANGE",
18997 b"g",
18998 b"500",
18999 b"600",
19000 b"AGGREGATION",
19001 b"last",
19002 b"100",
19003 b"EMPTY"
19004 ]),
19005 "*2\r\n*2\r\n:500\r\n,nan\r\n*2\r\n:600\r\n,3\r\n"
19006 );
19007 }
19008
19009 fn joined() -> Fixture {
19012 let mut f = Fixture::new();
19013 f.run(&[b"TS.CREATE", b"z"]);
19014 for (at, v) in [
19015 (b"10".as_slice(), b"1".as_slice()),
19016 (b"20", b"2"),
19017 (b"40", b"4"),
19018 (b"50", b"5"),
19019 ] {
19020 f.run(&[b"TS.ADD", b"x", at, v]);
19021 }
19022 for (at, v) in [
19023 (b"20".as_slice(), b"20".as_slice()),
19024 (b"30", b"30"),
19025 (b"50", b"50"),
19026 (b"60", b"60"),
19027 ] {
19028 f.run(&[b"TS.ADD", b"y", at, v]);
19029 }
19030 f
19031 }
19032
19033 #[test]
19037 fn an_nrange_joins_its_keys_on_the_timestamp() {
19038 let mut f = joined();
19039 assert_eq!(
19041 f.run(&[b"TS.NRANGE", b"1", b"x", b"-", b"+"]),
19042 "*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\
19043 *2\r\n:40\r\n*1\r\n+4\r\n*2\r\n:50\r\n*1\r\n+5\r\n"
19044 );
19045 assert_eq!(
19047 f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+"]),
19048 "*6\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n\
19049 *2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
19050 *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
19051 *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
19052 *2\r\n:50\r\n*2\r\n+5\r\n+50\r\n\
19053 *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
19054 );
19055 assert_eq!(
19058 f.run(&[b"TS.NRANGE", b"2", b"x", b"z", b"20", b"40"]),
19059 "*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"
19060 );
19061 assert_eq!(
19062 f.run(&[b"TS.NRANGE", b"2", b"x", b"x", b"40", b"50"]),
19063 "*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"
19064 );
19065 assert_eq!(
19068 f.run(&[
19069 b"TS.NREVRANGE",
19070 b"2",
19071 b"x",
19072 b"y",
19073 b"-",
19074 b"+",
19075 b"COUNT",
19076 b"1"
19077 ]),
19078 "*1\r\n*2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
19079 );
19080 assert_eq!(
19081 f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+", b"COUNT", b"1"]),
19082 "*1\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n"
19083 );
19084 assert_eq!(
19086 f.run(&[
19087 b"TS.NRANGE",
19088 b"2",
19089 b"x",
19090 b"y",
19091 b"-",
19092 b"+",
19093 b"FILTER_BY_VALUE",
19094 b"2",
19095 b"30"
19096 ]),
19097 "*4\r\n*2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
19098 *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
19099 *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
19100 *2\r\n:50\r\n*2\r\n+5\r\n+NaN\r\n"
19101 );
19102 }
19103
19104 #[test]
19108 fn an_nrange_aggregation_names_one_reduction_a_key() {
19109 let mut f = joined();
19110 assert_eq!(
19111 f.run(&[
19112 b"TS.NRANGE",
19113 b"2",
19114 b"x",
19115 b"y",
19116 b"-",
19117 b"+",
19118 b"AGGREGATION",
19119 b"sum",
19120 b"sum",
19121 b"20"
19122 ]),
19123 "*4\r\n*2\r\n:0\r\n*2\r\n+1\r\n+NaN\r\n\
19124 *2\r\n:20\r\n*2\r\n+2\r\n+50\r\n\
19125 *2\r\n:40\r\n*2\r\n+9\r\n+50\r\n\
19126 *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
19127 );
19128 assert_eq!(
19130 f.run(&[
19131 b"TS.NRANGE",
19132 b"2",
19133 b"x",
19134 b"y",
19135 b"-",
19136 b"+",
19137 b"AGGREGATION",
19138 b"sum,count",
19139 b"avg",
19140 b"20"
19141 ]),
19142 "*4\r\n*2\r\n:0\r\n*3\r\n+1\r\n+1\r\n+NaN\r\n\
19143 *2\r\n:20\r\n*3\r\n+2\r\n+1\r\n+25\r\n\
19144 *2\r\n:40\r\n*3\r\n+9\r\n+2\r\n+50\r\n\
19145 *2\r\n:60\r\n*3\r\n+NaN\r\n+NaN\r\n+60\r\n"
19146 );
19147 assert_eq!(
19150 f.run(&[
19151 b"TS.NRANGE",
19152 b"2",
19153 b"x",
19154 b"y",
19155 b"-",
19156 b"+",
19157 b"AGGREGATION",
19158 b"avg",
19159 b"sum",
19160 b"100",
19161 b"EMPTY",
19162 b"BUCKETTIMESTAMP",
19163 b"end"
19164 ]),
19165 "*1\r\n*2\r\n:100\r\n*2\r\n+3\r\n+160\r\n"
19166 );
19167 assert_eq!(
19170 f.run(&[
19171 b"TS.NRANGE",
19172 b"2",
19173 b"x",
19174 b"y",
19175 b"-",
19176 b"+",
19177 b"AGGREGATION",
19178 b"avg",
19179 b"COUNT",
19180 b"100"
19181 ]),
19182 "*1\r\n*2\r\n:0\r\n*2\r\n+3\r\n+4\r\n"
19183 );
19184 }
19185
19186 #[test]
19189 fn an_nrange_says_what_the_module_says_when_it_does_not_add_up() {
19190 let mut f = joined();
19191 f.run(&[b"SET", b"str", b"hi"]);
19192 let bad_keys = "-ERR TSDB: numkeys must be a positive integer\r\n";
19193 let numkeys = "-ERR TSDB: the number of AGGREGATION arguments \
19194 must be equal to numkeys\r\n";
19195 let cases: &[(&[&[u8]], &str)] = &[
19196 (&[b"TS.NRANGE", b"0", b"x", b"-", b"+"], bad_keys),
19197 (&[b"TS.NRANGE", b"-1", b"x", b"-", b"+"], bad_keys),
19198 (&[b"TS.NRANGE", b"abc", b"x", b"-", b"+"], bad_keys),
19199 (
19202 &[b"TS.NRANGE", b"2", b"x", b"-", b"+"],
19203 "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
19204 ),
19205 (
19206 &[b"TS.NRANGE", b"99", b"x", b"-", b"+"],
19207 "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
19208 ),
19209 (
19212 &[
19213 b"TS.NRANGE",
19214 b"2",
19215 b"x",
19216 b"y",
19217 b"abc",
19218 b"+",
19219 b"AGGREGATION",
19220 b"nope",
19221 b"sum",
19222 b"100",
19223 ],
19224 "-ERR TSDB: Unknown aggregation type\r\n",
19225 ),
19226 (
19227 &[b"TS.NRANGE", b"2", b"x", b"y", b"abc", b"+"],
19228 "-ERR TSDB: wrong fromTimestamp\r\n",
19229 ),
19230 (
19231 &[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"abc"],
19232 "-ERR TSDB: wrong toTimestamp\r\n",
19233 ),
19234 (
19238 &[
19239 b"TS.NRANGE",
19240 b"2",
19241 b"x",
19242 b"y",
19243 b"-",
19244 b"+",
19245 b"AGGREGATION",
19246 b"avg",
19247 ],
19248 numkeys,
19249 ),
19250 (
19251 &[
19252 b"TS.NRANGE",
19253 b"2",
19254 b"x",
19255 b"y",
19256 b"-",
19257 b"+",
19258 b"AGGREGATION",
19259 b"100",
19260 b"sum",
19261 b"100",
19262 ],
19263 numkeys,
19264 ),
19265 (
19266 &[
19267 b"TS.NRANGE",
19268 b"2",
19269 b"x",
19270 b"y",
19271 b"-",
19272 b"+",
19273 b"AGGREGATION",
19274 b"avg",
19275 b"sum",
19276 b"sum",
19277 b"100",
19278 ],
19279 numkeys,
19280 ),
19281 (
19282 &[
19283 b"TS.NRANGE",
19284 b"2",
19285 b"x",
19286 b"y",
19287 b"-",
19288 b"+",
19289 b"AGGREGATION",
19290 b"avg",
19291 b"sum",
19292 b"abc",
19293 ],
19294 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
19295 ),
19296 (
19297 &[
19298 b"TS.NRANGE",
19299 b"2",
19300 b"x",
19301 b"y",
19302 b"-",
19303 b"+",
19304 b"AGGREGATION",
19305 b"avg",
19306 b"sum",
19307 b"0",
19308 ],
19309 "-ERR TSDB: bucketDuration must be greater than zero\r\n",
19310 ),
19311 (
19314 &[b"TS.NRANGE", b"1", b"x", b"-", b"+", b"AGGREGATION", b"100"],
19315 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
19316 ),
19317 (
19318 &[
19319 b"TS.NRANGE",
19320 b"1",
19321 b"x",
19322 b"-",
19323 b"+",
19324 b"AGGREGATION",
19325 b"100",
19326 b"200",
19327 ],
19328 "-ERR TSDB: Unknown aggregation type\r\n",
19329 ),
19330 (
19332 &[b"TS.NRANGE", b"2", b"x", b"nope", b"-", b"+"],
19333 "-ERR TSDB: the key does not exist\r\n",
19334 ),
19335 (
19336 &[b"TS.NRANGE", b"2", b"str", b"nope", b"-", b"+"],
19337 "-ERR WRONGTYPE Operation against a key \
19338 holding the wrong kind of value\r\n",
19339 ),
19340 ];
19341 for (argv, want) in cases {
19342 let got = f.run(argv);
19343 assert_eq!(&got, want, "{argv:?}");
19344 }
19345 }
19346
19347 #[test]
19349 fn a_read_walks_from_a_timestamp_to_the_end_of_the_series() {
19350 let mut f = joined();
19351 assert_eq!(
19352 f.run(&[b"TS.READ", b"x", b"-"]),
19353 "*4\r\n*2\r\n:10\r\n+1\r\n*2\r\n:20\r\n+2\r\n\
19354 *2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
19355 );
19356 assert_eq!(
19359 f.run(&[b"TS.READ", b"x", b"+"]),
19360 "*1\r\n*2\r\n:50\r\n+5\r\n"
19361 );
19362 assert_eq!(
19363 f.run(&[b"TS.READ", b"x", b"25"]),
19364 "*2\r\n*2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
19365 );
19366 assert_eq!(f.run(&[b"TS.READ", b"x", b"99"]), "*0\r\n");
19369 assert_eq!(f.run(&[b"TS.READ", b"z", b"-"]), "*0\r\n");
19370 assert_eq!(f.run(&[b"TS.READ", b"z", b"+"]), "*0\r\n");
19371 assert_eq!(f.run(&[b"TS.READ", b"nope", b"-"]), "*0\r\n");
19372 assert_eq!(
19376 f.run(&[b"TS.READ", b"x", b"abc"]),
19377 "-TSDB: invalid timestamp\r\n"
19378 );
19379 assert_eq!(
19380 f.run(&[b"TS.READ", b"x", b"-1"]),
19381 "-TSDB: invalid timestamp\r\n"
19382 );
19383 f.run(&[b"SET", b"str", b"hi"]);
19384 assert_eq!(
19385 f.run(&[b"TS.READ", b"str", b"-"]),
19386 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19387 );
19388 assert_eq!(
19391 f.run(&[b"TS.READ", b"x"]),
19392 "-ERR wrong number of arguments for 'ts.read' command\r\n"
19393 );
19394 assert_eq!(
19395 f.run(&[b"TS.READ", b"x", b"-", b"COUNT", b"1"]),
19396 "-ERR wrong number of arguments for 'ts.read' command\r\n"
19397 );
19398 }
19399
19400 #[test]
19403 fn getkeys_reads_the_count_of_a_joined_read() {
19404 let mut f = Fixture::new();
19405 assert_eq!(
19406 f.run(&[
19407 b"COMMAND",
19408 b"GETKEYS",
19409 b"TS.NRANGE",
19410 b"2",
19411 b"a",
19412 b"b",
19413 b"-",
19414 b"+"
19415 ]),
19416 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
19417 );
19418 assert_eq!(
19419 f.run(&[
19420 b"COMMAND",
19421 b"GETKEYS",
19422 b"TS.NREVRANGE",
19423 b"1",
19424 b"a",
19425 b"-",
19426 b"+"
19427 ]),
19428 "*1\r\n$1\r\na\r\n"
19429 );
19430 for n in [b"0".as_slice(), b"9", b"abc"] {
19433 assert_eq!(
19434 f.run(&[b"COMMAND", b"GETKEYS", b"TS.NRANGE", n, b"a", b"-", b"+"]),
19435 "-ERR Invalid arguments specified for command\r\n"
19436 );
19437 }
19438 }
19439
19440 fn labelled() -> Fixture {
19442 let mut f = Fixture::new();
19443 f.run(&[
19444 b"TS.CREATE",
19445 b"a",
19446 b"LABELS",
19447 b"room",
19448 b"kitchen",
19449 b"x",
19450 b"1",
19451 ]);
19452 f.run(&[
19453 b"TS.CREATE",
19454 b"b",
19455 b"LABELS",
19456 b"room",
19457 b"bedroom",
19458 b"x",
19459 b"2",
19460 ]);
19461 f.run(&[b"TS.CREATE", b"c", b"LABELS", b"room", b"kitchen"]);
19462 f.run(&[b"TS.CREATE", b"d"]);
19463 f.run(&[b"TS.CREATE", b"e", b"LABELS", b"r", b"bb", b"r", b"b"]);
19464 f.run(&[b"TS.ADD", b"a", b"100", b"1.5"]);
19465 f.run(&[b"TS.ADD", b"b", b"200", b"2"]);
19466 f
19467 }
19468
19469 #[test]
19472 fn a_filter_is_taken_apart_the_way_the_module_takes_one_apart() {
19473 let mut f = labelled();
19474 let cases: &[(&[&[u8]], &str)] = &[
19475 (
19478 &[b"TS.QUERYINDEX", b"room=kitchen"],
19479 "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
19480 ),
19481 (
19482 &[b"TS.QUERYINDEX", b"room=kitchen", b"x=1"],
19483 "*1\r\n$1\r\na\r\n",
19484 ),
19485 (
19486 &[b"TS.QUERYINDEX", b"room=(kitchen,bedroom)"],
19487 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n",
19488 ),
19489 (&[b"TS.QUERYINDEX", b"room=()"], "*0\r\n"),
19492 (
19494 &[b"TS.QUERYINDEX", b"x=", b"room=kitchen"],
19495 "*1\r\n$1\r\nc\r\n",
19496 ),
19497 (
19498 &[b"TS.QUERYINDEX", b"room=kitchen", b"x!="],
19499 "*1\r\n$1\r\na\r\n",
19500 ),
19501 (
19502 &[b"TS.QUERYINDEX", b"room!=kitchen", b"x!="],
19503 "-ERR TSDB: please provide at least one matcher\r\n",
19504 ),
19505 (
19508 &[b"TS.QUERYINDEX", b"room==kitchen"],
19509 "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
19510 ),
19511 (
19512 &[b"TS.QUERYINDEX", b"room=kitchen=zz"],
19513 "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
19514 ),
19515 (&[b"TS.QUERYINDEX", b"room!!=kitchen", b"x=1"], "*0\r\n"),
19516 (&[b"TS.QUERYINDEX", b"()=1"], "*0\r\n"),
19519 (
19520 &[b"TS.QUERYINDEX", b"=(1)"],
19521 "-ERR TSDB: failed parsing labels\r\n",
19522 ),
19523 (
19524 &[b"TS.QUERYINDEX", b"room=(kitchen,)"],
19525 "-ERR TSDB: failed parsing labels\r\n",
19526 ),
19527 (
19528 &[b"TS.QUERYINDEX", b"room=(kitchen"],
19529 "-ERR TSDB: failed parsing labels\r\n",
19530 ),
19531 (&[b"TS.QUERYINDEX", b"room=x()"], "*0\r\n"),
19532 (
19533 &[b"TS.QUERYINDEX", b"nonsense"],
19534 "-ERR TSDB: failed parsing labels\r\n",
19535 ),
19536 (
19538 &[b"TS.QUERYINDEX", b"room!=kitchen"],
19539 "-ERR TSDB: please provide at least one matcher\r\n",
19540 ),
19541 (&[b"TS.QUERYINDEX", b"ROOM=kitchen"], "*0\r\n"),
19543 (&[b"TS.QUERYINDEX", b"room=KITCHEN"], "*0\r\n"),
19544 (
19545 &[b"TS.QUERYINDEX"],
19546 "-ERR wrong number of arguments for 'ts.queryindex' command\r\n",
19547 ),
19548 ];
19549 for (argv, want) in cases {
19550 let got = f.run(argv);
19551 assert_eq!(&got, want, "{:?}", argv.last());
19552 }
19553 }
19554
19555 #[test]
19557 fn querylabels_says_which_names_are_worn_and_what_they_are_set_to() {
19558 let mut f = labelled();
19559 let cases: &[(&[&[u8]], &str)] = &[
19560 (
19561 &[b"TS.QUERYLABELS", b"LABELS"],
19562 "*3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
19563 ),
19564 (
19565 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=kitchen"],
19566 "*2\r\n$4\r\nroom\r\n$1\r\nx\r\n",
19567 ),
19568 (
19569 &[b"TS.QUERYLABELS", b"VALUES", b"room"],
19570 "*2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
19571 ),
19572 (&[b"TS.QUERYLABELS", b"VALUES", b"r"], "*1\r\n$1\r\nb\r\n"),
19575 (&[b"TS.QUERYLABELS", b"VALUES", b"nolabel"], "*0\r\n"),
19576 (
19577 &[b"TS.QUERYLABELS", b"VALUES"],
19578 "-ERR wrong number of arguments for 'ts.querylabels' command\r\n",
19579 ),
19580 (
19581 &[b"TS.QUERYLABELS", b"ZZZ"],
19582 "-ERR TSDB: unknown subtype, must be one of LABELS|VALUES\r\n",
19583 ),
19584 (
19585 &[b"TS.QUERYLABELS", b"LABELS", b"ZZZ"],
19586 "-ERR TSDB: unknown argument, expected FILTER\r\n",
19587 ),
19588 (
19589 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER"],
19590 "-ERR TSDB: FILTER given with no filter expressions\r\n",
19591 ),
19592 (
19596 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room!=kitchen"],
19597 "-ERR TSDB: please provide at least one matcher\r\n",
19598 ),
19599 (
19600 &[
19601 b"TS.QUERYLABELS",
19602 b"LABELS",
19603 b"FILTER",
19604 b"room=kitchen",
19605 b"x=",
19606 ],
19607 "*1\r\n$4\r\nroom\r\n",
19608 ),
19609 ];
19610 for (argv, want) in cases {
19611 let got = f.run(argv);
19612 assert_eq!(&got, want, "{:?}", argv.last());
19613 }
19614 }
19615
19616 #[test]
19619 fn mget_writes_the_newest_sample_and_the_labels_that_were_asked_for() {
19620 let mut f = labelled();
19621 let cases: &[(&[&[u8]], &str)] = &[
19622 (
19625 &[b"TS.MGET", b"FILTER", b"room=kitchen"],
19626 "*2\r\n*3\r\n$1\r\na\r\n*0\r\n*2\r\n:100\r\n+1.5\r\n\
19627 *3\r\n$1\r\nc\r\n*0\r\n*0\r\n",
19628 ),
19629 (
19630 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
19631 "*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\
19632 *2\r\n$1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n+1.5\r\n\
19633 *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",
19634 ),
19635 (
19637 &[
19638 b"TS.MGET",
19639 b"SELECTED_LABELS",
19640 b"x",
19641 b"FILTER",
19642 b"room=kitchen",
19643 ],
19644 "*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\
19645 *2\r\n:100\r\n+1.5\r\n\
19646 *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",
19647 ),
19648 (
19651 &[b"TS.MGET", b"SELECTED_LABELS", b"r", b"FILTER", b"r=b"],
19652 "*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",
19653 ),
19654 (
19655 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
19656 "*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\
19657 *2\r\n$1\r\nr\r\n$1\r\nb\r\n*0\r\n",
19658 ),
19659 (
19662 &[b"TS.MGET", b"ZZZ", b"FILTER", b"room=bedroom"],
19663 "*1\r\n*3\r\n$1\r\nb\r\n*0\r\n*2\r\n:200\r\n+2\r\n",
19664 ),
19665 (
19666 &[b"TS.MGET", b"a", b"b", b"c"],
19667 "-ERR wrong number of arguments for 'ts.mget' command\r\n",
19668 ),
19669 (
19670 &[b"TS.MGET", b"FILTER"],
19671 "-ERR wrong number of arguments for 'ts.mget' command\r\n",
19672 ),
19673 (
19676 &[
19677 b"TS.MGET",
19678 b"WITHLABELS",
19679 b"SELECTED_LABELS",
19680 b"x",
19681 b"FILTER",
19682 b"bad",
19683 ],
19684 "-ERR TSDB: cannot accept WITHLABELS and SELECT_LABELS together\r\n",
19685 ),
19686 (
19687 &[b"TS.MGET", b"SELECTED_LABELS", b"FILTER", b"bad"],
19688 "-ERR TSDB: SELECT_LABELS should have at least 1 parameter\r\n",
19689 ),
19690 ];
19691 for (argv, want) in cases {
19692 let got = f.run(argv);
19693 assert_eq!(&got, want, "{:?}", argv.last());
19694 }
19695 }
19696
19697 #[test]
19700 fn resp3_writes_the_label_surface_as_sets_and_maps() {
19701 let mut f = labelled();
19702 f.out = Out::new(Proto::Resp3);
19703 let cases: &[(&[&[u8]], &str)] = &[
19704 (
19705 &[b"TS.QUERYINDEX", b"room=kitchen"],
19706 "~2\r\n$1\r\na\r\n$1\r\nc\r\n",
19707 ),
19708 (
19709 &[b"TS.QUERYLABELS", b"LABELS"],
19710 "~3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
19711 ),
19712 (
19713 &[b"TS.QUERYLABELS", b"VALUES", b"room"],
19714 "~2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
19715 ),
19716 (
19719 &[b"TS.MGET", b"FILTER", b"room=kitchen"],
19720 "%2\r\n$1\r\na\r\n*2\r\n%0\r\n*2\r\n:100\r\n,1.5\r\n\
19721 $1\r\nc\r\n*2\r\n%0\r\n*0\r\n",
19722 ),
19723 (
19724 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
19725 "%2\r\n$1\r\na\r\n*2\r\n%2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
19726 $1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n,1.5\r\n\
19727 $1\r\nc\r\n*2\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n*0\r\n",
19728 ),
19729 (
19730 &[
19731 b"TS.MGET",
19732 b"SELECTED_LABELS",
19733 b"x",
19734 b"FILTER",
19735 b"room=kitchen",
19736 ],
19737 "%2\r\n$1\r\na\r\n*2\r\n%1\r\n$1\r\nx\r\n$1\r\n1\r\n\
19738 *2\r\n:100\r\n,1.5\r\n\
19739 $1\r\nc\r\n*2\r\n%1\r\n$1\r\nx\r\n_\r\n*0\r\n",
19740 ),
19741 (
19744 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
19745 "%1\r\n$1\r\ne\r\n*2\r\n%2\r\n$1\r\nr\r\n$2\r\nbb\r\n\
19746 $1\r\nr\r\n$1\r\nb\r\n*0\r\n",
19747 ),
19748 ];
19749 for (argv, want) in cases {
19750 let got = f.run(argv);
19751 assert_eq!(&got, want, "{:?}", argv.last());
19752 }
19753 }
19754
19755 fn spanned() -> Fixture {
19758 let mut f = labelled();
19759 f.run(&[b"TS.ADD", b"a", b"200", b"2.5"]);
19760 f.run(&[b"TS.ADD", b"c", b"100", b"10"]);
19761 f.run(&[b"TS.ADD", b"c", b"300", b"30"]);
19762 f
19763 }
19764
19765 #[test]
19768 fn mrange_reads_every_series_and_folds_the_groups_it_is_asked_for() {
19769 let mut f = spanned();
19770 let cases: &[(&[&[u8]], &str)] = &[
19771 (
19772 &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=kitchen"],
19773 "*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\
19774 *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",
19775 ),
19776 (
19779 &[
19780 b"TS.MREVRANGE",
19781 b"-",
19782 b"+",
19783 b"WITHLABELS",
19784 b"FILTER",
19785 b"room=kitchen",
19786 ],
19787 "*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\
19788 *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\
19789 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
19790 *2\r\n*2\r\n:300\r\n+30\r\n*2\r\n:100\r\n+10\r\n",
19791 ),
19792 (
19795 &[
19796 b"TS.MRANGE",
19797 b"-",
19798 b"+",
19799 b"SELECTED_LABELS",
19800 b"x",
19801 b"FILTER",
19802 b"room=kitchen",
19803 ],
19804 "*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\
19805 *2\r\n*2\r\n:100\r\n+1.5\r\n*2\r\n:200\r\n+2.5\r\n\
19806 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$1\r\nx\r\n$-1\r\n\
19807 *2\r\n*2\r\n:100\r\n+10\r\n*2\r\n:300\r\n+30\r\n",
19808 ),
19809 (
19812 &[
19813 b"TS.MRANGE",
19814 b"-",
19815 b"+",
19816 b"FILTER",
19817 b"room=kitchen",
19818 b"GROUPBY",
19819 b"room",
19820 b"REDUCE",
19821 b"sum",
19822 ],
19823 "*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\
19824 *2\r\n:200\r\n+2.5\r\n*2\r\n:300\r\n+30\r\n",
19825 ),
19826 (
19829 &[
19830 b"TS.MRANGE",
19831 b"-",
19832 b"+",
19833 b"WITHLABELS",
19834 b"FILTER",
19835 b"room=kitchen",
19836 b"GROUPBY",
19837 b"room",
19838 b"REDUCE",
19839 b"max",
19840 ],
19841 "*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\
19842 *2\r\n$11\r\n__reducer__\r\n$3\r\nmax\r\n\
19843 *2\r\n$10\r\n__source__\r\n$3\r\na,c\r\n\
19844 *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",
19845 ),
19846 (
19848 &[
19849 b"TS.MREVRANGE",
19850 b"-",
19851 b"+",
19852 b"COUNT",
19853 b"1",
19854 b"FILTER",
19855 b"room=kitchen",
19856 b"GROUPBY",
19857 b"room",
19858 b"REDUCE",
19859 b"count",
19860 ],
19861 "*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",
19862 ),
19863 (
19865 &[
19866 b"TS.MRANGE",
19867 b"-",
19868 b"+",
19869 b"FILTER",
19870 b"room=kitchen",
19871 b"GROUPBY",
19872 b"nope",
19873 b"REDUCE",
19874 b"sum",
19875 ],
19876 "*0\r\n",
19877 ),
19878 (
19879 &[
19880 b"TS.MRANGE",
19881 b"-",
19882 b"+",
19883 b"AGGREGATION",
19884 b"sum,avg",
19885 b"100",
19886 b"FILTER",
19887 b"room=bedroom",
19888 ],
19889 "*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",
19890 ),
19891 (
19893 &[b"TS.MRANGE", b"-", b"+", b"room=kitchen"],
19894 "-ERR TSDB: missing FILTER argument\r\n",
19895 ),
19896 (
19897 &[b"TS.MRANGE", b"-", b"+", b"FILTER"],
19898 "-ERR TSDB: missing labels for filter argument\r\n",
19899 ),
19900 (
19901 &[
19902 b"TS.MRANGE",
19903 b"-",
19904 b"+",
19905 b"GROUPBY",
19906 b"room",
19907 b"REDUCE",
19908 b"sum",
19909 b"FILTER",
19910 b"room=kitchen",
19911 ],
19912 "-ERR TSDB: GROUPBY should always come after filter\r\n",
19913 ),
19914 (
19917 &[
19918 b"TS.MRANGE",
19919 b"-",
19920 b"+",
19921 b"FILTER",
19922 b"room=kitchen",
19923 b"GROUPBY",
19924 b"room",
19925 b"REDUCE",
19926 b"sum",
19927 b"x",
19928 ],
19929 "-ERR wrong number of arguments for 'ts.mrange' command\r\n",
19930 ),
19931 (
19933 &[
19934 b"TS.MRANGE",
19935 b"-",
19936 b"+",
19937 b"FILTER",
19938 b"nope",
19939 b"GROUPBY",
19940 b"room",
19941 b"REDUCE",
19942 b"sum",
19943 b"x",
19944 ],
19945 "-ERR TSDB: failed parsing labels\r\n",
19946 ),
19947 (
19948 &[
19949 b"TS.MRANGE",
19950 b"-",
19951 b"+",
19952 b"FILTER",
19953 b"room=kitchen",
19954 b"GROUPBY",
19955 b"room",
19956 b"REDUCE",
19957 b"twa",
19958 ],
19959 "-ERR TSDB: Invalid reducer type\r\n",
19960 ),
19961 (
19962 &[
19963 b"TS.MRANGE",
19964 b"-",
19965 b"+",
19966 b"AGGREGATION",
19967 b"sum,avg",
19968 b"100",
19969 b"FILTER",
19970 b"room=kitchen",
19971 b"GROUPBY",
19972 b"room",
19973 b"REDUCE",
19974 b"sum",
19975 ],
19976 "-ERR TSDB: GROUPBY is not allowed when multiple aggregators are specified\r\n",
19977 ),
19978 (
19981 &[
19982 b"TS.MRANGE",
19983 b"-",
19984 b"+",
19985 b"SELECTED_LABELS",
19986 b"COUNT",
19987 b"FILTER",
19988 b"room=kitchen",
19989 ],
19990 "-ERR TSDB: Couldn't parse COUNT\r\n",
19991 ),
19992 ];
19993 for (argv, want) in cases {
19994 let got = f.run(argv);
19995 assert_eq!(&got, want, "{argv:?}");
19996 }
19997 }
19998
19999 #[test]
20002 fn resp3_writes_a_multi_key_read_as_a_map_of_four() {
20003 let mut f = spanned();
20004 f.out = Out::new(Proto::Resp3);
20005 let cases: &[(&[&[u8]], &str)] = &[
20006 (
20007 &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=bedroom"],
20008 "%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\
20009 *1\r\n*2\r\n:200\r\n,2\r\n",
20010 ),
20011 (
20014 &[
20015 b"TS.MRANGE",
20016 b"-",
20017 b"+",
20018 b"AGGREGATION",
20019 b"sum,avg",
20020 b"100",
20021 b"FILTER",
20022 b"room=bedroom",
20023 ],
20024 "%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\
20025 $3\r\navg\r\n*1\r\n*3\r\n:200\r\n,2\r\n,2\r\n",
20026 ),
20027 (
20028 &[
20029 b"TS.MRANGE",
20030 b"-",
20031 b"+",
20032 b"FILTER",
20033 b"room=kitchen",
20034 b"GROUPBY",
20035 b"room",
20036 b"REDUCE",
20037 b"sum",
20038 ],
20039 "%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\
20040 $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\
20041 *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",
20042 ),
20043 (
20046 &[
20047 b"TS.MRANGE",
20048 b"-",
20049 b"+",
20050 b"WITHLABELS",
20051 b"FILTER",
20052 b"room=kitchen",
20053 b"GROUPBY",
20054 b"room",
20055 b"REDUCE",
20056 b"max",
20057 ],
20058 "%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\
20059 %1\r\n$8\r\nreducers\r\n*1\r\n$3\r\nmax\r\n\
20060 %1\r\n$7\r\nsources\r\n*2\r\n$1\r\na\r\n$1\r\nc\r\n\
20061 *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",
20062 ),
20063 (
20064 &[
20065 b"TS.MRANGE",
20066 b"-",
20067 b"+",
20068 b"FILTER",
20069 b"room=kitchen",
20070 b"GROUPBY",
20071 b"nope",
20072 b"REDUCE",
20073 b"sum",
20074 ],
20075 "%0\r\n",
20076 ),
20077 ];
20078 for (argv, want) in cases {
20079 let got = f.run(argv);
20080 assert_eq!(&got, want, "{argv:?}");
20081 }
20082 }
20083
20084 #[test]
20086 fn createrule_checks_the_two_keys_last_and_the_two_links_after_that() {
20087 let mut f = Fixture::new();
20088 f.run(&[b"TS.CREATE", b"src"]);
20089 f.run(&[b"TS.CREATE", b"dst"]);
20090 f.run(&[b"SET", b"plain", b"v"]);
20091 let cases: &[(&[&[u8]], &str)] = &[
20092 (
20096 &[
20097 b"TS.CREATERULE",
20098 b"src",
20099 b"dst",
20100 b"AGGREGATION",
20101 b"nope",
20102 b"x",
20103 ],
20104 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
20105 ),
20106 (
20107 &[
20108 b"TS.CREATERULE",
20109 b"src",
20110 b"dst",
20111 b"AGGREGATION",
20112 b"nope",
20113 b"10",
20114 ],
20115 "-ERR TSDB: Unknown aggregation type\r\n",
20116 ),
20117 (
20118 &[
20119 b"TS.CREATERULE",
20120 b"src",
20121 b"dst",
20122 b"AGGREGATION",
20123 b"avg",
20124 b"0",
20125 ],
20126 "-ERR TSDB: bucketDuration must be greater than zero\r\n",
20127 ),
20128 (
20129 &[
20130 b"TS.CREATERULE",
20131 b"src",
20132 b"dst",
20133 b"AGGREGATION",
20134 b"avg",
20135 b"10",
20136 b"x",
20137 ],
20138 "-ERR TSDB: Couldn't parse alignTimestamp\r\n",
20139 ),
20140 (
20141 &[
20142 b"TS.CREATERULE",
20143 b"src",
20144 b"src",
20145 b"AGGREGATION",
20146 b"avg",
20147 b"10",
20148 ],
20149 "-ERR TSDB: the source key and destination key should be different\r\n",
20150 ),
20151 (
20155 &[
20156 b"TS.CREATERULE",
20157 b"nope",
20158 b"plain",
20159 b"AGGREGATION",
20160 b"avg",
20161 b"10",
20162 ],
20163 "-ERR TSDB: the key does not exist\r\n",
20164 ),
20165 (
20166 &[
20167 b"TS.CREATERULE",
20168 b"src",
20169 b"nope",
20170 b"AGGREGATION",
20171 b"avg",
20172 b"10",
20173 ],
20174 "-ERR TSDB: the key does not exist\r\n",
20175 ),
20176 (
20179 &[b"TS.CREATERULE", b"src", b"dst", b"NOPE", b"avg", b"10"],
20180 "-ERR wrong number of arguments for 'ts.createrule' command\r\n",
20181 ),
20182 (
20183 &[
20184 b"TS.CREATERULE",
20185 b"src",
20186 b"dst",
20187 b"AGGREGATION",
20188 b"avg",
20189 b"10",
20190 ],
20191 "+OK\r\n",
20192 ),
20193 (
20196 &[
20197 b"TS.CREATERULE",
20198 b"src",
20199 b"dst",
20200 b"AGGREGATION",
20201 b"avg",
20202 b"10",
20203 ],
20204 "-ERR TSDB: the destination key already has a src rule\r\n",
20205 ),
20206 (
20209 &[
20210 b"TS.CREATERULE",
20211 b"dst",
20212 b"src",
20213 b"AGGREGATION",
20214 b"avg",
20215 b"10",
20216 ],
20217 "-ERR TSDB: the source key already has a source rule\r\n",
20218 ),
20219 (&[b"TS.DELETERULE", b"src", b"dst"], "+OK\r\n"),
20220 (
20221 &[b"TS.DELETERULE", b"src", b"dst"],
20222 "-ERR TSDB: compaction rule does not exist\r\n",
20223 ),
20224 (
20228 &[b"TS.DELETERULE", b"src", b"nope"],
20229 "-ERR TSDB: compaction rule does not exist\r\n",
20230 ),
20231 (
20232 &[b"TS.DELETERULE", b"nope", b"dst"],
20233 "-ERR TSDB: the key does not exist\r\n",
20234 ),
20235 ];
20236 for (argv, want) in cases {
20237 let got = f.run(argv);
20238 assert_eq!(&got, want, "{argv:?}");
20239 }
20240 }
20241
20242 #[test]
20244 fn a_rule_writes_a_bucket_when_a_later_reading_closes_it() {
20245 let mut f = Fixture::new();
20246 f.run(&[b"TS.CREATE", b"src"]);
20247 f.run(&[b"TS.CREATE", b"dst"]);
20248 f.run(&[b"TS.ADD", b"src", b"10", b"1"]);
20251 f.run(&[
20252 b"TS.CREATERULE",
20253 b"src",
20254 b"dst",
20255 b"AGGREGATION",
20256 b"sum",
20257 b"100",
20258 ]);
20259 f.run(&[b"TS.ADD", b"src", b"20", b"2"]);
20260 assert_eq!(f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]), "*0\r\n");
20261 assert_eq!(f.run(&[b"TS.GET", b"dst", b"LATEST"]), "*2\r\n:0\r\n+2\r\n");
20264 f.run(&[b"TS.ADD", b"src", b"110", b"4"]);
20265 assert_eq!(
20266 f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
20267 "*1\r\n*2\r\n:0\r\n+2\r\n"
20268 );
20269 f.run(&[b"TS.ADD", b"src", b"30", b"8"]);
20272 assert_eq!(
20273 f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
20274 "*1\r\n*2\r\n:0\r\n+11\r\n"
20275 );
20276 assert_eq!(f.run(&[b"TS.DEL", b"src", b"0", b"25"]), ":2\r\n");
20279 assert_eq!(
20280 f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
20281 "*1\r\n*2\r\n:0\r\n+8\r\n"
20282 );
20283 assert_eq!(
20284 f.run(&[b"TS.GET", b"dst", b"LATEST"]),
20285 "*2\r\n:100\r\n+4\r\n"
20286 );
20287 assert!(f.run(&[b"TS.INFO", b"dst"]).contains("sourceKey"));
20289 f.run(&[b"DEL", b"dst"]);
20290 assert_eq!(
20291 f.run(&[b"TS.DELETERULE", b"src", b"dst"]),
20292 "-ERR TSDB: compaction rule does not exist\r\n"
20293 );
20294 }
20295
20296 #[test]
20299 fn xadd_ids_only_ever_go_up() {
20300 let mut f = Fixture::new();
20301 assert_eq!(f.run(&[b"XADD", b"s", b"5", b"a", b"1"]), "$3\r\n5-0\r\n");
20303 assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"2"]), "$3\r\n5-1\r\n");
20305 assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"3"]), "$3\r\n5-2\r\n");
20306 assert_eq!(f.run(&[b"XADD", b"s", b"6-9", b"a", b"4"]), "$3\r\n6-9\r\n");
20307 assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
20308
20309 assert!(
20310 f.run(&[b"XADD", b"s", b"6-9", b"a", b"5"])
20311 .contains("equal or smaller")
20312 );
20313 assert!(
20314 f.run(&[b"XADD", b"s", b"0-0", b"a", b"5"])
20315 .contains("must be greater than 0-0")
20316 );
20317 assert!(
20318 f.run(&[b"XADD", b"s", b"nonsense", b"a", b"5"])
20319 .contains("Invalid stream ID")
20320 );
20321 assert!(
20324 f.run(&[b"XADD", b"s", b"*", b"a"])
20325 .contains("wrong number of arguments")
20326 );
20327
20328 assert_eq!(
20331 f.run(&[b"XADD", b"gone", b"NOMKSTREAM", b"*", b"a", b"1"]),
20332 "$-1\r\n"
20333 );
20334 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
20335 assert_eq!(f.run(&[b"TYPE", b"s"]), "+stream\r\n");
20336 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$6\r\nstream\r\n");
20337 }
20338
20339 #[test]
20342 fn trimming_reads_its_options_the_way_redis_does() {
20343 let mut f = Fixture::new();
20344 for i in 1..=10u32 {
20345 f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
20346 }
20347 assert_eq!(f.run(&[b"XTRIM", b"s", b"MAXLEN", b"4"]), ":6\r\n");
20348 assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
20349 assert_eq!(f.run(&[b"XTRIM", b"s", b"MINID", b"9"]), ":2\r\n");
20350 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20351
20352 assert!(
20356 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"~"])
20357 .contains("not an integer")
20358 );
20359 assert!(
20360 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"-1"])
20361 .contains("MAXLEN argument must be >= 0")
20362 );
20363 assert!(
20366 f.run(&[b"XTRIM", b"s", b"LIMIT", b"5"])
20367 .contains("without specifying a trimming strategy")
20368 );
20369 assert!(
20370 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"LIMIT", b"5"])
20371 .contains("without the special ~ option")
20372 );
20373 assert!(
20374 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"MINID", b"5"])
20375 .contains("at the same time are not compatible")
20376 );
20377 assert!(
20379 f.run(&[b"XTRIM", b"s", b"NOMKSTREAM", b"MAXLEN", b"5"])
20380 .contains("syntax error")
20381 );
20382 assert_eq!(f.run(&[b"XTRIM", b"missing", b"MAXLEN", b"5"]), ":0\r\n");
20383 }
20384
20385 #[test]
20387 fn xrange_looks_the_key_up_before_it_reads_the_count() {
20388 let mut f = Fixture::new();
20389 f.run(&[b"XADD", b"s", b"5-1", b"a", b"1"]);
20390 f.run(&[b"XADD", b"s", b"6-1", b"b", b"2"]);
20391
20392 assert_eq!(
20393 f.run(&[b"XRANGE", b"s", b"-", b"+"]),
20394 "*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\
20395 *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
20396 );
20397 assert_eq!(
20398 f.run(&[b"XREVRANGE", b"s", b"+", b"-", b"COUNT", b"1"]),
20399 "*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"
20400 );
20401 assert_eq!(
20405 f.run(&[b"XRANGE", b"s", b"-", b"(6"]),
20406 "*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\
20407 *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
20408 );
20409 assert_eq!(
20410 f.run(&[b"XRANGE", b"s", b"(5-1", b"+"]),
20411 "*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"
20412 );
20413 assert!(
20414 f.run(&[b"XRANGE", b"s", b"(-", b"+"])
20415 .contains("Invalid stream ID")
20416 );
20417
20418 assert_eq!(
20422 f.run(&[b"XRANGE", b"missing", b"-", b"+", b"COUNT", b"0"]),
20423 "*0\r\n"
20424 );
20425 assert_eq!(
20426 f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"]),
20427 "*-1\r\n"
20428 );
20429 f.run(&[b"SET", b"str", b"v"]);
20430 assert!(
20431 f.run(&[b"XRANGE", b"str", b"-", b"+", b"COUNT", b"0"])
20432 .starts_with("-WRONGTYPE")
20433 );
20434 assert_eq!(
20436 f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"2", b"COUNT", b"1"]),
20437 "*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"
20438 );
20439 }
20440
20441 #[test]
20443 fn a_bad_id_late_in_the_list_stops_the_whole_command() {
20444 let mut f = Fixture::new();
20445 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20446 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20447 assert!(
20448 f.run(&[b"XDEL", b"s", b"1-1", b"nonsense"])
20449 .contains("Invalid stream ID")
20450 );
20451 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20452 assert_eq!(f.run(&[b"XDEL", b"s", b"1-1", b"9-9"]), ":1\r\n");
20453 assert_eq!(f.run(&[b"XLEN", b"s"]), ":1\r\n");
20454 assert_eq!(f.run(&[b"XDEL", b"missing", b"1-1"]), ":0\r\n");
20455 assert_eq!(f.run(&[b"XACK", b"missing", b"g", b"1-1"]), ":0\r\n");
20456 }
20457
20458 #[test]
20460 fn xgroup_has_an_arity_per_subcommand() {
20461 let mut f = Fixture::new();
20462 assert!(
20463 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
20464 .contains("requires the key")
20465 );
20466 assert_eq!(
20467 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$", b"MKSTREAM"]),
20468 "+OK\r\n"
20469 );
20470 assert!(
20473 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
20474 .starts_with("-BUSYGROUP")
20475 );
20476 assert_eq!(
20477 f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
20478 ":1\r\n"
20479 );
20480 assert_eq!(
20481 f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
20482 ":0\r\n"
20483 );
20484 assert_eq!(
20485 f.run(&[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c"]),
20486 ":0\r\n"
20487 );
20488
20489 let short = f.run(&[b"XGROUP", b"DESTROY", b"s"]);
20491 assert!(
20492 short.contains("wrong number of arguments for 'xgroup|destroy' command"),
20493 "{short}"
20494 );
20495 let odd = f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0", b"ENTRIESREAD"]);
20497 assert!(
20498 odd.contains("unknown subcommand or wrong number of arguments for 'SETID'"),
20499 "{odd}"
20500 );
20501 assert!(
20502 f.run(&[b"XGROUP", b"NOSUCH", b"s"])
20503 .contains("Try XGROUP HELP")
20504 );
20505
20506 assert_eq!(f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0"]), "+OK\r\n");
20507 assert!(
20508 f.run(&[b"XGROUP", b"SETID", b"s", b"nogroup", b"0"])
20509 .starts_with("-NOGROUP")
20510 );
20511 assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":1\r\n");
20512 assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":0\r\n");
20513 assert!(
20514 f.run(&[b"XGROUP", b"DESTROY", b"missing", b"g"])
20515 .contains("requires the key")
20516 );
20517 }
20518
20519 #[test]
20521 fn xreadgroup_hands_out_and_xack_takes_back() {
20522 let mut f = Fixture::new();
20523 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20524 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20525 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20526
20527 let first = f.run(&[
20528 b"XREADGROUP",
20529 b"GROUP",
20530 b"g",
20531 b"c1",
20532 b"COUNT",
20533 b"1",
20534 b"STREAMS",
20535 b"s",
20536 b">",
20537 ]);
20538 assert_eq!(
20539 first,
20540 "*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"
20541 );
20542 assert_eq!(
20545 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b"0"]),
20546 "*1\r\n*2\r\n$1\r\ns\r\n*0\r\n"
20547 );
20548 assert_eq!(
20549 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
20550 "*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"
20551 );
20552
20553 assert_eq!(
20554 f.run(&[b"XPENDING", b"s", b"g"]),
20555 "*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"
20556 );
20557 assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":1\r\n");
20558 assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":0\r\n");
20559 assert_eq!(
20561 f.run(&[b"XPENDING", b"s", b"g"]),
20562 "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n"
20563 );
20564
20565 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
20568 f.run(&[b"XDEL", b"s", b"2-1"]);
20569 assert_eq!(
20570 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
20571 "*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"
20572 );
20573
20574 assert!(
20577 f.run(&[
20578 b"XREADGROUP",
20579 b"GROUP",
20580 b"nope",
20581 b"c",
20582 b"STREAMS",
20583 b"s",
20584 b"+"
20585 ])
20586 .starts_with("-NOGROUP")
20587 );
20588 assert!(
20589 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"])
20590 .contains("meaningless in the context of XREADGROUP")
20591 );
20592 assert!(
20593 f.run(&[b"XREAD", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"0"])
20594 .contains("only supported by XREADGROUP")
20595 );
20596 assert!(
20597 f.run(&[
20598 b"XREADGROUP",
20599 b"GROUP",
20600 b"g",
20601 b"c",
20602 b"STREAMS",
20603 b"s",
20604 b"a",
20605 b"b"
20606 ])
20607 .contains("Unbalanced 'xreadgroup' list of streams")
20608 );
20609 }
20610
20611 #[test]
20614 fn xread_with_no_block_writes_the_null_itself() {
20615 let mut f = Fixture::new();
20616 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20617 assert_eq!(
20618 f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
20619 "*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"
20620 );
20621 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "*-1\r\n");
20624 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"missing", b"0"]), "*-1\r\n");
20625 f.run(&[b"XADD", b"other", b"1-1", b"b", b"2"]);
20626 assert_eq!(
20627 f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"1-1", b"0"]),
20628 "*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"
20629 );
20630 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"$"]), "*-1\r\n");
20632 assert_eq!(
20634 f.run(&[b"XREAD", b"COUNT", b"5", b"STREAMS", b"s", b"+"]),
20635 "*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"
20636 );
20637 assert_eq!(
20640 f.run(&[b"XREAD", b"COUNT", b"0", b"STREAMS", b"s", b"0"]),
20641 "*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"
20642 );
20643 assert!(
20645 f.run(&[b"XREAD", b"BLOCK", b"0.5", b"STREAMS", b"s", b"$"])
20646 .contains("not an integer")
20647 );
20648 assert!(
20649 f.run(&[b"XREAD", b"BLOCK", b"-1", b"STREAMS", b"s", b"$"])
20650 .contains("timeout is negative")
20651 );
20652 assert!(
20653 f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"0"])
20654 .contains("Unbalanced 'xread' list of streams")
20655 );
20656 }
20657
20658 #[test]
20660 fn a_blocked_xread_wakes_on_the_next_entry() {
20661 let mut f = Fixture::new();
20662 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20663 let (flow, reply) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
20664 assert_eq!(flow, Flow::Block);
20665 assert!(reply.is_empty());
20666
20667 f.session = Session::new(8);
20672 let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
20673 assert_eq!(flow, Flow::Block);
20674 assert_eq!(f.server.parked(), 2);
20675
20676 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20677 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";
20678 for client in [7, 8] {
20679 let mut out = Out::new(Proto::Resp2);
20680 assert!(f.server.serve_waiter(client, 0, &mut out));
20681 assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
20682 }
20683
20684 f.server.forget_waiters(7);
20687 f.server.forget_waiters(8);
20688 let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"50", b"STREAMS", b"s", b"$"]);
20689 assert_eq!(flow, Flow::Block);
20690 let mut out = Out::new(Proto::Resp2);
20691 assert!(!f.server.serve_waiter(8, 0, &mut out));
20692 assert!(out.as_slice().is_empty());
20693 assert!(f.server.serve_waiter(8, u64::MAX, &mut out));
20694 assert_eq!(
20695 core::str::from_utf8(out.as_slice()).expect("ascii"),
20696 "*-1\r\n"
20697 );
20698 }
20699
20700 #[test]
20702 fn losing_a_group_while_blocked_is_the_ordinary_sentence() {
20703 let mut f = Fixture::new();
20704 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20705 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"]);
20706 let (flow, _) = f.flow(&[
20707 b"XREADGROUP",
20708 b"GROUP",
20709 b"g",
20710 b"c",
20711 b"BLOCK",
20712 b"0",
20713 b"STREAMS",
20714 b"s",
20715 b">",
20716 ]);
20717 assert_eq!(flow, Flow::Block);
20718
20719 f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]);
20720 let mut out = Out::new(Proto::Resp2);
20721 assert!(f.server.serve_waiter(7, 0, &mut out));
20722 assert_eq!(
20725 core::str::from_utf8(out.as_slice()).expect("ascii"),
20726 "-NOGROUP No such key 's' or consumer group 'g' in XREADGROUP with GROUP option\r\n"
20727 );
20728 }
20729
20730 #[test]
20732 fn xclaim_reads_ids_until_one_will_not_parse() {
20733 let mut f = Fixture::new();
20734 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20735 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20736 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20737 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
20738
20739 assert!(
20742 f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"-"])
20743 .contains("Unrecognized XCLAIM option '-'")
20744 );
20745 assert_eq!(
20746 f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1", b"JUSTID"]),
20747 "*1\r\n$3\r\n1-1\r\n"
20748 );
20749 f.run(&[b"XDEL", b"s", b"2-1"]);
20752 assert_eq!(
20753 f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1"]),
20754 "*0\r\n"
20755 );
20756 assert!(
20757 f.run(&[b"XPENDING", b"s", b"g"])
20758 .starts_with("*4\r\n:1\r\n")
20759 );
20760 assert!(
20761 f.run(&[b"XCLAIM", b"s", b"nope", b"c", b"0", b"1-1"])
20762 .starts_with("-NOGROUP")
20763 );
20764 assert!(
20765 f.run(&[b"XCLAIM", b"s", b"g", b"c", b"nan", b"1-1"])
20766 .contains("Invalid min-idle-time argument for XCLAIM")
20767 );
20768 }
20769
20770 #[test]
20772 fn xautoclaim_reports_what_it_dropped() {
20773 let mut f = Fixture::new();
20774 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20775 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20776 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20777 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
20778 f.run(&[b"XDEL", b"s", b"1-1"]);
20779
20780 assert_eq!(
20783 f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"JUSTID"]),
20784 "*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"
20785 );
20786 assert!(
20787 f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"COUNT", b"0"])
20788 .contains("COUNT must be > 0")
20789 );
20790 assert!(
20791 f.run(&[b"XAUTOCLAIM", b"s", b"nope", b"c", b"0", b"-"])
20792 .starts_with("-NOGROUP")
20793 );
20794 }
20795
20796 #[test]
20798 fn xdelex_answers_one_integer_an_id() {
20799 let mut f = Fixture::new();
20800 for i in 1..=4 {
20801 f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
20802 }
20803 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20804 f.run(&[
20805 b"XREADGROUP",
20806 b"GROUP",
20807 b"g",
20808 b"c",
20809 b"COUNT",
20810 b"2",
20811 b"STREAMS",
20812 b"s",
20813 b">",
20814 ]);
20815
20816 assert_eq!(
20818 f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1", b"9-9"]),
20819 "*2\r\n:1\r\n:-1\r\n"
20820 );
20821 assert!(
20824 f.run(&[b"XPENDING", b"s", b"g"])
20825 .starts_with("*4\r\n:2\r\n")
20826 );
20827 assert_eq!(
20829 f.run(&[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"]),
20830 "*1\r\n:1\r\n"
20831 );
20832 assert_eq!(
20834 f.run(&[b"XPENDING", b"s", b"g"]),
20835 "*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"
20836 );
20837
20838 assert_eq!(
20842 f.run(&[b"XDELEX", b"s", b"ACKED", b"IDS", b"2", b"3-1", b"4-1"]),
20843 "*2\r\n:2\r\n:2\r\n"
20844 );
20845
20846 assert_eq!(
20848 f.run(&[b"XDELEX", b"nope", b"IDS", b"2", b"bad", b"worse"]),
20849 "*2\r\n:-1\r\n:-1\r\n"
20850 );
20851 assert!(
20853 f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"3-1", b"bad"])
20854 .starts_with("-ERR Invalid stream ID")
20855 );
20856 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20857
20858 assert!(
20859 f.run(&[b"XDELEX", b"s", b"IDS", b"0", b"1-1"])
20860 .contains("Number of IDs must be a positive integer")
20861 );
20862 assert!(
20863 f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1"])
20864 .contains("The `numids` parameter must match the number of arguments")
20865 );
20866 assert!(
20869 f.run(&[b"XDELEX", b"s", b"KEEPREF", b"DELREF", b"IDS", b"1", b"1-1"])
20870 .starts_with("-ERR syntax error")
20871 );
20872 assert!(
20873 f.run(&[b"XDELEX", b"s", b"IDS", b"1", b"1-1", b"2-1"])
20874 .starts_with("-ERR syntax error")
20875 );
20876 f.run(&[b"SET", b"str", b"v"]);
20878 assert!(
20879 f.run(&[b"XDELEX", b"str", b"BOGUS", b"IDS", b"0", b"1-1"])
20880 .starts_with("-WRONGTYPE")
20881 );
20882 }
20883
20884 #[test]
20886 fn xackdel_reports_what_the_group_was_holding() {
20887 let mut f = Fixture::new();
20888 for i in 1..=3 {
20889 f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
20890 }
20891 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20892 f.run(&[
20893 b"XREADGROUP",
20894 b"GROUP",
20895 b"g",
20896 b"c",
20897 b"COUNT",
20898 b"1",
20899 b"STREAMS",
20900 b"s",
20901 b">",
20902 ]);
20903
20904 assert_eq!(
20908 f.run(&[b"XACKDEL", b"s", b"g", b"IDS", b"2", b"1-1", b"2-1"]),
20909 "*2\r\n:1\r\n:-1\r\n"
20910 );
20911 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20912
20913 assert_eq!(
20915 f.run(&[b"XACKDEL", b"s", b"nope", b"IDS", b"1", b"2-1"]),
20916 "*1\r\n:-1\r\n"
20917 );
20918 assert_eq!(
20919 f.run(&[b"XACKDEL", b"nope", b"g", b"IDS", b"1", b"2-1"]),
20920 "*1\r\n:-1\r\n"
20921 );
20922
20923 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b">"]);
20926 f.run(&[b"XGROUP", b"CREATE", b"s", b"g2", b"0"]);
20927 assert_eq!(
20928 f.run(&[b"XACKDEL", b"s", b"g", b"ACKED", b"IDS", b"1", b"2-1"]),
20929 "*1\r\n:2\r\n"
20930 );
20931 assert_eq!(
20932 f.run(&[b"XPENDING", b"s", b"g"]),
20933 "*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"
20934 );
20935 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20936 }
20937
20938 #[test]
20940 fn xnack_releases_an_entry_for_the_next_claim() {
20941 let mut f = Fixture::new();
20942 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20943 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20944 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20945 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
20946 f.run(&[b"XCLAIM", b"s", b"g", b"c1", b"0", b"1-1", b"2-1"]);
20949
20950 assert_eq!(
20951 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1"]),
20952 ":1\r\n"
20953 );
20954 assert_eq!(
20958 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]),
20959 "*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"
20960 );
20961 assert_eq!(
20963 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
20964 "*1\r\n*4\r\n$3\r\n2-1\r\n$2\r\nc1\r\n:0\r\n:2\r\n"
20965 );
20966 assert_eq!(
20968 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b">"]),
20969 "*-1\r\n"
20970 );
20971 assert_eq!(
20973 f.run(&[
20974 b"XAUTOCLAIM",
20975 b"s",
20976 b"g",
20977 b"c2",
20978 b"99999999",
20979 b"-",
20980 b"JUSTID"
20981 ]),
20982 "*3\r\n$3\r\n0-0\r\n*1\r\n$3\r\n1-1\r\n*0\r\n"
20983 );
20984
20985 f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
20989 assert!(
20990 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
20991 .contains(":-1\r\n:1\r\n")
20992 );
20993 f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
20995 f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
20996 assert!(
20997 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
20998 .contains(":-1\r\n:0\r\n")
20999 );
21000 f.run(&[b"XNACK", b"s", b"g", b"FATAL", b"IDS", b"1", b"1-1"]);
21002 assert!(
21003 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21004 .contains(":9223372036854775807\r\n")
21005 );
21006 f.run(&[
21007 b"XNACK",
21008 b"s",
21009 b"g",
21010 b"FATAL",
21011 b"IDS",
21012 b"1",
21013 b"1-1",
21014 b"RETRYCOUNT",
21015 b"3",
21016 ]);
21017 assert!(
21018 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21019 .contains(":-1\r\n:3\r\n")
21020 );
21021
21022 f.run(&[b"XACK", b"s", b"g", b"2-1"]);
21026 assert_eq!(
21027 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"]),
21028 ":0\r\n"
21029 );
21030 assert_eq!(
21031 f.run(&[
21032 b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1", b"FORCE"
21033 ]),
21034 ":1\r\n"
21035 );
21036 assert!(
21037 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21038 .contains(":-1\r\n:0\r\n")
21039 );
21040 assert_eq!(
21042 f.run(&[
21043 b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"9-9", b"FORCE"
21044 ]),
21045 ":0\r\n"
21046 );
21047
21048 assert_eq!(
21051 f.run(&[b"XNACK", b"s", b"nope", b"BOGUS", b"IDS", b"1", b"1-1"]),
21052 "-NOGROUP No such key 's' or consumer group 'nope'\r\n"
21053 );
21054 assert!(
21055 f.run(&[b"XNACK", b"s", b"g", b"BOGUS", b"IDS", b"1", b"1-1"])
21056 .starts_with("-ERR")
21057 );
21058 assert!(
21060 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"0", b"1-1"])
21061 .contains("numids must be a positive integer")
21062 );
21063 assert!(
21064 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"2", b"1-1"])
21065 .contains("number of IDs doesn't match numids")
21066 );
21067 assert!(
21070 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1", b"2-1"])
21071 .contains("Unrecognized XNACK option '2-1'")
21072 );
21073 }
21074
21075 #[test]
21077 fn xinfo_reports_the_stream_the_groups_and_the_consumers() {
21078 let mut f = Fixture::new();
21079 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21080 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
21081 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
21082 f.run(&[
21083 b"XREADGROUP",
21084 b"GROUP",
21085 b"g",
21086 b"c1",
21087 b"COUNT",
21088 b"1",
21089 b"STREAMS",
21090 b"s",
21091 b">",
21092 ]);
21093
21094 let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
21095 assert!(info.starts_with("*20\r\n"), "{info}");
21098 assert!(info.contains("$6\r\nlength\r\n:2\r\n"), "{info}");
21099 assert!(
21100 info.contains("$17\r\nlast-generated-id\r\n$3\r\n2-1\r\n"),
21101 "{info}"
21102 );
21103 assert!(info.contains("$13\r\nentries-added\r\n:2\r\n"), "{info}");
21104 assert!(info.contains("$6\r\ngroups\r\n:1\r\n"), "{info}");
21105
21106 let groups = f.run(&[b"XINFO", b"GROUPS", b"s"]);
21107 assert!(groups.starts_with("*1\r\n*12\r\n"), "{groups}");
21108 assert!(groups.contains("$9\r\nconsumers\r\n:1\r\n"), "{groups}");
21109 assert!(groups.contains("$7\r\npending\r\n:1\r\n"), "{groups}");
21110 assert!(groups.contains("$3\r\nlag\r\n:1\r\n"), "{groups}");
21111
21112 f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c2"]);
21116 let consumers = f.run(&[b"XINFO", b"CONSUMERS", b"s", b"g"]);
21117 assert!(consumers.starts_with("*2\r\n"), "{consumers}");
21118 assert!(
21119 consumers.contains("$8\r\ninactive\r\n:-1\r\n"),
21120 "{consumers}"
21121 );
21122 let c1 = consumers.find("c1").unwrap();
21124 let c2 = consumers.find("c2").unwrap();
21125 assert!(c1 < c2, "{consumers}");
21126
21127 let full = f.run(&[b"XINFO", b"STREAM", b"s", b"FULL"]);
21128 assert!(full.starts_with("*18\r\n"), "{full}");
21129 assert!(full.contains("$12\r\nnacked-count\r\n:0\r\n"), "{full}");
21130 assert!(full.contains("$11\r\nactive-time\r\n"), "{full}");
21131
21132 assert!(
21133 f.run(&[b"XINFO", b"STREAM", b"missing"])
21134 .contains("no such key")
21135 );
21136 assert!(
21137 f.run(&[b"XINFO", b"GROUPS", b"missing"])
21138 .contains("no such key")
21139 );
21140 assert!(
21141 f.run(&[b"XINFO", b"CONSUMERS", b"s", b"nope"])
21142 .starts_with("-NOGROUP")
21143 );
21144 assert!(
21145 f.run(&[b"XINFO", b"NOSUCH", b"s"])
21146 .contains("Try XINFO HELP")
21147 );
21148 assert!(f.run(&[b"XINFO", b"HELP"]).contains("XINFO <subcommand>"));
21149 assert!(f.run(&[b"XGROUP", b"HELP"]).contains("XGROUP <subcommand>"));
21150 }
21151
21152 #[test]
21154 fn xpending_takes_the_consumer_only_when_the_count_comes_out_right() {
21155 let mut f = Fixture::new();
21156 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21157 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
21158 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
21159
21160 let list = f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]);
21161 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");
21162 assert_eq!(
21163 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
21164 "*1\r\n*4\r\n$3\r\n1-1\r\n$2\r\nc1\r\n:0\r\n:1\r\n"
21165 );
21166 assert_eq!(
21168 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"nope"]),
21169 "*0\r\n"
21170 );
21171 assert_eq!(
21172 f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0", b"-", b"+", b"10"]),
21173 list
21174 );
21175 assert!(
21177 f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0"])
21178 .contains("syntax error")
21179 );
21180 assert!(
21181 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+"])
21182 .contains("syntax error")
21183 );
21184 assert_eq!(
21185 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"-1"]),
21186 "*0\r\n"
21187 );
21188 assert!(
21189 f.run(&[b"XPENDING", b"missing", b"g"])
21190 .starts_with("-NOGROUP")
21191 );
21192 }
21193
21194 #[test]
21196 fn xsetid_will_not_go_below_what_is_there() {
21197 let mut f = Fixture::new();
21198 f.run(&[b"XADD", b"s", b"5-5", b"a", b"1"]);
21199 assert_eq!(f.run(&[b"XSETID", b"s", b"9-9"]), "+OK\r\n");
21200 assert_eq!(
21201 f.run(&[
21202 b"XSETID",
21203 b"s",
21204 b"10-1",
21205 b"ENTRIESADDED",
21206 b"7",
21207 b"MAXDELETEDID",
21208 b"9-1"
21209 ]),
21210 "+OK\r\n"
21211 );
21212 let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
21213 assert!(info.contains("$13\r\nentries-added\r\n:7\r\n"), "{info}");
21214 assert!(
21215 info.contains("$20\r\nmax-deleted-entry-id\r\n$3\r\n9-1\r\n"),
21216 "{info}"
21217 );
21218
21219 assert!(
21220 f.run(&[b"XSETID", b"s", b"1-1"])
21221 .contains("smaller than the target stream top item")
21222 );
21223 assert!(
21224 f.run(&[b"XSETID", b"s", b"10-1", b"ENTRIESADDED", b"-1"])
21225 .contains("entries_added must be positive")
21226 );
21227 assert!(
21228 f.run(&[b"XSETID", b"missing", b"1-1"])
21229 .contains("no such key")
21230 );
21231 }
21232
21233 #[test]
21235 fn xread_answers_a_map_on_resp3_and_the_fields_stay_flat() {
21236 let mut f = Fixture::new();
21237 f.run(&[b"HELLO", b"3"]);
21238 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21239 assert_eq!(
21242 f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
21243 "%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"
21244 );
21245 assert_eq!(
21248 f.run(&[b"XRANGE", b"s", b"-", b"+"]),
21249 "*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"
21250 );
21251 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "_\r\n");
21252 }
21253
21254 struct Mem {
21260 blobs: Vec<Vec<u8>>,
21261 }
21262
21263 impl yo_kv::cold::Blocks for Mem {
21264 fn put(&mut self, bytes: &[u8]) -> yo_common::Result<yo_common::Addr> {
21265 self.blobs.push(bytes.to_vec());
21266 Ok(yo_common::Addr::new(
21267 yo_common::Space::Log,
21268 (self.blobs.len() - 1) as u64,
21269 ))
21270 }
21271
21272 fn get(&self, at: yo_common::Addr) -> yo_common::Result<&[u8]> {
21273 self.blobs
21274 .get(at.offset() as usize)
21275 .map(Vec::as_slice)
21276 .ok_or_else(|| {
21277 yo_common::Error::new(yo_common::Code::Corrupt, "no chunk at that address")
21278 })
21279 }
21280
21281 fn bytes(&self) -> u64 {
21282 self.blobs.iter().map(|b| b.len() as u64).sum()
21283 }
21284 }
21285
21286 fn filled(attach: bool) -> (Fixture, usize) {
21299 let mut f = Fixture::new();
21300 if attach {
21301 f.server
21302 .striped(0)
21303 .hold_stripe(0)
21304 .attach(Box::new(Mem { blobs: Vec::new() }));
21305 }
21306 let val = vec![b'v'; 256];
21307 for i in 0..24000u32 {
21308 let k = format!("key:{i:08}");
21309 f.run(&[b"SET", k.as_bytes(), &val]);
21310 }
21311 let full = f.server.memory_bytes();
21312 assert!(full > 3 * 1024 * 1024, "the arena is several segments");
21313 (f, full)
21314 }
21315
21316 fn press(f: &mut Fixture, limit: usize) {
21322 let val = vec![b'v'; 256];
21323 for i in 0..3000u32 {
21324 let k = format!("new:{i:08}");
21325 assert_eq!(
21326 f.run(&[b"SET", k.as_bytes(), &val]),
21327 "+OK\r\n",
21328 "write {i} was refused"
21329 );
21330 f.server.refresh_memory();
21331 if f.server.memory_bytes() <= limit {
21332 return;
21333 }
21334 }
21335 panic!(
21336 "it never got under: {} against {limit}",
21337 f.server.memory_bytes()
21338 );
21339 }
21340
21341 #[test]
21342 fn the_storage_limit_reads_back_and_minus_one_is_no_limit() {
21343 let mut f = Fixture::new();
21344 assert_eq!(
21345 f.run(&[b"CONFIG", b"GET", b"maxstore"]),
21346 "*2\r\n$8\r\nmaxstore\r\n$2\r\n-1\r\n",
21347 "no limit is the default"
21348 );
21349 for (typed, bytes) in [
21352 (&b"0"[..], "0"),
21353 (b"1024", "1024"),
21354 (b"1k", "1000"),
21355 (b"1gb", "1073741824"),
21356 (b"-1", "-1"),
21357 ] {
21358 assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxstore", typed]), "+OK\r\n");
21359 assert_eq!(
21360 f.run(&[b"CONFIG", b"GET", b"maxstore"]),
21361 format!("*2\r\n$8\r\nmaxstore\r\n${}\r\n{bytes}\r\n", bytes.len()),
21362 "set {}",
21363 String::from_utf8_lossy(typed)
21364 );
21365 }
21366 for bad in [&b"1tb"[..], b"-2", b"", b"lots"] {
21367 assert_eq!(
21368 f.run(&[b"CONFIG", b"SET", b"maxstore", bad]),
21369 "-ERR CONFIG SET failed (possibly related to argument 'maxstore') - argument must be a memory value or -1\r\n",
21370 "refused {}",
21371 String::from_utf8_lossy(bad)
21372 );
21373 }
21374 let info = f.run(&[b"INFO", b"memory"]);
21376 assert!(info.contains("maxstore:-1"), "{info}");
21377 assert!(info.contains("yo_memory_regime:evict"), "{info}");
21378 assert!(info.contains("yo_store_bytes:0"), "{info}");
21379 }
21380
21381 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
21382 #[test]
21383 fn a_memory_limit_moves_values_to_the_file_instead_of_dropping_keys() {
21384 let (mut f, full) = filled(true);
21388 let keys = f.run(&[b"DBSIZE"]);
21389 assert!(
21390 f.run(&[b"INFO", b"memory"])
21391 .contains("yo_memory_regime:migrate"),
21392 "a database with somewhere to put values migrates"
21393 );
21394
21395 let limit = full - 2 * 1024 * 1024;
21396 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
21397 f.run(&[
21398 b"CONFIG",
21399 b"SET",
21400 b"maxmemory",
21401 limit.to_string().as_bytes(),
21402 ]);
21403 press(&mut f, limit);
21404
21405 assert!(
21406 f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
21407 "nothing was thrown away"
21408 );
21409 let after: usize = f.run(&[b"DBSIZE"])[1..]
21410 .trim_end()
21411 .parse()
21412 .expect("a count");
21413 let before: usize = keys[1..].trim_end().parse().expect("a count");
21414 assert!(after > before, "the keys that came in are all still here");
21415 assert!(
21416 f.server.store_bytes() > 0,
21417 "and what came out of memory went to the file"
21418 );
21419 let val = format!("$256\r\n{}\r\n", "v".repeat(256));
21422 assert_eq!(f.run(&[b"GET", b"key:00000000"]), val);
21423 assert_eq!(f.run(&[b"GET", b"key:00023999"]), val);
21424 }
21425
21426 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
21427 #[test]
21428 fn a_storage_limit_of_zero_restores_redis_behaviour_exactly() {
21429 let (mut f, full) = filled(true);
21433 f.run(&[b"CONFIG", b"SET", b"maxstore", b"0"]);
21434 assert!(
21435 f.run(&[b"INFO", b"memory"])
21436 .contains("yo_memory_regime:evict"),
21437 "nothing may go to the file"
21438 );
21439
21440 let limit = full - 2 * 1024 * 1024;
21441 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
21442 f.run(&[
21443 b"CONFIG",
21444 b"SET",
21445 b"maxmemory",
21446 limit.to_string().as_bytes(),
21447 ]);
21448 press(&mut f, limit);
21449
21450 assert!(
21451 !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
21452 "keys were thrown away, which is what was asked for"
21453 );
21454 assert_eq!(f.server.store_bytes(), 0, "and the file was never written");
21455 }
21456
21457 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
21458 #[test]
21459 fn a_full_file_goes_back_to_evicting() {
21460 let (mut f, full) = filled(true);
21464 f.run(&[b"CONFIG", b"SET", b"maxstore", b"64kb"]);
21465 let limit = full - 2 * 1024 * 1024;
21466 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
21467 f.run(&[
21468 b"CONFIG",
21469 b"SET",
21470 b"maxmemory",
21471 limit.to_string().as_bytes(),
21472 ]);
21473 press(&mut f, limit);
21474
21475 assert!(f.server.store_bytes() >= 64 * 1024, "the file filled up");
21476 assert!(
21477 !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
21478 "and then it started evicting"
21479 );
21480 assert!(
21481 f.run(&[b"INFO", b"memory"])
21482 .contains("yo_memory_regime:evict"),
21483 "and it says so"
21484 );
21485 }
21486 #[test]
21502 fn the_string_group_answers_the_same_however_many_stripes_there_are() {
21503 let script: &[&[&[u8]]] = &[
21504 &[b"SET", b"k1", b"v1"],
21507 &[b"SET", b"k2", b"v2"],
21508 &[b"GET", b"k1"],
21509 &[b"GET", b"nothing"],
21510 &[b"GETSET", b"k1", b"v1b"],
21511 &[b"SETNX", b"k1", b"no"],
21512 &[b"SETNX", b"k3", b"yes"],
21513 &[b"APPEND", b"k3", b"!"],
21514 &[b"STRLEN", b"k3"],
21515 &[b"SETRANGE", b"k3", b"1", b"XY"],
21516 &[b"GETRANGE", b"k3", b"0", b"-1"],
21517 &[b"INCR", b"n1"],
21518 &[b"INCRBY", b"n1", b"41"],
21519 &[b"DECRBY", b"n1", b"2"],
21520 &[b"INCRBYFLOAT", b"f1", b"1.5"],
21521 &[b"SETEX", b"e1", b"100", b"v"],
21522 &[b"PSETEX", b"e2", b"100000", b"v"],
21523 &[b"GETEX", b"e1", b"PERSIST"],
21524 &[b"GETDEL", b"k2"],
21525 &[b"GET", b"k2"],
21526 &[b"DIGEST", b"k1"],
21527 &[b"DELEX", b"k3"],
21528 &[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"],
21531 &[b"MGET", b"a", b"b", b"c", b"missing"],
21532 &[b"MSETNX", b"d", b"4", b"e", b"5"],
21533 &[b"MSETNX", b"e", b"6", b"f", b"7"],
21534 &[b"MGET", b"d", b"e", b"f"],
21535 &[b"MSETEX", b"2", b"g", b"7", b"h", b"8", b"NX"],
21536 &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"NX"],
21537 &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"XX"],
21538 &[b"MGET", b"g", b"h"],
21539 &[b"SET", b"s1", b"ohmytext"],
21540 &[b"SET", b"s2", b"mynewtext"],
21541 &[b"LCS", b"s1", b"s2"],
21542 &[b"LCS", b"s1", b"s2", b"LEN"],
21543 &[b"LCS", b"s1", b"s2", b"IDX", b"MINMATCHLEN", b"4"],
21544 &[b"LCS", b"s1", b"s2", b"IDX", b"WITHMATCHLEN"],
21545 &[b"LCS", b"s1", b"gone"],
21546 &[b"MSET", b"odd"],
21548 &[b"LCS", b"s1", b"s2", b"LEN", b"IDX"],
21549 &[b"MGET"],
21550 ];
21551
21552 let mut one = Fixture::new();
21553 let mut many = Fixture::striped(8);
21554 for parts in script {
21555 let a = one.run(parts);
21556 let b = many.run(parts);
21557 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
21558 }
21559 }
21560
21561 #[test]
21566 fn a_striped_database_spreads_the_keys_it_is_given() {
21567 let mut f = Fixture::striped(8);
21568 for i in 0..256 {
21569 let key = format!("key:{i}");
21570 f.run(&[b"SET", key.as_bytes(), b"v"]);
21571 }
21572 assert_eq!(f.run(&[b"DBSIZE"]), ":256\r\n");
21573 }
21574
21575 #[test]
21578 fn a_wrong_type_in_the_middle_of_an_mget_is_still_one_nil() {
21579 let mut one = Fixture::new();
21580 let mut many = Fixture::striped(8);
21581 for f in [&mut one, &mut many] {
21582 f.run(&[b"SET", b"str", b"v"]);
21583 f.server
21589 .striped(0)
21590 .hold(b"list")
21591 .push(b"list", yo_kv::End::Right, core::iter::once(&b"v"[..]))
21592 .expect("a new list");
21593 }
21594 assert_eq!(
21595 one.run(&[b"MGET", b"str", b"list", b"gone"]),
21596 many.run(&[b"MGET", b"str", b"list", b"gone"])
21597 );
21598 }
21599
21600 #[test]
21608 fn the_keyspace_group_answers_the_same_however_many_stripes_there_are() {
21609 let script: &[&[&[u8]]] = &[
21610 &[b"SET", b"k1", b"v1"],
21611 &[b"SET", b"k2", b"v2"],
21612 &[b"EXISTS", b"k1", b"k2", b"k1", b"gone"],
21613 &[b"TYPE", b"k1"],
21614 &[b"TYPE", b"gone"],
21615 &[b"TOUCH", b"k1", b"k2", b"k1", b"gone"],
21616 &[b"EXPIRE", b"k1", b"100"],
21617 &[b"TTL", b"k1"],
21618 &[b"EXPIRE", b"k1", b"200", b"NX"],
21619 &[b"PERSIST", b"k1"],
21620 &[b"TTL", b"k1"],
21621 &[b"PEXPIREAT", b"k2", b"1900000000000"],
21622 &[b"EXPIRETIME", b"k2"],
21623 &[b"PEXPIRETIME", b"k2"],
21624 &[b"PERSIST", b"k2"],
21625 &[b"OBJECT", b"ENCODING", b"k1"],
21626 &[b"OBJECT", b"REFCOUNT", b"k1"],
21627 &[b"OBJECT", b"IDLETIME", b"k1"],
21628 &[b"OBJECT", b"FREQ", b"k1"],
21629 &[b"OBJECT", b"ENCODING", b"gone"],
21630 &[b"OBJECT", b"HELP"],
21631 &[b"RENAME", b"k1", b"k9"],
21632 &[b"GET", b"k9"],
21633 &[b"RENAME", b"gone", b"x"],
21634 &[b"RENAMENX", b"k9", b"k2"],
21635 &[b"RENAMENX", b"k9", b"k8"],
21636 &[b"GET", b"k8"],
21637 &[b"COPY", b"k8", b"c1"],
21638 &[b"COPY", b"k8", b"c1"],
21639 &[b"COPY", b"k8", b"c1", b"REPLACE"],
21640 &[b"COPY", b"k8", b"k8"],
21641 &[b"COPY", b"gone", b"c2"],
21642 &[b"COPY", b"k8", b"k8", b"DB", b"1"],
21643 &[b"COPY", b"k8", b"c9", b"DB", b"9"],
21644 &[b"MOVE", b"c1", b"1"],
21645 &[b"MOVE", b"c1", b"1"],
21646 &[b"MOVE", b"k8", b"0"],
21647 &[b"DEL", b"k2", b"gone"],
21648 &[b"UNLINK", b"k8", b"k8"],
21649 &[b"DBSIZE"],
21650 ];
21651
21652 let mut one = Fixture::new();
21653 let mut many = Fixture::striped(8);
21654 for parts in script {
21655 let a = one.run(parts);
21656 let b = many.run(parts);
21657 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
21658 }
21659
21660 for f in [&mut one, &mut many] {
21666 f.run(&[b"SET", b"d1", b"payload"]);
21667 let payload = f
21668 .server
21669 .striped(0)
21670 .hold(b"d1")
21671 .dump(b"d1")
21672 .expect("a key that is there");
21673 assert!(
21674 f.run(&[b"DUMP", b"d1"])
21675 .starts_with(&format!("${}", payload.len())),
21676 "a payload of the length the store gave"
21677 );
21678 assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
21679 assert_eq!(f.run(&[b"RESTORE", b"d2", b"0", &payload]), "+OK\r\n");
21680 assert_eq!(f.run(&[b"GET", b"d2"]), "$7\r\npayload\r\n");
21681 assert_eq!(
21682 f.run(&[b"RESTORE", b"d2", b"0", &payload]),
21683 "-BUSYKEY Target key name already exists.\r\n"
21684 );
21685 assert_eq!(
21686 f.run(&[b"RESTORE", b"d3", b"0", b"rubbish"]),
21687 "-ERR DUMP payload version or checksum are wrong\r\n"
21688 );
21689 }
21690 }
21691
21692 #[test]
21699 fn a_scan_of_a_striped_database_walks_all_of_it() {
21700 let n = if cfg!(miri) { 80 } else { 500 };
21704 let mut f = Fixture::striped(8);
21705 for i in 0..n {
21706 let key = format!("key:{i}");
21707 f.run(&[b"SET", key.as_bytes(), b"v"]);
21708 }
21709
21710 let mut seen = Vec::new();
21711 let mut cursor = "0".to_owned();
21712 let mut calls = 0;
21713 loop {
21714 let reply = f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"10"]);
21715 let (next, keys) = scan_reply(&reply);
21716 seen.extend(keys);
21717 cursor = next;
21718 calls += 1;
21719 assert!(calls < 5_000, "a scan that will not finish");
21720 if cursor == "0" {
21721 break;
21722 }
21723 }
21724 seen.sort();
21725 assert_eq!(seen.len(), n, "a quiet scan answered a key twice");
21726 assert_eq!(seen, sorted(&f.run(&[b"KEYS", b"*"])));
21727
21728 let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"MATCH", b"key:4?"]);
21732 let (_, keys) = scan_reply(&reply);
21733 assert_eq!(keys.len(), 10, "key:40 through key:49");
21734 let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"TYPE", b"list"]);
21735 let (_, keys) = scan_reply(&reply);
21736 assert!(keys.is_empty(), "nothing here is a list");
21737 }
21738
21739 #[test]
21745 fn a_random_key_can_come_from_any_stripe() {
21746 let mut f = Fixture::striped(8);
21747 assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
21748 for i in 0..200 {
21749 let key = format!("key:{i}");
21750 f.run(&[b"SET", key.as_bytes(), b"v"]);
21751 }
21752 let mut homes = std::collections::HashSet::new();
21753 for _ in 0..200 {
21754 let got = f.run(&[b"RANDOMKEY"]);
21755 let key = got.split("\r\n").nth(1).expect("a key").to_owned();
21756 assert_eq!(f.run(&[b"EXISTS", key.as_bytes()]), ":1\r\n");
21757 homes.insert(f.server.striped(0).stripe_of(key.as_bytes()));
21758 }
21759 assert_eq!(homes.len(), 8, "some stripe was never drawn from");
21760 }
21761
21762 fn apart(f: &mut Fixture, src: &str) -> String {
21766 let home = f.server.striped(0).stripe_of(src.as_bytes());
21767 for i in 0..1_000 {
21768 let dst = format!("dst:{i}");
21769 if f.server.striped(0).stripe_of(dst.as_bytes()) != home {
21770 return dst;
21771 }
21772 }
21773 panic!("eight stripes and a thousand keys all landed in one place");
21774 }
21775
21776 #[test]
21779 fn a_rename_across_stripes_takes_everything_with_it() {
21780 let mut f = Fixture::striped(8);
21781 let dst = apart(&mut f, "src");
21782 let (src, dst) = (b"src".as_slice(), dst.as_bytes());
21783
21784 f.run(&[b"SET", src, b"v"]);
21785 f.run(&[b"EXPIRE", src, b"100"]);
21786 assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
21787 assert_eq!(f.run(&[b"EXISTS", src, dst]), ":1\r\n");
21788 assert_eq!(f.run(&[b"GET", dst]), "$1\r\nv\r\n");
21789 assert_eq!(f.run(&[b"TTL", dst]), ":100\r\n", "the deadline came too");
21790
21791 f.server
21796 .striped(0)
21797 .hold(src)
21798 .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
21799 .expect("a new list");
21800 assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
21801 assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n");
21802 assert_eq!(
21803 f.server.striped(0).hold(dst).llen(dst).expect("a list"),
21804 2,
21805 "the members are on the stripe the key moved to"
21806 );
21807
21808 f.run(&[b"SET", src, b"v"]);
21811 assert_eq!(f.run(&[b"RENAMENX", src, dst]), ":0\r\n");
21812 assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n", "and left it alone");
21813 assert_eq!(f.run(&[b"GET", src]), "$1\r\nv\r\n", "and left the source");
21814 }
21815
21816 #[test]
21818 fn a_copy_across_stripes_leaves_the_source_where_it_was() {
21819 let mut f = Fixture::striped(8);
21820 let dst = apart(&mut f, "src");
21821 let (src, dst) = (b"src".as_slice(), dst.as_bytes());
21822
21823 f.run(&[b"SET", src, b"v"]);
21824 assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
21825 assert_eq!(f.run(&[b"EXISTS", src, dst]), ":2\r\n");
21826 assert_eq!(
21827 f.run(&[b"COPY", src, dst]),
21828 ":0\r\n",
21829 "the destination is taken"
21830 );
21831 f.run(&[b"SET", src, b"w"]);
21832 assert_eq!(f.run(&[b"COPY", src, dst, b"REPLACE"]), ":1\r\n");
21833 assert_eq!(f.run(&[b"GET", dst]), "$1\r\nw\r\n");
21834
21835 f.run(&[b"DEL", src, dst]);
21839 f.server
21840 .striped(0)
21841 .hold(src)
21842 .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
21843 .expect("a new list");
21844 assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
21845 f.server
21846 .striped(0)
21847 .hold(src)
21848 .push(src, yo_kv::End::Right, core::iter::once(&b"c"[..]))
21849 .expect("a list that is there");
21850 assert_eq!(f.server.striped(0).hold(src).llen(src).expect("a list"), 3);
21851 assert_eq!(f.server.striped(0).hold(dst).llen(dst).expect("a list"), 2);
21852 }
21853
21854 #[test]
21862 fn the_bitmap_group_answers_the_same_however_many_stripes_there_are() {
21863 let script: &[&[&[u8]]] = &[
21864 &[b"SET", b"k1", b"foobar"],
21865 &[b"SETBIT", b"b1", b"7", b"1"],
21866 &[b"SETBIT", b"b1", b"7", b"0"],
21867 &[b"GETBIT", b"k1", b"6"],
21868 &[b"GETBIT", b"k1", b"100"],
21869 &[b"BITCOUNT", b"k1"],
21870 &[b"BITCOUNT", b"k1", b"0", b"0"],
21871 &[b"BITCOUNT", b"k1", b"5", b"30", b"BIT"],
21872 &[b"BITPOS", b"k1", b"1"],
21873 &[b"BITPOS", b"k1", b"0", b"2"],
21874 &[b"BITPOS", b"k1", b"1", b"2", b"-1", b"BIT"],
21875 &[
21876 b"BITFIELD",
21877 b"bf",
21878 b"SET",
21879 b"u8",
21880 b"0",
21881 b"255",
21882 b"GET",
21883 b"u8",
21884 b"0",
21885 ],
21886 &[
21887 b"BITFIELD",
21888 b"bf",
21889 b"OVERFLOW",
21890 b"SAT",
21891 b"INCRBY",
21892 b"u8",
21893 b"0",
21894 b"10",
21895 ],
21896 &[b"BITFIELD_RO", b"bf", b"GET", b"u8", b"0"],
21897 &[b"SET", b"s1", b"abc"],
21900 &[b"SET", b"s2", b"abd"],
21901 &[b"SET", b"s3", b"a"],
21902 &[b"BITOP", b"AND", b"d1", b"s1", b"s2"],
21903 &[b"GET", b"d1"],
21904 &[b"BITOP", b"OR", b"d2", b"s1", b"s2", b"s3"],
21905 &[b"GET", b"d2"],
21906 &[b"BITOP", b"XOR", b"d3", b"s1", b"s2"],
21907 &[b"STRLEN", b"d3"],
21908 &[b"BITOP", b"NOT", b"d4", b"s1"],
21909 &[b"STRLEN", b"d4"],
21910 &[b"BITOP", b"DIFF", b"d5", b"s1", b"s2"],
21911 &[b"BITOP", b"DIFF1", b"d6", b"s1", b"s2"],
21912 &[b"BITOP", b"ANDOR", b"d7", b"s1", b"s2"],
21913 &[b"BITOP", b"ONE", b"d8", b"s1", b"s2"],
21914 &[b"BITOP", b"AND", b"d1", b"gone", b"also-gone"],
21917 &[b"EXISTS", b"d1"],
21918 &[b"BITOP", b"OR", b"d9", b"s1", b"gone"],
21919 &[b"GET", b"d9"],
21920 &[b"BITOP", b"AND", b"d1", b"s1", b"list"],
21924 &[b"BITOP", b"AND", b"list", b"s1", b"s2"],
21925 &[b"BITOP", b"NOT", b"d1", b"s1", b"s2"],
21926 &[b"BITOP", b"DIFF", b"d1", b"s1"],
21927 &[b"BITOP", b"NOPE", b"d1", b"s1"],
21928 &[b"BITCOUNT", b"list"],
21929 &[b"BITFIELD_RO", b"bf", b"SET", b"u8", b"0", b"1"],
21930 ];
21931
21932 let mut one = Fixture::new();
21933 let mut many = Fixture::striped(8);
21934 for f in [&mut one, &mut many] {
21935 plant_list(f, b"list");
21936 }
21937 for parts in script {
21938 let a = one.run(parts);
21939 let b = many.run(parts);
21940 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
21941 }
21942 }
21943
21944 fn plant_list(f: &mut Fixture, key: &[u8]) {
21950 f.server
21951 .striped(0)
21952 .hold(key)
21953 .push(key, yo_kv::End::Right, core::iter::once(&b"x"[..]))
21954 .expect("a new list");
21955 }
21956
21957 #[test]
21964 fn a_bitop_across_stripes_reads_every_source() {
21965 let mut f = Fixture::striped(8);
21966 let other = apart(&mut f, "src");
21967 let (src, far) = (b"src".as_slice(), other.as_bytes());
21968 assert_ne!(
21969 f.server.striped(0).stripe_of(src),
21970 f.server.striped(0).stripe_of(far),
21971 "the two keys are the point of the test"
21972 );
21973
21974 f.run(&[b"SET", src, b"abc"]);
21975 f.run(&[b"SET", far, b"abd"]);
21976 assert_eq!(f.run(&[b"BITOP", b"AND", far, src, far]), ":3\r\n");
21977 assert_eq!(
21978 f.run(&[b"GET", far]),
21979 "$3\r\nab`\r\n",
21980 "a destination that is also a source"
21981 );
21982 f.run(&[b"SET", far, b"abd"]);
21983 assert_eq!(f.run(&[b"BITOP", b"XOR", src, src, far]), ":3\r\n");
21984 assert_eq!(
21985 f.run(&[b"GET", src]),
21986 "$3\r\n\0\0\x07\r\n",
21987 "and the other way round"
21988 );
21989
21990 f.run(&[b"SET", src, b"abc"]);
21994 f.run(&[b"DEL", far]);
21995 assert_eq!(f.run(&[b"BITOP", b"AND", src, far, b"gone"]), ":0\r\n");
21996 assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n");
21997 f.run(&[b"SET", src, b"abc"]);
21998 f.run(&[b"DEL", far]);
21999 plant_list(&mut f, far);
22000 assert_eq!(
22001 f.run(&[b"BITOP", b"OR", b"out", src, far]),
22002 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22003 );
22004 assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
22005 }
22006
22007 #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
22013 #[test]
22014 fn the_hyperloglog_group_answers_the_same_however_many_stripes_there_are() {
22015 let script: &[&[&[u8]]] = &[
22016 &[b"PFADD", b"h1", b"a", b"b", b"c"],
22017 &[b"PFADD", b"h1", b"a"],
22018 &[b"PFADD", b"h2"],
22019 &[b"PFADD", b"h2", b"c", b"d", b"e"],
22020 &[b"PFCOUNT", b"h1"],
22021 &[b"PFCOUNT", b"h2"],
22022 &[b"PFCOUNT", b"missing"],
22023 &[b"PFCOUNT", b"h1", b"h2"],
22025 &[b"PFCOUNT", b"h1", b"missing"],
22026 &[b"PFMERGE", b"m", b"h1", b"h2"],
22027 &[b"PFCOUNT", b"m"],
22028 &[b"STRLEN", b"m"],
22029 &[b"PFMERGE", b"m"],
22030 &[b"PFCOUNT", b"m"],
22031 &[b"PFMERGE", b"m2", b"missing"],
22032 &[b"PFCOUNT", b"m2"],
22033 &[b"PFDEBUG", b"ENCODING", b"h1"],
22036 &[b"PFDEBUG", b"DECODE", b"h1"],
22037 &[b"PFDEBUG", b"TODENSE", b"h1"],
22038 &[b"PFDEBUG", b"ENCODING", b"h1"],
22039 &[b"PFDEBUG", b"TODENSE", b"h1"],
22040 &[b"PFCOUNT", b"h1", b"h2"],
22041 &[b"PFSELFTEST"],
22042 &[b"SET", b"plain", b"not a sketch at all"],
22044 &[b"PFADD", b"plain", b"a"],
22045 &[b"PFCOUNT", b"plain"],
22046 &[b"PFCOUNT", b"h1", b"plain"],
22047 &[b"PFMERGE", b"plain", b"h1"],
22048 &[b"PFMERGE", b"m", b"plain"],
22049 &[b"PFDEBUG", b"ENCODING", b"gone"],
22050 &[b"PFDEBUG", b"NOPE", b"h1"],
22051 ];
22052
22053 let mut one = Fixture::new();
22054 let mut many = Fixture::striped(8);
22055 for parts in script {
22056 let a = one.run(parts);
22057 let b = many.run(parts);
22058 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22059 }
22060 }
22061
22062 #[test]
22071 fn the_set_group_answers_the_same_however_many_stripes_there_are() {
22072 const UNORDERED: [&str; 4] = ["SMEMBERS", "SINTER", "SUNION", "SDIFF"];
22073 let script: &[&[&[u8]]] = &[
22074 &[b"SADD", b"s1", b"a", b"b", b"c"],
22075 &[b"SADD", b"s1", b"a"],
22076 &[b"SADD", b"s2", b"b", b"c", b"d"],
22077 &[b"SADD", b"ints", b"1", b"2", b"3"],
22078 &[b"SCARD", b"s1"],
22079 &[b"SISMEMBER", b"s1", b"a"],
22080 &[b"SISMEMBER", b"s1", b"z"],
22081 &[b"SMISMEMBER", b"s1", b"a", b"z", b"c"],
22082 &[b"SMEMBERS", b"s1"],
22083 &[b"SREM", b"s1", b"c"],
22084 &[b"SADD", b"s1", b"c"],
22085 &[b"SSCAN", b"s1", b"0"],
22086 &[b"SSCAN", b"s1", b"0", b"COUNT", b"100", b"MATCH", b"a*"],
22087 &[b"SADD", b"one", b"m"],
22090 &[b"SRANDMEMBER", b"one"],
22091 &[b"SRANDMEMBER", b"one", b"-3"],
22092 &[b"SRANDMEMBER", b"gone"],
22093 &[b"SPOP", b"one"],
22094 &[b"SPOP", b"one"],
22095 &[b"SPOP", b"gone", b"2"],
22096 &[b"SMOVE", b"s1", b"s2", b"a"],
22098 &[b"SMOVE", b"s1", b"s2", b"zzz"],
22099 &[b"SMOVE", b"gone", b"s2", b"a"],
22100 &[b"SMEMBERS", b"s1"],
22101 &[b"SMEMBERS", b"s2"],
22102 &[b"SINTER", b"s1", b"s2"],
22104 &[b"SUNION", b"s1", b"s2"],
22105 &[b"SDIFF", b"s2", b"s1"],
22106 &[b"SINTER", b"s1", b"gone"],
22107 &[b"SUNION", b"s1", b"gone"],
22108 &[b"SDIFF", b"gone", b"s1"],
22109 &[b"SINTER", b"ints", b"s1"],
22110 &[b"SINTERCARD", b"2", b"s1", b"s2"],
22111 &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"1"],
22112 &[b"SUNIONCARD", b"2", b"s1", b"s2"],
22113 &[b"SDIFFCARD", b"2", b"s2", b"s1"],
22114 &[b"SINTERSTORE", b"d1", b"s1", b"s2"],
22115 &[b"SMEMBERS", b"d1"],
22116 &[b"SUNIONSTORE", b"d2", b"s1", b"s2"],
22117 &[b"SCARD", b"d2"],
22118 &[b"SDIFFSTORE", b"d3", b"s2", b"s1"],
22119 &[b"SCARD", b"d3"],
22120 &[b"SINTERSTORE", b"d4", b"s1", b"gone"],
22123 &[b"EXISTS", b"d4"],
22124 &[b"SUNIONSTORE", b"s2", b"s1", b"s2"],
22126 &[b"SCARD", b"s2"],
22127 &[b"SET", b"str", b"v"],
22129 &[b"SADD", b"str", b"a"],
22130 &[b"SINTER", b"s1", b"str"],
22131 &[b"SINTERSTORE", b"d5", b"s1", b"str"],
22132 &[b"EXISTS", b"d5"],
22133 &[b"SMOVE", b"str", b"s2", b"a"],
22134 &[b"SMOVE", b"s1", b"str", b"b"],
22135 &[b"SMOVE", b"gone", b"str", b"b"],
22136 &[b"SINTERCARD", b"0", b"s1"],
22137 &[b"SINTERCARD", b"3", b"s1", b"s2"],
22138 &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"-1"],
22139 &[b"SPOP", b"s1", b"-1"],
22140 ];
22141
22142 let mut one = Fixture::new();
22143 let mut many = Fixture::striped(8);
22144 for parts in script {
22145 let a = one.run(parts);
22146 let b = many.run(parts);
22147 let name = String::from_utf8_lossy(parts[0]).to_uppercase();
22148 if UNORDERED.contains(&name.as_str()) && a.starts_with(['*', '~']) {
22149 assert_eq!(sorted(&a), sorted(&b), "{name}");
22150 } else {
22151 assert_eq!(a, b, "{name}");
22152 }
22153 }
22154 }
22155
22156 #[test]
22158 fn a_set_operation_across_stripes_reads_every_set() {
22159 let mut f = Fixture::striped(8);
22160 let second = apart(&mut f, "s1");
22161 let third = apart(&mut f, &second);
22162 let (s1, s2, s3) = (b"s1".as_slice(), second.as_bytes(), third.as_bytes());
22163
22164 f.run(&[b"SADD", s1, b"a", b"b", b"c"]);
22165 f.run(&[b"SADD", s2, b"b", b"c", b"d"]);
22166 assert_eq!(sorted(&f.run(&[b"SINTER", s1, s2])), ["b", "c"]);
22167 assert_eq!(
22168 sorted(&f.run(&[b"SUNION", s1, s2])),
22169 ["a", "b", "c", "d"],
22170 "a union of two stripes is both of them"
22171 );
22172 assert_eq!(sorted(&f.run(&[b"SDIFF", s1, s2])), ["a"]);
22173 assert_eq!(f.run(&[b"SINTERCARD", b"2", s1, s2]), ":2\r\n");
22174 assert_eq!(f.run(&[b"SUNIONCARD", b"2", s1, s2]), ":4\r\n");
22175 assert_eq!(f.run(&[b"SDIFFCARD", b"2", s1, s2]), ":1\r\n");
22176
22177 assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, s2]), ":2\r\n");
22179 assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["b", "c"]);
22180 assert_eq!(f.run(&[b"SUNIONSTORE", s2, s1, s2]), ":4\r\n");
22181 assert_eq!(sorted(&f.run(&[b"SMEMBERS", s2])), ["a", "b", "c", "d"]);
22182 assert_eq!(f.run(&[b"SDIFFSTORE", s3, s2, s1]), ":1\r\n");
22183 assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["d"]);
22184
22185 assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, b"gone"]), ":0\r\n");
22188 assert_eq!(f.run(&[b"EXISTS", s3]), ":0\r\n");
22189 f.run(&[b"SET", s3, b"v"]);
22190 assert_eq!(
22191 f.run(&[b"SINTER", s1, s3]),
22192 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22193 );
22194 assert_eq!(f.run(&[b"GET", s3]), "$1\r\nv\r\n", "and left it alone");
22195 }
22196
22197 #[test]
22199 fn a_move_across_stripes_takes_the_member_with_it() {
22200 let mut f = Fixture::striped(8);
22201 let other = apart(&mut f, "src");
22202 let (src, dst) = (b"src".as_slice(), other.as_bytes());
22203
22204 f.run(&[b"SADD", src, b"a", b"b"]);
22205 f.run(&[b"SADD", dst, b"c"]);
22206 assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":1\r\n");
22207 assert_eq!(sorted(&f.run(&[b"SMEMBERS", src])), ["b"]);
22208 assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["a", "c"]);
22209 assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":0\r\n", "it has gone");
22210
22211 f.run(&[b"DEL", dst]);
22214 assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":1\r\n");
22215 assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n", "the source is empty");
22216 assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["b"]);
22217
22218 f.run(&[b"SET", dst, b"v"]);
22222 assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":0\r\n");
22223 f.run(&[b"SADD", src, b"b"]);
22224 assert_eq!(
22225 f.run(&[b"SMOVE", src, dst, b"b"]),
22226 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22227 );
22228 }
22229
22230 #[test]
22232 fn a_pfcount_and_a_pfmerge_reach_across_stripes() {
22233 let mut f = Fixture::striped(8);
22234 let other = apart(&mut f, "src");
22235 let (src, far) = (b"src".as_slice(), other.as_bytes());
22236
22237 for i in 0..150 {
22238 let ele = format!("e:{i}");
22239 f.run(&[b"PFADD", src, ele.as_bytes()]);
22240 }
22241 for i in 150..200 {
22242 let ele = format!("e:{i}");
22243 f.run(&[b"PFADD", far, ele.as_bytes()]);
22244 }
22245 assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n");
22248 assert_eq!(f.run(&[b"PFCOUNT", far]), ":49\r\n");
22249 assert_eq!(f.run(&[b"PFCOUNT", src, far]), ":199\r\n");
22250
22251 let dest = apart(&mut f, &other);
22254 assert_eq!(f.run(&[b"PFMERGE", dest.as_bytes(), src, far]), "+OK\r\n");
22255 assert_eq!(f.run(&[b"PFCOUNT", dest.as_bytes()]), ":199\r\n");
22256 assert_eq!(f.run(&[b"PFMERGE", far, src]), "+OK\r\n");
22257 assert_eq!(f.run(&[b"PFCOUNT", far]), ":199\r\n", "and kept its own");
22258 assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n", "and left the source");
22259 }
22260
22261 #[test]
22268 fn the_sorted_set_group_answers_the_same_however_many_stripes_there_are() {
22269 let script: &[&[&[u8]]] = &[
22270 &[b"ZADD", b"z1", b"1", b"a", b"2", b"b", b"3", b"c"],
22271 &[b"ZADD", b"z1", b"NX", b"9", b"a"],
22272 &[b"ZADD", b"z1", b"XX", b"CH", b"5", b"a"],
22273 &[b"ZADD", b"z1", b"GT", b"CH", b"1", b"a"],
22274 &[b"ZADD", b"z1", b"INCR", b"2", b"a"],
22275 &[b"ZINCRBY", b"z1", b"1.5", b"b"],
22276 &[b"ZADD", b"z2", b"1", b"b", b"2", b"c", b"3", b"d"],
22277 &[b"ZADD", b"lex", b"0", b"a", b"0", b"b", b"0", b"c"],
22278 &[b"ZADD", b"one", b"1", b"m"],
22279 &[b"ZCARD", b"z1"],
22280 &[b"ZCARD", b"gone"],
22281 &[b"ZSCORE", b"z1", b"a"],
22282 &[b"ZSCORE", b"z1", b"zz"],
22283 &[b"ZMSCORE", b"z1", b"a", b"zz", b"c"],
22284 &[b"ZRANK", b"z1", b"c"],
22285 &[b"ZRANK", b"z1", b"c", b"WITHSCORE"],
22286 &[b"ZREVRANK", b"z1", b"c"],
22287 &[b"ZRANK", b"z1", b"gone"],
22288 &[b"ZCOUNT", b"z1", b"-inf", b"+inf"],
22289 &[b"ZCOUNT", b"z1", b"(1", b"3"],
22290 &[b"ZLEXCOUNT", b"lex", b"-", b"+"],
22291 &[b"ZRANGE", b"z1", b"0", b"-1"],
22293 &[b"ZRANGE", b"z1", b"0", b"-1", b"WITHSCORES"],
22294 &[b"ZRANGE", b"z1", b"1", b"9", b"BYSCORE"],
22295 &[b"ZRANGE", b"z1", b"9", b"1", b"BYSCORE", b"REV"],
22296 &[b"ZRANGE", b"lex", b"[a", b"(c", b"BYLEX"],
22297 &[b"ZREVRANGE", b"z1", b"0", b"-1"],
22298 &[
22299 b"ZRANGEBYSCORE",
22300 b"z1",
22301 b"-inf",
22302 b"+inf",
22303 b"LIMIT",
22304 b"1",
22305 b"1",
22306 ],
22307 &[b"ZREVRANGEBYLEX", b"lex", b"+", b"-"],
22308 &[b"ZSCAN", b"z1", b"0"],
22309 &[b"ZSCAN", b"z1", b"0", b"MATCH", b"a*", b"COUNT", b"100"],
22310 &[b"ZRANDMEMBER", b"one"],
22313 &[b"ZRANDMEMBER", b"one", b"-3", b"WITHSCORES"],
22314 &[b"ZRANDMEMBER", b"gone"],
22315 &[b"ZRANGESTORE", b"d0", b"z1", b"0", b"1"],
22317 &[b"ZRANGE", b"d0", b"0", b"-1", b"WITHSCORES"],
22318 &[b"ZRANGESTORE", b"d0", b"z1", b"5", b"1"],
22319 &[b"EXISTS", b"d0"],
22320 &[b"ZUNION", b"2", b"z1", b"z2"],
22322 &[b"ZUNION", b"2", b"z1", b"z2", b"WITHSCORES"],
22323 &[
22324 b"ZUNION",
22325 b"2",
22326 b"z1",
22327 b"z2",
22328 b"WEIGHTS",
22329 b"2",
22330 b"3",
22331 b"AGGREGATE",
22332 b"MAX",
22333 b"WITHSCORES",
22334 ],
22335 &[b"ZINTER", b"2", b"z1", b"z2", b"WITHSCORES"],
22336 &[b"ZDIFF", b"2", b"z1", b"z2", b"WITHSCORES"],
22337 &[b"ZDIFF", b"2", b"gone", b"z1"],
22338 &[b"ZINTERCARD", b"2", b"z1", b"z2"],
22339 &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"1"],
22340 &[b"ZUNIONSTORE", b"d1", b"2", b"z1", b"z2"],
22341 &[b"ZRANGE", b"d1", b"0", b"-1", b"WITHSCORES"],
22342 &[
22343 b"ZINTERSTORE",
22344 b"d2",
22345 b"2",
22346 b"z1",
22347 b"z2",
22348 b"AGGREGATE",
22349 b"MIN",
22350 ],
22351 &[b"ZRANGE", b"d2", b"0", b"-1", b"WITHSCORES"],
22352 &[b"ZDIFFSTORE", b"d3", b"2", b"z1", b"z2"],
22353 &[b"ZCARD", b"d3"],
22354 &[b"ZINTERSTORE", b"d4", b"2", b"z1", b"gone"],
22357 &[b"EXISTS", b"d4"],
22358 &[b"SADD", b"plain", b"a", b"x"],
22361 &[b"ZUNIONSTORE", b"d5", b"2", b"z1", b"plain"],
22362 &[b"ZRANGE", b"d5", b"0", b"-1", b"WITHSCORES"],
22363 &[b"ZUNIONSTORE", b"z2", b"2", b"z1", b"z2"],
22365 &[b"ZRANGE", b"z2", b"0", b"-1", b"WITHSCORES"],
22366 &[b"ZREM", b"d5", b"x", b"nothere"],
22368 &[b"ZREMRANGEBYRANK", b"d5", b"0", b"0"],
22369 &[b"ZREMRANGEBYSCORE", b"d1", b"-inf", b"1"],
22370 &[b"ZREMRANGEBYLEX", b"lex", b"[a", b"[a"],
22371 &[b"ZPOPMIN", b"z1"],
22372 &[b"ZPOPMAX", b"z1", b"2"],
22373 &[b"ZPOPMIN", b"gone"],
22374 &[b"ZMPOP", b"2", b"gone", b"z2", b"MIN"],
22375 &[b"ZMPOP", b"2", b"gone", b"nothere", b"MAX", b"COUNT", b"2"],
22376 &[b"SET", b"str", b"v"],
22378 &[b"ZADD", b"str", b"1", b"a"],
22379 &[b"ZSCORE", b"str", b"a"],
22380 &[b"ZADD", b"z1", b"nan", b"a"],
22381 &[b"ZUNION", b"2", b"z1", b"str"],
22382 &[b"ZUNIONSTORE", b"d6", b"2", b"z1", b"str"],
22383 &[b"EXISTS", b"d6"],
22384 &[b"ZINTERCARD", b"0", b"z1"],
22385 &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"-1"],
22386 &[b"ZRANGESTORE", b"d7", b"str", b"0", b"-1"],
22387 &[b"ZMPOP", b"1", b"str", b"MIN"],
22388 &[b"ZPOPMIN", b"z1", b"-1"],
22389 ];
22390
22391 let mut one = Fixture::new();
22392 let mut many = Fixture::striped(8);
22393 for parts in script {
22394 let a = one.run(parts);
22395 let b = many.run(parts);
22396 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22397 }
22398 }
22399
22400 #[test]
22402 fn a_sorted_set_operation_across_stripes_reads_every_input() {
22403 let mut f = Fixture::striped(8);
22404 let second = apart(&mut f, "z1");
22405 let third = apart(&mut f, &second);
22406 let (z1, z2, z3) = (b"z1".as_slice(), second.as_bytes(), third.as_bytes());
22407
22408 f.run(&[b"ZADD", z1, b"1", b"a", b"2", b"b"]);
22409 f.run(&[b"ZADD", z2, b"3", b"b", b"4", b"c"]);
22410 assert_eq!(
22413 f.run(&[b"ZUNION", b"2", z1, z2]),
22414 "*3\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n"
22415 );
22416 assert_eq!(f.run(&[b"ZINTER", b"2", z1, z2]), "*1\r\n$1\r\nb\r\n");
22417 assert_eq!(f.run(&[b"ZDIFF", b"2", z1, z2]), "*1\r\n$1\r\na\r\n");
22418 assert_eq!(f.run(&[b"ZINTERCARD", b"2", z1, z2]), ":1\r\n");
22419 assert_eq!(
22420 f.run(&[b"ZINTERCARD", b"2", z1, z2, b"LIMIT", b"1"]),
22421 ":1\r\n"
22422 );
22423
22424 assert_eq!(f.run(&[b"ZUNIONSTORE", z3, b"2", z1, z2]), ":3\r\n");
22427 assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n5\r\n");
22428 assert_eq!(
22429 f.run(&[
22430 b"ZUNIONSTORE",
22431 z3,
22432 b"2",
22433 z1,
22434 z2,
22435 b"WEIGHTS",
22436 b"2",
22437 b"3",
22438 b"AGGREGATE",
22439 b"MAX"
22440 ]),
22441 ":3\r\n"
22442 );
22443 assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n9\r\n");
22444 assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, z2]), ":1\r\n");
22445 assert_eq!(f.run(&[b"ZCARD", z3]), ":1\r\n");
22446 assert_eq!(f.run(&[b"ZDIFFSTORE", z3, b"2", z2, z1]), ":1\r\n");
22447 assert_eq!(f.run(&[b"ZSCORE", z3, b"c"]), "$1\r\n4\r\n");
22448
22449 let popped = format!(
22452 "*2\r\n${}\r\n{second}\r\n*1\r\n*2\r\n$1\r\nb\r\n$1\r\n3\r\n",
22453 second.len()
22454 );
22455 assert_eq!(f.run(&[b"ZMPOP", b"3", b"gone", z2, z1, b"MIN"]), popped);
22456 f.run(&[b"ZADD", z2, b"3", b"b"]);
22457
22458 assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, b"gone"]), ":0\r\n");
22461 assert_eq!(f.run(&[b"EXISTS", z3]), ":0\r\n");
22462 f.run(&[b"SET", z3, b"v"]);
22463 assert_eq!(
22464 f.run(&[b"ZUNION", b"2", z1, z3]),
22465 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22466 );
22467 assert_eq!(f.run(&[b"GET", z3]), "$1\r\nv\r\n", "and left it alone");
22468
22469 assert_eq!(f.run(&[b"ZUNIONSTORE", z2, b"2", z1, z2]), ":3\r\n");
22473 assert_eq!(f.run(&[b"ZSCORE", z2, b"b"]), "$1\r\n5\r\n");
22474 assert_eq!(f.run(&[b"ZCARD", z2]), ":3\r\n");
22475 }
22476
22477 #[test]
22479 fn a_range_store_across_stripes_copies_the_window() {
22480 let mut f = Fixture::striped(8);
22481 let other = apart(&mut f, "src");
22482 let third = apart(&mut f, &other);
22483 let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
22484
22485 f.run(&[b"ZADD", src, b"1", b"a", b"2", b"b", b"3", b"c"]);
22486 assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"0", b"1"]), ":2\r\n");
22487 assert_eq!(
22488 f.run(&[b"ZRANGE", dst, b"0", b"-1", b"WITHSCORES"]),
22489 "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
22490 );
22491 assert_eq!(f.run(&[b"ZCARD", src]), ":3\r\n", "the source kept its own");
22492
22493 assert_eq!(
22496 f.run(&[
22497 b"ZRANGESTORE",
22498 dst,
22499 src,
22500 b"+inf",
22501 b"-inf",
22502 b"BYSCORE",
22503 b"REV",
22504 b"LIMIT",
22505 b"0",
22506 b"2"
22507 ]),
22508 ":2\r\n"
22509 );
22510 assert_eq!(
22511 f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
22512 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
22513 );
22514
22515 assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"5", b"1"]), ":0\r\n");
22518 assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
22519 f.run(&[b"ZRANGESTORE", dst, src, b"0", b"-1"]);
22520 f.run(&[b"SET", plain, b"v"]);
22521 assert_eq!(
22522 f.run(&[b"ZRANGESTORE", dst, plain, b"0", b"-1"]),
22523 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22524 );
22525 assert_eq!(
22526 f.run(&[b"ZCARD", dst]),
22527 ":3\r\n",
22528 "and left the destination"
22529 );
22530 }
22531
22532 #[test]
22539 fn the_list_group_answers_the_same_however_many_stripes_there_are() {
22540 let script: &[&[&[u8]]] = &[
22541 &[b"RPUSH", b"l1", b"a", b"b", b"c"],
22542 &[b"LPUSH", b"l1", b"z"],
22543 &[b"RPUSHX", b"l1", b"d"],
22544 &[b"LPUSHX", b"gone", b"x"],
22545 &[b"RPUSHX", b"gone", b"x"],
22546 &[b"LLEN", b"l1"],
22547 &[b"LLEN", b"gone"],
22548 &[b"LRANGE", b"l1", b"0", b"-1"],
22549 &[b"LRANGE", b"l1", b"1", b"2"],
22550 &[b"LRANGE", b"l1", b"5", b"9"],
22551 &[b"LINDEX", b"l1", b"0"],
22552 &[b"LINDEX", b"l1", b"-1"],
22553 &[b"LINDEX", b"l1", b"99"],
22554 &[b"LSET", b"l1", b"0", b"y"],
22555 &[b"LINSERT", b"l1", b"BEFORE", b"b", b"aa"],
22556 &[b"LINSERT", b"l1", b"AFTER", b"nothere", b"x"],
22557 &[b"LPOS", b"l1", b"b"],
22558 &[b"LPOS", b"l1", b"b", b"COUNT", b"0"],
22559 &[b"LPOS", b"l1", b"nothere"],
22560 &[b"LPOS", b"l1", b"b", b"RANK", b"-1", b"MAXLEN", b"2"],
22561 &[b"LREM", b"l1", b"1", b"aa"],
22562 &[b"LTRIM", b"l1", b"0", b"3"],
22563 &[b"LRANGE", b"l1", b"0", b"-1"],
22564 &[b"LPOP", b"l1"],
22565 &[b"RPOP", b"l1"],
22566 &[b"LPOP", b"l1", b"2"],
22567 &[b"LPOP", b"gone"],
22568 &[b"LPOP", b"gone", b"2"],
22569 &[b"EXISTS", b"l1"],
22570 &[b"RPUSH", b"src", b"a", b"b", b"c", b"d"],
22573 &[b"LMOVE", b"src", b"dst", b"LEFT", b"RIGHT"],
22574 &[b"RPOPLPUSH", b"src", b"dst"],
22575 &[b"LRANGE", b"dst", b"0", b"-1"],
22576 &[b"LMOVE", b"gone", b"dst", b"LEFT", b"RIGHT"],
22577 &[b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT"],
22578 &[
22579 b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK",
22580 ],
22581 &[
22582 b"LMOVEM", b"dst", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"OBO",
22583 ],
22584 &[b"LRANGE", b"dst", b"0", b"-1"],
22585 &[
22586 b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"EXACTLY", b"9", b"BULK",
22587 ],
22588 &[b"LMPOP", b"2", b"gone", b"dst", b"LEFT"],
22589 &[b"LMPOP", b"2", b"gone", b"dst", b"RIGHT", b"COUNT", b"2"],
22590 &[b"LMPOP", b"1", b"gone", b"LEFT"],
22591 &[b"RPUSH", b"q", b"a", b"b", b"c"],
22594 &[b"BLPOP", b"gone", b"q", b"0"],
22595 &[b"BRPOP", b"q", b"0"],
22596 &[b"BLMPOP", b"0", b"2", b"gone", b"q", b"LEFT"],
22597 &[b"RPUSH", b"q", b"x", b"y", b"z"],
22598 &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
22599 &[b"BRPOPLPUSH", b"q", b"dst", b"0"],
22600 &[b"BLMOVEM", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
22601 &[b"BLPOP", b"q", b"0"],
22602 &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
22603 &[b"SET", b"plain", b"v"],
22605 &[b"LPUSH", b"plain", b"a"],
22606 &[b"LLEN", b"plain"],
22607 &[b"LMOVE", b"dst", b"plain", b"LEFT", b"RIGHT"],
22608 &[b"LRANGE", b"dst", b"0", b"-1"],
22609 &[b"LMOVEM", b"dst", b"plain", b"LEFT", b"RIGHT"],
22610 &[b"LSET", b"gone", b"0", b"v"],
22611 &[b"LSET", b"dst", b"99", b"v"],
22612 &[b"LPOP", b"dst", b"-1"],
22613 &[b"LMPOP", b"0", b"dst", b"LEFT"],
22614 &[b"LPOS", b"dst", b"a", b"RANK", b"0"],
22615 ];
22616
22617 let mut one = Fixture::new();
22618 let mut many = Fixture::striped(8);
22619 for parts in script {
22620 let a = one.run(parts);
22621 let b = many.run(parts);
22622 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22623 }
22624 }
22625
22626 #[test]
22628 fn a_list_move_across_stripes_takes_the_elements_with_it() {
22629 let mut f = Fixture::striped(8);
22630 let other = apart(&mut f, "src");
22631 let third = apart(&mut f, &other);
22632 let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
22633
22634 f.run(&[b"RPUSH", src, b"a", b"b", b"c", b"d"]);
22635 assert_eq!(
22636 f.run(&[b"LMOVE", src, dst, b"LEFT", b"RIGHT"]),
22637 "$1\r\na\r\n"
22638 );
22639 assert_eq!(f.run(&[b"RPOPLPUSH", src, dst]), "$1\r\nd\r\n");
22640 assert_eq!(
22641 f.run(&[b"LRANGE", dst, b"0", b"-1"]),
22642 "*2\r\n$1\r\nd\r\n$1\r\na\r\n",
22643 "one went on each end of the destination"
22644 );
22645 assert_eq!(
22646 f.run(&[b"LRANGE", src, b"0", b"-1"]),
22647 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
22648 );
22649
22650 assert_eq!(
22652 f.run(&[
22653 b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
22654 ]),
22655 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
22656 );
22657 assert_eq!(
22658 f.run(&[b"LRANGE", dst, b"0", b"-1"]),
22659 "*4\r\n$1\r\nd\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
22660 );
22661 assert_eq!(
22662 f.run(&[b"EXISTS", src]),
22663 ":0\r\n",
22664 "and the source is gone with its last element"
22665 );
22666
22667 f.run(&[b"RPUSH", src, b"e", b"f"]);
22670 assert_eq!(
22671 f.run(&[
22672 b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"EXACTLY", b"3", b"BULK"
22673 ]),
22674 "*-1\r\n"
22675 );
22676 assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n", "and took none of them");
22677 assert_eq!(
22678 f.run(&[b"LMOVE", b"gone", dst, b"LEFT", b"RIGHT"]),
22679 "$-1\r\n"
22680 );
22681 assert_eq!(
22682 f.run(&[b"LMOVEM", b"gone", dst, b"LEFT", b"RIGHT"]),
22683 "*-1\r\n"
22684 );
22685
22686 f.run(&[b"SET", plain, b"v"]);
22690 assert_eq!(
22691 f.run(&[b"LMOVE", src, plain, b"LEFT", b"RIGHT"]),
22692 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22693 );
22694 assert_eq!(
22695 f.run(&[b"LLEN", src]),
22696 ":2\r\n",
22697 "and left the source alone"
22698 );
22699 assert_eq!(
22700 f.run(&[b"LMOVEM", src, plain, b"LEFT", b"RIGHT"]),
22701 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22702 );
22703 assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n");
22704 }
22705
22706 #[test]
22712 fn a_parked_client_is_served_from_the_stripe_its_key_is_on() {
22713 let mut f = Fixture::striped(8);
22714 let other = apart(&mut f, "q");
22715 let (q, far) = (b"q".as_slice(), other.as_bytes());
22716
22717 assert_eq!(f.flow(&[b"BLPOP", q, far, b"0"]).0, Flow::Block);
22718 assert_eq!(f.server.parked(), 1);
22719 f.run(&[b"RPUSH", far, b"v"]);
22720 let mut out = Out::new(Proto::Resp2);
22721 assert!(f.server.serve_waiter(7, 0, &mut out));
22722 let want = format!("*2\r\n${}\r\n{other}\r\n$1\r\nv\r\n", other.len());
22723 assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
22724 assert_eq!(
22725 f.run(&[b"EXISTS", far]),
22726 ":0\r\n",
22727 "and it took the element with it"
22728 );
22729
22730 f.server.forget_waiters(7);
22733 assert_eq!(
22734 f.flow(&[b"BLMOVE", q, far, b"LEFT", b"RIGHT", b"0"]).0,
22735 Flow::Block
22736 );
22737 f.run(&[b"RPUSH", q, b"w"]);
22738 let mut out = Out::new(Proto::Resp2);
22739 assert!(f.server.serve_waiter(7, 0, &mut out));
22740 assert_eq!(
22741 core::str::from_utf8(out.as_slice()).expect("ascii"),
22742 "$1\r\nw\r\n"
22743 );
22744 assert_eq!(f.run(&[b"LRANGE", far, b"0", b"-1"]), "*1\r\n$1\r\nw\r\n");
22745 }
22746
22747 #[test]
22753 fn the_stream_group_answers_the_same_however_many_stripes_there_are() {
22754 let script: &[&[&[u8]]] = &[
22755 &[b"XADD", b"s", b"1-1", b"a", b"1"],
22756 &[b"XADD", b"s", b"2-1", b"b", b"2", b"c", b"3"],
22757 &[b"XADD", b"s", b"3-1", b"d", b"4"],
22758 &[b"XADD", b"s", b"1-1", b"e", b"5"],
22759 &[b"XADD", b"nomk", b"NOMKSTREAM", b"1-1", b"a", b"1"],
22760 &[b"XLEN", b"s"],
22761 &[b"XLEN", b"gone"],
22762 &[b"XRANGE", b"s", b"-", b"+"],
22763 &[b"XRANGE", b"s", b"2", b"+", b"COUNT", b"1"],
22764 &[b"XRANGE", b"gone", b"-", b"+", b"COUNT", b"0"],
22765 &[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"],
22766 &[b"XREVRANGE", b"s", b"+", b"-"],
22767 &[b"XREAD", b"COUNT", b"2", b"STREAMS", b"s", b"0"],
22768 &[b"XREAD", b"STREAMS", b"s", b"gone", b"0", b"0"],
22769 &[b"XREAD", b"STREAMS", b"s", b"$"],
22770 &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
22772 &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
22773 &[b"XGROUP", b"CREATE", b"gone", b"g", b"0"],
22774 &[b"XGROUP", b"CREATE", b"made", b"g", b"$", b"MKSTREAM"],
22775 &[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"idle"],
22776 &[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"],
22777 &[
22778 b"XREADGROUP",
22779 b"GROUP",
22780 b"g",
22781 b"c1",
22782 b"COUNT",
22783 b"1",
22784 b"STREAMS",
22785 b"s",
22786 b"0",
22787 ],
22788 &[
22789 b"XREADGROUP",
22790 b"GROUP",
22791 b"nope",
22792 b"c1",
22793 b"STREAMS",
22794 b"s",
22795 b">",
22796 ],
22797 &[b"XPENDING", b"s", b"g"],
22798 &[b"XPENDING", b"s", b"g", b"-", b"+", b"10"],
22799 &[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"],
22800 &[b"XPENDING", b"s", b"nope"],
22801 &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1"],
22802 &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1", b"JUSTID"],
22803 &[b"XAUTOCLAIM", b"s", b"g", b"c3", b"0", b"0"],
22804 &[b"XACK", b"s", b"g", b"1-1"],
22805 &[b"XACK", b"s", b"g", b"1-1"],
22806 &[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"],
22807 &[b"XPENDING", b"s", b"g"],
22808 &[b"XINFO", b"STREAM", b"s"],
22809 &[b"XINFO", b"GROUPS", b"s"],
22810 &[b"XINFO", b"CONSUMERS", b"s", b"g"],
22811 &[b"XINFO", b"STREAM", b"gone"],
22812 &[b"XDEL", b"s", b"3-1"],
22814 &[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"],
22815 &[b"XACKDEL", b"s", b"g", b"KEEPREF", b"IDS", b"1", b"1-1"],
22816 &[b"XADD", b"s", b"9-1", b"z", b"9"],
22817 &[b"XTRIM", b"s", b"MAXLEN", b"1"],
22818 &[b"XTRIM", b"s", b"MINID", b"9"],
22819 &[b"XSETID", b"s", b"99-1"],
22820 &[b"XSETID", b"s", b"1-1"],
22821 &[b"XLEN", b"s"],
22822 &[b"XGROUP", b"SETID", b"s", b"g", b"0"],
22823 &[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c1"],
22824 &[b"XGROUP", b"DESTROY", b"s", b"g"],
22825 &[b"XGROUP", b"DESTROY", b"s", b"g"],
22826 &[b"SET", b"plain", b"v"],
22828 &[b"XADD", b"plain", b"1-1", b"a", b"1"],
22829 &[b"XLEN", b"plain"],
22830 &[b"XREAD", b"STREAMS", b"plain", b"0"],
22831 &[b"XRANGE", b"s", b"bogus", b"+"],
22832 &[b"XADD", b"s", b"1-1", b"a"],
22833 &[b"XREAD", b"STREAMS", b"s", b"gone", b"0"],
22834 &[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"],
22835 ];
22836
22837 let mut one = Fixture::new();
22838 let mut many = Fixture::striped(8);
22839 for parts in script {
22840 let a = one.run(parts);
22841 let b = many.run(parts);
22842 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22843 }
22844 }
22845
22846 #[test]
22852 fn a_stream_read_across_stripes_reads_every_key() {
22853 let mut f = Fixture::striped(8);
22854 let other = apart(&mut f, "s1");
22855 let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
22856
22857 f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
22858 f.run(&[b"XADD", s2, b"2-1", b"b", b"2"]);
22859 let got = f.run(&[b"XREAD", b"STREAMS", s1, s2, b"0", b"0"]);
22860 assert!(got.starts_with("*2\r\n"), "both streams answered: {got}");
22861 assert!(got.contains("1-1"), "the first one is in there: {got}");
22862 assert!(got.contains("2-1"), "and so is the second: {got}");
22863
22864 f.run(&[b"XGROUP", b"CREATE", s1, b"g", b"0"]);
22867 let got = f.run(&[
22868 b"XREADGROUP",
22869 b"GROUP",
22870 b"g",
22871 b"c",
22872 b"STREAMS",
22873 s1,
22874 s2,
22875 b">",
22876 b">",
22877 ]);
22878 assert!(got.starts_with("-NOGROUP"), "{got}");
22879 assert_eq!(
22880 f.run(&[b"XPENDING", s1, b"g"]),
22881 "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n",
22882 "and read nothing from the key that did have the group"
22883 );
22884
22885 f.run(&[b"XGROUP", b"CREATE", s2, b"g", b"0"]);
22886 let got = f.run(&[
22887 b"XREADGROUP",
22888 b"GROUP",
22889 b"g",
22890 b"c",
22891 b"STREAMS",
22892 s1,
22893 s2,
22894 b">",
22895 b">",
22896 ]);
22897 assert!(got.starts_with("*2\r\n"), "now both are read: {got}");
22898 }
22899
22900 #[test]
22902 fn a_parked_stream_reader_is_served_from_the_stripe_its_key_is_on() {
22903 let mut f = Fixture::striped(8);
22904 let other = apart(&mut f, "s1");
22905 let (s1, far) = (b"s1".as_slice(), other.as_bytes());
22906 f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
22907 f.run(&[b"XADD", far, b"1-1", b"a", b"1"]);
22908
22909 assert_eq!(
22910 f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", s1, far, b"$", b"$"])
22911 .0,
22912 Flow::Block
22913 );
22914 f.run(&[b"XADD", far, b"2-1", b"b", b"2"]);
22915 let mut out = Out::new(Proto::Resp2);
22916 assert!(f.server.serve_waiter(7, 0, &mut out));
22917 let want = format!(
22918 "*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",
22919 other.len()
22920 );
22921 assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
22922 }
22923
22924 #[test]
22926 fn the_json_group_answers_the_same_however_many_stripes_there_are() {
22927 let script: &[&[&[u8]]] = &[
22928 &[
22929 b"JSON.SET",
22930 b"d",
22931 b"$",
22932 br#"{"a":1,"b":[1,2,3],"s":"hi","t":true}"#,
22933 ],
22934 &[b"JSON.SET", b"d", b"$.a", b"2"],
22935 &[b"JSON.SET", b"d", b"$.new", b"9", b"NX"],
22936 &[b"JSON.SET", b"d", b"$.new", b"8", b"NX"],
22937 &[b"JSON.SET", b"d", b"$.nope", b"7", b"XX"],
22938 &[b"JSON.GET", b"d"],
22939 &[b"JSON.GET", b"d", b"$.b"],
22940 &[b"JSON.GET", b"gone", b"$"],
22941 &[b"JSON.TYPE", b"d", b"$.b"],
22942 &[b"JSON.TYPE", b"d", b"$.s"],
22943 &[b"JSON.TOGGLE", b"d", b"$.t"],
22944 &[b"JSON.ARRLEN", b"d", b"$.b"],
22945 &[b"JSON.OBJLEN", b"d", b"$"],
22946 &[b"JSON.OBJKEYS", b"d", b"$"],
22947 &[b"JSON.STRLEN", b"d", b"$.s"],
22948 &[b"JSON.STRAPPEND", b"d", b"$.s", br#""there""#],
22949 &[b"JSON.ARRAPPEND", b"d", b"$.b", b"4"],
22950 &[b"JSON.ARRINSERT", b"d", b"$.b", b"0", b"0"],
22951 &[b"JSON.ARRINDEX", b"d", b"$.b", b"3"],
22952 &[b"JSON.ARRTRIM", b"d", b"$.b", b"1", b"3"],
22953 &[b"JSON.ARRPOP", b"d", b"$.b"],
22954 &[b"JSON.NUMINCRBY", b"d", b"$.a", b"5"],
22955 &[b"JSON.NUMMULTBY", b"d", b"$.a", b"2"],
22956 &[b"JSON.NUMPOWBY", b"d", b"$.a", b"2"],
22957 &[b"JSON.MERGE", b"d", b"$", br#"{"a":null,"m":1}"#],
22958 &[b"JSON.RESP", b"d", b"$.b"],
22959 &[b"JSON.DEBUG", b"MEMORY", b"d"],
22960 &[b"JSON.CLEAR", b"d", b"$.b"],
22961 &[b"JSON.DEL", b"d", b"$.m"],
22962 &[b"JSON.FORGET", b"d", b"$.nothere"],
22963 &[
22965 b"JSON.MSET",
22966 b"m1",
22967 b"$",
22968 b"1",
22969 b"m2",
22970 b"$",
22971 b"2",
22972 b"m3",
22973 b"$",
22974 b"3",
22975 ],
22976 &[b"JSON.MGET", b"m1", b"m2", b"m3", b"gone", b"$"],
22977 &[b"JSON.MSET", b"m1", b"$", b"9", b"m2", b"$.deep", b"9"],
22978 &[b"JSON.GET", b"m1", b"$"],
22979 &[b"JSON.MSET", b"m1", b"$", b"nonsense", b"m2", b"$", b"5"],
22980 &[b"JSON.GET", b"m2", b"$"],
22981 &[b"SET", b"plain", b"v"],
22983 &[b"JSON.GET", b"plain", b"$"],
22984 &[b"JSON.SET", b"plain", b"$", b"1"],
22985 &[b"JSON.MGET", b"m1", b"plain", b"$"],
22986 &[b"JSON.SET", b"d", b"$.b", b"["],
22987 &[b"JSON.DEL", b"plain"],
22988 ];
22989
22990 let mut one = Fixture::new();
22991 let mut many = Fixture::striped(8);
22992 for parts in script {
22993 let a = one.run(parts);
22994 let b = many.run(parts);
22995 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22996 }
22997 }
22998
22999 #[test]
23006 fn a_json_multi_write_across_stripes_reaches_every_key() {
23007 let mut f = Fixture::striped(8);
23008 let second = apart(&mut f, "m1");
23009 let third = apart(&mut f, &second);
23010 let (m1, m2, m3) = (b"m1".as_slice(), second.as_bytes(), third.as_bytes());
23011
23012 assert_eq!(
23013 f.run(&[b"JSON.MSET", m1, b"$", b"1", m2, b"$", b"2", m3, b"$", b"3"]),
23014 "+OK\r\n"
23015 );
23016 assert_eq!(
23017 f.run(&[b"JSON.MGET", m1, m2, m3, b"gone", b"$"]),
23018 "*4\r\n$3\r\n[1]\r\n$3\r\n[2]\r\n$3\r\n[3]\r\n$-1\r\n"
23019 );
23020
23021 assert_eq!(
23024 f.run(&[b"JSON.MSET", m1, b"$", b"9", m2, b"$", b"nonsense"]),
23025 "-this is not the start of a value, at byte 0 of the JSON text\r\n"
23026 );
23027 assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[1]\r\n");
23028
23029 assert_eq!(
23033 f.run(&[
23034 b"JSON.MSET",
23035 m1,
23036 b"$",
23037 b"9",
23038 m2,
23039 b"$.deep",
23040 b"9",
23041 m3,
23042 b"$",
23043 b"7"
23044 ]),
23045 "$-1\r\n"
23046 );
23047 assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[9]\r\n");
23048 assert_eq!(f.run(&[b"JSON.GET", m2, b"$"]), "$3\r\n[2]\r\n");
23049 assert_eq!(f.run(&[b"JSON.GET", m3, b"$"]), "$3\r\n[7]\r\n");
23050 }
23051
23052 #[test]
23054 fn the_geo_group_answers_the_same_however_many_stripes_there_are() {
23055 let script: &[&[&[u8]]] = &[
23056 &[
23057 b"GEOADD",
23058 b"g",
23059 b"13.361389",
23060 b"38.115556",
23061 b"palermo",
23062 b"15.087269",
23063 b"37.502669",
23064 b"catania",
23065 ],
23066 &[
23067 b"GEOADD",
23068 b"g",
23069 b"NX",
23070 b"13.361389",
23071 b"38.115556",
23072 b"palermo",
23073 ],
23074 &[b"GEOADD", b"g", b"XX", b"CH", b"13.4", b"38.1", b"palermo"],
23075 &[b"GEOPOS", b"g", b"palermo", b"nothere"],
23076 &[b"GEOHASH", b"g", b"palermo", b"catania"],
23077 &[b"GEODIST", b"g", b"palermo", b"catania"],
23078 &[b"GEODIST", b"g", b"palermo", b"catania", b"KM"],
23079 &[b"GEODIST", b"g", b"palermo", b"nothere"],
23080 &[
23081 b"GEOSEARCH",
23082 b"g",
23083 b"FROMLONLAT",
23084 b"15",
23085 b"37",
23086 b"BYRADIUS",
23087 b"200",
23088 b"KM",
23089 b"ASC",
23090 b"WITHCOORD",
23091 b"WITHDIST",
23092 b"WITHHASH",
23093 ],
23094 &[
23095 b"GEOSEARCH",
23096 b"g",
23097 b"FROMMEMBER",
23098 b"palermo",
23099 b"BYBOX",
23100 b"400",
23101 b"400",
23102 b"KM",
23103 b"DESC",
23104 ],
23105 &[
23106 b"GEORADIUS",
23107 b"g",
23108 b"15",
23109 b"37",
23110 b"200",
23111 b"KM",
23112 b"COUNT",
23113 b"1",
23114 ],
23115 &[b"GEORADIUSBYMEMBER", b"g", b"palermo", b"200", b"KM"],
23116 &[b"GEORADIUSBYMEMBER_RO", b"g", b"nothere", b"200", b"KM"],
23117 &[
23118 b"GEOSEARCHSTORE",
23119 b"dst",
23120 b"g",
23121 b"FROMLONLAT",
23122 b"15",
23123 b"37",
23124 b"BYRADIUS",
23125 b"200",
23126 b"KM",
23127 ],
23128 &[b"ZRANGE", b"dst", b"0", b"-1"],
23129 &[
23130 b"GEOSEARCHSTORE",
23131 b"dst",
23132 b"g",
23133 b"FROMLONLAT",
23134 b"15",
23135 b"37",
23136 b"BYRADIUS",
23137 b"1",
23138 b"M",
23139 b"STOREDIST",
23140 ],
23141 &[b"EXISTS", b"dst"],
23142 &[
23143 b"GEORADIUS",
23144 b"g",
23145 b"15",
23146 b"37",
23147 b"200",
23148 b"KM",
23149 b"STORE",
23150 b"dst",
23151 ],
23152 &[b"ZCARD", b"dst"],
23153 &[b"GEOADD", b"g", b"181", b"38", b"nowhere"],
23155 &[b"SET", b"plain", b"v"],
23156 &[b"GEOPOS", b"plain", b"a"],
23157 &[b"GEOSEARCH", b"g", b"FROMLONLAT", b"15", b"37"],
23158 &[
23159 b"GEOSEARCHSTORE",
23160 b"dst",
23161 b"g",
23162 b"FROMLONLAT",
23163 b"15",
23164 b"37",
23165 b"BYRADIUS",
23166 b"200",
23167 b"KM",
23168 b"WITHCOORD",
23169 ],
23170 ];
23171
23172 let mut one = Fixture::new();
23173 let mut many = Fixture::striped(8);
23174 for parts in script {
23175 let a = one.run(parts);
23176 let b = many.run(parts);
23177 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23178 }
23179 }
23180
23181 #[test]
23183 fn a_geo_search_store_across_stripes_writes_what_it_found() {
23184 let mut f = Fixture::striped(8);
23185 let other = apart(&mut f, "g");
23186 let third = apart(&mut f, &other);
23187 let (g, dst, plain) = (b"g".as_slice(), other.as_bytes(), third.as_bytes());
23188
23189 f.run(&[
23190 b"GEOADD",
23191 g,
23192 b"13.361389",
23193 b"38.115556",
23194 b"palermo",
23195 b"15.087269",
23196 b"37.502669",
23197 b"catania",
23198 ]);
23199 assert_eq!(
23200 f.run(&[
23201 b"GEOSEARCHSTORE",
23202 dst,
23203 g,
23204 b"FROMLONLAT",
23205 b"15",
23206 b"37",
23207 b"BYRADIUS",
23208 b"200",
23209 b"KM",
23210 b"ASC",
23211 ]),
23212 ":2\r\n"
23213 );
23214 assert_eq!(
23215 f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
23216 "*2\r\n$7\r\npalermo\r\n$7\r\ncatania\r\n",
23217 "the geohash is the score, so the order is not the search order"
23218 );
23219 assert_eq!(f.run(&[b"ZCARD", g]), ":2\r\n", "the source is untouched");
23220
23221 assert_eq!(
23224 f.run(&[
23225 b"GEOSEARCHSTORE",
23226 dst,
23227 g,
23228 b"FROMMEMBER",
23229 b"palermo",
23230 b"BYRADIUS",
23231 b"200",
23232 b"KM",
23233 b"STOREDIST",
23234 ]),
23235 ":2\r\n"
23236 );
23237 assert_eq!(
23238 f.run(&[b"ZSCORE", dst, b"palermo"]),
23239 "$1\r\n0\r\n",
23240 "the centre is nought away from itself"
23241 );
23242
23243 assert_eq!(
23247 f.run(&[
23248 b"GEOSEARCHSTORE",
23249 dst,
23250 g,
23251 b"FROMLONLAT",
23252 b"0",
23253 b"0",
23254 b"BYRADIUS",
23255 b"1",
23256 b"M",
23257 ]),
23258 ":0\r\n"
23259 );
23260 assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
23261 f.run(&[
23262 b"GEOSEARCHSTORE",
23263 dst,
23264 g,
23265 b"FROMLONLAT",
23266 b"15",
23267 b"37",
23268 b"BYRADIUS",
23269 b"200",
23270 b"KM",
23271 ]);
23272 f.run(&[b"SET", plain, b"v"]);
23273 assert_eq!(
23274 f.run(&[
23275 b"GEOSEARCHSTORE",
23276 dst,
23277 plain,
23278 b"FROMLONLAT",
23279 b"15",
23280 b"37",
23281 b"BYRADIUS",
23282 b"200",
23283 b"KM",
23284 ]),
23285 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
23286 );
23287 assert_eq!(
23288 f.run(&[b"ZCARD", dst]),
23289 ":2\r\n",
23290 "and left the destination"
23291 );
23292 }
23293
23294 #[test]
23300 fn the_time_series_group_answers_the_same_however_many_stripes_there_are() {
23301 let script: &[&[&[u8]]] = &[
23302 &[
23303 b"TS.CREATE",
23304 b"ts:a",
23305 b"LABELS",
23306 b"sensor",
23307 b"a",
23308 b"room",
23309 b"1",
23310 ],
23311 &[b"TS.CREATE", b"ts:a"],
23312 &[b"TS.ALTER", b"ts:a", b"RETENTION", b"0"],
23313 &[b"TS.ADD", b"ts:a", b"1000", b"1.5"],
23314 &[
23315 b"TS.ADD", b"ts:b", b"1000", b"2", b"LABELS", b"sensor", b"b", b"room", b"1",
23316 ],
23317 &[
23318 b"TS.MADD", b"ts:a", b"2000", b"2.5", b"ts:b", b"2000", b"3", b"gone", b"1", b"1",
23319 ],
23320 &[b"TS.INCRBY", b"ts:a", b"1", b"TIMESTAMP", b"3000"],
23321 &[b"TS.DECRBY", b"ts:a", b"0.5", b"TIMESTAMP", b"4000"],
23322 &[b"TS.GET", b"ts:a"],
23323 &[b"TS.GET", b"gone"],
23324 &[b"TS.RANGE", b"ts:a", b"-", b"+"],
23325 &[b"TS.RANGE", b"ts:a", b"1000", b"3000", b"COUNT", b"2"],
23326 &[
23327 b"TS.RANGE",
23328 b"ts:a",
23329 b"-",
23330 b"+",
23331 b"AGGREGATION",
23332 b"avg",
23333 b"2000",
23334 ],
23335 &[b"TS.REVRANGE", b"ts:a", b"-", b"+"],
23336 &[b"TS.NRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
23337 &[b"TS.NREVRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
23338 &[b"TS.NRANGE", b"2", b"ts:a", b"gone", b"-", b"+"],
23339 &[b"TS.READ", b"ts:a", b"0"],
23340 &[b"TS.READ", b"ts:a", b"+"],
23341 &[b"TS.QUERYINDEX", b"sensor=a"],
23343 &[b"TS.QUERYINDEX", b"room=1"],
23344 &[b"TS.QUERYINDEX", b"room=9"],
23345 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"],
23346 &[
23347 b"TS.QUERYLABELS",
23348 b"VALUES",
23349 b"sensor",
23350 b"FILTER",
23351 b"room=1",
23352 ],
23353 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=1"],
23354 &[
23355 b"TS.MGET",
23356 b"SELECTED_LABELS",
23357 b"sensor",
23358 b"FILTER",
23359 b"sensor=a",
23360 ],
23361 &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"],
23362 &[
23363 b"TS.MREVRANGE",
23364 b"-",
23365 b"+",
23366 b"WITHLABELS",
23367 b"FILTER",
23368 b"sensor=a",
23369 ],
23370 &[
23371 b"TS.MRANGE",
23372 b"-",
23373 b"+",
23374 b"FILTER",
23375 b"room=1",
23376 b"GROUPBY",
23377 b"room",
23378 b"REDUCE",
23379 b"max",
23380 ],
23381 &[b"TS.INFO", b"ts:a"],
23382 &[
23384 b"TS.CREATERULE",
23385 b"ts:a",
23386 b"ts:down",
23387 b"AGGREGATION",
23388 b"avg",
23389 b"1000",
23390 ],
23391 &[b"TS.CREATE", b"ts:down"],
23392 &[
23393 b"TS.CREATERULE",
23394 b"ts:a",
23395 b"ts:down",
23396 b"AGGREGATION",
23397 b"avg",
23398 b"1000",
23399 ],
23400 &[b"TS.ADD", b"ts:a", b"5000", b"4"],
23401 &[b"TS.ADD", b"ts:a", b"6000", b"5"],
23402 &[b"TS.RANGE", b"ts:down", b"-", b"+"],
23403 &[b"TS.GET", b"ts:down", b"LATEST"],
23404 &[b"TS.INFO", b"ts:down"],
23405 &[b"TS.DEL", b"ts:a", b"5000", b"6000"],
23406 &[b"TS.RANGE", b"ts:down", b"-", b"+"],
23407 &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
23408 &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
23409 &[b"TS.DEL", b"ts:a", b"0", b"1000"],
23410 &[b"SET", b"plain", b"v"],
23412 &[b"TS.ADD", b"plain", b"1", b"1"],
23413 &[b"TS.GET", b"plain"],
23414 &[b"TS.READ", b"plain", b"0"],
23415 &[b"TS.ALTER", b"gone", b"RETENTION", b"0"],
23416 &[b"TS.RANGE", b"gone", b"-", b"+"],
23417 &[b"TS.INFO", b"gone"],
23418 ];
23419
23420 let mut one = Fixture::new();
23421 let mut many = Fixture::striped(8);
23422 for parts in script {
23423 let a = one.run(parts);
23424 let b = many.run(parts);
23425 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23426 }
23427 }
23428
23429 #[test]
23439 fn a_compaction_rule_across_stripes_reaches_both_ends() {
23440 let mut many = Fixture::striped(8);
23441 let other = apart(&mut many, "src");
23442 let (src, dst) = (b"src".as_slice(), other.as_bytes());
23443 let mut one = Fixture::new();
23444 let mut both = |parts: &[&[u8]]| {
23445 let a = one.run(parts);
23446 let b = many.run(parts);
23447 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23448 a
23449 };
23450
23451 both(&[b"TS.CREATE", src]);
23452 both(&[b"TS.CREATE", dst]);
23453 assert_eq!(
23454 both(&[b"TS.CREATERULE", src, dst, b"AGGREGATION", b"avg", b"1000"]),
23455 "+OK\r\n"
23456 );
23457 both(&[b"TS.ADD", src, b"1000", b"1"]);
23458 both(&[b"TS.ADD", src, b"1500", b"3"]);
23459 assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
23462 let open = both(&[b"TS.GET", dst, b"LATEST"]);
23463 assert!(open.contains(":1000"), "the open bucket is folded: {open}");
23464
23465 both(&[b"TS.ADD", src, b"2000", b"5"]);
23468 let got = both(&[b"TS.RANGE", dst, b"-", b"+"]);
23469 assert!(got.starts_with("*1\r\n"), "the bucket was written: {got}");
23470 assert!(got.contains(":1000"), "{got}");
23471
23472 both(&[b"TS.DEL", src, b"1000", b"1999"]);
23474 assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
23475
23476 assert!(
23479 both(&[b"TS.INFO", dst]).contains("src"),
23480 "the source is named"
23481 );
23482 assert_eq!(both(&[b"TS.DELETERULE", src, dst]), "+OK\r\n");
23483 assert_eq!(
23484 both(&[b"TS.DELETERULE", src, dst]),
23485 "-ERR TSDB: compaction rule does not exist\r\n"
23486 );
23487 }
23488
23489 #[test]
23491 fn a_label_query_across_stripes_finds_every_series() {
23492 let names: [&[u8]; 6] = [b"q:1", b"q:2", b"q:3", b"q:4", b"q:5", b"q:6"];
23493 let mut many = Fixture::striped(8);
23494 let mut homes: Vec<usize> = names
23495 .iter()
23496 .map(|name| many.server.striped(0).stripe_of(name))
23497 .collect();
23498 homes.sort_unstable();
23499 homes.dedup();
23500 assert!(homes.len() > 1, "the six keys are not all on one stripe");
23501
23502 let mut one = Fixture::new();
23503 let mut both = |parts: &[&[u8]]| {
23504 let a = one.run(parts);
23505 let b = many.run(parts);
23506 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23507 a
23508 };
23509 for name in &names {
23510 both(&[b"TS.CREATE", name, b"LABELS", b"room", b"1"]);
23511 both(&[b"TS.ADD", name, b"1000", b"1"]);
23512 }
23513
23514 let got = both(&[b"TS.QUERYINDEX", b"room=1"]);
23515 assert!(got.starts_with("*6\r\n"), "every series answered: {got}");
23516 assert!(both(&[b"TS.MGET", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
23517 assert!(both(&[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
23518 assert_eq!(
23519 both(&[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"]),
23520 "*1\r\n$4\r\nroom\r\n"
23521 );
23522 }
23523
23524 #[test]
23531 fn the_hash_group_answers_the_same_however_many_stripes_there_are() {
23532 let script: &[&[&[u8]]] = &[
23533 &[b"HSET", b"h", b"a", b"1", b"b", b"2"],
23534 &[b"HMSET", b"h", b"c", b"3"],
23535 &[b"HSETNX", b"h", b"a", b"9"],
23536 &[b"HSETNX", b"h", b"d", b"4"],
23537 &[b"HGET", b"h", b"a"],
23538 &[b"HGET", b"h", b"nope"],
23539 &[b"HMGET", b"h", b"a", b"nope"],
23540 &[b"HLEN", b"h"],
23541 &[b"HEXISTS", b"h", b"a"],
23542 &[b"HSTRLEN", b"h", b"a"],
23543 &[b"HGETALL", b"h"],
23544 &[b"HKEYS", b"h"],
23545 &[b"HVALS", b"h"],
23546 &[b"HINCRBY", b"h", b"a", b"5"],
23547 &[b"HINCRBYFLOAT", b"h", b"a", b"1.5"],
23548 &[b"HSCAN", b"h", b"0"],
23549 &[b"HSCAN", b"h", b"0", b"MATCH", b"a", b"COUNT", b"10"],
23550 &[b"HSCAN", b"h", b"0", b"NOVALUES"],
23551 &[b"HDEL", b"h", b"d"],
23552 &[b"HSET", b"one", b"f", b"v"],
23553 &[b"HRANDFIELD", b"one"],
23554 &[b"HRANDFIELD", b"one", b"1", b"WITHVALUES"],
23555 &[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"],
23557 &[b"HTTL", b"h", b"FIELDS", b"1", b"a"],
23558 &[b"HPTTL", b"h", b"FIELDS", b"1", b"a"],
23559 &[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
23560 &[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
23561 &[b"HPERSIST", b"h", b"FIELDS", b"1", b"a"],
23562 &[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"],
23563 &[b"HGET", b"h", b"b"],
23564 &[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"e", b"5"],
23566 &[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"e"],
23567 &[b"HGETDEL", b"h", b"FIELDS", b"1", b"e"],
23568 &[b"HGET", b"h", b"e"],
23569 &[b"HIMPORT", b"PREPARE", b"fs", b"x", b"y"],
23571 &[b"HIMPORT", b"SET", b"imp", b"fs", b"1", b"2"],
23572 &[b"HGETALL", b"imp"],
23573 &[b"HIMPORT", b"SET", b"imp", b"nofs", b"1", b"2"],
23574 &[b"HIMPORT", b"DISCARD", b"fs"],
23575 &[b"SET", b"plain", b"v"],
23577 &[b"HSET", b"plain", b"a", b"1"],
23578 &[b"HGETALL", b"plain"],
23579 &[b"HGET", b"gone", b"a"],
23580 &[b"HINCRBY", b"h", b"a", b"nan"],
23581 ];
23582
23583 let mut one = Fixture::new();
23584 let mut many = Fixture::striped(8);
23585 one.server.set_clock_ms(1_700_000_000_000);
23589 many.server.set_clock_ms(1_700_000_000_000);
23590 for parts in script {
23591 let a = one.run(parts);
23592 let b = many.run(parts);
23593 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23594 }
23595 }
23596
23597 #[test]
23599 fn the_array_group_answers_the_same_however_many_stripes_there_are() {
23600 let script: &[&[&[u8]]] = &[
23601 &[b"ARSET", b"a", b"0", b"x", b"y", b"z"],
23602 &[b"ARMSET", b"a", b"5", b"p", b"7", b"q"],
23603 &[b"ARGET", b"a", b"1"],
23604 &[b"ARGET", b"a", b"99"],
23605 &[b"ARMGET", b"a", b"0", b"5", b"99"],
23606 &[b"ARGETRANGE", b"a", b"0", b"7"],
23607 &[b"ARLEN", b"a"],
23608 &[b"ARCOUNT", b"a"],
23609 &[b"ARINSERT", b"a", b"m", b"n"],
23610 &[b"ARSCAN", b"a", b"0", b"20"],
23611 &[b"ARSCAN", b"a", b"0", b"20", b"LIMIT", b"2"],
23612 &[b"ARGREP", b"a", b"0", b"20", b"EXACT", b"x"],
23613 &[b"ARGREP", b"a", b"0", b"20", b"GLOB", b"*", b"WITHVALUES"],
23614 &[b"ARLASTITEMS", b"a", b"2"],
23615 &[b"ARLASTITEMS", b"a", b"2", b"REV"],
23616 &[b"ARNEXT", b"a"],
23617 &[b"ARSEEK", b"a", b"3"],
23618 &[b"AROP", b"a", b"0", b"20", b"USED"],
23619 &[b"AROP", b"a", b"0", b"20", b"MATCH", b"x"],
23620 &[b"ARINFO", b"a"],
23621 &[b"ARINFO", b"a", b"FULL"],
23622 &[b"ARDEL", b"a", b"0"],
23623 &[b"ARDELRANGE", b"a", b"1", b"2"],
23624 &[b"ARCOUNT", b"a"],
23625 &[b"ARRING", b"r", b"3", b"1", b"2", b"3", b"4"],
23626 &[b"ARGETRANGE", b"r", b"0", b"9"],
23627 &[b"SET", b"plain", b"v"],
23629 &[b"ARGET", b"plain", b"0"],
23630 &[b"ARSET", b"plain", b"0", b"v"],
23631 &[b"ARGET", b"gone", b"0"],
23632 &[b"ARSET", b"a", b"bad", b"v"],
23633 ];
23634
23635 let mut one = Fixture::new();
23636 let mut many = Fixture::striped(8);
23637 for parts in script {
23638 let a = one.run(parts);
23639 let b = many.run(parts);
23640 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23641 }
23642 }
23643
23644 #[test]
23650 fn the_graph_and_vector_groups_answer_the_same_however_many_stripes_there_are() {
23651 let script: &[&[&[u8]]] = &[
23652 &[b"G.NADD", b"g", b"n1", b"name", b"one"],
23653 &[b"G.NADD", b"g", b"n2", b"name", b"two"],
23654 &[b"G.NADD", b"g", b"n3"],
23655 &[b"G.NGET", b"g", b"n1"],
23656 &[b"G.NGET", b"g", b"gone"],
23657 &[b"G.EADD", b"g", b"n1", b"n2", b"knows"],
23658 &[b"G.EADD", b"g", b"n2", b"n3", b"knows"],
23659 &[b"G.OUT", b"g", b"n1", b"knows"],
23660 &[b"G.IN", b"g", b"n2", b"knows"],
23661 &[b"G.DEG", b"g", b"n1", b"knows"],
23662 &[b"G.DEG", b"g", b"n2", b"knows", b"BOTH"],
23663 &[b"G.NEIGH", b"g", b"n1", b"knows", b"DEPTH", b"2"],
23664 &[b"G.PATH", b"g", b"n1", b"n3"],
23665 &[b"G.EDEL", b"g", b"n1", b"n2", b"knows"],
23666 &[b"G.NDEL", b"g", b"n3"],
23667 &[b"G.NGET", b"g", b"n3"],
23668 &[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"e1"],
23670 &[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"e2"],
23671 &[b"VCARD", b"v"],
23672 &[b"VDIM", b"v"],
23673 &[b"VEMB", b"v", b"e1"],
23674 &[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0"],
23675 &[b"VSIM", b"v", b"ELE", b"e1"],
23676 &[b"VISMEMBER", b"v", b"e1"],
23677 &[b"VISMEMBER", b"v", b"gone"],
23678 &[b"VSETATTR", b"v", b"e1", b"{\"k\":1}"],
23679 &[b"VGETATTR", b"v", b"e1"],
23680 &[b"VRANGE", b"v", b"-", b"+"],
23681 &[b"VLINKS", b"v", b"e1"],
23682 &[b"VINFO", b"v"],
23683 &[b"VREM", b"v", b"e2"],
23684 &[b"VCARD", b"v"],
23685 &[b"SET", b"plain", b"v"],
23687 &[b"G.NGET", b"plain", b"n1"],
23688 &[b"VCARD", b"plain"],
23689 &[b"G.NADD", b"gone2", b"n"],
23690 &[b"VEMB", b"gone3", b"e"],
23691 ];
23692
23693 let mut one = Fixture::new();
23694 let mut many = Fixture::striped(8);
23695 for parts in script {
23696 let a = one.run(parts);
23697 let b = many.run(parts);
23698 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23699 }
23700 }
23701
23702 #[test]
23705 fn the_probabilistic_groups_answer_the_same_however_many_stripes_there_are() {
23706 let script: &[&[&[u8]]] = &[
23707 &[b"BF.RESERVE", b"bf", b"0.01", b"100"],
23709 &[b"BF.ADD", b"bf", b"a"],
23710 &[b"BF.ADD", b"bf", b"a"],
23711 &[b"BF.MADD", b"bf", b"b", b"c"],
23712 &[b"BF.EXISTS", b"bf", b"a"],
23713 &[b"BF.MEXISTS", b"bf", b"a", b"zz"],
23714 &[b"BF.CARD", b"bf"],
23715 &[b"BF.INFO", b"bf"],
23716 &[b"BF.INFO", b"bf", b"CAPACITY"],
23717 &[b"BF.DEBUG", b"bf"],
23718 &[b"BF.INSERT", b"made", b"CAPACITY", b"50", b"ITEMS", b"x"],
23719 &[b"BF.EXISTS", b"made", b"x"],
23720 &[b"BF.SCANDUMP", b"bf", b"0"],
23721 &[b"CF.RESERVE", b"cf", b"100"],
23723 &[b"CF.ADD", b"cf", b"a"],
23724 &[b"CF.ADDNX", b"cf", b"a"],
23725 &[b"CF.COUNT", b"cf", b"a"],
23726 &[b"CF.EXISTS", b"cf", b"a"],
23727 &[b"CF.MEXISTS", b"cf", b"a", b"zz"],
23728 &[b"CF.INSERT", b"cf", b"ITEMS", b"b", b"c"],
23729 &[b"CF.DEL", b"cf", b"a"],
23730 &[b"CF.COMPACT", b"cf"],
23731 &[b"CF.INFO", b"cf"],
23732 &[b"CF.DEBUG", b"cf"],
23733 &[b"CF.SCANDUMP", b"cf", b"0"],
23734 &[b"CMS.INITBYDIM", b"cms", b"100", b"5"],
23736 &[b"CMS.INITBYPROB", b"cms2", b"0.01", b"0.01"],
23737 &[b"CMS.INCRBY", b"cms", b"a", b"5", b"b", b"3"],
23738 &[b"CMS.QUERY", b"cms", b"a", b"b", b"gone"],
23739 &[b"CMS.INFO", b"cms"],
23740 &[b"TOPK.RESERVE", b"tk", b"3"],
23742 &[b"TOPK.ADD", b"tk", b"a", b"b", b"a"],
23743 &[b"TOPK.INCRBY", b"tk", b"c", b"4"],
23744 &[b"TOPK.QUERY", b"tk", b"a", b"zz"],
23745 &[b"TOPK.COUNT", b"tk", b"a", b"c"],
23746 &[b"TOPK.LIST", b"tk"],
23747 &[b"TOPK.LIST", b"tk", b"WITHCOUNT"],
23748 &[b"TOPK.INFO", b"tk"],
23749 &[b"TDIGEST.CREATE", b"td"],
23751 &[b"TDIGEST.ADD", b"td", b"1", b"2", b"3", b"4", b"5"],
23752 &[b"TDIGEST.MIN", b"td"],
23753 &[b"TDIGEST.MAX", b"td"],
23754 &[b"TDIGEST.QUANTILE", b"td", b"0.5"],
23755 &[b"TDIGEST.CDF", b"td", b"3"],
23756 &[b"TDIGEST.RANK", b"td", b"3"],
23757 &[b"TDIGEST.REVRANK", b"td", b"3"],
23758 &[b"TDIGEST.BYRANK", b"td", b"0"],
23759 &[b"TDIGEST.BYREVRANK", b"td", b"0"],
23760 &[b"TDIGEST.TRIMMED_MEAN", b"td", b"0.1", b"0.9"],
23761 &[b"TDIGEST.INFO", b"td"],
23762 &[b"TDIGEST.RESET", b"td"],
23763 &[b"TDIGEST.MIN", b"td"],
23764 &[b"SET", b"plain", b"v"],
23766 &[b"BF.ADD", b"plain", b"a"],
23767 &[b"CF.ADD", b"plain", b"a"],
23768 &[b"CMS.QUERY", b"plain", b"a"],
23769 &[b"TOPK.ADD", b"plain", b"a"],
23770 &[b"TDIGEST.ADD", b"plain", b"1"],
23771 &[b"CMS.INFO", b"gone"],
23772 &[b"TOPK.INFO", b"gone"],
23773 &[b"TDIGEST.INFO", b"gone"],
23774 ];
23775
23776 let mut one = Fixture::new();
23777 let mut many = Fixture::striped(8);
23778 for parts in script {
23779 let a = one.run(parts);
23780 let b = many.run(parts);
23781 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23782 }
23783 }
23784
23785 #[test]
23792 fn a_sketch_merge_across_stripes_reads_every_source() {
23793 let mut many = Fixture::striped(8);
23794 let other = apart(&mut many, "s1");
23795 let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
23796 let mut one = Fixture::new();
23797 let mut both = |parts: &[&[u8]]| {
23798 let a = one.run(parts);
23799 let b = many.run(parts);
23800 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23801 a
23802 };
23803
23804 for key in [b"cd".as_slice(), s1, s2] {
23808 both(&[b"CMS.INITBYDIM", key, b"100", b"5"]);
23809 }
23810 both(&[b"CMS.INCRBY", s1, b"x", b"5"]);
23811 both(&[b"CMS.INCRBY", s2, b"x", b"3"]);
23812 assert_eq!(
23813 both(&[b"CMS.MERGE", b"cd", b"2", s1, s2]),
23814 "+OK\r\n",
23815 "the merge took both sources"
23816 );
23817 assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:8\r\n");
23818 both(&[b"CMS.MERGE", b"cd", b"2", s1, s2, b"WEIGHTS", b"2", b"1"]);
23820 assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
23821 both(&[b"SET", b"plain", b"v"]);
23823 assert!(both(&[b"CMS.MERGE", b"cd", b"2", s1, b"plain"]).starts_with('-'));
23824 assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
23825
23826 both(&[b"FLUSHALL"]);
23829 both(&[b"TDIGEST.CREATE", b"td"]);
23830 both(&[b"TDIGEST.CREATE", s1]);
23831 both(&[b"TDIGEST.CREATE", s2]);
23832 both(&[b"TDIGEST.ADD", s1, b"1", b"2"]);
23833 both(&[b"TDIGEST.ADD", s2, b"9", b"10"]);
23834 assert_eq!(both(&[b"TDIGEST.MERGE", b"td", b"2", s1, s2]), "+OK\r\n");
23835 assert_eq!(both(&[b"TDIGEST.MIN", b"td"]), "$1\r\n1\r\n");
23836 assert_eq!(both(&[b"TDIGEST.MAX", b"td"]), "$2\r\n10\r\n");
23837 }
23838
23839 #[test]
23846 fn the_sort_command_answers_the_same_however_many_stripes_there_are() {
23847 let script: &[&[&[u8]]] = &[
23848 &[b"RPUSH", b"l", b"3", b"1", b"2", b"10"],
23849 &[b"SORT", b"l"],
23850 &[b"SORT", b"l", b"DESC"],
23851 &[b"SORT", b"l", b"ALPHA"],
23852 &[b"SORT", b"l", b"LIMIT", b"1", b"2"],
23853 &[b"SORT_RO", b"l"],
23854 &[
23857 b"MSET", b"w_1", b"4", b"w_2", b"3", b"w_3", b"2", b"w_10", b"1",
23858 ],
23859 &[b"SORT", b"l", b"BY", b"w_*"],
23860 &[b"SORT", b"l", b"BY", b"w_*", b"DESC"],
23861 &[b"DEL", b"w_2"],
23862 &[b"SORT", b"l", b"BY", b"w_*"],
23863 &[b"MSET", b"d_1", b"one", b"d_3", b"three"],
23866 &[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"],
23867 &[b"HSET", b"h_1", b"f", b"9"],
23869 &[b"HSET", b"h_2", b"f", b"8"],
23870 &[b"HSET", b"h_3", b"f", b"7"],
23871 &[b"HSET", b"h_10", b"f", b"6"],
23872 &[b"SORT", b"l", b"BY", b"h_*->f"],
23873 &[b"SORT", b"l", b"BY", b"nosort", b"GET", b"h_*->f"],
23874 &[b"SORT", b"l", b"BY", b"w_*", b"STORE", b"out"],
23876 &[b"LRANGE", b"out", b"0", b"-1"],
23877 &[b"SORT", b"l", b"STORE", b"l"],
23878 &[b"LRANGE", b"l", b"0", b"-1"],
23879 &[b"SORT", b"missing", b"STORE", b"out"],
23882 &[b"EXISTS", b"out"],
23883 &[b"SADD", b"s", b"c", b"a", b"b"],
23886 &[b"SORT", b"s", b"ALPHA"],
23887 &[b"SORT", b"s", b"BY", b"nosort", b"STORE", b"out"],
23888 &[b"LRANGE", b"out", b"0", b"-1"],
23889 &[b"ZADD", b"z", b"3", b"c", b"1", b"a", b"2", b"b"],
23890 &[b"SORT", b"z", b"BY", b"nosort"],
23891 &[b"SORT", b"z", b"ALPHA", b"DESC"],
23892 &[b"SET", b"str", b"v"],
23895 &[b"SORT", b"str"],
23896 &[b"RPUSH", b"words", b"one", b"two"],
23897 &[b"SORT", b"words"],
23898 &[b"SORT_RO", b"l", b"STORE", b"out"],
23899 ];
23900
23901 let mut one = Fixture::new();
23902 let mut many = Fixture::striped(8);
23903 for parts in script {
23904 let a = one.run(parts);
23905 let b = many.run(parts);
23906 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23907 }
23908 }
23909
23910 #[test]
23917 fn a_sort_across_stripes_reads_every_pattern_key() {
23918 let mut f = Fixture::striped(8);
23919 let out = apart(&mut f, "l");
23920 let (list, dest) = (b"l".as_slice(), out.as_bytes());
23921
23922 f.run(&[b"RPUSH", list, b"a", b"b", b"c", b"d"]);
23923 f.run(&[
23924 b"MSET", b"w_a", b"4", b"w_b", b"3", b"w_c", b"2", b"w_d", b"1",
23925 ]);
23926 f.run(&[
23927 b"MSET", b"d_a", b"A", b"d_b", b"B", b"d_c", b"C", b"d_d", b"D",
23928 ]);
23929
23930 let db = f.server.striped(0);
23933 let weights: Vec<usize> = [b"w_a", b"w_b", b"w_c", b"w_d"]
23934 .iter()
23935 .map(|k| db.stripe_of(k.as_slice()))
23936 .collect();
23937 assert!(
23938 weights.iter().any(|s| *s != weights[0]),
23939 "the four weight keys all landed on one stripe, so this proves nothing"
23940 );
23941
23942 assert_eq!(
23943 f.run(&[b"SORT", list, b"BY", b"w_*", b"GET", b"d_*"]),
23944 "*4\r\n$1\r\nD\r\n$1\r\nC\r\n$1\r\nB\r\n$1\r\nA\r\n",
23945 "the order came off the weights and the answer off the data keys"
23946 );
23947 assert_eq!(
23948 f.run(&[b"SORT", list, b"BY", b"w_*", b"STORE", dest]),
23949 ":4\r\n"
23950 );
23951 assert_eq!(
23952 f.run(&[b"LRANGE", dest, b"0", b"-1"]),
23953 "*4\r\n$1\r\nd\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n",
23954 "the destination is on a stripe of its own and got the whole answer"
23955 );
23956 }
23957
23958 #[test]
23966 fn a_setting_reaches_every_stripe_and_reads_back_from_any_of_them() {
23967 let mut f = Fixture::striped(8);
23968 let other = apart(&mut f, "h");
23969 let (first, second) = (b"h".as_slice(), other.as_bytes());
23970
23971 assert_eq!(
23972 f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"2"]),
23973 "+OK\r\n"
23974 );
23975 assert_eq!(
23976 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
23977 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n",
23978 "the read comes off one stripe and has to answer for all of them"
23979 );
23980 for key in [first, second] {
23981 f.run(&[b"HSET", key, b"a", b"1", b"b", b"2"]);
23982 assert_eq!(
23983 f.run(&[b"OBJECT", b"ENCODING", key]),
23984 "$8\r\nlistpack\r\n",
23985 "two fields is still under the ladder"
23986 );
23987 f.run(&[b"HSET", key, b"c", b"3"]);
23988 assert_eq!(
23989 f.run(&[b"OBJECT", b"ENCODING", key]),
23990 "$9\r\nhashtable\r\n",
23991 "three fields is over it, on whichever stripe the key is on"
23992 );
23993 }
23994
23995 assert_eq!(
23998 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]),
23999 "+OK\r\n"
24000 );
24001 let db = f.server.striped(0);
24002 assert!(
24003 (0..db.width()).all(|i| db.hold_stripe(i).policy().name() == "allkeys-lru"),
24004 "a stripe kept the old policy"
24005 );
24006 }
24007
24008 fn held(f: &Fixture, name: &[u8]) -> (usize, u32) {
24014 let search = f.server.search.lock();
24015 let index = search.named(name).expect("the index is there");
24016 (index.held.docs.len(), index.held.docs.last())
24017 }
24018
24019 #[test]
24022 fn a_hash_that_is_written_reaches_the_index_that_follows_it() {
24023 let mut f = Fixture::new();
24024 f.run(&[
24025 b"FT.CREATE",
24026 b"ix",
24027 b"PREFIX",
24028 b"1",
24029 b"p:",
24030 b"SCHEMA",
24031 b"t",
24032 b"TEXT",
24033 ]);
24034 f.run(&[b"HSET", b"p:1", b"t", b"running dogs"]);
24035 assert_eq!(held(&f, b"ix"), (1, 1));
24036 f.run(&[b"HSET", b"other:1", b"t", b"running dogs"]);
24037 assert_eq!(held(&f, b"ix"), (1, 1));
24038
24039 f.run(&[b"HSET", b"p:1", b"u", b"beta"]);
24042 f.run(&[b"HDEL", b"p:1", b"u"]);
24043 assert_eq!(held(&f, b"ix"), (1, 3));
24044 let search = f.server.search.lock();
24045 let index = search.named(b"ix").expect("there");
24046 assert_eq!(index.held.docs.id(b"p:1"), Some(3));
24047 }
24048
24049 #[test]
24052 fn a_fresh_index_reads_the_keys_that_were_already_there() {
24053 let mut f = Fixture::new();
24054 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24055 f.run(&[b"SET", b"p:str", b"not a hash"]);
24056 f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
24057 f.run(&[
24058 b"FT.CREATE",
24059 b"ix",
24060 b"PREFIX",
24061 b"1",
24062 b"p:",
24063 b"SCHEMA",
24064 b"t",
24065 b"TEXT",
24066 ]);
24067
24068 assert_eq!(held(&f, b"ix"), (1, 1));
24069 let search = f.server.search.lock();
24070 let index = search.named(b"ix").expect("there");
24071 assert_eq!(index.trouble.whole().failures(), 0);
24072 }
24073
24074 #[test]
24077 fn an_index_that_skipped_the_scan_fills_up_on_the_next_write() {
24078 let mut f = Fixture::new();
24079 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24080 f.run(&[
24081 b"FT.CREATE",
24082 b"ix",
24083 b"PREFIX",
24084 b"1",
24085 b"p:",
24086 b"SKIPINITIALSCAN",
24087 b"SCHEMA",
24088 b"t",
24089 b"TEXT",
24090 ]);
24091 assert_eq!(held(&f, b"ix"), (0, 0));
24092 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24093 assert_eq!(held(&f, b"ix"), (1, 1));
24094 }
24095
24096 #[test]
24104 fn only_a_real_change_gives_the_document_a_new_number() {
24105 let mut f = Fixture::new();
24106 f.run(&[
24107 b"FT.CREATE",
24108 b"ix",
24109 b"PREFIX",
24110 b"1",
24111 b"p:",
24112 b"SCHEMA",
24113 b"t",
24114 b"TEXT",
24115 ]);
24116 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24117 assert_eq!(held(&f, b"ix"), (1, 1));
24118
24119 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24120 assert_eq!(held(&f, b"ix"), (1, 2), "the same value still rewrites");
24121
24122 for quiet in [
24123 vec![b"HSETNX".as_slice(), b"p:1", b"t", b"other"],
24124 vec![b"HDEL".as_slice(), b"p:1", b"nosuch"],
24125 vec![b"HGET".as_slice(), b"p:1", b"t"],
24126 vec![b"HGETALL".as_slice(), b"p:1"],
24127 vec![b"HEXPIRE".as_slice(), b"p:1", b"100", b"FIELDS", b"1", b"t"],
24128 vec![b"HPERSIST".as_slice(), b"p:1", b"FIELDS", b"1", b"t"],
24129 vec![
24130 b"HGETEX".as_slice(),
24131 b"p:1",
24132 b"EX",
24133 b"100",
24134 b"FIELDS",
24135 b"1",
24136 b"t",
24137 ],
24138 vec![b"HGETDEL".as_slice(), b"p:1", b"FIELDS", b"1", b"nosuch"],
24139 ] {
24140 f.run(&quiet);
24141 assert_eq!(held(&f, b"ix"), (1, 2), "{:?} moved the document", quiet[0]);
24142 }
24143
24144 f.run(&[b"HSET", b"p:2", b"n", b"1"]);
24146 f.run(&[b"HINCRBY", b"p:2", b"n", b"1"]);
24147 assert_eq!(held(&f, b"ix"), (2, 4));
24148 f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"n"]);
24154 assert_eq!(held(&f, b"ix"), (1, 5));
24155 }
24156
24157 #[test]
24163 fn a_key_emptied_by_a_deadline_spends_a_number_and_one_emptied_by_hdel_does_not() {
24164 fn refused(f: &Fixture, name: &[u8]) -> u64 {
24166 let search = f.server.search.lock();
24167 let index = search.named(name).expect("the index is there");
24168 index.trouble.whole().failures()
24169 }
24170
24171 let mut f = Fixture::new();
24172 f.run(&[
24173 b"FT.CREATE",
24174 b"ix",
24175 b"PREFIX",
24176 b"1",
24177 b"p:",
24178 b"SCHEMA",
24179 b"t",
24180 b"TEXT",
24181 ]);
24182 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24183 assert_eq!(held(&f, b"ix"), (1, 1));
24184 f.run(&[b"HDEL", b"p:1", b"t"]);
24185 assert_eq!(
24186 held(&f, b"ix"),
24187 (0, 1),
24188 "HDEL of the last field spends none"
24189 );
24190 assert_eq!(refused(&f, b"ix"), 1, "and is counted as a refusal");
24191
24192 f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
24193 assert_eq!(held(&f, b"ix"), (1, 2));
24194 f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"t"]);
24195 assert_eq!(held(&f, b"ix"), (0, 3), "a deadline spends one");
24196 assert_eq!(refused(&f, b"ix"), 1, "and is counted as nothing");
24197
24198 f.run(&[b"HSET", b"p:3", b"t", b"alpha"]);
24199 assert_eq!(held(&f, b"ix"), (1, 4));
24200 f.run(&[b"HGETDEL", b"p:3", b"FIELDS", b"1", b"t"]);
24201 assert_eq!(held(&f, b"ix"), (0, 5), "and so does HGETDEL");
24202
24203 f.run(&[b"HSET", b"p:4", b"t", b"alpha", b"u", b"beta"]);
24206 assert_eq!(held(&f, b"ix"), (1, 6));
24207 f.run(&[b"HEXPIRE", b"p:4", b"0", b"FIELDS", b"2", b"t", b"u"]);
24208 assert_eq!(held(&f, b"ix"), (0, 7));
24209 assert_eq!(refused(&f, b"ix"), 1);
24210 }
24211
24212 #[test]
24216 fn a_field_written_already_past_its_deadline_moves_the_number_twice() {
24217 let mut f = Fixture::new();
24218 f.run(&[
24219 b"FT.CREATE",
24220 b"ix",
24221 b"PREFIX",
24222 b"1",
24223 b"p:",
24224 b"SCHEMA",
24225 b"t",
24226 b"TEXT",
24227 b"u",
24228 b"TEXT",
24229 ]);
24230 f.run(&[b"HSET", b"p:1", b"u", b"keepme"]);
24231 assert_eq!(held(&f, b"ix"), (1, 1));
24232 f.run(&[
24233 b"HSETEX", b"p:1", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
24234 ]);
24235 assert_eq!(
24236 held(&f, b"ix"),
24237 (1, 3),
24238 "the key lived and the field did not"
24239 );
24240
24241 f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
24243 assert_eq!(held(&f, b"ix"), (2, 4));
24244 f.run(&[
24245 b"HSETEX", b"p:2", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
24246 ]);
24247 assert_eq!(held(&f, b"ix"), (1, 6));
24248 }
24249
24250 fn number(f: &Fixture, name: &[u8], key: &[u8]) -> Option<u32> {
24253 let search = f.server.search.lock();
24254 let index = search.named(name).expect("the index is there");
24255 index.held.docs.id(key)
24256 }
24257
24258 fn indexed() -> Fixture {
24261 let mut f = Fixture::new();
24262 f.run(&[
24263 b"FT.CREATE",
24264 b"ix",
24265 b"PREFIX",
24266 b"1",
24267 b"p:",
24268 b"SCHEMA",
24269 b"t",
24270 b"TEXT",
24271 ]);
24272 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24273 f
24274 }
24275
24276 #[test]
24279 fn a_key_a_keyspace_command_takes_away_loses_its_document() {
24280 for take in [
24281 vec![b"DEL".as_slice(), b"p:1"],
24282 vec![b"UNLINK".as_slice(), b"p:1"],
24283 vec![b"PEXPIREAT".as_slice(), b"p:1", b"1"],
24284 vec![b"EXPIRE".as_slice(), b"p:1", b"-1"],
24285 ] {
24286 let mut f = indexed();
24287 assert_eq!(held(&f, b"ix"), (1, 1));
24288 f.run(&take);
24289 assert_eq!(held(&f, b"ix"), (0, 1), "{:?} left something", take[0]);
24290 let search = f.server.search.lock();
24291 let index = search.named(b"ix").expect("the index is there");
24292 assert_eq!(index.trouble.whole().failures(), 0, "{:?}", take[0]);
24293 }
24294
24295 let mut f = indexed();
24297 f.run(&[b"EXPIRE", b"p:1", b"1000"]);
24298 assert_eq!(held(&f, b"ix"), (1, 1));
24299 f.run(&[b"PERSIST", b"p:1"]);
24300 assert_eq!(held(&f, b"ix"), (1, 1));
24301 }
24302
24303 #[test]
24307 fn a_rename_inside_the_prefix_keeps_the_number_the_document_had() {
24308 let mut f = indexed();
24309 f.run(&[b"RENAME", b"p:1", b"p:2"]);
24310 assert_eq!(held(&f, b"ix"), (1, 1), "nothing was read again");
24311 assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
24312 assert_eq!(number(&f, b"ix", b"p:1"), None);
24313
24314 f.run(&[b"RENAME", b"p:2", b"q:1"]);
24315 assert_eq!(held(&f, b"ix"), (0, 1), "out of the prefix is an erase");
24316
24317 f.run(&[b"RENAME", b"q:1", b"p:3"]);
24318 assert_eq!(held(&f, b"ix"), (1, 2), "and into it is a reading");
24319 assert_eq!(number(&f, b"ix", b"p:3"), Some(2));
24320
24321 f.run(&[b"HSET", b"p:4", b"t", b"beta"]);
24324 assert_eq!(f.run(&[b"RENAMENX", b"p:3", b"p:4"]), ":0\r\n");
24325 assert_eq!(held(&f, b"ix"), (2, 3));
24326 f.run(&[b"RENAMENX", b"p:3", b"p:5"]);
24327 assert_eq!(number(&f, b"ix", b"p:5"), Some(2));
24328 }
24329
24330 #[test]
24333 fn a_rename_over_a_document_leaves_one_of_them() {
24334 let mut f = indexed();
24335 f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
24336 assert_eq!(held(&f, b"ix"), (2, 2));
24337 f.run(&[b"RENAME", b"p:1", b"p:2"]);
24338 assert_eq!(held(&f, b"ix"), (1, 2));
24339 assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
24340 }
24341
24342 #[test]
24346 fn a_key_that_arrives_under_the_prefix_is_read_and_one_overwritten_is_erased() {
24347 let mut f = indexed();
24348 f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
24349 f.run(&[b"COPY", b"q:1", b"p:2"]);
24350 assert_eq!(held(&f, b"ix"), (2, 2));
24351 assert_eq!(number(&f, b"ix", b"p:2"), Some(2));
24352
24353 f.run(&[b"COPY", b"p:1", b"q:2"]);
24355 assert_eq!(held(&f, b"ix"), (2, 2));
24356
24357 f.run(&[b"COPY", b"q:1", b"p:1", b"REPLACE"]);
24359 assert_eq!(held(&f, b"ix"), (2, 3));
24360 assert_eq!(number(&f, b"ix", b"p:1"), Some(3));
24361
24362 f.run(&[b"SET", b"s:1", b"plain"]);
24365 f.run(&[b"COPY", b"s:1", b"p:1", b"REPLACE"]);
24366 assert_eq!(held(&f, b"ix"), (1, 3));
24367 let dump = f.run(&[b"DUMP", b"q:1"]);
24368 assert!(dump.starts_with('$'), "{dump}");
24369 }
24370
24371 #[test]
24376 fn the_keyspace_group_reads_database_zero_whatever_database_it_ran_on() {
24377 let mut f = indexed();
24378 f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
24379 assert_eq!(held(&f, b"ix"), (2, 2));
24380 f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
24383 assert_eq!(held(&f, b"ix"), (2, 3));
24384 f.run(&[b"DEL", b"p:2"]);
24387 assert_eq!(held(&f, b"ix"), (1, 3));
24388 f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
24389 assert_eq!(held(&f, b"ix"), (1, 3), "the copy landed out of sight");
24390
24391 let dump = f.run(&[b"DUMP", b"p:1"]);
24393 assert!(dump.starts_with('$'), "{dump}");
24394 f.run(&[b"SELECT", b"1"]);
24395 f.run(&[b"HSET", b"q:1", b"t", b"gamma"]);
24396 f.run(&[b"RENAME", b"q:1", b"p:3"]);
24397 assert_eq!(held(&f, b"ix"), (1, 3), "and so is a rename");
24398 }
24399
24400 #[test]
24403 fn a_move_leaves_the_document_where_it_is() {
24404 let mut f = indexed();
24405 f.run(&[b"MOVE", b"p:1", b"1"]);
24406 assert_eq!(held(&f, b"ix"), (1, 1), "the key moved and nothing else");
24407 assert_eq!(number(&f, b"ix", b"p:1"), Some(1));
24408
24409 f.run(&[b"SELECT", b"1"]);
24410 f.run(&[b"HSET", b"p:1", b"t", b"beta"]);
24411 assert_eq!(held(&f, b"ix"), (1, 2), "and a write there still lands");
24412 f.run(&[b"DEL", b"p:1"]);
24413 assert_eq!(held(&f, b"ix"), (0, 2));
24414 }
24415
24416 #[test]
24418 fn a_flush_drops_the_indexes() {
24419 for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
24420 let mut f = indexed();
24421 f.run(&[flush]);
24422 assert!(f.server.search.lock().is_empty(), "{flush:?} kept an index");
24423 assert_eq!(f.run(&[b"FT._LIST"]), "*0\r\n");
24424 }
24425
24426 let mut f = indexed();
24429 f.run(&[b"SELECT", b"9"]);
24430 f.run(&[b"FLUSHDB"]);
24431 assert!(f.server.search.lock().is_empty());
24432 }
24433
24434 fn tagged() -> Fixture {
24437 let mut f = Fixture::new();
24438 f.run(&[
24439 b"FT.CREATE",
24440 b"tv",
24441 b"PREFIX",
24442 b"1",
24443 b"tv:",
24444 b"SCHEMA",
24445 b"g",
24446 b"AS",
24447 b"gg",
24448 b"TAG",
24449 b"h",
24450 b"TAG",
24451 b"SEPARATOR",
24452 b"|",
24453 b"CASESENSITIVE",
24454 b"n",
24455 b"NUMERIC",
24456 ]);
24457 f.run(&[
24458 b"HSET",
24459 b"tv:1",
24460 b"g",
24461 b"Red, BLUE ",
24462 b"h",
24463 b"Aa|bB",
24464 b"n",
24465 b"1",
24466 ]);
24467 f.run(&[b"HSET", b"tv:2", b"g", b"red", b"h", b"aa", b"n", b"2"]);
24468 f
24469 }
24470
24471 #[test]
24475 fn tag_values_come_back_as_they_are_stored_and_sorted_by_their_bytes() {
24476 let mut f = tagged();
24477 assert_eq!(
24478 f.run(&[b"FT.TAGVALS", b"tv", b"gg"]),
24479 "*2\r\n$4\r\nblue\r\n$3\r\nred\r\n"
24480 );
24481 assert_eq!(
24482 f.run(&[b"FT.TAGVALS", b"tv", b"h"]),
24483 "*3\r\n$2\r\nAa\r\n$2\r\naa\r\n$2\r\nbB\r\n"
24484 );
24485 }
24486
24487 #[test]
24490 fn tag_values_are_asked_for_by_the_attribute_and_not_the_identifier() {
24491 let mut f = tagged();
24492 for (name, want) in [
24493 (b"g".as_slice(), "-SEARCH_ATTR_BAD No such field\r\n"),
24494 (b"zz", "-SEARCH_ATTR_BAD No such field\r\n"),
24495 (b"n", "-SEARCH_ATTR_BAD Not a tag field\r\n"),
24496 ] {
24497 assert_eq!(f.run(&[b"FT.TAGVALS", b"tv", name]), want);
24498 }
24499 assert_eq!(
24500 f.run(&[b"FT.TAGVALS", b"nope", b"g"]),
24501 "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
24502 );
24503 }
24504
24505 #[test]
24508 fn asking_for_tag_values_counts_a_use_of_the_index() {
24509 let mut f = tagged();
24510 let uses = |f: &mut Fixture| {
24511 let reply = f.run(&[b"FT.INFO", b"tv"]);
24512 let at = reply.find("number_of_uses").expect("the field is reported");
24513 let value = reply[at..].split("\r\n").nth(1).unwrap();
24514 value.trim_start_matches(':').parse::<i64>().unwrap()
24515 };
24516 let before = uses(&mut f);
24517 f.run(&[b"FT.TAGVALS", b"tv", b"gg"]);
24518 f.run(&[b"FT.TAGVALS", b"tv", b"zz"]);
24519 assert_eq!(uses(&mut f), before + 3);
24521 }
24522
24523 #[test]
24526 fn a_tag_field_with_nothing_in_it_answers_empty() {
24527 let mut f = Fixture::new();
24528 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"g", b"TAG"]);
24529 assert_eq!(f.run(&[b"FT.TAGVALS", b"e", b"g"]), "*0\r\n");
24530 }
24531
24532 #[test]
24535 fn a_dictionary_is_not_a_key() {
24536 let mut f = Fixture::new();
24537 assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"a", b"b"]), ":2\r\n");
24538 assert_eq!(f.run(&[b"TYPE", b"d"]), "+none\r\n");
24539 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
24540 assert_eq!(f.run(&[b"KEYS", b"d"]), "*0\r\n");
24541 }
24542
24543 #[test]
24546 fn a_dictionary_counts_the_terms_it_had_not_seen() {
24547 let mut f = Fixture::new();
24548 assert_eq!(
24549 f.run(&[b"FT.DICTADD", b"d", b"zeta", b"alpha", b"Beta", b"alpha"]),
24550 ":3\r\n"
24551 );
24552 assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"alpha"]), ":0\r\n");
24553 assert_eq!(f.run(&[b"FT.DICTADD", b"d", b""]), ":0\r\n");
24554 assert_eq!(
24555 f.run(&[b"FT.DICTDUMP", b"d"]),
24556 "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nzeta\r\n"
24557 );
24558 assert_eq!(f.run(&[b"FT.DICTDEL", b"d", b"alpha", b"nope"]), ":1\r\n");
24559 }
24560
24561 #[test]
24565 fn a_dictionary_nobody_made_dumps_empty_rather_than_failing() {
24566 let mut f = Fixture::new();
24567 assert_eq!(f.run(&[b"FT.DICTDUMP", b"nope"]), "*0\r\n");
24568 assert_eq!(f.run(&[b"FT.DICTDEL", b"nope", b"a"]), ":0\r\n");
24569 }
24570
24571 #[test]
24573 fn a_flush_drops_the_dictionaries() {
24574 for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
24575 let mut f = Fixture::new();
24576 f.run(&[b"FT.DICTADD", b"d", b"a"]);
24577 f.run(&[flush]);
24578 assert_eq!(f.run(&[b"FT.DICTDUMP", b"d"]), "*0\r\n", "{flush:?}");
24579 }
24580 }
24581
24582 fn profiling() -> Fixture {
24587 let mut f = Fixture::new();
24588 f.run(&[
24589 b"FT.CREATE",
24590 b"ix",
24591 b"PREFIX",
24592 b"1",
24593 b"p:",
24594 b"SCHEMA",
24595 b"t",
24596 b"TEXT",
24597 b"n",
24598 b"NUMERIC",
24599 ]);
24600 f.run(&[b"HSET", b"p:1", b"t", b"alpha", b"n", b"1"]);
24601 f.run(&[b"HSET", b"p:2", b"t", b"alpha beta", b"n", b"2"]);
24602 f.run(&[b"HSET", b"p:3", b"t", b"beta", b"n", b"3"]);
24603 f
24604 }
24605
24606 fn timeless(reply: &str) -> String {
24609 const KEYS: &[&str] = &[
24610 "+Total profile time",
24611 "+Parsing time",
24612 "+Workers queue time",
24613 "+Pipeline creation time",
24614 "+Time",
24615 ];
24616 let mut out = String::new();
24617 let mut parts = reply.split("\r\n").peekable();
24618 while let Some(part) = parts.next() {
24619 out.push_str(part);
24620 out.push_str("\r\n");
24621 if !KEYS.contains(&part) {
24622 continue;
24623 }
24624 match parts.next() {
24627 Some(head) if head.starts_with('$') => {
24628 parts.next();
24629 }
24630 _ => {}
24631 }
24632 out.push_str("<t>\r\n");
24633 }
24634 out.truncate(out.len() - 2);
24636 out
24637 }
24638
24639 #[test]
24642 fn a_profile_wraps_the_reply_it_would_have_answered_anyway() {
24643 let mut f = profiling();
24644 assert_eq!(
24645 timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"])),
24646 "*2\r\n\
24647 *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\
24648 $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\
24649 *4\r\n+Shards\r\n*1\r\n*14\r\n\
24650 +Total profile time\r\n<t>\r\n+Parsing time\r\n<t>\r\n\
24651 +Workers queue time\r\n<t>\r\n+Pipeline creation time\r\n<t>\r\n\
24652 +Warning\r\n*1\r\n+None\r\n\
24653 +Iterators profile\r\n*10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
24654 +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
24655 +Estimated number of matches\r\n:2\r\n\
24656 +Result processors profile\r\n*4\r\n\
24657 *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
24658 *6\r\n+Type\r\n+Scorer\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
24659 *6\r\n+Type\r\n+Sorter\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
24660 *6\r\n+Type\r\n+Loader\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
24661 +Coordinator\r\n*0\r\n"
24662 );
24663 let mut g = profiling();
24664 g.run(&[b"HELLO", b"3"]);
24665 let three = timeless(&g.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"]));
24666 assert!(three.starts_with("%2\r\n+Results\r\n"), "{three}");
24667 assert!(
24668 three.contains("+Profile\r\n%2\r\n+Shards\r\n*1\r\n%7\r\n"),
24669 "{three}"
24670 );
24671 assert!(three.ends_with("+Coordinator\r\n%0\r\n"), "{three}");
24672 assert!(
24673 three.contains(
24674 "+Iterators profile\r\n%5\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
24675 +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
24676 +Estimated number of matches\r\n:2\r\n"
24677 ),
24678 "{three}"
24679 );
24680 }
24681
24682 #[test]
24685 fn each_kind_of_step_writes_the_keys_that_belong_to_it() {
24686 let mut f = profiling();
24687 let tree = |f: &mut Fixture, query: &[u8]| {
24688 let reply = timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", query]));
24689 let at = reply.find("+Iterators profile").expect("a tree");
24690 let end = reply.find("+Result processors").expect("a list of steps");
24691 reply[at..end].to_string()
24692 };
24693 assert_eq!(
24694 tree(&mut f, b"alpha beta"),
24695 "+Iterators profile\r\n*8\r\n+Type\r\n+INTERSECT\r\n+Time\r\n<t>\r\n\
24696 +Number of reading operations\r\n:1\r\n+Child iterators\r\n*2\r\n\
24697 *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n+Time\r\n<t>\r\n\
24698 +Number of reading operations\r\n:2\r\n+Estimated number of matches\r\n:2\r\n\
24699 *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$4\r\nbeta\r\n+Time\r\n<t>\r\n\
24700 +Number of reading operations\r\n:1\r\n+Estimated number of matches\r\n:2\r\n"
24701 );
24702 assert!(tree(&mut f, b"alpha|beta").starts_with(
24703 "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n+Query type\r\n+UNION\r\n\
24704 +Time\r\n<t>\r\n+Number of reading operations\r\n:3\r\n+Child iterators\r\n*2\r\n"
24705 ));
24706 assert!(tree(&mut f, b"-alpha").starts_with(
24709 "+Iterators profile\r\n*8\r\n+Type\r\n+NOT\r\n+Time\r\n<t>\r\n\
24710 +Number of reading operations\r\n:1\r\n+Child iterator\r\n*10\r\n"
24711 ));
24712 assert!(tree(&mut f, b"~alpha").starts_with(
24713 "+Iterators profile\r\n*8\r\n+Type\r\n+OPTIONAL\r\n+Time\r\n<t>\r\n\
24714 +Number of reading operations\r\n:3\r\n+Child iterator\r\n*10\r\n"
24715 ));
24716 assert_eq!(
24718 tree(&mut f, b"*"),
24719 "+Iterators profile\r\n*6\r\n+Type\r\n+WILDCARD\r\n+Time\r\n<t>\r\n\
24720 +Number of reading operations\r\n:3\r\n"
24721 );
24722 assert!(tree(&mut f, b"@n:[1 2]").starts_with(
24723 "+Iterators profile\r\n*10\r\n+Type\r\n+NUMERIC\r\n+Term\r\n\
24724 $19\r\n1.000000 - 2.000000\r\n"
24725 ));
24726 }
24727
24728 #[test]
24731 fn limited_folds_the_branches_an_expansion_made_and_leaves_a_bar_alone() {
24732 let mut f = profiling();
24733 f.run(&[b"HSET", b"p:4", b"t", b"alps"]);
24734 let tree = |f: &mut Fixture, words: &[&[u8]]| {
24735 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH"];
24736 argv.extend_from_slice(words);
24737 let reply = timeless(&f.run(&argv));
24738 let at = reply.find("+Iterators profile").expect("a tree");
24739 let end = reply.find("+Result processors").expect("a list of steps");
24740 reply[at..end].to_string()
24741 };
24742 assert_eq!(
24743 tree(&mut f, &[b"LIMITED", b"QUERY", b"al*"]),
24744 "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n\
24745 +Query type\r\n$11\r\nPREFIX - al\r\n+Time\r\n<t>\r\n\
24746 +Number of reading operations\r\n:3\r\n+Child iterators\r\n\
24747 +The number of iterators in the union is 2\r\n"
24748 );
24749 assert!(tree(&mut f, &[b"QUERY", b"al*"]).contains("+Child iterators\r\n*2\r\n"));
24750 assert!(
24751 tree(&mut f, &[b"LIMITED", b"QUERY", b"alpha|beta"])
24752 .contains("+Child iterators\r\n*2\r\n")
24753 );
24754 assert!(tree(&mut f, &[b"QUERY", b"alpha|beta"]).contains("+Query type\r\n+UNION\r\n"));
24758 assert!(
24759 tree(&mut f, &[b"QUERY", b"al*"]).contains("+Query type\r\n$11\r\nPREFIX - al\r\n")
24760 );
24761 }
24762
24763 #[test]
24766 fn the_steps_a_search_runs_depend_on_what_was_asked_for() {
24767 let mut f = profiling();
24768 let steps = |f: &mut Fixture, words: &[&[u8]]| {
24769 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"];
24770 argv.extend_from_slice(words);
24771 let reply = timeless(&f.run(&argv));
24772 let at = reply.find("+Result processors").expect("a list of steps");
24773 let end = reply.find("+Coordinator").expect("an end");
24774 let mut out = Vec::new();
24775 let mut parts = reply[at..end].split("\r\n").peekable();
24776 while let Some(part) = parts.next() {
24777 if part == "+Type" {
24778 out.push(parts.next().unwrap_or_default().to_string());
24779 }
24780 }
24781 out
24782 };
24783 assert_eq!(
24784 steps(&mut f, &[]),
24785 ["+Index", "+Scorer", "+Sorter", "+Loader"]
24786 );
24787 assert_eq!(
24788 steps(&mut f, &[b"NOCONTENT"]),
24789 ["+Index", "+Scorer", "+Sorter"]
24790 );
24791 assert_eq!(
24794 steps(&mut f, &[b"LIMIT", b"0", b"0"]),
24795 ["+Index", "+Counter"]
24796 );
24797 assert_eq!(
24800 steps(&mut f, &[b"SORTBY", b"n"]),
24801 ["+Index", "+Sorter", "+Loader"]
24802 );
24803 assert_eq!(
24804 steps(&mut f, &[b"SORTBY", b"n", b"WITHSCORES"]),
24805 ["+Index", "+Scorer", "+Sorter", "+Loader"]
24806 );
24807 assert_eq!(
24808 steps(&mut f, &[b"HIGHLIGHT"]),
24809 ["+Index", "+Scorer", "+Sorter", "+Loader", "+Highlighter"]
24810 );
24811 assert_eq!(
24812 steps(&mut f, &[b"SUMMARIZE", b"NOCONTENT"]),
24813 ["+Index", "+Scorer", "+Sorter"]
24814 );
24815 }
24816
24817 #[test]
24820 fn a_pipeline_names_every_step_after_what_it_runs() {
24821 let mut f = profiling();
24822 let steps = |f: &mut Fixture, words: &[&[u8]]| {
24823 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"AGGREGATE", b"QUERY", b"*"];
24824 argv.extend_from_slice(words);
24825 let reply = timeless(&f.run(&argv));
24826 let at = reply.find("+Result processors").expect("a list of steps");
24827 let end = reply.find("+Coordinator").expect("an end");
24828 let mut out = Vec::new();
24829 let mut parts = reply[at..end].split("\r\n").peekable();
24830 while let Some(part) = parts.next() {
24831 if part == "+Type" {
24832 out.push(parts.next().unwrap_or_default().to_string());
24833 }
24834 }
24835 out
24836 };
24837 assert_eq!(steps(&mut f, &[]), ["+Index"]);
24838 assert_eq!(
24839 steps(&mut f, &[b"APPLY", b"1", b"AS", b"one"]),
24840 ["+Index", "+Projector - Literal 1"]
24841 );
24842 assert_eq!(
24843 steps(
24844 &mut f,
24845 &[b"LOAD", b"1", b"@n", b"APPLY", b"@n * 2", b"AS", b"d"]
24846 ),
24847 ["+Index", "+Loader", "+Projector - Operator *"]
24848 );
24849 assert_eq!(
24850 steps(&mut f, &[b"LOAD", b"1", b"@n", b"FILTER", b"@n > 1"]),
24851 ["+Index", "+Loader", "+Filter - Predicate >"]
24852 );
24853 assert_eq!(
24854 steps(
24855 &mut f,
24856 &[b"GROUPBY", b"1", b"@n", b"REDUCE", b"COUNT", b"0"]
24857 ),
24858 ["+Index", "+Loader", "+Grouper"]
24859 );
24860 assert_eq!(
24861 steps(&mut f, &[b"SORTBY", b"1", b"@n"]),
24862 ["+Index", "+Loader", "+Sorter"]
24863 );
24864 assert_eq!(
24865 steps(&mut f, &[b"LIMIT", b"0", b"2"]),
24866 ["+Index", "+Pager/Limiter"]
24867 );
24868 assert_eq!(
24871 steps(
24872 &mut f,
24873 &[
24874 b"ADDSCORES",
24875 b"LOAD",
24876 b"1",
24877 b"@n",
24878 b"APPLY",
24879 b"@__score",
24880 b"AS",
24881 b"s"
24882 ]
24883 ),
24884 [
24885 "+Index",
24886 "+Scorer",
24887 "+Loader",
24888 "+Projector - Property __score"
24889 ]
24890 );
24891 }
24892
24893 #[test]
24901 fn a_sortable_field_is_read_without_the_key_being_opened() {
24902 let mut f = Fixture::new();
24903 f.run(&[
24904 b"FT.CREATE",
24905 b"sx",
24906 b"PREFIX",
24907 b"1",
24908 b"s:",
24909 b"SCHEMA",
24910 b"n",
24911 b"NUMERIC",
24912 b"SORTABLE",
24913 b"g",
24914 b"TAG",
24915 ]);
24916 f.run(&[b"HSET", b"s:1", b"n", b"1", b"g", b"one"]);
24917 f.run(&[b"HSET", b"s:2", b"n", b"2", b"g", b"two"]);
24918 let loads = |f: &mut Fixture, words: &[&[u8]]| {
24919 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"sx", b"AGGREGATE", b"QUERY", b"*"];
24920 argv.extend_from_slice(words);
24921 f.run(&argv).contains("+Loader")
24922 };
24923 assert!(!loads(&mut f, &[b"LOAD", b"1", b"@n"]));
24924 assert!(!loads(&mut f, &[b"SORTBY", b"1", b"@n"]));
24925 assert!(!loads(&mut f, &[b"APPLY", b"@n * 2", b"AS", b"d"]));
24926 assert!(loads(&mut f, &[b"LOAD", b"1", b"@g"]));
24927 assert!(loads(&mut f, &[b"LOAD", b"2", b"@n", b"@g"]));
24928 assert!(loads(
24929 &mut f,
24930 &[b"GROUPBY", b"1", b"@g", b"REDUCE", b"COUNT", b"0"]
24931 ));
24932 assert!(loads(&mut f, &[b"LOAD", b"*"]));
24933 }
24934
24935 #[test]
24938 fn a_profile_checks_its_own_words_before_it_runs_anything() {
24939 let mut f = profiling();
24940 assert_eq!(
24941 f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY"]),
24942 "-ERR wrong number of arguments for 'FT.PROFILE' command\r\n"
24943 );
24944 assert_eq!(
24945 f.run(&[b"FT.PROFILE", b"ix", b"BOGUS", b"QUERY", b"alpha"]),
24946 "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
24947 );
24948 assert_eq!(
24951 f.run(&[
24952 b"FT.PROFILE",
24953 b"ix",
24954 b"LIMITED",
24955 b"SEARCH",
24956 b"QUERY",
24957 b"alpha"
24958 ]),
24959 "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
24960 );
24961 assert_eq!(
24962 f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"BOGUS", b"alpha"]),
24963 "-The QUERY keyword is expected\r\n"
24964 );
24965 assert_eq!(
24966 f.run(&[
24967 b"FT.PROFILE",
24968 b"ix",
24969 b"AGGREGATE",
24970 b"QUERY",
24971 b"alpha",
24972 b"WITHCURSOR"
24973 ]),
24974 "-FT.PROFILE does not support cursor\r\n"
24975 );
24976 assert_eq!(
24979 f.run(&[b"FT.PROFILE", b"nope", b"SEARCH", b"QUERY", b"alpha"]),
24980 "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
24981 );
24982 assert_eq!(
24983 f.run(&[
24984 b"FT.PROFILE",
24985 b"ix",
24986 b"SEARCH",
24987 b"QUERY",
24988 b"alpha",
24989 b"extra"
24990 ]),
24991 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `extra` at position 1 for <main>\r\n"
24992 );
24993 }
24994
24995 #[test]
24997 fn the_words_of_a_profile_are_read_the_way_every_other_word_is() {
24998 let mut f = profiling();
24999 let one = f.run(&[
25000 b"FT.PROFILE",
25001 b"ix",
25002 b"search",
25003 b"limited",
25004 b"query",
25005 b"alpha",
25006 ]);
25007 let two = f.run(&[
25008 b"FT.PROFILE",
25009 b"ix",
25010 b"SEARCH",
25011 b"LIMITED",
25012 b"QUERY",
25013 b"alpha",
25014 ]);
25015 assert_eq!(timeless(&one), timeless(&two));
25016 }
25017
25018 #[test]
25023 fn the_two_ways_of_dropping_an_index_disagree_about_the_documents() {
25024 let mut f = profiling();
25025 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix"]), "+OK\r\n");
25026 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25027
25028 let mut f = profiling();
25029 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
25030 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
25031
25032 let mut f = profiling();
25033 assert_eq!(f.run(&[b"FT.DROP", b"ix"]), "+OK\r\n");
25034 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
25035
25036 let mut f = profiling();
25037 assert_eq!(f.run(&[b"FT.DROP", b"ix", b"KEEPDOCS"]), "+OK\r\n");
25038 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25039 }
25040
25041 #[test]
25044 fn neither_way_of_dropping_an_index_takes_the_other_ones_word() {
25045 let mut f = profiling();
25046 let line = "-SEARCH_ARG_UNRECOGNIZED Unknown argument\r\n";
25047 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"KEEPDOCS"]), line);
25048 assert_eq!(f.run(&[b"FT.DROP", b"ix", b"DD"]), line);
25049 assert_eq!(f.run(&[b"FT._LIST"]), "*1\r\n+ix\r\n");
25051 }
25052
25053 #[test]
25056 fn dropping_the_documents_leaves_a_key_the_index_never_read() {
25057 let mut f = profiling();
25058 f.run(&[b"SET", b"p:4", b"alpha"]);
25059 f.run(&[b"HSET", b"q:1", b"t", b"alpha"]);
25060 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
25061 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
25062 assert_eq!(f.run(&[b"EXISTS", b"p:4", b"q:1"]), ":2\r\n");
25063 }
25064
25065 #[test]
25068 fn another_index_over_the_same_keys_loses_the_documents_too() {
25069 let mut f = profiling();
25070 f.run(&[
25071 b"FT.CREATE",
25072 b"other",
25073 b"PREFIX",
25074 b"1",
25075 b"p:",
25076 b"SCHEMA",
25077 b"t",
25078 b"TEXT",
25079 ]);
25080 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
25081 assert_eq!(
25082 f.run(&[b"FT.SEARCH", b"other", b"alpha", b"NOCONTENT"]),
25083 "*1\r\n:0\r\n"
25084 );
25085 }
25086
25087 #[test]
25090 fn a_drop_of_an_index_that_is_not_there_touches_no_keys() {
25091 let mut f = profiling();
25092 assert_eq!(f.run(&[b"FT._DROPINDEXIFX", b"nope", b"DD"]), "+OK\r\n");
25093 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25094 assert_eq!(f.run(&[b"FT._DROPIFX", b"nope"]), "+OK\r\n");
25095 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25096 }
25097
25098 #[test]
25103 fn a_setting_reads_back_as_a_pair_on_one_protocol_and_a_map_on_the_other() {
25104 let mut f = Fixture::new();
25105 assert_eq!(
25106 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25107 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25108 );
25109 assert_eq!(
25110 f.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
25111 "*1\r\n*2\r\n+EXTLOAD\r\n$-1\r\n"
25112 );
25113 let mut g = Fixture::new();
25114 g.run(&[b"HELLO", b"3"]);
25115 assert_eq!(
25116 g.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25117 "%1\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25118 );
25119 assert_eq!(
25120 g.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
25121 "%1\r\n+EXTLOAD\r\n_\r\n"
25122 );
25123 }
25124
25125 #[test]
25128 fn a_help_row_carries_the_description_and_the_value_together() {
25129 let mut f = Fixture::new();
25130 assert_eq!(
25131 f.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
25132 "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
25133 +Value\r\n$3\r\n500\r\n"
25134 );
25135 let mut g = Fixture::new();
25136 g.run(&[b"HELLO", b"3"]);
25137 assert_eq!(
25138 g.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
25139 "%1\r\n+TIMEOUT\r\n%2\r\n+Description\r\n+Query (search) timeout\r\n\
25140 +Value\r\n$3\r\n500\r\n"
25141 );
25142 }
25143
25144 #[test]
25147 fn only_a_bare_star_asks_for_every_setting_and_nothing_else_globs() {
25148 let mut f = Fixture::new();
25149 assert_eq!(
25150 f.run(&[b"FT.CONFIG", b"GET", b"timeout"]),
25151 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25152 );
25153 for name in [
25154 b"TIMEOUT*".as_slice(),
25155 b"?IMEOUT",
25156 b"*TIMEOUT*",
25157 b"TIME",
25158 b"NOSUCH",
25159 b"",
25160 ] {
25161 assert_eq!(f.run(&[b"FT.CONFIG", b"GET", name]), "*0\r\n", "{name:?}");
25162 }
25163 assert!(f.run(&[b"FT.CONFIG", b"GET", b"*"]).starts_with("*69\r\n"));
25164 assert!(f.run(&[b"FT.CONFIG", b"HELP", b"*"]).starts_with("*69\r\n"));
25165 }
25166
25167 #[test]
25170 fn a_read_ignores_whatever_follows_the_name() {
25171 let mut f = Fixture::new();
25172 assert_eq!(
25173 f.run(&[b"FT.CONFIG", b"GET", b"timeout", b"extra", b"more"]),
25174 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25175 );
25176 assert_eq!(
25177 f.run(&[b"FT.CONFIG", b"HELP", b"timeout", b"extra"]),
25178 "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
25179 +Value\r\n$3\r\n500\r\n"
25180 );
25181 }
25182
25183 #[test]
25186 fn a_missing_subcommand_and_a_missing_name_are_told_apart() {
25187 let mut f = Fixture::new();
25188 assert_eq!(
25189 f.run(&[b"FT.CONFIG"]),
25190 "-ERR wrong number of arguments for 'FT.CONFIG' command\r\n"
25191 );
25192 for sub in [b"GET".as_slice(), b"SET", b"HELP"] {
25193 let want = format!(
25194 "-ERR wrong number of arguments for 'FT.CONFIG|{}' command\r\n",
25195 String::from_utf8_lossy(sub)
25196 );
25197 assert_eq!(f.run(&[b"FT.CONFIG", sub]), want);
25198 }
25199 assert_eq!(
25200 f.run(&[b"ft.config", b"get"]),
25201 "-ERR wrong number of arguments for 'FT.CONFIG|GET' command\r\n"
25202 );
25203 assert_eq!(
25204 f.run(&[b"FT.CONFIG", b"bogus"]),
25205 "-ERR unknown subcommand 'bogus'. Try FT.CONFIG HELP.\r\n"
25206 );
25207 }
25208
25209 #[test]
25212 fn a_write_checks_the_name_then_the_setting_then_the_value() {
25213 let mut f = Fixture::new();
25214 for tail in [vec![b"1".as_slice()], vec![], vec![b"1", b"2", b"3"]] {
25215 let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"NOSUCH"];
25216 cmd.extend(tail);
25217 assert_eq!(f.run(&cmd), "-SEARCH_OPTION_INVALID Invalid option\r\n");
25218 }
25219 for tail in [vec![b"1000".as_slice()], vec![], vec![b"x", b"y"]] {
25220 let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"MAXDOCTABLESIZE"];
25221 cmd.extend(tail);
25222 assert_eq!(
25223 f.run(&cmd),
25224 "-SEARCH_OPTION_BAD Not modifiable at runtime\r\n"
25225 );
25226 }
25227 assert_eq!(
25228 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"x", b"y", b"z"]),
25229 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n"
25230 );
25231 }
25232
25233 #[test]
25236 fn an_excess_of_words_is_noticed_after_the_value_is_kept() {
25237 let mut f = Fixture::new();
25238 assert_eq!(
25239 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"500"]),
25240 "+OK\r\n"
25241 );
25242 assert_eq!(
25243 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"600", b"junk"]),
25244 "+EXCESSARGS\r\n"
25245 );
25246 assert_eq!(
25247 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25248 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n600\r\n"
25249 );
25250 }
25251
25252 #[test]
25255 fn a_number_is_read_the_strict_way_and_then_the_loose_one() {
25256 let mut f = Fixture::new();
25257 for (given, want) in [
25258 (b"0x10".as_slice(), "16"),
25259 (b"0X1f", "31"),
25260 (b"+0x10", "16"),
25261 (b"+5", "5"),
25262 (b"010", "10"),
25263 (b"08", "8"),
25264 (b"0777", "777"),
25265 (b"1e3", "1000"),
25266 (b"0.0", "0"),
25267 (b"-0.0", "0"),
25268 ] {
25269 assert_eq!(
25270 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25271 "+OK\r\n",
25272 "{given:?}"
25273 );
25274 let want = format!("*1\r\n*2\r\n+TIMEOUT\r\n${}\r\n{want}\r\n", want.len());
25275 assert_eq!(
25276 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25277 want,
25278 "{given:?}"
25279 );
25280 }
25281 for given in [
25282 b" 5".as_slice(),
25283 b"5 ",
25284 b"1.5",
25285 b"1e-3",
25286 b"x",
25287 b"",
25288 b"0b11",
25289 b"0xg",
25290 b"nan",
25291 b"inf",
25292 b"1e100",
25293 b"99999999999999999999",
25294 ] {
25295 assert_eq!(
25296 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25297 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
25298 "{given:?}"
25299 );
25300 }
25301 }
25302
25303 #[test]
25306 fn a_negative_is_answered_by_whichever_reader_found_it() {
25307 let mut f = Fixture::new();
25308 for given in [b"-1".as_slice(), b"-16"] {
25309 assert_eq!(
25310 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25311 "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n",
25312 "{given:?}"
25313 );
25314 }
25315 for given in [b"-0x10".as_slice(), b"-1e3", b"-010", b"-2.0"] {
25316 assert_eq!(
25317 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25318 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
25319 "{given:?}"
25320 );
25321 }
25322 let unlimited = "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n$9\r\nunlimited\r\n";
25323 for given in [b"-1".as_slice(), b"-0x10", b"-1e3", b"-010"] {
25324 assert_eq!(
25325 f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
25326 "+OK\r\n",
25327 "{given:?}"
25328 );
25329 assert_eq!(
25330 f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
25331 unlimited,
25332 "{given:?}"
25333 );
25334 }
25335 }
25336
25337 #[test]
25340 fn a_wide_setting_wraps_into_its_slot_before_it_is_read_back() {
25341 let mut f = Fixture::new();
25342 for (given, want) in [
25343 (b"2147483647".as_slice(), "2147483647"),
25344 (b"2147483648", "unlimited"),
25345 (b"4294967295", "unlimited"),
25346 (b"9223372036854775806", "unlimited"),
25347 (b"0", "0"),
25348 ] {
25349 assert_eq!(
25350 f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
25351 "+OK\r\n",
25352 "{given:?}"
25353 );
25354 let want = format!(
25355 "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n${}\r\n{want}\r\n",
25356 want.len()
25357 );
25358 assert_eq!(
25359 f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
25360 want,
25361 "{given:?}"
25362 );
25363 }
25364 }
25365
25366 #[test]
25369 fn a_number_out_of_range_names_the_limit_it_crossed() {
25370 let mut f = Fixture::new();
25371 let bounds = "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n";
25372 for (name, given) in [
25373 (b"MINPREFIX".as_slice(), b"0".as_slice()),
25374 (b"MAX_AGGREGATE_GROUPS", b"0"),
25375 (b"BM25STD_TANH_FACTOR", b"0"),
25376 (b"DEFAULT_DIALECT", b"0"),
25377 (b"MINSTEMLEN", b"4294967296"),
25378 (b"_BG_INDEX_OOM_PAUSE_TIME", b"4294967296"),
25379 (b"INDEXER_YIELD_EVERY_OPS", b"4294967296"),
25380 (b"CONNECT_TIMEOUT", b"2147483648"),
25381 ] {
25382 assert_eq!(
25383 f.run(&[b"FT.CONFIG", b"SET", name, given]),
25384 bounds,
25385 "{name:?}"
25386 );
25387 }
25388 for (name, given, want) in [
25389 (
25390 b"MINSTEMLEN".as_slice(),
25391 b"1".as_slice(),
25392 "-SEARCH_SYNTAX Minimum stem length cannot be lower than 2\r\n",
25393 ),
25394 (
25395 b"MAX_AGGREGATE_GROUPS",
25396 b"67108865",
25397 "-SEARCH_LIMIT_OVER Value exceeds maximum possible aggregate groups\r\n",
25398 ),
25399 (
25400 b"WORKERS",
25401 b"17",
25402 "-SEARCH_LIMIT_OVER Number of worker threads cannot exceed 16\r\n",
25403 ),
25404 (
25405 b"_NUMERIC_RANGES_PARENTS",
25406 b"3",
25407 "-SEARCH_PARSE_ARGS Max depth for range cannot be higher than max \
25408 depth for balance\r\n",
25409 ),
25410 (
25411 b"DEFAULT_DIALECT",
25412 b"5",
25413 "-SEARCH_VALUE_BAD Default dialect version cannot be higher than 4\r\n",
25414 ),
25415 (
25416 b"_BG_INDEX_MEM_PCT_THR",
25417 b"101",
25418 "-SEARCH_LIMIT_OVER Memory limit for indexing cannot be greater then \
25419 100%\r\n",
25420 ),
25421 (
25422 b"BM25STD_TANH_FACTOR",
25423 b"10001",
25424 "-SEARCH_LIMIT_OVER BM25STD_TANH_FACTOR must be between 1 and 10000 \
25425 inclusive\r\n",
25426 ),
25427 (
25428 b"BG_INDEX_SLEEP_DURATION_US",
25429 b"1000000",
25430 "-SEARCH_LIMIT_OVER BG_INDEX_SLEEP_DURATION_US must be between 1 and \
25431 999999 (usleep POSIX limit)\r\n",
25432 ),
25433 ] {
25434 assert_eq!(
25435 f.run(&[b"FT.CONFIG", b"SET", name, given]),
25436 want,
25437 "{name:?}"
25438 );
25439 }
25440 }
25441
25442 #[test]
25445 fn the_trimming_delays_are_checked_against_one_another() {
25446 let mut f = Fixture::new();
25447 assert_eq!(
25448 f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"5000"]),
25449 "-SEARCH_PARSE_ARGS _MIN_TRIM_DELAY_MS (5000) must be less than \
25450 _MAX_TRIM_DELAY_MS (5000)\r\n"
25451 );
25452 assert_eq!(
25453 f.run(&[b"FT.CONFIG", b"SET", b"_MAX_TRIM_DELAY_MS", b"1999"]),
25454 "-SEARCH_PARSE_ARGS _MAX_TRIM_DELAY_MS (1999) must be greater than \
25455 _MIN_TRIM_DELAY_MS (2000)\r\n"
25456 );
25457 assert_eq!(
25458 f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"4999"]),
25459 "+OK\r\n"
25460 );
25461 }
25462
25463 #[test]
25466 fn a_word_setting_folds_where_a_real_server_folds_and_not_otherwise() {
25467 let mut f = Fixture::new();
25468 assert_eq!(
25469 f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"RETURN"]),
25470 "+OK\r\n"
25471 );
25472 assert_eq!(
25473 f.run(&[b"FT.CONFIG", b"GET", b"ON_TIMEOUT"]),
25474 "*1\r\n*2\r\n+ON_TIMEOUT\r\n$6\r\nreturn\r\n"
25475 );
25476 assert_eq!(
25477 f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"nope"]),
25478 "-SEARCH_VALUE_BAD Invalid ON_TIMEOUT value\r\n"
25479 );
25480 assert_eq!(
25481 f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"IGNORE"]),
25482 "+OK\r\n"
25483 );
25484 assert_eq!(
25485 f.run(&[b"FT.CONFIG", b"GET", b"ON_OOM"]),
25486 "*1\r\n*2\r\n+ON_OOM\r\n$6\r\nignore\r\n"
25487 );
25488 assert_eq!(
25489 f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"nope"]),
25490 "-SEARCH_VALUE_BAD Invalid ON_OOM value\r\n"
25491 );
25492 let bad = "-SEARCH_VALUE_BAD Invalid default scorer value\r\n";
25493 for given in [b"bm25std".as_slice(), b"Bm25", b"TFIDF.docnorm", b""] {
25494 assert_eq!(
25495 f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", given]),
25496 bad,
25497 "{given:?}"
25498 );
25499 }
25500 assert_eq!(
25501 f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", b"TFIDF.DOCNORM"]),
25502 "+OK\r\n"
25503 );
25504 }
25505
25506 #[test]
25509 fn a_yes_or_no_setting_takes_those_two_words_only() {
25510 let mut f = Fixture::new();
25511 assert_eq!(
25512 f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", b"TRUE"]),
25513 "+OK\r\n"
25514 );
25515 assert_eq!(
25516 f.run(&[b"FT.CONFIG", b"GET", b"_NUMERIC_COMPRESS"]),
25517 "*1\r\n*2\r\n+_NUMERIC_COMPRESS\r\n$4\r\ntrue\r\n"
25518 );
25519 for given in [b"yes".as_slice(), b"no", b"1", b"0", b"enabled", b""] {
25520 assert_eq!(
25521 f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", given]),
25522 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
25523 "{given:?}"
25524 );
25525 }
25526 }
25527
25528 #[test]
25531 fn two_names_for_one_setting_move_together() {
25532 let mut f = Fixture::new();
25533 f.run(&[b"FT.CONFIG", b"SET", b"MAXEXPANSIONS", b"300"]);
25534 assert_eq!(
25535 f.run(&[b"FT.CONFIG", b"GET", b"MAXPREFIXEXPANSIONS"]),
25536 "*1\r\n*2\r\n+MAXPREFIXEXPANSIONS\r\n$3\r\n300\r\n"
25537 );
25538 f.run(&[b"FT.CONFIG", b"SET", b"MAXPREFIXEXPANSIONS", b"200"]);
25539 assert_eq!(
25540 f.run(&[b"FT.CONFIG", b"GET", b"MAXEXPANSIONS"]),
25541 "*1\r\n*2\r\n+MAXEXPANSIONS\r\n$3\r\n200\r\n"
25542 );
25543 let long = b"_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
25544 let short = b"FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
25545 f.run(&[b"FT.CONFIG", b"SET", long, b"false"]);
25546 assert_eq!(
25547 f.run(&[b"FT.CONFIG", b"GET", short]),
25548 "*1\r\n*2\r\n+FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$5\r\nfalse\r\n"
25549 );
25550 assert_eq!(f.run(&[b"FT.CONFIG", b"SET", short]), "+OK\r\n");
25551 assert_eq!(
25552 f.run(&[b"FT.CONFIG", b"GET", long]),
25553 "*1\r\n*2\r\n+_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$4\r\ntrue\r\n"
25554 );
25555 }
25556
25557 #[test]
25559 fn a_password_reads_back_as_stars_whatever_was_written() {
25560 let mut f = Fixture::new();
25561 assert_eq!(
25562 f.run(&[b"FT.CONFIG", b"SET", b"OSS_GLOBAL_PASSWORD", b"hunter2"]),
25563 "+OK\r\n"
25564 );
25565 assert_eq!(
25566 f.run(&[b"FT.CONFIG", b"GET", b"OSS_GLOBAL_PASSWORD"]),
25567 "*1\r\n*2\r\n+OSS_GLOBAL_PASSWORD\r\n$17\r\nPassword: *******\r\n"
25568 );
25569 }
25570
25571 #[test]
25574 fn a_flush_leaves_the_settings_alone() {
25575 for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
25576 let mut f = Fixture::new();
25577 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"777"]);
25578 f.run(&[flush]);
25579 assert_eq!(
25580 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25581 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n777\r\n",
25582 "{flush:?}"
25583 );
25584 }
25585 }
25586
25587 fn debugging() -> Fixture {
25592 let mut f = Fixture::new();
25593 f.run(&[
25594 b"FT.CREATE",
25595 b"dx",
25596 b"PREFIX",
25597 b"1",
25598 b"d:",
25599 b"SCHEMA",
25600 b"t",
25601 b"TEXT",
25602 b"g",
25603 b"TAG",
25604 b"n",
25605 b"NUMERIC",
25606 b"s",
25607 b"TEXT",
25608 b"SORTABLE",
25609 ]);
25610 f.run(&[
25611 b"HSET",
25612 b"d:1",
25613 b"t",
25614 b"running dogs",
25615 b"g",
25616 b"red,blue",
25617 b"n",
25618 b"1",
25619 b"s",
25620 b"Alpha",
25621 ]);
25622 f.run(&[
25623 b"HSET", b"d:2", b"t", b"running", b"g", b"red", b"n", b"2", b"s", b"beta",
25624 ]);
25625 f.run(&[
25626 b"HSET",
25627 b"d:3",
25628 b"t",
25629 b"dogs alpha",
25630 b"g",
25631 b"green",
25632 b"n",
25633 b"3",
25634 ]);
25635 f
25636 }
25637
25638 #[test]
25641 fn a_term_dump_lists_the_stems_beside_the_words() {
25642 let mut f = debugging();
25643 assert_eq!(
25644 f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"dx"]),
25645 "*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\
25646 $4\r\ndogs\r\n$7\r\nrunning\r\n"
25647 );
25648 }
25649
25650 #[test]
25653 fn a_posting_list_is_read_by_the_bytes_and_not_by_the_word() {
25654 let mut f = debugging();
25655 assert_eq!(
25656 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
25657 "*2\r\n:1\r\n:2\r\n"
25658 );
25659 assert_eq!(
25660 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"+run"]),
25661 "*2\r\n:1\r\n:2\r\n"
25662 );
25663 for term in [b"RUNNING".as_slice(), b"nosuchterm", b""] {
25664 assert_eq!(
25665 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", term]),
25666 "-Can not find the inverted index\r\n",
25667 "{term:?}"
25668 );
25669 }
25670 }
25671
25672 #[test]
25675 fn a_tag_dump_pairs_every_value_with_its_documents() {
25676 let mut f = debugging();
25677 assert_eq!(
25678 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"dx", b"g"]),
25679 "*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\
25680 *2\r\n$3\r\nred\r\n*2\r\n:1\r\n:2\r\n"
25681 );
25682 }
25683
25684 #[test]
25687 fn a_number_dump_answers_a_single_range() {
25688 let mut f = debugging();
25689 assert_eq!(
25690 f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"dx", b"n"]),
25691 "*1\r\n*3\r\n:1\r\n:2\r\n:3\r\n"
25692 );
25693 }
25694
25695 #[test]
25698 fn a_geo_field_is_dumped_as_a_numeric_one() {
25699 let mut f = Fixture::new();
25700 f.run(&[
25701 b"FT.CREATE",
25702 b"gx",
25703 b"PREFIX",
25704 b"1",
25705 b"q:",
25706 b"SCHEMA",
25707 b"loc",
25708 b"GEO",
25709 b"gg",
25710 b"AS",
25711 b"tag",
25712 b"TAG",
25713 ]);
25714 f.run(&[b"HSET", b"q:1", b"loc", b"1,2", b"gg", b"red"]);
25715 f.run(&[b"HSET", b"q:2", b"loc", b"3,4", b"gg", b"BLUE"]);
25716 assert_eq!(
25717 f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"gx", b"loc"]),
25718 "*1\r\n*2\r\n:1\r\n:2\r\n"
25719 );
25720 assert_eq!(
25721 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"gx", b"loc"]),
25722 "-Could not find given field in index spec\r\n"
25723 );
25724 }
25725
25726 #[test]
25729 fn a_dump_takes_the_attribute_and_not_the_identifier() {
25730 let mut f = Fixture::new();
25731 f.run(&[
25732 b"FT.CREATE",
25733 b"zx",
25734 b"PREFIX",
25735 b"1",
25736 b"z:",
25737 b"SCHEMA",
25738 b"gg",
25739 b"AS",
25740 b"tag",
25741 b"TAG",
25742 ]);
25743 f.run(&[b"HSET", b"z:1", b"gg", b"red"]);
25744 assert_eq!(
25745 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"zx", b"tag"]),
25746 "*1\r\n*2\r\n$3\r\nred\r\n*1\r\n:1\r\n"
25747 );
25748 assert_eq!(
25749 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"zx", b"gg"]),
25750 "-Could not find given field in index spec\r\n"
25751 );
25752 }
25753
25754 #[test]
25757 fn a_document_row_is_flat_on_one_protocol_and_a_map_on_the_other() {
25758 let mut f = debugging();
25759 assert_eq!(
25760 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]),
25761 "*14\r\n+internal_id\r\n:1\r\n$5\r\nflags\r\n\
25762 $36\r\n(0xc):HasSortVector,HasOffsetVector,\r\n+score\r\n$1\r\n1\r\n\
25763 +num_tokens\r\n:3\r\n+max_freq\r\n:1\r\n+refcount\r\n:1\r\n\
25764 +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\
25765 $5\r\nvalue\r\n$5\r\nalpha\r\n"
25766 );
25767 let mut g = debugging();
25768 g.run(&[b"HELLO", b"3"]);
25769 assert_eq!(
25770 g.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]),
25771 "%7\r\n+internal_id\r\n:1\r\n$5\r\nflags\r\n\
25772 $36\r\n(0xc):HasSortVector,HasOffsetVector,\r\n+score\r\n,1\r\n\
25773 +num_tokens\r\n:3\r\n+max_freq\r\n:1\r\n+refcount\r\n:1\r\n\
25774 +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\
25775 $5\r\nvalue\r\n$5\r\nalpha\r\n"
25776 );
25777 }
25778
25779 #[test]
25782 fn a_document_with_no_sortable_value_drops_the_key() {
25783 let mut f = debugging();
25784 assert_eq!(
25785 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:3", b"REVEAL"]),
25786 "*12\r\n+internal_id\r\n:3\r\n$5\r\nflags\r\n$22\r\n(0x8):HasOffsetVector,\r\n\
25787 +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"
25788 );
25789 }
25790
25791 #[test]
25794 fn the_flag_word_spells_out_the_bits_it_carries() {
25795 let mut f = Fixture::new();
25796 f.run(&[
25797 b"FT.CREATE",
25798 b"nx",
25799 b"NOOFFSETS",
25800 b"PREFIX",
25801 b"1",
25802 b"o:",
25803 b"SCHEMA",
25804 b"t",
25805 b"TEXT",
25806 ]);
25807 f.run(&[b"HSET", b"o:1", b"t", b"alpha"]);
25808 assert!(
25809 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"nx", b"o:1", b"REVEAL"])
25810 .contains("$6\r\n(0x0):\r\n")
25811 );
25812 }
25813
25814 #[test]
25817 fn obfuscation_numbers_a_field_by_its_place_in_the_schema() {
25818 let mut f = debugging();
25819 assert!(
25820 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"OBFUSCATE"])
25821 .contains("$22\r\nFieldPath@3 AS Field@3\r\n")
25822 );
25823 }
25824
25825 #[test]
25828 fn a_document_row_reads_its_keyword_at_a_fixed_place() {
25829 let mut f = debugging();
25830 let want = f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]);
25831 assert_eq!(
25832 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL", b"more"]),
25833 want
25834 );
25835 assert_eq!(
25836 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"more", b"REVEAL"]),
25837 "-Invalid argument. Expected REVEAL or OBFUSCATE as the last argument\r\n"
25838 );
25839 assert_eq!(
25840 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1"]),
25841 "-ERR wrong number of arguments for '_FT.DEBUG|DOCINFO' command\r\n"
25842 );
25843 }
25844
25845 #[test]
25848 fn a_document_row_looks_the_key_up_before_it_reads_the_keyword() {
25849 let mut f = debugging();
25850 assert_eq!(
25851 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"nope", b"zz"]),
25852 "-Document not found in index\r\n"
25853 );
25854 assert_eq!(
25855 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"zz"]),
25856 "-Invalid argument. Expected REVEAL or OBFUSCATE as the last argument\r\n"
25857 );
25858 }
25859
25860 #[test]
25863 fn a_document_number_goes_both_ways() {
25864 let mut f = debugging();
25865 assert_eq!(
25866 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"2"]),
25867 "$3\r\nd:2\r\n"
25868 );
25869 assert_eq!(
25870 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:2"]),
25871 ":2\r\n"
25872 );
25873 assert_eq!(
25874 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"nope"]),
25875 ":0\r\n"
25876 );
25877 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"dx"]), ":3\r\n");
25878 for id in [b"9".as_slice(), b"0", b"-1", b"9223372036854775807"] {
25879 assert_eq!(
25880 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", id]),
25881 "-document was removed\r\n",
25882 "{id:?}"
25883 );
25884 }
25885 }
25886
25887 #[test]
25890 fn a_document_number_is_read_the_strict_way() {
25891 let mut f = debugging();
25892 for id in [
25893 b"x".as_slice(),
25894 b"1.5",
25895 b" 1",
25896 b"+1",
25897 b"01",
25898 b"0x1",
25899 b"",
25900 b"9223372036854775808",
25901 b"18446744073709551615",
25902 ] {
25903 assert_eq!(
25904 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", id]),
25905 "-bad id given\r\n",
25906 "{id:?}"
25907 );
25908 }
25909 }
25910
25911 #[test]
25914 fn a_dump_keeps_a_number_the_table_has_given_up() {
25915 let mut f = debugging();
25916 f.run(&[b"DEL", b"d:2"]);
25917 assert_eq!(
25918 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
25919 "*2\r\n:1\r\n:2\r\n"
25920 );
25921 assert_eq!(
25922 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"2"]),
25923 "-document was removed\r\n"
25924 );
25925 assert_eq!(
25926 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:2"]),
25927 ":0\r\n"
25928 );
25929 }
25930
25931 #[test]
25934 fn a_rewrite_takes_a_number_of_its_own() {
25935 let mut f = debugging();
25936 f.run(&[b"HSET", b"d:1", b"t", b"cats"]);
25937 assert_eq!(
25938 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:1"]),
25939 ":4\r\n"
25940 );
25941 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"dx"]), ":4\r\n");
25942 assert_eq!(
25943 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"1"]),
25944 "-document was removed\r\n"
25945 );
25946 assert_eq!(
25947 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
25948 "*2\r\n:1\r\n:2\r\n"
25949 );
25950 }
25951
25952 #[test]
25954 fn a_dump_follows_an_alias() {
25955 let mut f = debugging();
25956 f.run(&[b"FT.ALIASADD", b"da", b"dx"]);
25957 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"da"]), ":3\r\n");
25958 assert_eq!(
25959 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"da", b"1"]),
25960 "$3\r\nd:1\r\n"
25961 );
25962 }
25963
25964 #[test]
25967 fn an_index_name_is_case_sensitive_and_a_subcommand_name_is_not() {
25968 let mut f = debugging();
25969 assert_eq!(f.run(&[b"_FT.DEBUG", b"get_max_doc_id", b"dx"]), ":3\r\n");
25970 assert_eq!(
25971 f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"DX"]),
25972 "-Can not create a search ctx\r\n"
25973 );
25974 assert_eq!(
25975 f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"nope"]),
25976 "-Can not create a search ctx\r\n"
25977 );
25978 }
25979
25980 #[test]
25983 fn an_empty_field_dumps_as_nothing_at_all() {
25984 let mut f = Fixture::new();
25985 f.run(&[
25986 b"FT.CREATE",
25987 b"ex",
25988 b"PREFIX",
25989 b"1",
25990 b"e:",
25991 b"SCHEMA",
25992 b"t",
25993 b"TEXT",
25994 b"g",
25995 b"TAG",
25996 b"n",
25997 b"NUMERIC",
25998 ]);
25999 assert_eq!(f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"ex"]), "*0\r\n");
26000 assert_eq!(
26001 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"ex", b"g"]),
26002 "*0\r\n"
26003 );
26004 assert_eq!(
26005 f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"ex", b"n"]),
26006 "*0\r\n"
26007 );
26008 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"ex"]), ":0\r\n");
26009 }
26010
26011 #[test]
26014 fn the_two_lines_with_a_code_word_are_the_arity_and_the_unknown_one() {
26015 let mut f = debugging();
26016 for (sub, extra) in [
26017 (b"DUMP_TERMS".as_slice(), 1),
26018 (b"GET_MAX_DOC_ID", 1),
26019 (b"DUMP_INVIDX", 2),
26020 (b"DUMP_TAGIDX", 2),
26021 (b"DUMP_NUMIDX", 2),
26022 (b"IDTODOCID", 2),
26023 (b"DOCIDTOID", 2),
26024 ] {
26025 let want = format!(
26026 "-ERR wrong number of arguments for '_FT.DEBUG|{}' command\r\n",
26027 str::from_utf8(sub).unwrap()
26028 );
26029 for given in [extra - 1, extra + 1] {
26030 let mut cmd: Vec<&[u8]> = vec![b"_FT.DEBUG", sub];
26031 cmd.extend(std::iter::repeat_n(b"dx".as_slice(), given));
26032 assert_eq!(f.run(&cmd), want, "{sub:?} {given}");
26033 }
26034 let mut right: Vec<&[u8]> = vec![b"_FT.DEBUG", sub, b"dx"];
26035 right.extend(std::iter::repeat_n(b"g".as_slice(), extra - 1));
26036 assert_ne!(f.run(&right), want, "{sub:?}");
26037 }
26038 assert_eq!(
26039 f.run(&[b"_FT.DEBUG", b"bogus", b"dx"]),
26040 "-ERR unknown subcommand 'bogus'. Try _FT.DEBUG HELP.\r\n"
26041 );
26042 }
26043
26044 #[test]
26047 fn the_help_names_the_subcommands_that_answer() {
26048 let mut f = Fixture::new();
26049 let want = "*8\r\n$11\r\nDUMP_INVIDX\r\n$11\r\nDUMP_NUMIDX\r\n$11\r\nDUMP_TAGIDX\r\n\
26050 $9\r\nIDTODOCID\r\n$9\r\nDOCIDTOID\r\n$7\r\nDOCINFO\r\n$10\r\nDUMP_TERMS\r\n\
26051 $14\r\nGET_MAX_DOC_ID\r\n";
26052 assert_eq!(f.run(&[b"_FT.DEBUG", b"HELP"]), want);
26053 assert_eq!(f.run(&[b"_FT.DEBUG", b"HELP", b"extra"]), want);
26054 }
26055
26056 #[test]
26061 fn a_synonym_dump_folds_the_terms_and_keeps_the_ids_as_given() {
26062 let mut f = Fixture::new();
26063 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26064 assert_eq!(
26065 f.run(&[b"FT.SYNUPDATE", b"e", b"G1", b"BOY", b"kid"]),
26066 "+OK\r\n"
26067 );
26068 assert_eq!(f.run(&[b"FT.SYNUPDATE", b"e", b"g2", b"boy"]), "+OK\r\n");
26069 assert_eq!(
26070 f.run(&[b"FT.SYNDUMP", b"e"]),
26071 "*4\r\n$3\r\nboy\r\n*2\r\n$2\r\nG1\r\n$2\r\ng2\r\n\
26072 $3\r\nkid\r\n*1\r\n$2\r\nG1\r\n"
26073 );
26074 }
26075
26076 #[test]
26080 fn a_word_in_a_group_reads_as_a_union_with_the_group_term() {
26081 let mut f = Fixture::new();
26082 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26083 f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"jogging"]);
26084 assert_eq!(
26085 f.run(&[b"FT.EXPLAIN", b"e", b"jogging"]),
26086 "$69\r\nUNION {\n jogging\n ~gr(expanded)\n +jog(expanded)\n jog(expanded)\n}\n\r\n"
26087 );
26088 }
26089
26090 #[test]
26099 fn a_group_matches_the_word_it_holds_and_not_a_stem_of_it() {
26100 let mut f = Fixture::new();
26101 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26102 f.run(&[b"HSET", b"d1", b"t", b"boy"]);
26103 f.run(&[b"HSET", b"d2", b"t", b"runs"]);
26104 f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"boy", b"child", b"running"]);
26105 assert_eq!(
26106 f.run(&[b"FT.SEARCH", b"e", b"child", b"NOCONTENT"]),
26107 "*2\r\n:1\r\n$2\r\nd1\r\n"
26108 );
26109 assert_eq!(
26110 f.run(&[b"FT.SEARCH", b"e", b"running", b"NOCONTENT"]),
26111 "*3\r\n:2\r\n$2\r\nd1\r\n$2\r\nd2\r\n"
26112 );
26113 }
26114
26115 #[test]
26118 fn a_synonym_command_on_a_name_that_is_not_there_fails() {
26119 let mut f = Fixture::new();
26120 let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
26121 assert_eq!(f.run(&[b"FT.SYNDUMP", b"nope"]), missing);
26122 assert_eq!(f.run(&[b"FT.SYNUPDATE", b"nope", b"g", b"a"]), missing);
26123 }
26124
26125 #[test]
26129 fn params_counts_the_words_before_it_pairs_them_up() {
26130 let mut f = Fixture::new();
26131 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26132 let none = "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: \
26133 Expected an argument, but none provided\r\n";
26134 let odd = "-SEARCH_ADD_ARGS Parameters must be specified in PARAM VALUE pairs\r\n";
26135 assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1"]), none);
26136 assert_eq!(
26137 f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"3", b"a", b"b"]),
26138 none
26139 );
26140 assert_eq!(
26141 f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1", b"a"]),
26142 odd
26143 );
26144 assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"0"]), odd);
26145 assert_eq!(
26146 f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"-1"]),
26147 "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: Value is outside acceptable bounds\r\n"
26148 );
26149 }
26150
26151 fn vectored(f: &mut Fixture) {
26161 f.run(&[
26162 b"FT.CREATE",
26163 b"h",
26164 b"SCHEMA",
26165 b"t",
26166 b"TEXT",
26167 b"v",
26168 b"VECTOR",
26169 b"FLAT",
26170 b"6",
26171 b"TYPE",
26172 b"FLOAT32",
26173 b"DIM",
26174 b"2",
26175 b"DISTANCE_METRIC",
26176 b"L2",
26177 ]);
26178 let at: [&[u8]; 5] = [
26179 b"\x00\x00\x80\x40\x00\x00\x00\x00",
26180 b"\x00\x00\x40\x40\x00\x00\x00\x00",
26181 b"\x00\x00\x00\x40\x00\x00\x00\x00",
26182 b"\x00\x00\x80\x3f\x00\x00\x00\x00",
26183 b"\x00\x00\x00\x00\x00\x00\x00\x00",
26184 ];
26185 for (n, point) in at.iter().enumerate() {
26186 let key = format!("d{}", n + 1);
26187 let word: &[u8] = match n {
26188 0 | 1 | 3 => b"alpha",
26189 _ => b"beta",
26190 };
26191 f.run(&[b"HSET", key.as_bytes(), b"t", word, b"v", point]);
26192 }
26193 }
26194
26195 const ORIGIN: &[u8] = b"\x00\x00\x00\x00\x00\x00\x00\x00";
26197
26198 #[test]
26202 fn a_knn_picks_the_nearest_and_answers_them_in_document_order() {
26203 let mut f = Fixture::new();
26204 vectored(&mut f);
26205 assert_eq!(
26206 f.run(&[
26207 b"FT.SEARCH",
26208 b"h",
26209 b"*=>[KNN 5 @v $vec]",
26210 b"PARAMS",
26211 b"2",
26212 b"vec",
26213 ORIGIN,
26214 b"DIALECT",
26215 b"2",
26216 b"NOCONTENT",
26217 ]),
26218 "*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"
26219 );
26220 assert_eq!(
26221 f.run(&[
26222 b"FT.SEARCH",
26223 b"h",
26224 b"*=>[KNN 3 @v $vec]",
26225 b"PARAMS",
26226 b"2",
26227 b"vec",
26228 ORIGIN,
26229 b"DIALECT",
26230 b"2",
26231 b"NOCONTENT",
26232 ]),
26233 "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
26234 );
26235 }
26236
26237 #[test]
26240 fn a_range_takes_what_is_inside_it_and_the_distance_is_squared() {
26241 let mut f = Fixture::new();
26242 vectored(&mut f);
26243 for (radius, want) in [
26244 ("0", "*2\r\n:1\r\n$2\r\nd5\r\n"),
26245 ("2", "*3\r\n:2\r\n$2\r\nd4\r\n$2\r\nd5\r\n"),
26246 (
26247 "9",
26248 "*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",
26249 ),
26250 ] {
26251 let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
26252 assert_eq!(
26253 f.run(&[
26254 b"FT.SEARCH",
26255 b"h",
26256 query.as_bytes(),
26257 b"PARAMS",
26258 b"2",
26259 b"vec",
26260 ORIGIN,
26261 b"DIALECT",
26262 b"2",
26263 b"NOCONTENT",
26264 ]),
26265 want,
26266 "radius {radius}"
26267 );
26268 }
26269 }
26270
26271 #[test]
26275 fn a_knn_measures_what_the_query_in_front_of_it_matched() {
26276 let mut f = Fixture::new();
26277 vectored(&mut f);
26278 assert_eq!(
26279 f.run(&[
26280 b"FT.SEARCH",
26281 b"h",
26282 b"alpha=>[KNN 2 @v $vec]",
26283 b"PARAMS",
26284 b"2",
26285 b"vec",
26286 ORIGIN,
26287 b"DIALECT",
26288 b"2",
26289 b"NOCONTENT",
26290 ]),
26291 "*3\r\n:2\r\n$2\r\nd2\r\n$2\r\nd4\r\n"
26292 );
26293 }
26294
26295 #[test]
26304 fn a_count_and_a_radius_are_refused_in_their_own_words() {
26305 let mut f = Fixture::new();
26306 vectored(&mut f);
26307 let ask = |f: &mut Fixture, query: &str| {
26308 f.run(&[
26309 b"FT.SEARCH",
26310 b"h",
26311 query.as_bytes(),
26312 b"PARAMS",
26313 b"2",
26314 b"vec",
26315 ORIGIN,
26316 b"DIALECT",
26317 b"2",
26318 b"NOCONTENT",
26319 ])
26320 };
26321 for (query, at, near) in [
26322 ("*=>[KNN -1 @v $vec]", 8, "-1"),
26323 ("*=>[KNN 1.5 @v $vec]", 8, "1.5"),
26324 ("*=>[KNN +3 @v $vec]", 8, "+3"),
26325 ("*=>[KNN 0x10 @v $vec]", 8, "0x10"),
26326 ("*=>[KNN abc @v $vec]", 8, "abc"),
26327 ("*=>[KNN 3 $vec]", 10, "vec"),
26328 ("*=>[KNN 3 @v vec]", 13, "vec"),
26329 ("@v:[VECTOR_RANGE 2 -1]", 19, "-1"),
26330 ] {
26331 assert_eq!(
26332 ask(&mut f, query),
26333 format!("-SEARCH_SYNTAX Syntax error at offset {at} near {near}\r\n"),
26334 "{query}"
26335 );
26336 }
26337
26338 let large = "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26341 query KNN K parameter is too large, must not exceed 288230376151711744\r\n";
26342 assert_eq!(
26343 ask(&mut f, "*=>[KNN 288230376151711776 @v $vec]"),
26344 "*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"
26345 );
26346 assert_eq!(ask(&mut f, "*=>[KNN 288230376151711777 @v $vec]"), large);
26347 assert_eq!(ask(&mut f, "*=>[KNN 99999999999999999999 @v $vec]"), large);
26348
26349 for (radius, printed) in [("-1", "-1"), ("-0.5", "-0.5"), ("-1e2", "-100")] {
26350 let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
26351 assert_eq!(
26352 ask(&mut f, &query),
26353 format!(
26354 "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26355 negative radius ({printed}) given in a range query\r\n"
26356 ),
26357 "{query}"
26358 );
26359 }
26360 assert_eq!(
26362 ask(&mut f, "@v:[VECTOR_RANGE -0 $vec]"),
26363 "*2\r\n:1\r\n$2\r\nd5\r\n"
26364 );
26365 }
26366
26367 #[test]
26370 fn a_count_that_came_from_params_is_read_by_its_own_rules() {
26371 let mut f = Fixture::new();
26372 vectored(&mut f);
26373 let ask = |f: &mut Fixture, count: &[u8]| {
26374 f.run(&[
26375 b"FT.SEARCH",
26376 b"h",
26377 b"*=>[KNN $k @v $vec]",
26378 b"PARAMS",
26379 b"4",
26380 b"vec",
26381 ORIGIN,
26382 b"k",
26383 count,
26384 b"DIALECT",
26385 b"2",
26386 b"NOCONTENT",
26387 ])
26388 };
26389 let three = "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n";
26390 assert_eq!(ask(&mut f, b"3"), three);
26391 assert_eq!(ask(&mut f, b" 3"), three);
26392 assert_eq!(ask(&mut f, b"+3"), three);
26393 for bad in [
26394 &b"3.0"[..],
26395 b"0x3",
26396 b"-1",
26397 b"abc",
26398 b"",
26399 b"99999999999999999999",
26400 ] {
26401 let value = String::from_utf8_lossy(bad).into_owned();
26402 assert_eq!(
26403 ask(&mut f, bad),
26404 format!(
26405 "-SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value ({value}) \
26406 for parameter `k`\r\n"
26407 ),
26408 "{value}"
26409 );
26410 }
26411 assert_eq!(
26412 ask(&mut f, b"288230376151711777"),
26413 "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26414 query KNN K parameter is too large, must not exceed 288230376151711744\r\n"
26415 );
26416 }
26417
26418 #[test]
26421 fn a_vector_the_wrong_size_is_refused_by_the_field_it_reached() {
26422 let mut f = Fixture::new();
26423 vectored(&mut f);
26424 assert_eq!(
26425 f.run(&[
26426 b"FT.SEARCH",
26427 b"h",
26428 b"*=>[KNN 5 @v $vec]",
26429 b"PARAMS",
26430 b"2",
26431 b"vec",
26432 b"abc",
26433 b"DIALECT",
26434 b"2",
26435 b"NOCONTENT",
26436 ]),
26437 "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26438 query vector blob size (3) does not match index's expected size (8).\r\n"
26439 );
26440 }
26441
26442 #[test]
26447 fn a_vector_clause_yields_its_distance_under_the_name_it_was_given() {
26448 let mut f = Fixture::new();
26449 vectored(&mut f);
26450 let ask = |f: &mut Fixture, query: &str| {
26451 f.run(&[
26452 b"FT.SEARCH",
26453 b"h",
26454 query.as_bytes(),
26455 b"PARAMS",
26456 b"2",
26457 b"vec",
26458 ORIGIN,
26459 b"DIALECT",
26460 b"2",
26461 b"LIMIT",
26462 b"0",
26463 b"1",
26464 ])
26465 };
26466 assert_eq!(
26467 ask(&mut f, "*=>[KNN 3 @v $vec]"),
26468 "*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"
26469 );
26470 assert_eq!(
26471 ask(&mut f, "*=>[KNN 3 @v $vec AS d]"),
26472 "*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"
26473 );
26474 assert_eq!(
26475 ask(&mut f, "@v:[VECTOR_RANGE 4 $vec]"),
26476 "*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"
26477 );
26478 assert_eq!(
26479 ask(&mut f, "@v:[VECTOR_RANGE 4 $vec]=>{$YIELD_DISTANCE_AS: d}"),
26480 "*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"
26481 );
26482 }
26483
26484 #[test]
26491 fn a_return_answers_the_distance_by_the_name_the_row_carries_it_under() {
26492 let mut f = Fixture::new();
26493 vectored(&mut f);
26494 let ask = |f: &mut Fixture, ret: &[&[u8]]| {
26495 let mut args: Vec<&[u8]> = vec![b"FT.SEARCH", b"h", b"*=>[KNN 1 @v $vec]"];
26496 args.extend_from_slice(ret);
26497 args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
26498 f.run(&args)
26499 };
26500 assert_eq!(
26501 ask(&mut f, &[b"RETURN", b"1", b"__v_score"]),
26502 "*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"
26503 );
26504 assert_eq!(
26505 ask(&mut f, &[b"RETURN", b"3", b"__v_score", b"AS", b"x"]),
26506 "*3\r\n:1\r\n$2\r\nd5\r\n*0\r\n"
26507 );
26508 assert_eq!(
26509 ask(&mut f, &[b"RETURN", b"3", b"t", b"AS", b"__v_score"]),
26510 "*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"
26511 );
26512 assert_eq!(
26513 ask(&mut f, &[b"RETURN", b"1", b"t"]),
26514 "*3\r\n:1\r\n$2\r\nd5\r\n*2\r\n$1\r\nt\r\n$4\r\nbeta\r\n"
26515 );
26516 assert_eq!(
26519 ask(&mut f, &[b"RETURN", b"2", b"t", b"__v_score"]),
26520 "*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"
26521 );
26522 assert_eq!(ask(&mut f, &[b"NOCONTENT"]), "*2\r\n:1\r\n$2\r\nd5\r\n");
26523 }
26524
26525 #[test]
26529 fn a_sortby_can_name_a_distance_the_query_yielded() {
26530 let mut f = Fixture::new();
26531 vectored(&mut f);
26532 let ask = |f: &mut Fixture, query: &str, by: &[u8], desc: bool| {
26533 let mut args: Vec<&[u8]> = vec![b"FT.SEARCH", b"h", query.as_bytes(), b"SORTBY", by];
26534 if desc {
26535 args.push(b"DESC");
26536 }
26537 args.extend_from_slice(&[
26538 b"PARAMS",
26539 b"2",
26540 b"vec",
26541 ORIGIN,
26542 b"DIALECT",
26543 b"2",
26544 b"NOCONTENT",
26545 ]);
26546 f.run(&args)
26547 };
26548 assert_eq!(
26549 ask(&mut f, "*=>[KNN 3 @v $vec]", b"__v_score", false),
26550 "*4\r\n:3\r\n$2\r\nd5\r\n$2\r\nd4\r\n$2\r\nd3\r\n"
26551 );
26552 assert_eq!(
26553 ask(&mut f, "*=>[KNN 3 @v $vec]", b"__v_score", true),
26554 "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
26555 );
26556 assert_eq!(
26557 ask(&mut f, "*=>[KNN 3 @v $vec AS d]", b"d", false),
26558 "*4\r\n:3\r\n$2\r\nd5\r\n$2\r\nd4\r\n$2\r\nd3\r\n"
26559 );
26560 let missing = "-SEARCH_PROP_NOT_FOUND Property `__v_score` \
26563 not loaded nor in schema\r\n";
26564 assert_eq!(
26565 ask(&mut f, "*=>[KNN 3 @v $vec AS d]", b"__v_score", false),
26566 missing
26567 );
26568 assert_eq!(ask(&mut f, "alpha", b"__v_score", false), missing);
26569 assert_eq!(
26572 ask(&mut f, "foo(", b"zz", false),
26573 "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
26574 );
26575 }
26576
26577 #[test]
26579 fn two_vector_clauses_answer_two_distances() {
26580 let mut f = Fixture::new();
26581 vectored(&mut f);
26582 assert_eq!(
26583 f.run(&[
26584 b"FT.SEARCH",
26585 b"h",
26586 b"@v:[VECTOR_RANGE 9 $vec]=>{$YIELD_DISTANCE_AS: rr}=>[KNN 2 @v $vec]",
26587 b"RETURN",
26588 b"2",
26589 b"rr",
26590 b"__v_score",
26591 b"PARAMS",
26592 b"2",
26593 b"vec",
26594 ORIGIN,
26595 b"DIALECT",
26596 b"2",
26597 ]),
26598 "*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"
26599 );
26600 }
26601
26602 #[test]
26606 fn an_aggregation_answers_a_distance_nothing_asked_for() {
26607 let mut f = Fixture::new();
26608 vectored(&mut f);
26609 let ask = |f: &mut Fixture, query: &str, rest: &[&[u8]]| {
26610 let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h", query.as_bytes()];
26611 args.extend_from_slice(rest);
26612 args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
26613 f.run(&args)
26614 };
26615 assert_eq!(
26616 ask(&mut f, "*=>[KNN 2 @v $vec]", &[]),
26617 "*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"
26618 );
26619 assert_eq!(
26620 ask(&mut f, "*=>[KNN 2 @v $vec]", &[b"LOAD", b"1", b"@t"]),
26621 "*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"
26622 );
26623 assert_eq!(
26624 ask(&mut f, "*=>[KNN 2 @v $vec AS d]", &[]),
26625 "*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"
26626 );
26627 assert_eq!(
26629 ask(&mut f, "@v:[VECTOR_RANGE 1 $vec]", &[]),
26630 "*3\r\n:1\r\n*0\r\n*0\r\n"
26631 );
26632 assert_eq!(
26633 ask(
26634 &mut f,
26635 "@v:[VECTOR_RANGE 1 $vec]=>{$YIELD_DISTANCE_AS: rr}",
26636 &[]
26637 ),
26638 "*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"
26639 );
26640 }
26641
26642 #[test]
26646 fn an_aggregation_keeps_the_order_a_nearest_neighbour_clause_made() {
26647 let mut f = Fixture::new();
26648 vectored(&mut f);
26649 const MIDDLE: &[u8] = b"\x00\x00\x00\x40\x00\x00\x00\x00";
26651 let ask = |f: &mut Fixture, query: &str, vec: &[u8]| {
26652 f.run(&[
26653 b"FT.AGGREGATE",
26654 b"h",
26655 query.as_bytes(),
26656 b"LOAD",
26657 b"1",
26658 b"@t",
26659 b"PARAMS",
26660 b"2",
26661 b"vec",
26662 vec,
26663 b"DIALECT",
26664 b"2",
26665 ])
26666 };
26667 assert_eq!(
26668 ask(&mut f, "*=>[KNN 3 @v $vec]", MIDDLE),
26669 "*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"
26670 );
26671 assert_eq!(
26673 ask(
26674 &mut f,
26675 "@v:[VECTOR_RANGE 1 $vec]=>{$YIELD_DISTANCE_AS: rr}",
26676 MIDDLE
26677 ),
26678 "*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"
26679 );
26680 }
26681
26682 #[test]
26686 fn a_pipeline_step_can_name_a_distance_the_query_yielded() {
26687 let mut f = Fixture::new();
26688 vectored(&mut f);
26689 let ask = |f: &mut Fixture, query: &str, rest: &[&[u8]]| {
26690 let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h", query.as_bytes()];
26691 args.extend_from_slice(rest);
26692 args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
26693 f.run(&args)
26694 };
26695 let knn = "*=>[KNN 2 @v $vec]";
26696 assert_eq!(
26697 ask(&mut f, knn, &[b"APPLY", b"@__v_score * 2", b"AS", b"x"]),
26698 "*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"
26699 );
26700 assert_eq!(
26701 ask(&mut f, knn, &[b"FILTER", b"@__v_score > 0"]),
26702 "*2\r\n:1\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n1\r\n"
26703 );
26704 assert_eq!(
26705 ask(&mut f, knn, &[b"SORTBY", b"2", b"@__v_score", b"DESC"]),
26706 "*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"
26707 );
26708 assert_eq!(
26709 ask(
26710 &mut f,
26711 knn,
26712 &[
26713 b"GROUPBY",
26714 b"1",
26715 b"@t",
26716 b"REDUCE",
26717 b"MAX",
26718 b"1",
26719 b"@__v_score",
26720 b"AS",
26721 b"m"
26722 ]
26723 ),
26724 "*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"
26725 );
26726 assert_eq!(
26727 ask(&mut f, "*", &[b"APPLY", b"@__v_score", b"AS", b"x"]),
26728 "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: \
26729 `__v_score`\r\n"
26730 );
26731 assert_eq!(
26732 ask(&mut f, "*", &[b"GROUPBY", b"1", b"@__v_score"]),
26733 "-SEARCH_PROP_NOT_FOUND No such property `__v_score`\r\n"
26734 );
26735 assert_eq!(
26736 ask(&mut f, "*", &[b"SORTBY", b"2", b"@__v_score", b"ASC"]),
26737 "-SEARCH_PROP_NOT_FOUND Property `__v_score` not loaded nor in \
26738 schema\r\n"
26739 );
26740 }
26741
26742 #[test]
26750 fn the_words_come_before_the_query_and_the_query_before_the_pipeline() {
26751 let mut f = Fixture::new();
26752 vectored(&mut f);
26753 let ask = |f: &mut Fixture, rest: &[&[u8]]| {
26754 let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h"];
26755 args.extend_from_slice(rest);
26756 f.run(&args)
26757 };
26758 assert_eq!(
26759 ask(
26760 &mut f,
26761 &[b"foo(", b"APPLY", b"@zz", b"AS", b"x", b"LIMIT", b"x", b"1"]
26762 ),
26763 "-SEARCH_PARSE_ARGS LIMIT needs two numeric arguments\r\n"
26764 );
26765 assert_eq!(
26766 ask(&mut f, &[b"foo(", b"APPLY", b"@zz", b"AS", b"x"]),
26767 "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
26768 );
26769 assert_eq!(
26770 ask(&mut f, &[b"*", b"APPLY", b"@zz", b"AS", b"x"]),
26771 "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: `zz`\r\n"
26772 );
26773 assert_eq!(
26776 ask(&mut f, &[b"foo(", b"APPLY", b"@@@", b"AS", b"x"]),
26777 "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
26778 );
26779 assert_eq!(
26780 ask(
26781 &mut f,
26782 &[
26783 b"*", b"APPLY", b"@zz", b"AS", b"x", b"APPLY", b"@@@", b"AS", b"y"
26784 ]
26785 ),
26786 "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: `zz`\r\n"
26787 );
26788 assert_eq!(
26789 ask(&mut f, &[b"*", b"APPLY", b"@@@", b"AS", b"x"]),
26790 "-SEARCH_EXPR Syntax error at offset 0 near ''\r\n"
26791 );
26792 }
26793
26794 #[test]
26797 fn a_vector_step_says_which_way_it_was_answered() {
26798 let mut f = Fixture::new();
26799 vectored(&mut f);
26800 let tree = |f: &mut Fixture, query: &[u8]| {
26801 let reply = timeless(&f.run(&[
26802 b"FT.PROFILE",
26803 b"h",
26804 b"AGGREGATE",
26805 b"QUERY",
26806 query,
26807 b"PARAMS",
26808 b"2",
26809 b"vec",
26810 ORIGIN,
26811 b"DIALECT",
26812 b"2",
26813 ]));
26814 let at = reply.find("+Iterators profile").expect("a tree");
26815 let end = reply.find("+Result processors").expect("a list of steps");
26816 reply[at..end].to_string()
26817 };
26818 assert_eq!(
26819 tree(&mut f, b"*=>[KNN 3 @v $vec]"),
26820 "+Iterators profile\r\n*8\r\n+Type\r\n+VECTOR\r\n+Time\r\n<t>\r\n\
26821 +Number of reading operations\r\n:3\r\n\
26822 +Vector search mode\r\n+STANDARD_KNN\r\n"
26823 );
26824 assert_eq!(
26826 tree(&mut f, b"*=>[KNN 3 @v $vec AS d]"),
26827 tree(&mut f, b"*=>[KNN 3 @v $vec]")
26828 );
26829 assert_eq!(
26832 tree(&mut f, b"@v:[VECTOR_RANGE 9 $vec]"),
26833 "+Iterators profile\r\n*6\r\n+Type\r\n+ID-LIST-SORTED\r\n+Time\r\n<t>\r\n\
26834 +Number of reading operations\r\n:4\r\n"
26835 );
26836 assert_eq!(
26837 tree(
26838 &mut f,
26839 b"@v:[VECTOR_RANGE 9 $vec]=>{$YIELD_DISTANCE_AS: rr}"
26840 ),
26841 "+Iterators profile\r\n*8\r\n\
26842 +Type\r\n+METRIC SORTED BY ID - VECTOR DISTANCE\r\n+Time\r\n<t>\r\n\
26843 +Number of reading operations\r\n:4\r\n\
26844 +Vector search mode\r\n+RANGE_QUERY\r\n"
26845 );
26846 }
26847
26848 #[test]
26852 fn a_clause_in_front_of_a_vector_hangs_under_it_as_one_child() {
26853 let mut f = Fixture::new();
26854 vectored(&mut f);
26855 let ask = |f: &mut Fixture, query: &[u8]| {
26856 timeless(&f.run(&[
26857 b"FT.PROFILE",
26858 b"h",
26859 b"AGGREGATE",
26860 b"QUERY",
26861 query,
26862 b"PARAMS",
26863 b"2",
26864 b"vec",
26865 ORIGIN,
26866 b"DIALECT",
26867 b"2",
26868 ]))
26869 };
26870 let cut = |reply: &str| {
26871 let at = reply.find("+Iterators profile").expect("a tree");
26872 reply[at..].to_string()
26873 };
26874 assert_eq!(
26875 cut(&ask(&mut f, b"@t:alpha=>[KNN 3 @v $vec]")),
26876 "+Iterators profile\r\n*10\r\n+Type\r\n+VECTOR\r\n+Time\r\n<t>\r\n\
26877 +Number of reading operations\r\n:3\r\n\
26878 +Vector search mode\r\n+HYBRID_ADHOC_BF\r\n+Child iterator\r\n\
26879 *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n+Time\r\n<t>\r\n\
26880 +Number of reading operations\r\n:3\r\n\
26881 +Estimated number of matches\r\n:3\r\n\
26882 +Result processors profile\r\n*2\r\n\
26883 *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:3\r\n\
26884 *6\r\n+Type\r\n+Metrics Applier\r\n+Time\r\n<t>\r\n\
26885 +Results processed\r\n:3\r\n+Coordinator\r\n*0\r\n"
26886 );
26887 assert!(ask(&mut f, b"@v:[VECTOR_RANGE 9 $vec]").ends_with(
26890 "+Result processors profile\r\n*1\r\n*6\r\n+Type\r\n+Index\r\n\
26891 +Time\r\n<t>\r\n+Results processed\r\n:4\r\n+Coordinator\r\n*0\r\n"
26892 ));
26893 assert!(ask(&mut f, b"*=>[KNN 3 @v $vec]").contains("+Type\r\n+Metrics Applier\r\n"));
26896 }
26897
26898 #[test]
26902 fn a_window_of_nothing_on_an_aggregation_counts_rather_than_pages() {
26903 let mut f = profiling();
26904 let steps = |f: &mut Fixture, words: &[&[u8]]| {
26905 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"AGGREGATE", b"QUERY", b"*"];
26906 argv.extend_from_slice(words);
26907 let reply = timeless(&f.run(&argv));
26908 let at = reply.find("+Result processors").expect("a list of steps");
26909 reply[at..].to_string()
26910 };
26911 assert_eq!(
26912 steps(&mut f, &[b"LIMIT", b"0", b"0"]),
26913 "+Result processors profile\r\n*2\r\n\
26914 *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:3\r\n\
26915 *6\r\n+Type\r\n+Counter\r\n+Time\r\n<t>\r\n+Results processed\r\n:1\r\n\
26916 +Coordinator\r\n*0\r\n"
26917 );
26918 assert!(
26919 steps(
26920 &mut f,
26921 &[b"SORTBY", b"2", b"@n", b"ASC", b"LIMIT", b"0", b"0"]
26922 )
26923 .contains("+Type\r\n+Counter\r\n")
26924 );
26925 assert!(steps(&mut f, &[b"LIMIT", b"0", b"2"]).contains(
26927 "+Type\r\n+Pager/Limiter\r\n+Time\r\n<t>\r\n\
26928 +Results processed\r\n:2\r\n"
26929 ));
26930 }
26931
26932 #[test]
26938 fn a_spellcheck_scores_a_suggestion_by_how_common_it_is() {
26939 let mut f = Fixture::new();
26940 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
26941 f.run(&[b"HSET", b"d1", b"t", b"hello"]);
26942 f.run(&[b"HSET", b"d2", b"t", b"hallo hello"]);
26943 assert_eq!(
26944 f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"2"]),
26945 "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
26946 *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"
26947 );
26948 }
26949
26950 #[test]
26954 fn a_spellcheck_answers_a_map_of_maps_on_resp3() {
26955 let mut f = Fixture::new();
26956 f.run(&[b"HELLO", b"3"]);
26957 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
26958 f.run(&[b"HSET", b"d1", b"t", b"hello"]);
26959 assert_eq!(
26960 f.run(&[b"FT.SPELLCHECK", b"e", b"hellp"]),
26961 "%1\r\n$7\r\nresults\r\n%1\r\n$5\r\nhellp\r\n\
26962 *1\r\n%1\r\n$5\r\nhello\r\n,1\r\n"
26963 );
26964 }
26965
26966 #[test]
26970 fn a_word_the_index_holds_is_never_asked_about_whatever_field_it_names() {
26971 let mut f = Fixture::new();
26972 f.run(&[
26973 b"FT.CREATE",
26974 b"e",
26975 b"SCHEMA",
26976 b"a",
26977 b"TEXT",
26978 b"NOSTEM",
26979 b"b",
26980 b"TEXT",
26981 b"NOSTEM",
26982 ]);
26983 f.run(&[b"HSET", b"d1", b"b", b"world"]);
26984 assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"@a:world"]), "*0\r\n");
26985 assert_eq!(
26986 f.run(&[b"FT.SPELLCHECK", b"e", b"@a:worlt"]),
26987 "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nworlt\r\n*0\r\n"
26988 );
26989 }
26990
26991 #[test]
26995 fn a_spellcheck_reads_the_dictionaries_it_is_pointed_at() {
26996 let mut f = Fixture::new();
26997 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
26998 f.run(&[b"FT.DICTADD", b"d", b"Hellp", b"hellq"]);
26999 assert_eq!(
27000 f.run(&[b"FT.SPELLCHECK", b"e", b"hellz", b"TERMS", b"INCLUDE", b"d"]),
27001 "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellz\r\n\
27002 *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"
27003 );
27004 assert_eq!(
27005 f.run(&[b"FT.SPELLCHECK", b"e", b"hellq", b"TERMS", b"EXCLUDE", b"d"]),
27006 "*0\r\n"
27007 );
27008 assert_eq!(
27009 f.run(&[b"FT.SPELLCHECK", b"e", b"x", b"TERMS", b"INCLUDE", b"nope"]),
27010 "-Dict does not exist: nope\r\n"
27011 );
27012 }
27013
27014 #[test]
27018 fn a_spellcheck_reads_its_arguments_leniently() {
27019 let mut f = Fixture::new();
27020 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
27021 f.run(&[b"HSET", b"d1", b"t", b"hello"]);
27022 let one = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
27023 *1\r\n*2\r\n$1\r\n1\r\n$5\r\nhello\r\n";
27024 assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"BOGUS"]), one);
27025 let none = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhelqp\r\n*0\r\n";
27026 let args: &[&[u8]] = &[
27027 b"FT.SPELLCHECK",
27028 b"e",
27029 b"helqp",
27030 b"DISTANCE",
27031 b"1",
27032 b"DISTANCE",
27033 b"4",
27034 ];
27035 assert_eq!(f.run(args), none);
27036 assert_eq!(
27037 f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"5"]),
27038 "-bad distance given, distance must be a natural number between 1 to 4\r\n"
27039 );
27040 assert_eq!(
27041 f.run(&[b"FT.SPELLCHECK", b"nope", b"hellp"]),
27042 "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
27043 );
27044 }
27045
27046 #[test]
27051 fn an_add_answers_how_many_suggestions_are_in_there_now() {
27052 let mut f = Fixture::new();
27053 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]), ":1\r\n");
27054 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"9"]), ":1\r\n");
27055 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]), ":2\r\n");
27056 assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":2\r\n");
27057 assert_eq!(f.run(&[b"FT.SUGLEN", b"nokey"]), ":0\r\n");
27058 }
27059
27060 #[test]
27063 fn a_suggestion_dictionary_is_a_key_with_a_type_of_its_own() {
27064 let mut f = Fixture::new();
27065 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27066 assert_eq!(f.run(&[b"TYPE", b"s"]), "+trietype0\r\n");
27067 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$3\r\nraw\r\n");
27068 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
27069 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\ns\r\n");
27070 assert_eq!(f.run(&[b"EXPIRE", b"s", b"100"]), ":1\r\n");
27071 assert_eq!(f.run(&[b"TTL", b"s"]), ":100\r\n");
27072 assert_eq!(f.run(&[b"DEL", b"s"]), ":1\r\n");
27073 assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":0\r\n");
27074 }
27075
27076 #[test]
27079 fn deleting_the_last_suggestion_deletes_the_key() {
27080 let mut f = Fixture::new();
27081 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27082 assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"nope"]), ":0\r\n");
27083 assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"one"]), ":1\r\n");
27084 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
27085 assert_eq!(f.run(&[b"FT.SUGDEL", b"nokey", b"a"]), ":0\r\n");
27086 }
27087
27088 #[test]
27091 fn a_suggestion_command_on_another_kind_of_key_is_wrongtype() {
27092 let mut f = Fixture::new();
27093 f.run(&[b"SET", b"s", b"x"]);
27094 for cmd in [
27095 vec![&b"FT.SUGADD"[..], b"s", b"t", b"1"],
27096 vec![&b"FT.SUGGET"[..], b"s", b"t"],
27097 vec![&b"FT.SUGDEL"[..], b"s", b"t"],
27098 vec![&b"FT.SUGLEN"[..], b"s"],
27099 ] {
27100 assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{cmd:?}");
27101 }
27102 assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nx\r\n");
27103 }
27104
27105 #[test]
27108 fn a_lookup_answers_a_score_it_works_out_rather_than_the_one_stored() {
27109 let mut f = Fixture::new();
27110 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27111 f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
27112 f.run(&[b"FT.SUGADD", b"s", b"ontario", b"3"]);
27113 assert_eq!(
27114 f.run(&[b"FT.SUGGET", b"s", b"on", b"WITHSCORES"]),
27115 "*6\r\n$7\r\nontario\r\n$18\r\n1.2247449159622192\r\n\
27116 $4\r\nonly\r\n$17\r\n1.154700517654419\r\n\
27117 $3\r\none\r\n$18\r\n0.7071067690849304\r\n"
27118 );
27119 assert_eq!(
27120 f.run(&[b"FT.SUGGET", b"s", b"one", b"WITHSCORES"]),
27121 "*2\r\n$3\r\none\r\n$10\r\n2147483648\r\n"
27122 );
27123 assert_eq!(f.run(&[b"FT.SUGGET", b"nokey", b"a"]), "*0\r\n");
27124 }
27125
27126 #[test]
27128 fn fuzzy_allows_one_edit_and_nothing_allows_two() {
27129 let mut f = Fixture::new();
27130 f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
27131 assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"one"]), "*0\r\n");
27132 assert_eq!(
27133 f.run(&[b"FT.SUGGET", b"s", b"one", b"FUZZY", b"WITHSCORES"]),
27134 "*2\r\n$4\r\nonly\r\n$19\r\n0.19139298796653748\r\n"
27135 );
27136 assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"xyz", b"FUZZY"]), "*0\r\n");
27137 }
27138
27139 #[test]
27141 fn a_lookup_answers_five_unless_it_is_told_otherwise() {
27142 let mut f = Fixture::new();
27143 for (term, score) in [
27144 (&b"a1"[..], &b"1"[..]),
27145 (b"a2", b"2"),
27146 (b"a3", b"3"),
27147 (b"a4", b"4"),
27148 (b"a5", b"5"),
27149 (b"a6", b"6"),
27150 ] {
27151 f.run(&[b"FT.SUGADD", b"s", term, score]);
27152 }
27153 assert_eq!(
27154 f.run(&[b"FT.SUGGET", b"s", b"a"]),
27155 "*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"
27156 );
27157 assert_eq!(
27158 f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"2"]),
27159 "*2\r\n$2\r\na6\r\n$2\r\na5\r\n"
27160 );
27161 assert!(
27163 f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"100"])
27164 .starts_with("*6\r\n")
27165 );
27166 }
27167
27168 #[test]
27171 fn a_payload_comes_back_beside_the_term_or_a_null_does() {
27172 let mut f = Fixture::new();
27173 f.run(&[b"FT.SUGADD", b"s", b"one", b"1", b"PAYLOAD", b"p"]);
27174 assert_eq!(
27175 f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
27176 "*2\r\n$3\r\none\r\n$1\r\np\r\n"
27177 );
27178 f.run(&[b"FT.SUGADD", b"s", b"one", b"2"]);
27179 assert_eq!(
27180 f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
27181 "*2\r\n$3\r\none\r\n$1\r\np\r\n"
27182 );
27183 f.run(&[b"FT.SUGADD", b"s", b"one", b"2", b"PAYLOAD", b""]);
27186 assert_eq!(
27187 f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
27188 "*2\r\n$3\r\none\r\n$1\r\np\r\n"
27189 );
27190 f.run(&[b"FT.SUGADD", b"s", b"other", b"1", b"PAYLOAD", b""]);
27192 assert_eq!(
27193 f.run(&[b"FT.SUGGET", b"s", b"ot", b"WITHPAYLOADS"]),
27194 "*2\r\n$5\r\nother\r\n$-1\r\n"
27195 );
27196 }
27197
27198 #[test]
27202 fn incr_adds_to_the_score_that_is_already_there() {
27203 let mut f = Fixture::new();
27204 for _ in 0..3 {
27205 f.run(&[b"FT.SUGADD", b"s", b"xxx", b"0.1", b"INCR"]);
27206 }
27207 assert_eq!(
27208 f.run(&[b"FT.SUGGET", b"s", b"xx", b"WITHSCORES"]),
27209 "*2\r\n$3\r\nxxx\r\n$18\r\n0.2121320366859436\r\n"
27210 );
27211 }
27212
27213 #[test]
27215 fn the_suggestion_errors_are_the_lines_the_module_sends() {
27216 let mut f = Fixture::new();
27217 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27218 assert_eq!(
27219 f.run(&[b"FT.SUGADD", b"s", b"t", b"abc"]),
27220 "-ERR invalid score\r\n"
27221 );
27222 assert_eq!(
27224 f.run(&[b"FT.SUGADD", b"s", b"t", b"abc", b"NOPE"]),
27225 "-Unknown argument `NOPE`\r\n"
27226 );
27227 assert_eq!(
27228 f.run(&[b"FT.SUGADD", b"s", b"t", b"1", b"PAYLOAD"]),
27229 "-Invalid payload: Expected an argument, but none provided\r\n"
27230 );
27231 assert!(
27233 f.run(&[
27234 b"FT.SUGADD",
27235 b"s",
27236 b"t",
27237 b"1",
27238 b"PAYLOAD",
27239 b"a",
27240 b"PAYLOAD",
27241 b"b"
27242 ])
27243 .contains("wrong number of arguments")
27244 );
27245 assert_eq!(
27246 f.run(&[b"FT.SUGGET", b"s", b"o", b"NOPE"]),
27247 "-SEARCH_PARSE_ARGS Unrecognized argument: NOPE\r\n"
27248 );
27249 for max in [&b"0"[..], b"-1", b"4294967296", b"1e10", b"inf"] {
27253 assert_eq!(
27254 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
27255 "-SEARCH_PARSE_ARGS MAX: Value is outside acceptable bounds\r\n",
27256 "{}",
27257 String::from_utf8_lossy(max)
27258 );
27259 }
27260 for max in [
27261 &b"abc"[..],
27262 b"0.0",
27263 b"00",
27264 b"-0",
27265 b"+0",
27266 b"0.5",
27267 b"-1.5",
27268 b"1e400",
27269 ] {
27270 assert_eq!(
27271 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
27272 "-SEARCH_PARSE_ARGS MAX: Could not convert argument to expected type\r\n",
27273 "{}",
27274 String::from_utf8_lossy(max)
27275 );
27276 }
27277 for max in [&b"01"[..], b"+1", b"1.5", b"0x10", b"1e2"] {
27278 assert_eq!(
27279 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
27280 "*1\r\n$3\r\none\r\n",
27281 "{}",
27282 String::from_utf8_lossy(max)
27283 );
27284 }
27285 assert_eq!(
27286 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX"]),
27287 "-SEARCH_PARSE_ARGS MAX: Expected an argument, but none provided\r\n"
27288 );
27289 assert_eq!(
27292 f.run(&[b"FT.SUGADD", b"s", b"t", b"1e400"]),
27293 "-ERR invalid score\r\n"
27294 );
27295 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"t", b"inf"]), ":2\r\n");
27296 }
27297
27298 #[test]
27302 fn an_empty_suggestion_is_taken_and_dropped_but_still_makes_the_key() {
27303 let mut f = Fixture::new();
27304 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27305 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"", b"1"]), ":1\r\n");
27306 assert_eq!(f.run(&[b"FT.SUGGET", b"s", b""]), "*1\r\n$3\r\none\r\n");
27307 assert_eq!(f.run(&[b"FT.SUGADD", b"e", b"", b"1"]), ":0\r\n");
27308 assert_eq!(f.run(&[b"EXISTS", b"e"]), ":1\r\n");
27309 assert_eq!(f.run(&[b"TYPE", b"e"]), "+trietype0\r\n");
27310 assert_eq!(f.run(&[b"FT.SUGDEL", b"e", b"nothing"]), ":0\r\n");
27311 assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
27312 }
27313
27314 #[test]
27317 fn a_hash_that_will_not_read_is_counted_where_ft_info_reports_it() {
27318 let mut f = Fixture::new();
27319 f.run(&[
27320 b"FT.CREATE",
27321 b"ix",
27322 b"PREFIX",
27323 b"1",
27324 b"p:",
27325 b"SCHEMA",
27326 b"n",
27327 b"NUMERIC",
27328 ]);
27329 f.run(&[b"HSET", b"p:1", b"n", b"notanumber"]);
27330 assert_eq!(held(&f, b"ix"), (0, 0));
27331
27332 let reply = f.run(&[b"FT.INFO", b"ix"]);
27333 assert!(
27334 reply.contains("SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value: 'notanumber'"),
27335 "{reply}"
27336 );
27337 assert!(reply.contains("hash_indexing_failures"), "{reply}");
27338 }
27339
27340 #[test]
27343 fn an_index_can_only_be_made_on_database_zero() {
27344 let mut f = Fixture::new();
27345 f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]);
27346 f.run(&[b"SELECT", b"1"]);
27347 let refused = "-Cannot create index on db != 0\r\n";
27348 assert_eq!(
27349 f.run(&[b"FT.CREATE", b"jx", b"SCHEMA", b"t", b"TEXT"]),
27350 refused
27351 );
27352 assert_eq!(
27354 f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]),
27355 refused
27356 );
27357 assert_eq!(
27359 f.run(&[b"FT.CREATE", b"zz", b"BOGUS", b"SCHEMA", b"t", b"TEXT"]),
27360 refused
27361 );
27362 assert_eq!(
27364 f.run(&[b"FT._CREATEIFNX", b"ix", b"SCHEMA", b"t", b"TEXT"]),
27365 "+OK\r\n"
27366 );
27367 assert_eq!(f.server.search.lock().len(), 1);
27368 }
27369
27370 #[test]
27376 fn the_scan_is_one_database_and_the_following_is_all_of_them() {
27377 let mut f = Fixture::new();
27378 f.run(&[b"SELECT", b"1"]);
27379 f.run(&[b"HSET", b"p:9", b"t", b"on one"]);
27380 f.run(&[b"SELECT", b"0"]);
27381 f.run(&[b"HSET", b"p:0", b"t", b"on zero"]);
27382 f.run(&[
27383 b"FT.CREATE",
27384 b"ix",
27385 b"PREFIX",
27386 b"1",
27387 b"p:",
27388 b"SCHEMA",
27389 b"t",
27390 b"TEXT",
27391 ]);
27392 assert_eq!(held(&f, b"ix"), (1, 1), "the scan read database zero only");
27393
27394 f.run(&[b"SELECT", b"1"]);
27395 f.run(&[b"HSET", b"p:8", b"t", b"later"]);
27396 assert_eq!(
27397 held(&f, b"ix"),
27398 (2, 2),
27399 "and then it follows every database"
27400 );
27401 }
27402
27403 fn corpus(f: &mut Fixture) {
27406 f.run(&[
27407 b"FT.CREATE",
27408 b"sx",
27409 b"PREFIX",
27410 b"1",
27411 b"d:",
27412 b"SCHEMA",
27413 b"t",
27414 b"TEXT",
27415 b"g",
27416 b"TAG",
27417 b"n",
27418 b"NUMERIC",
27419 ]);
27420 for (key, text, tag, number) in [
27421 (b"d:1".as_slice(), "alpha beta", "aa,bb", "1"),
27422 (b"d:2", "alpha gamma", "bb", "2"),
27423 (b"d:3", "delta", "cc", "3"),
27424 (b"d:4", "alpha beta gamma", "aa,cc", "4"),
27425 ] {
27426 f.run(&[
27427 b"HSET",
27428 key,
27429 b"t",
27430 text.as_bytes(),
27431 b"g",
27432 tag.as_bytes(),
27433 b"n",
27434 number.as_bytes(),
27435 ]);
27436 }
27437 }
27438
27439 fn sortable(f: &mut Fixture) {
27443 f.run(&[
27444 b"FT.CREATE",
27445 b"sy",
27446 b"PREFIX",
27447 b"1",
27448 b"s:",
27449 b"SCHEMA",
27450 b"t",
27451 b"TEXT",
27452 b"SORTABLE",
27453 b"n",
27454 b"NUMERIC",
27455 b"SORTABLE",
27456 b"body",
27457 b"AS",
27458 b"b",
27459 b"TEXT",
27460 b"SORTABLE",
27461 b"p",
27462 b"TEXT",
27463 ]);
27464 for (key, text, number) in [
27465 (b"s:1".as_slice(), "Banana Split", "2"),
27466 (b"s:2", "apple", "10"),
27467 ] {
27468 f.run(&[
27469 b"HSET",
27470 key,
27471 b"t",
27472 text.as_bytes(),
27473 b"n",
27474 number.as_bytes(),
27475 b"body",
27476 text.as_bytes(),
27477 b"p",
27478 b"alpha",
27479 ]);
27480 }
27481 f.run(&[b"HSET", b"s:3", b"p", b"alpha"]);
27484 }
27485
27486 #[test]
27489 fn a_search_sorts_by_a_field_the_index_keeps_a_copy_of() {
27490 let mut f = Fixture::new();
27491 sortable(&mut f);
27492 assert_eq!(
27493 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"NOCONTENT"]),
27494 "*4\r\n:3\r\n$3\r\ns:1\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
27495 );
27496 assert_eq!(
27497 f.run(&[
27498 b"FT.SEARCH",
27499 b"sy",
27500 b"alpha",
27501 b"SORTBY",
27502 b"n",
27503 b"DESC",
27504 b"NOCONTENT"
27505 ]),
27506 "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
27507 );
27508 assert_eq!(
27511 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"t", b"NOCONTENT"]),
27512 "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
27513 );
27514 }
27515
27516 #[test]
27519 fn a_search_sorts_by_a_field_it_has_to_read_the_key_for() {
27520 let mut f = Fixture::new();
27521 sortable(&mut f);
27522 f.run(&[b"HSET", b"s:1", b"p", b"alpha zulu"]);
27523 assert_eq!(
27524 f.run(&[
27525 b"FT.SEARCH",
27526 b"sy",
27527 b"alpha",
27528 b"SORTBY",
27529 b"p",
27530 b"NOCONTENT",
27531 b"LIMIT",
27532 b"0",
27533 b"2"
27534 ]),
27535 "*3\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
27536 );
27537 assert_eq!(
27540 f.run(&[
27541 b"FT.SEARCH",
27542 b"sy",
27543 b"alpha",
27544 b"SORTBY",
27545 b"p",
27546 b"WITHSORTKEYS",
27547 b"NOCONTENT",
27548 b"LIMIT",
27549 b"2",
27550 b"1"
27551 ]),
27552 "*3\r\n:3\r\n$3\r\ns:1\r\n$11\r\n$alpha zulu\r\n"
27553 );
27554 }
27555
27556 #[test]
27559 fn a_search_can_send_the_value_it_sorted_by_back() {
27560 let mut f = Fixture::new();
27561 sortable(&mut f);
27562 assert_eq!(
27563 f.run(&[
27564 b"FT.SEARCH",
27565 b"sy",
27566 b"alpha",
27567 b"SORTBY",
27568 b"n",
27569 b"WITHSORTKEYS",
27570 b"NOCONTENT"
27571 ]),
27572 concat!(
27573 "*7\r\n:3\r\n",
27574 "$3\r\ns:1\r\n$2\r\n#2\r\n",
27575 "$3\r\ns:2\r\n$3\r\n#10\r\n",
27576 "$3\r\ns:3\r\n$-1\r\n"
27577 )
27578 );
27579 assert_eq!(
27580 f.run(&[
27581 b"FT.SEARCH",
27582 b"sy",
27583 b"alpha",
27584 b"SORTBY",
27585 b"t",
27586 b"WITHSORTKEYS",
27587 b"NOCONTENT"
27588 ]),
27589 concat!(
27590 "*7\r\n:3\r\n",
27591 "$3\r\ns:2\r\n$6\r\n$apple\r\n",
27592 "$3\r\ns:1\r\n$13\r\n$banana split\r\n",
27593 "$3\r\ns:3\r\n$-1\r\n"
27594 )
27595 );
27596 assert_eq!(
27599 f.run(&[
27600 b"FT.SEARCH",
27601 b"sy",
27602 b"banana",
27603 b"WITHSORTKEYS",
27604 b"NOCONTENT"
27605 ]),
27606 "*3\r\n:1\r\n$3\r\ns:1\r\n$-1\r\n"
27607 );
27608 }
27609
27610 #[test]
27613 fn a_sort_puts_the_field_it_sorted_by_in_front_of_the_row() {
27614 let mut f = Fixture::new();
27615 sortable(&mut f);
27616 assert_eq!(
27620 f.run(&[
27621 b"FT.SEARCH",
27622 b"sy",
27623 b"alpha",
27624 b"SORTBY",
27625 b"b",
27626 b"LIMIT",
27627 b"0",
27628 b"1"
27629 ]),
27630 concat!(
27631 "*3\r\n:3\r\n$3\r\ns:2\r\n*10\r\n",
27632 "$1\r\nb\r\n$5\r\napple\r\n",
27633 "$1\r\nt\r\n$5\r\napple\r\n",
27634 "$1\r\nn\r\n$2\r\n10\r\n",
27635 "$4\r\nbody\r\n$5\r\napple\r\n",
27636 "$1\r\np\r\n$5\r\nalpha\r\n"
27637 )
27638 );
27639 assert_eq!(
27642 f.run(&[
27643 b"FT.SEARCH",
27644 b"sy",
27645 b"alpha",
27646 b"SORTBY",
27647 b"b",
27648 b"RETURN",
27649 b"2",
27650 b"p",
27651 b"b",
27652 b"LIMIT",
27653 b"0",
27654 b"1"
27655 ]),
27656 concat!(
27657 "*3\r\n:3\r\n$3\r\ns:2\r\n*4\r\n",
27658 "$1\r\nb\r\n$5\r\napple\r\n",
27659 "$1\r\np\r\n$5\r\nalpha\r\n"
27660 )
27661 );
27662 }
27663
27664 #[test]
27666 fn a_search_refuses_the_sorts_it_cannot_run() {
27667 let mut f = Fixture::new();
27668 sortable(&mut f);
27669 assert_eq!(
27670 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY"]),
27671 "-SEARCH_PARSE_ARGS Bad SORTBY arguments\r\n"
27672 );
27673 assert_eq!(
27674 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"SORTBY"]),
27675 "-SEARCH_PARSE_ARGS Multiple SORTBY steps are not allowed\r\n"
27676 );
27677 assert_eq!(
27678 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"MAX", b"2"]),
27679 "-SEARCH_PARSE_ARGS SORTBY MAX is not supported by FT.SEARCH\r\n"
27680 );
27681 assert_eq!(
27682 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz"]),
27683 "-SEARCH_PROP_NOT_FOUND Property `zz` not loaded nor in schema\r\n"
27684 );
27685 assert_eq!(
27688 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz", b"NOPE"]),
27689 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `NOPE` at position 3 for <main>\r\n"
27690 );
27691 }
27692
27693 fn marking(f: &mut Fixture) {
27697 f.run(&[
27698 b"FT.CREATE",
27699 b"mk",
27700 b"ON",
27701 b"HASH",
27702 b"PREFIX",
27703 b"1",
27704 b"m:",
27705 b"SCHEMA",
27706 b"a",
27707 b"TEXT",
27708 b"b",
27709 b"TEXT",
27710 b"n",
27711 b"NUMERIC",
27712 b"g",
27713 b"TAG",
27714 ]);
27715 f.run(&[
27716 b"HSET",
27717 b"m:1",
27718 b"a",
27719 b"c1 c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2 e3",
27720 b"b",
27721 b"t1 t2 t3 t4 t5 t6 t7 t8",
27722 b"n",
27723 b"1",
27724 b"g",
27725 b"red",
27726 ]);
27727 }
27728
27729 #[test]
27732 fn a_summarize_cuts_a_field_down_to_what_matched() {
27733 let mut f = Fixture::new();
27734 marking(&mut f);
27735 let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"SUMMARIZE", b"LEN", b"2"]);
27736 assert!(got.contains("c3 fox d1 d2... d9 fox e1 e2... "), "{got}");
27737 assert!(got.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{got}");
27739 assert!(got.contains("$1\r\nr\r\n"), "{got}");
27741 }
27742
27743 #[test]
27747 fn a_dropped_fragment_stops_bounding_the_one_that_was_kept() {
27748 let mut f = Fixture::new();
27749 marking(&mut f);
27750 let got = f.run(&[
27751 b"FT.SEARCH",
27752 b"mk",
27753 b"fox",
27754 b"SUMMARIZE",
27755 b"FRAGS",
27756 b"1",
27757 b"LEN",
27758 b"20",
27759 ]);
27760 assert!(
27761 got.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2... "),
27762 "{got}"
27763 );
27764 let two = f.run(&[
27767 b"FT.SEARCH",
27768 b"mk",
27769 b"fox",
27770 b"SUMMARIZE",
27771 b"FRAGS",
27772 b"2",
27773 b"LEN",
27774 b"20",
27775 ]);
27776 assert!(
27777 two.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9... d1"),
27778 "{two}"
27779 );
27780 }
27781
27782 #[test]
27785 fn a_highlight_marks_the_matches_and_leaves_the_rest_of_the_field_alone() {
27786 let mut f = Fixture::new();
27787 marking(&mut f);
27788 let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"HIGHLIGHT"]);
27789 assert!(got.contains("<b>fox</b> d1 d2"), "{got}");
27790 let both = f.run(&[
27791 b"FT.SEARCH",
27792 b"mk",
27793 b"fox",
27794 b"SUMMARIZE",
27795 b"LEN",
27796 b"2",
27797 b"HIGHLIGHT",
27798 ]);
27799 assert!(both.contains("c3 <b>fox</b> d1 d2... "), "{both}");
27800 assert!(both.contains("t1 t2 t3 t4 t5 t6 t7 t8\r\n"), "{both}");
27802 assert!(both.contains("$3\r\nred\r\n"), "{both}");
27803 let split = f.run(&[
27805 b"FT.SEARCH",
27806 b"mk",
27807 b"fox",
27808 b"SUMMARIZE",
27809 b"FIELDS",
27810 b"1",
27811 b"b",
27812 b"LEN",
27813 b"2",
27814 b"HIGHLIGHT",
27815 b"FIELDS",
27816 b"1",
27817 b"a",
27818 ]);
27819 assert!(split.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{split}");
27820 }
27821
27822 #[test]
27824 fn a_highlight_does_not_mark_a_tag() {
27825 let mut f = Fixture::new();
27826 marking(&mut f);
27827 f.run(&[b"HSET", b"m:1", b"b", b"red and blue"]);
27828 let got = f.run(&[b"FT.SEARCH", b"mk", b"@g:{red}", b"HIGHLIGHT"]);
27829 assert!(!got.contains("<b>"), "{got}");
27830 assert!(got.contains("red and blue"), "{got}");
27831 }
27832
27833 #[test]
27836 fn a_search_answers_a_total_and_then_the_rows() {
27837 let mut f = Fixture::new();
27838 corpus(&mut f);
27839 assert_eq!(
27840 f.run(&[b"FT.SEARCH", b"sx", b"delta"]),
27841 "*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"
27842 );
27843 f.run(&[b"HSET", b"d:3", b"extra", b"more"]);
27846 assert!(f.run(&[b"FT.SEARCH", b"sx", b"delta"]).contains("extra"));
27847 assert_eq!(
27850 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
27851 "*2\r\n:1\r\n$3\r\nd:3\r\n"
27852 );
27853 assert_eq!(
27854 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
27855 "*1\r\n:3\r\n"
27856 );
27857 }
27858
27859 #[test]
27862 fn the_window_is_ten_rows_and_a_million_wide_at_most() {
27863 let mut f = Fixture::new();
27864 corpus(&mut f);
27865 assert_eq!(
27866 f.run(&[
27867 b"FT.SEARCH",
27868 b"sx",
27869 b"alpha",
27870 b"NOCONTENT",
27871 b"LIMIT",
27872 b"1",
27873 b"1"
27874 ]),
27875 "*2\r\n:3\r\n$3\r\nd:2\r\n"
27876 );
27877 assert_eq!(
27878 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0"]),
27879 "-SEARCH_PARSE_ARGS LIMIT requires two arguments\r\n"
27880 );
27881 assert_eq!(
27882 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"-1"]),
27883 "-SEARCH_PARSE_ARGS LIMIT needs two numeric arguments\r\n"
27884 );
27885 assert_eq!(
27886 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"1000001"]),
27887 "-SEARCH_LIMIT_OVER LIMIT exceeds maximum of 1000000\r\n"
27888 );
27889 assert_eq!(
27890 f.run(&[
27891 b"FT.SEARCH",
27892 b"sx",
27893 b"alpha",
27894 b"NOCONTENT",
27895 b"LIMIT",
27896 b"999999",
27897 b"1000000"
27898 ]),
27899 "*1\r\n:3\r\n"
27900 );
27901 }
27902
27903 #[test]
27907 fn a_return_of_nothing_is_not_the_same_as_nocontent() {
27908 let mut f = Fixture::new();
27909 corpus(&mut f);
27910 let bare = "*2\r\n:1\r\n$3\r\nd:3\r\n";
27911 assert_eq!(
27912 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"0"]),
27913 bare
27914 );
27915 assert_eq!(
27916 f.run(&[
27917 b"FT.SEARCH",
27918 b"sx",
27919 b"delta",
27920 b"NOCONTENT",
27921 b"RETURN",
27922 b"1",
27923 b"t"
27924 ]),
27925 bare
27926 );
27927 assert_eq!(
27928 f.run(&[
27929 b"FT.SEARCH",
27930 b"sx",
27931 b"delta",
27932 b"RETURN",
27933 b"0",
27934 b"RETURN",
27935 b"1",
27936 b"t"
27937 ]),
27938 "*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"
27939 );
27940 }
27941
27942 #[test]
27945 fn the_count_after_return_counts_words() {
27946 let mut f = Fixture::new();
27947 corpus(&mut f);
27948 assert_eq!(
27951 f.run(&[
27952 b"FT.SEARCH",
27953 b"sx",
27954 b"delta",
27955 b"RETURN",
27956 b"3",
27957 b"t",
27958 b"AS",
27959 b"x"
27960 ]),
27961 "*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"
27962 );
27963 assert_eq!(
27966 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"2", b"t", b"AS"]),
27967 "-SEARCH_PARSE_ARGS RETURN path AS name - must be accompanied with NAME\r\n"
27968 );
27969 assert_eq!(
27970 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"3", b"t", b"AS"]),
27971 "-SEARCH_PARSE_ARGS Bad arguments for RETURN: Expected an argument, but none provided\r\n"
27972 );
27973 assert_eq!(
27977 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"AS"]),
27978 "*3\r\n:1\r\n$3\r\nd:3\r\n*0\r\n"
27979 );
27980 }
27981
27982 #[test]
27985 fn a_filter_is_a_range_written_outside_the_query() {
27986 let mut f = Fixture::new();
27987 corpus(&mut f);
27988 assert_eq!(
27989 f.run(&[
27990 b"FT.SEARCH",
27991 b"sx",
27992 b"alpha",
27993 b"NOCONTENT",
27994 b"FILTER",
27995 b"n",
27996 b"2",
27997 b"4"
27998 ]),
27999 "*3\r\n:2\r\n$3\r\nd:2\r\n$3\r\nd:4\r\n"
28000 );
28001 assert_eq!(
28002 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2"]),
28003 "-SEARCH_PARSE_ARGS FILTER requires 3 arguments\r\n"
28004 );
28005 assert_eq!(
28006 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"x", b"1"]),
28007 "-SEARCH_PARSE_ARGS Bad lower range: x\r\n"
28008 );
28009 assert_eq!(
28010 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2", b"1"]),
28011 "-SEARCH_SYNTAX Invalid numeric range (min > max): @n:[2.000000 1.000000]\r\n"
28012 );
28013 for field in [b"g".as_slice(), b"nope"] {
28016 assert_eq!(
28017 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", field, b"2", b"1"]),
28018 "*1\r\n:0\r\n"
28019 );
28020 }
28021 }
28022
28023 #[test]
28026 fn the_index_is_found_before_the_arguments_are_read() {
28027 let mut f = Fixture::new();
28028 corpus(&mut f);
28029 let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
28030 assert_eq!(f.run(&[b"FT.SEARCH", b"nope", b"alpha", b"BOGUS"]), missing);
28031 assert_eq!(
28032 f.run(&[b"FT.EXPLAIN", b"nope", b"alpha", b"BOGUS"]),
28033 missing
28034 );
28035 assert_eq!(
28038 f.run(&[b"FT.SEARCH", b"sx", b"@@@", b"BOGUS"]),
28039 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `BOGUS` at position 1 for <main>\r\n"
28040 );
28041 }
28042
28043 #[test]
28046 fn inkeys_comes_off_the_total() {
28047 let mut f = Fixture::new();
28048 corpus(&mut f);
28049 assert_eq!(
28050 f.run(&[
28051 b"FT.SEARCH",
28052 b"sx",
28053 b"alpha",
28054 b"NOCONTENT",
28055 b"INKEYS",
28056 b"1",
28057 b"d:1"
28058 ]),
28059 "*2\r\n:1\r\n$3\r\nd:1\r\n"
28060 );
28061 assert_eq!(
28062 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"NOCONTENT", b"INKEYS", b"0"]),
28063 "*1\r\n:0\r\n"
28064 );
28065 }
28066
28067 #[test]
28074 fn the_fields_are_read_from_the_session_database() {
28075 let mut f = Fixture::new();
28076 corpus(&mut f);
28077 f.run(&[b"SELECT", b"1"]);
28078 f.run(&[b"HSET", b"d:9", b"t", b"delta", b"n", b"9"]);
28079 assert_eq!(
28082 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
28083 "*3\r\n:2\r\n$3\r\nd:3\r\n$3\r\nd:9\r\n"
28084 );
28085 assert_eq!(
28086 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
28087 "*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"
28088 );
28089 }
28090
28091 #[test]
28094 fn the_third_protocol_answers_a_map_of_five() {
28095 let mut f = Fixture::new();
28096 corpus(&mut f);
28097 f.out = Out::new(Proto::Resp3);
28098 assert_eq!(
28099 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
28100 concat!(
28101 "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
28102 "%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",
28103 "+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
28104 )
28105 );
28106 }
28107
28108 #[test]
28112 fn a_window_of_nothing_has_to_start_at_the_top() {
28113 let mut f = Fixture::new();
28114 corpus(&mut f);
28115 let refused = "-SEARCH_LIMIT_OVER The `offset` of the LIMIT must be 0 when `num` is 0\r\n";
28116 assert_eq!(
28117 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
28118 refused
28119 );
28120 assert_eq!(
28121 f.run(&[b"FT.EXPLAIN", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
28122 refused
28123 );
28124 assert_eq!(
28125 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
28126 refused
28127 );
28128 assert_eq!(
28129 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
28130 "*1\r\n:3\r\n"
28131 );
28132 }
28133
28134 #[test]
28137 fn an_aggregation_answers_a_count_and_then_the_properties() {
28138 let mut f = Fixture::new();
28139 corpus(&mut f);
28140 assert_eq!(
28141 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha"]),
28142 "*4\r\n:1\r\n*0\r\n*0\r\n*0\r\n"
28143 );
28144 assert_eq!(
28148 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"1", b"@t"]),
28149 concat!(
28150 "*4\r\n:1\r\n*2\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
28151 "*2\r\n$1\r\nt\r\n$11\r\nalpha gamma\r\n",
28152 "*2\r\n$1\r\nt\r\n$16\r\nalpha beta gamma\r\n"
28153 )
28154 );
28155 assert_eq!(
28159 f.run(&[
28160 b"FT.AGGREGATE",
28161 b"sx",
28162 b"alpha",
28163 b"LOAD",
28164 b"1",
28165 b"@n",
28166 b"LIMIT",
28167 b"1",
28168 b"2"
28169 ]),
28170 "*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"
28171 );
28172 assert_eq!(
28174 f.run(&[b"FT.AGGREGATE", b"sx", b"nope", b"LOAD", b"1", b"@t"]),
28175 "*1\r\n:0\r\n"
28176 );
28177 }
28178
28179 #[test]
28183 fn a_load_counts_words_and_can_rename_what_it_reads() {
28184 let mut f = Fixture::new();
28185 corpus(&mut f);
28186 assert_eq!(
28188 f.run(&[
28189 b"FT.AGGREGATE",
28190 b"sx",
28191 b"alpha",
28192 b"LOAD",
28193 b"3",
28194 b"@t",
28195 b"AS",
28196 b"text"
28197 ]),
28198 concat!(
28199 "*4\r\n:1\r\n*2\r\n$4\r\ntext\r\n$10\r\nalpha beta\r\n",
28200 "*2\r\n$4\r\ntext\r\n$11\r\nalpha gamma\r\n",
28201 "*2\r\n$4\r\ntext\r\n$16\r\nalpha beta gamma\r\n"
28202 )
28203 );
28204 assert_eq!(
28205 f.run(&[
28206 b"FT.AGGREGATE",
28207 b"sx",
28208 b"alpha",
28209 b"LOAD",
28210 b"*",
28211 b"LIMIT",
28212 b"0",
28213 b"1"
28214 ]),
28215 concat!(
28216 "*2\r\n:1\r\n*6\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
28217 "$1\r\ng\r\n$5\r\naa,bb\r\n$1\r\nn\r\n$1\r\n1\r\n"
28218 )
28219 );
28220 assert_eq!(
28222 f.run(&[
28223 b"FT.AGGREGATE",
28224 b"sx",
28225 b"alpha",
28226 b"LOAD",
28227 b"2",
28228 b"@n",
28229 b"@nope",
28230 b"LIMIT",
28231 b"0",
28232 b"2"
28233 ]),
28234 "*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"
28235 );
28236 }
28237
28238 #[test]
28241 fn a_load_refuses_a_count_it_cannot_use() {
28242 let mut f = Fixture::new();
28243 corpus(&mut f);
28244 let head = "-SEARCH_PARSE_ARGS Bad arguments for LOAD: ";
28245 assert_eq!(
28246 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"x"]),
28247 format!("{head}Expected number of fields or `*`\r\n")
28248 );
28249 assert_eq!(
28250 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"-1", b"@t"]),
28251 format!("{head}Value is outside acceptable bounds\r\n")
28252 );
28253 assert_eq!(
28254 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"5", b"@t"]),
28255 format!("{head}Expected an argument, but none provided\r\n")
28256 );
28257 assert_eq!(
28258 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD"]),
28259 format!("{head}Expected an argument, but none provided\r\n")
28260 );
28261 assert_eq!(
28265 f.run(&[
28266 b"FT.AGGREGATE",
28267 b"sx",
28268 b"alpha",
28269 b"LOAD",
28270 b"2",
28271 b"@t",
28272 b"AS"
28273 ]),
28274 "-SEARCH_PARSE_ARGS LOAD path AS name - must be accompanied with NAME\r\n"
28275 );
28276 assert_eq!(
28279 f.run(&[
28280 b"FT.AGGREGATE",
28281 b"sx",
28282 b"alpha",
28283 b"LOAD",
28284 b"2",
28285 b"@t",
28286 b"AS",
28287 b"VERBATIM"
28288 ]),
28289 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 5 for <main>\r\n"
28290 );
28291 assert_eq!(
28295 f.run(&[
28296 b"FT.AGGREGATE",
28297 b"sx",
28298 b"alpha",
28299 b"LOAD",
28300 b"0",
28301 b"LIMIT",
28302 b"0",
28303 b"1"
28304 ]),
28305 "*2\r\n:1\r\n*0\r\n"
28306 );
28307 }
28308
28309 #[test]
28312 fn a_pipeline_step_closes_the_door_on_the_search_words() {
28313 let mut f = Fixture::new();
28314 corpus(&mut f);
28315 assert_eq!(
28316 f.run(&[
28317 b"FT.AGGREGATE",
28318 b"sx",
28319 b"alpha",
28320 b"LOAD",
28321 b"1",
28322 b"@t",
28323 b"VERBATIM"
28324 ]),
28325 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 4 for <main>\r\n"
28326 );
28327 assert_eq!(
28328 f.run(&[
28329 b"FT.AGGREGATE",
28330 b"sx",
28331 b"alpha",
28332 b"LIMIT",
28333 b"0",
28334 b"1",
28335 b"VERBATIM"
28336 ]),
28337 "*2\r\n:1\r\n*0\r\n"
28338 );
28339 for word in [b"RETURN".as_slice(), b"SUMMARIZE", b"HIGHLIGHT"] {
28342 let name = core::str::from_utf8(word).expect("the three words are text");
28343 assert_eq!(
28344 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", word]),
28345 format!("-SEARCH_PARSE_ARGS {name} is not supported on FT.AGGREGATE\r\n")
28346 );
28347 }
28348 }
28349
28350 #[test]
28353 fn addscores_writes_a_shorter_score_than_withscores() {
28354 let mut f = Fixture::new();
28355 corpus(&mut f);
28356 assert_eq!(
28357 f.run(&[
28358 b"FT.AGGREGATE",
28359 b"sx",
28360 b"alpha",
28361 b"ADDSCORES",
28362 b"LOAD",
28363 b"1",
28364 b"@n",
28365 b"LIMIT",
28366 b"0",
28367 b"2"
28368 ]),
28369 concat!(
28370 "*3\r\n:1\r\n",
28371 "*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",
28372 "*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"
28373 )
28374 );
28375 assert_eq!(
28379 f.run(&[
28380 b"FT.AGGREGATE",
28381 b"sx",
28382 b"alpha",
28383 b"NOCONTENT",
28384 b"WITHSCORES",
28385 b"LIMIT",
28386 b"0",
28387 b"2"
28388 ]),
28389 "*3\r\n:1\r\n$18\r\n0.3566749439387324\r\n$18\r\n0.3566749439387324\r\n"
28390 );
28391 assert_eq!(
28392 f.run(&[
28393 b"FT.AGGREGATE",
28394 b"sx",
28395 b"alpha",
28396 b"WITHSORTKEYS",
28397 b"LOAD",
28398 b"1",
28399 b"@n",
28400 b"LIMIT",
28401 b"0",
28402 b"1"
28403 ]),
28404 "*3\r\n:1\r\n$-1\r\n*2\r\n$1\r\nn\r\n$1\r\n1\r\n"
28405 );
28406 }
28407
28408 #[test]
28411 fn a_normalising_scorer_answers_the_rows_backwards() {
28412 let mut f = Fixture::new();
28413 corpus(&mut f);
28414 assert_eq!(
28415 f.run(&[
28416 b"FT.AGGREGATE",
28417 b"sx",
28418 b"alpha",
28419 b"SCORER",
28420 b"BM25STD.NORM",
28421 b"ADDSCORES",
28422 b"LOAD",
28423 b"1",
28424 b"@n",
28425 b"LIMIT",
28426 b"1",
28427 b"2"
28428 ]),
28429 concat!(
28430 "*3\r\n:3\r\n",
28431 "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n2\r\n",
28432 "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n1\r\n"
28433 )
28434 );
28435 assert_eq!(
28438 f.run(&[
28439 b"FT.AGGREGATE",
28440 b"sx",
28441 b"alpha",
28442 b"SCORER",
28443 b"BM25STD.NORM",
28444 b"LOAD",
28445 b"1",
28446 b"@n",
28447 b"LIMIT",
28448 b"1",
28449 b"2"
28450 ]),
28451 "*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"
28452 );
28453 }
28454
28455 #[test]
28458 fn an_aggregation_answers_a_map_of_five_as_well() {
28459 let mut f = Fixture::new();
28460 corpus(&mut f);
28461 f.out = Out::new(Proto::Resp3);
28462 assert_eq!(
28463 f.run(&[
28464 b"FT.AGGREGATE",
28465 b"sx",
28466 b"alpha",
28467 b"ADDSCORES",
28468 b"WITHSCORES",
28469 b"WITHSORTKEYS",
28470 b"LOAD",
28471 b"1",
28472 b"@n",
28473 b"LIMIT",
28474 b"0",
28475 b"1"
28476 ]),
28477 concat!(
28478 "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
28479 "%4\r\n+score\r\n,0.3566749439387324\r\n+sortkey\r\n_\r\n",
28480 "+extra_attributes\r\n%2\r\n$7\r\n__score\r\n$14\r\n0.356674943939\r\n",
28481 "$1\r\nn\r\n$1\r\n1\r\n+values\r\n*0\r\n",
28482 "+total_results\r\n:1\r\n+warning\r\n*0\r\n"
28483 )
28484 );
28485 assert_eq!(
28488 f.run(&[
28489 b"FT.AGGREGATE",
28490 b"sx",
28491 b"alpha",
28492 b"NOCONTENT",
28493 b"LIMIT",
28494 b"0",
28495 b"1"
28496 ]),
28497 concat!(
28498 "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
28499 "%1\r\n+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
28500 )
28501 );
28502 }
28503}