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 let armed = notify::arm(self, self.slot_db(i));
1558 let gone = self.slot(i).evict_one();
1559 notify::drain(self, armed);
1560 gone
1561 };
1562 if gave {
1563 self.evict_db.store((i + 1) % self.slots(), Relaxed);
1564 self.mine().mark(1u64 << self.slot_db(i));
1565 return true;
1566 }
1567 }
1568 false
1569 }
1570
1571 pub fn expire_slice(&self, budget: usize) -> usize {
1586 let now = self.clock.now_ms();
1587 if now == self.expire_ms.load(Relaxed) {
1588 return 0;
1589 }
1590 self.expire_ms.store(now, Relaxed);
1591 self.expire_step(budget)
1592 }
1593
1594 pub fn expire_step(&self, budget: usize) -> usize {
1610 let mut spent = 0;
1611 let from = self.expire_db.load(Relaxed);
1612 for turn in 0..self.slots() {
1613 if spent >= budget {
1614 break;
1615 }
1616 let i = (from + turn) % self.slots();
1617 let armed = notify::arm(self, self.slot_db(i));
1622 let c = self.slot(i).expire_cycle(budget - spent);
1623 notify::drain(self, armed);
1624 spent += c.examined;
1625 if c.expired > 0 {
1626 self.expire_db.store((i + 1) % self.slots(), Relaxed);
1627 self.mine().note(1u64 << self.slot_db(i));
1628 }
1629 }
1630 spent
1631 }
1632
1633 fn compact_hard_step(&self) -> Option<usize> {
1639 let from = self.next_db.load(Relaxed);
1640 for turn in 0..self.slots() {
1641 let i = (from + turn) % self.slots();
1642 if let Some(moved) = self.slot(i).compact_hard() {
1643 self.next_db.store((i + 1) % self.slots(), Relaxed);
1644 return Some(moved);
1645 }
1646 }
1647 None
1648 }
1649
1650 fn collect_marks(&self) {
1659 let mut marked = 0;
1660 for thread in &self.locals {
1661 marked |= thread.dirty.swap(0, Relaxed);
1662 }
1663 self.mine().note(marked);
1664 }
1665
1666 pub fn compact_step(&self) -> Option<usize> {
1679 self.collect_marks();
1680 let mine = self.mine();
1681 let from = self.next_db.load(Relaxed);
1682 for turn in 0..self.slots() {
1683 let i = (from + turn) % self.slots();
1684 let at = self.slot_db(i);
1688 if !mine.wanted(at) {
1689 continue;
1690 }
1691 if let Some(moved) = self.slot(i).compact_step() {
1692 self.next_db.store((i + 1) % self.slots(), Relaxed);
1693 return Some(moved);
1694 }
1695 if i % self.width == self.width - 1 {
1699 mine.done(at);
1700 }
1701 }
1702 None
1703 }
1704}
1705
1706impl Server {
1707 fn watching(&self) -> bool {
1716 self.watched.load(Relaxed) != 0
1717 }
1718
1719 pub(crate) fn notify_flags(&self) -> u32 {
1726 self.notify.load(Relaxed)
1727 }
1728
1729 pub(crate) fn set_notify_flags(&self, flags: u32) {
1731 self.notify.store(flags, Relaxed);
1732 }
1733
1734 fn recount(&self, watches: &Watches) {
1739 self.watched.store(watches.len(), Relaxed);
1740 }
1741}
1742
1743impl Default for Server {
1744 fn default() -> Server {
1745 Server::new()
1746 }
1747}
1748
1749pub struct Session {
1751 db: usize,
1752 id: u64,
1753 conn: u32,
1761 name: Vec<u8>,
1762 sets: himport::Fieldsets,
1768 scripted: bool,
1778 multi: Option<multi::Queue>,
1784 watching: Vec<multi::Watched>,
1791 running: bool,
1798 replay: crate::request::Argv,
1807 subs: Option<Box<pubsub::Subs>>,
1815}
1816
1817impl Session {
1818 #[must_use]
1820 pub fn new(id: u64) -> Session {
1821 Session {
1822 db: 0,
1823 id,
1824 conn: u32::MAX,
1825 name: Vec::new(),
1826 sets: himport::Fieldsets::default(),
1827 scripted: false,
1828 multi: None,
1829 watching: Vec::new(),
1830 running: false,
1831 replay: crate::request::Argv::new(),
1832 subs: None,
1833 }
1834 }
1835
1836 pub(crate) const fn scripted(&self) -> bool {
1838 self.scripted
1839 }
1840
1841 pub(crate) const fn running(&self) -> bool {
1843 self.running
1844 }
1845
1846 pub(crate) const fn set_conn(&mut self, conn: u32) {
1852 self.conn = conn;
1853 }
1854
1855 #[must_use]
1857 pub const fn id(&self) -> u64 {
1858 self.id
1859 }
1860
1861 #[must_use]
1863 pub const fn db(&self) -> usize {
1864 self.db
1865 }
1866
1867 #[must_use]
1869 pub fn name(&self) -> &[u8] {
1870 &self.name
1871 }
1872
1873 pub fn reset(&mut self) {
1878 self.db = 0;
1879 self.name.clear();
1880 self.sets.clear();
1884 }
1885
1886 fn set_name(&mut self, name: &[u8]) {
1888 yo_alloc::allow(|| {
1889 self.name.clear();
1890 self.name.extend_from_slice(name);
1891 });
1892 }
1893}
1894
1895pub fn forget_session(server: &Server, session: &mut Session) {
1904 multi::release(server, session);
1905 pubsub::release(server, session);
1906}
1907
1908pub fn execute(server: &Server, session: &mut Session, args: Args<'_>, out: &mut Out) -> Flow {
1913 if args.is_empty() {
1916 return Flow::Continue;
1917 }
1918 resolved(server, session, lookup(args.name()), args, out)
1919}
1920
1921pub fn resolved(
1933 server: &Server,
1934 session: &mut Session,
1935 spec: Option<&'static Spec>,
1936 args: Args<'_>,
1937 out: &mut Out,
1938) -> Flow {
1939 if args.is_empty() {
1940 return Flow::Continue;
1941 }
1942 server.mine().stats.commands.bump();
1943
1944 let Some(spec) = spec else {
1951 multi::refuse(server, session, None, &args::unknown_command(args), out);
1952 return Flow::Continue;
1953 };
1954 if !arity_ok(spec, args.len()) {
1955 server.mine().cmdstats.at(spec).rejected.bump();
1956 multi::refuse(
1957 server,
1958 session,
1959 Some(spec),
1960 &args::wrong_arity(spec.name),
1961 out,
1962 );
1963 return Flow::Continue;
1964 }
1965 if session.in_multi()
1966 && let Some(e) = multi::refused_in_multi(spec)
1967 {
1968 server.mine().cmdstats.at(spec).rejected.bump();
1969 multi::refuse(server, session, Some(spec), &e, out);
1970 return Flow::Continue;
1971 }
1972
1973 if server.maxmemory() != 0 && !server.make_room() && spec.flags.contains(&"denyoom") {
1983 server.mine().cmdstats.at(spec).rejected.bump();
1984 session.dirty_multi();
1985 out.error_line(b"OOM ", OOM);
1986 return Flow::Continue;
1987 }
1988
1989 if let Some(e) = pubsub::refused(session, spec, out) {
1997 server.mine().cmdstats.at(spec).rejected.bump();
1998 multi::refuse(server, session, Some(spec), &e, out);
1999 return Flow::Continue;
2000 }
2001
2002 if session.queues(spec.name) {
2007 return multi::queue(session, args, out);
2008 }
2009
2010 server.mine().mark(match spec.group {
2017 "string" | "bitmap" | "hyperloglog" | "geo" | "set" | "hash" | "list" | "zset"
2018 | "array" | "stream" | "bloom" | "cuckoo" | "cms" | "topk" | "tdigest" | "ts" => {
2019 1u64 << session.db
2020 }
2021 _ => ALL_DATABASES,
2022 });
2023
2024 let mark = out.len();
2025 let armed = notify::arm(server, session.db);
2036 let done = if spec.flags.contains(&"blocking") {
2037 blocking::execute(server, session, spec, args, out)
2038 } else {
2039 match spec.group {
2040 "string" => {
2041 let db = session.db;
2042 strings::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2043 }
2044 "bitmap" => {
2048 let db = session.db;
2049 bits::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2050 }
2051 "hyperloglog" => {
2054 let db = session.db;
2055 hll::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2056 }
2057 "set" => {
2058 let db = session.db;
2059 sets::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2060 }
2061 "hash" if spec.name == "himport" => {
2066 let db = session.db;
2067 himport::execute(&server.dbs[db], &mut session.sets, args, out)
2068 .map(|()| Flow::Continue)
2069 }
2070 "hash" => {
2075 let db = session.db;
2076 let changed = hashes::execute(&server.dbs[db], db, spec, args, out);
2077 changed.map(|changed| {
2078 indexing::changed(server, db, args.get(1), changed);
2079 Flow::Continue
2080 })
2081 }
2082 "list" => {
2083 let db = session.db;
2084 lists::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2085 }
2086 "zset" => {
2087 let db = session.db;
2088 zsets::execute(&server.dbs[db], db, spec, args, out).map(|()| Flow::Continue)
2089 }
2090 "geo" => {
2094 let db = session.db;
2095 geo::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2096 }
2097 "array" => {
2098 let db = session.db;
2099 arrays::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2100 }
2101 "graph" => {
2102 let db = session.db;
2103 graph::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2104 }
2105 "json" => {
2109 let db = session.db;
2110 json::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2111 }
2112 "vector" => {
2113 let db = session.db;
2114 vectors::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2115 }
2116 "bloom" => {
2117 let db = session.db;
2118 bloom::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2119 }
2120 "cuckoo" => {
2121 let db = session.db;
2122 cuckoo::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2123 }
2124 "cms" => {
2125 let db = session.db;
2126 cms::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2127 }
2128 "topk" => {
2129 let db = session.db;
2130 topk::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2131 }
2132 "tdigest" => {
2133 let db = session.db;
2134 tdigest::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2135 }
2136 "ts" => {
2137 let db = session.db;
2138 ts::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2139 }
2140 "stream" => {
2145 let db = session.db;
2146 let now = server.now_ms();
2147 streams::execute(&server.dbs[db], db, spec, args, now, out).map(|()| Flow::Continue)
2148 }
2149 "keyspace" if spec.name == "migrate" => {
2153 migrate::execute(server, session.db, args, out).map(|()| Flow::Continue)
2154 }
2155 "keyspace" => {
2161 let mut touched = indexing::Touched::new(server);
2162 let done =
2163 keyspace::execute(&server.dbs, session.db, spec, args, out, &mut touched);
2164 done.map(|()| {
2165 indexing::touched(server, &touched);
2166 Flow::Continue
2167 })
2168 }
2169 "search" if spec.name == "FT.SEARCH" => {
2176 search::find(server, session.db, args, out).map(|()| Flow::Continue)
2181 }
2182 "search" if spec.name == "FT.AGGREGATE" => {
2183 search::roll(server, session.db, args, out).map(|()| Flow::Continue)
2184 }
2185 "search" if spec.name == "FT.HYBRID" => {
2186 search::hybrid(server, session.db, args, out).map(|()| Flow::Continue)
2187 }
2188 "search" if spec.name == "FT.PROFILE" => {
2189 search::profiled(server, session.db, args, out).map(|()| Flow::Continue)
2192 }
2193 "search" if spec.name.starts_with("FT.SUG") => {
2197 let db = session.db;
2198 suggest::execute(&server.dbs[db], spec, args, out).map(|()| Flow::Continue)
2199 }
2200 "search"
2204 if matches!(
2205 spec.name,
2206 "FT.ADD" | "FT.SAFEADD" | "FT.GET" | "FT.MGET" | "FT.DEL"
2207 ) =>
2208 {
2209 let db = session.db;
2210 search::docs::execute(server, db, spec, args, out).map(|()| Flow::Continue)
2211 }
2212 "search" if spec.name == "FT.CURSOR" => {
2213 search::cursor::execute(server, args, out).map(|()| Flow::Continue)
2217 }
2218 "search" => {
2219 let db = session.db;
2220 let made = search::execute(server, &mut server.search.lock(), db, spec, args, out);
2221 made.map(|made| {
2222 match made {
2223 Some(search::After::Scan(fill)) => indexing::scan(server, db, &fill),
2224 Some(search::After::Sweep(keys)) => indexing::sweep(server, db, &keys),
2225 None => {}
2226 }
2227 Flow::Continue
2228 })
2229 }
2230 "scripting" => {
2231 scripting::execute(server, session, spec, args, out).map(|()| Flow::Continue)
2232 }
2233 "transactions" => multi::execute(server, session, spec, args, out),
2234 "pubsub" => pubsub::execute(server, session, spec, args, out),
2238 _ => server::execute(server, session, spec, args, out),
2239 }
2240 };
2241 notify::drain(server, armed);
2246
2247 let flow = match done {
2248 Ok(flow) => flow,
2249 Err(e) => {
2250 out.truncate(mark);
2251 write_error(out, &e);
2252 Flow::Continue
2253 }
2254 };
2255
2256 if server.watching() && spec.flags.contains(&"write") {
2261 multi::touched(server, session, spec, args);
2262 }
2263
2264 let row = server.mine().cmdstats.at(spec);
2275 row.calls.bump();
2276 if matches!(out.as_slice().get(mark), Some(b'-' | b'!')) {
2277 row.failed.bump();
2278 }
2279 flow
2280}
2281
2282fn write_error(out: &mut Out, e: &Error) {
2292 let prefix: &[u8] = match e.code() {
2293 Code::WrongType => b"WRONGTYPE ",
2294 Code::Corrupt => b"INVALIDOBJ ",
2298 _ => b"ERR ",
2299 };
2300 out.error_line(prefix, e.message().as_bytes());
2301}
2302
2303#[cfg(test)]
2304mod tests {
2305 use super::*;
2306 use crate::proto::{Limits, Proto};
2307 use crate::request::Argv;
2308
2309 pub(crate) fn encode(parts: &[&[u8]]) -> Vec<u8> {
2314 let mut wire = format!("*{}\r\n", parts.len()).into_bytes();
2315 for p in parts {
2316 wire.extend_from_slice(format!("${}\r\n", p.len()).as_bytes());
2317 wire.extend_from_slice(p);
2318 wire.extend_from_slice(b"\r\n");
2319 }
2320 wire
2321 }
2322
2323 struct Fixture {
2325 server: Server,
2326 session: Session,
2327 argv: Argv,
2328 out: Out,
2329 }
2330
2331 impl Fixture {
2332 fn new() -> Fixture {
2333 Fixture::on(Server::new())
2334 }
2335
2336 fn striped(width: usize) -> Fixture {
2338 Fixture::on(Server::with_width(width))
2339 }
2340
2341 fn on(server: Server) -> Fixture {
2342 Fixture {
2343 server,
2344 session: Session::new(7),
2345 argv: Argv::new(),
2346 out: Out::new(Proto::Resp2),
2347 }
2348 }
2349
2350 fn run(&mut self, parts: &[&[u8]]) -> String {
2352 self.flow(parts).1
2353 }
2354
2355 fn raw(&mut self, parts: &[&[u8]]) -> Vec<u8> {
2361 let wire = encode(parts);
2362 self.argv.decode(&wire, &Limits::default()).unwrap();
2363 self.out.clear();
2364 execute(
2365 &self.server,
2366 &mut self.session,
2367 Args::new(&self.argv, &wire),
2368 &mut self.out,
2369 );
2370 self.out.as_slice().to_vec()
2371 }
2372
2373 fn advance(&mut self, ms: u64) {
2375 self.server.advance_clock_ms(ms);
2376 }
2377
2378 fn other(&mut self, parts: &[&[u8]]) -> String {
2383 self.other_in(self.session.db(), parts)
2384 }
2385
2386 fn other_in(&mut self, db: usize, parts: &[&[u8]]) -> String {
2388 let mut session = Session::new(8);
2389 session.db = db;
2390 let reply = self.by(&mut session, parts);
2391 forget_session(&self.server, &mut session);
2392 reply
2393 }
2394
2395 fn by(&mut self, session: &mut Session, parts: &[&[u8]]) -> String {
2397 let wire = encode(parts);
2398 let mut argv = Argv::new();
2399 argv.decode(&wire, &Limits::default()).unwrap();
2400 let mut out = Out::new(Proto::Resp2);
2401 execute(&self.server, session, Args::new(&argv, &wire), &mut out);
2402 String::from_utf8_lossy(out.as_slice()).into_owned()
2403 }
2404
2405 fn flow(&mut self, parts: &[&[u8]]) -> (Flow, String) {
2407 let wire = encode(parts);
2408 self.argv.decode(&wire, &Limits::default()).unwrap();
2409 self.out.clear();
2410 let flow = execute(
2411 &self.server,
2412 &mut self.session,
2413 Args::new(&self.argv, &wire),
2414 &mut self.out,
2415 );
2416 (
2417 flow,
2418 String::from_utf8_lossy(self.out.as_slice()).into_owned(),
2419 )
2420 }
2421 }
2422
2423 #[test]
2424 fn multi_holds_commands_and_exec_runs_them() {
2425 let mut f = Fixture::new();
2426 assert_eq!(f.run(&[b"MULTI"]), "+OK\r\n");
2427 assert_eq!(f.run(&[b"SET", b"k", b"1"]), "+QUEUED\r\n");
2428 assert_eq!(f.run(&[b"INCR", b"k"]), "+QUEUED\r\n");
2429 assert_eq!(f.other(&[b"GET", b"k"]), "$-1\r\n");
2431 assert_eq!(f.run(&[b"EXEC"]), "*2\r\n+OK\r\n:2\r\n");
2432 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n2\r\n");
2433 }
2434
2435 #[test]
2444 fn the_second_exec_of_a_shape_does_not_grow_the_buffer() {
2445 let mut f = Fixture::new();
2446 for _ in 0..2 {
2447 f.run(&[b"MULTI"]);
2448 f.run(&[b"SET", b"k", b"1"]);
2449 f.run(&[b"INCR", b"k"]);
2450 f.run(&[b"EXEC"]);
2451 }
2452 let room = f.session.replay.room();
2453 assert!(room > 0, "the first transaction should have set the room");
2454 f.run(&[b"MULTI"]);
2455 f.run(&[b"SET", b"k", b"1"]);
2456 f.run(&[b"INCR", b"k"]);
2457 f.run(&[b"EXEC"]);
2458 assert_eq!(f.session.replay.room(), room);
2459 }
2460
2461 #[test]
2462 fn an_empty_transaction_answers_an_empty_array() {
2463 let mut f = Fixture::new();
2464 f.run(&[b"MULTI"]);
2465 assert_eq!(f.run(&[b"EXEC"]), "*0\r\n");
2466 }
2467
2468 #[test]
2469 fn exec_and_discard_want_a_transaction_to_be_open() {
2470 let mut f = Fixture::new();
2471 assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2472 assert_eq!(f.run(&[b"DISCARD"]), "-ERR DISCARD without MULTI\r\n");
2473 assert_eq!(f.run(&[b"UNWATCH"]), "+OK\r\n");
2476 }
2477
2478 #[test]
2479 fn an_error_a_command_body_raises_leaves_the_transaction_alive() {
2480 let mut f = Fixture::new();
2481 f.run(&[b"MULTI"]);
2482 assert_eq!(
2483 f.run(&[b"MULTI"]),
2484 "-ERR MULTI calls can not be nested\r\n",
2485 "nested MULTI is raised by the command and not by the funnel"
2486 );
2487 assert_eq!(
2488 f.run(&[b"WATCH", b"k"]),
2489 "-ERR WATCH inside MULTI is not allowed\r\n"
2490 );
2491 f.run(&[b"SET", b"k", b"1"]);
2492 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+OK\r\n");
2493 }
2494
2495 #[test]
2496 fn an_error_the_funnel_raises_kills_the_transaction() {
2497 for bad in [
2498 &[b"NOSUCHCOMMAND".as_slice()] as &[&[u8]],
2499 &[b"GET".as_slice()],
2500 ] {
2501 let mut f = Fixture::new();
2502 f.run(&[b"MULTI"]);
2503 assert!(f.run(bad).starts_with("-ERR "));
2504 assert_eq!(
2505 f.run(&[b"SET", b"k", b"1"]),
2506 "+QUEUED\r\n",
2507 "a dead transaction still answers QUEUED, which is Redis"
2508 );
2509 assert_eq!(
2510 f.run(&[b"EXEC"]),
2511 "-EXECABORT Transaction discarded because of previous errors.\r\n"
2512 );
2513 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2514 }
2515 }
2516
2517 #[test]
2518 fn exec_with_an_argument_is_an_abort_and_not_an_arity_error() {
2519 let mut f = Fixture::new();
2520 f.run(&[b"MULTI"]);
2521 f.run(&[b"SET", b"k", b"1"]);
2522 assert_eq!(
2523 f.run(&[b"EXEC", b"x"]),
2524 "-EXECABORT Transaction discarded because of: wrong number of arguments for 'exec' command\r\n"
2525 );
2526 assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2527 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2528 }
2529
2530 #[test]
2531 fn a_command_a_transaction_may_not_hold_kills_it() {
2532 let mut f = Fixture::new();
2533 f.run(&[b"MULTI"]);
2534 assert_eq!(
2535 f.run(&[b"SHUTDOWN", b"NOSAVE"]),
2536 "-ERR Command not allowed inside a transaction\r\n"
2537 );
2538 assert_eq!(
2539 f.run(&[b"EXEC"]),
2540 "-EXECABORT Transaction discarded because of previous errors.\r\n"
2541 );
2542 }
2543
2544 #[test]
2545 fn a_failing_command_inside_exec_is_an_element_and_the_rest_still_runs() {
2546 let mut f = Fixture::new();
2547 f.run(&[b"RPUSH", b"l", b"v"]);
2548 f.run(&[b"MULTI"]);
2549 f.run(&[b"INCR", b"l"]);
2550 f.run(&[b"SET", b"y", b"2"]);
2551 assert_eq!(
2552 f.run(&[b"EXEC"]),
2553 "*2\r\n-WRONGTYPE Operation against a key holding the wrong kind of value\r\n+OK\r\n"
2554 );
2555 assert_eq!(f.run(&[b"GET", b"y"]), "$1\r\n2\r\n");
2556 }
2557
2558 #[test]
2559 fn discard_and_reset_both_throw_the_queue_away() {
2560 let mut f = Fixture::new();
2561 f.run(&[b"MULTI"]);
2562 f.run(&[b"SET", b"k", b"1"]);
2563 assert_eq!(f.run(&[b"DISCARD"]), "+OK\r\n");
2564 assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2565 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2566
2567 f.run(&[b"MULTI"]);
2568 f.run(&[b"SET", b"k", b"1"]);
2569 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
2570 assert_eq!(f.run(&[b"EXEC"]), "-ERR EXEC without MULTI\r\n");
2571 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2572 }
2573
2574 #[test]
2575 fn select_is_queued_and_applied_when_exec_runs_it() {
2576 let mut f = Fixture::new();
2577 f.run(&[b"MULTI"]);
2578 assert_eq!(f.run(&[b"SELECT", b"3"]), "+QUEUED\r\n");
2579 f.run(&[b"SET", b"k", b"1"]);
2580 assert_eq!(f.run(&[b"EXEC"]), "*2\r\n+OK\r\n+OK\r\n");
2581 assert_eq!(f.session.db(), 3, "the SELECT applied and stayed applied");
2582 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n1\r\n");
2583 }
2584
2585 #[test]
2586 fn a_write_by_another_connection_fails_the_transaction() {
2587 let mut f = Fixture::new();
2588 f.run(&[b"SET", b"k", b"1"]);
2589 assert_eq!(f.run(&[b"WATCH", b"k"]), "+OK\r\n");
2590 f.other(&[b"SET", b"k", b"2"]);
2591 f.run(&[b"MULTI"]);
2592 f.run(&[b"GET", b"k"]);
2593 assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
2594 }
2595
2596 #[test]
2597 fn a_write_that_puts_the_same_value_back_still_fails_it() {
2598 let mut f = Fixture::new();
2599 f.run(&[b"SET", b"k", b"1"]);
2600 f.run(&[b"WATCH", b"k"]);
2601 f.other(&[b"SET", b"k", b"1"]);
2602 f.run(&[b"MULTI"]);
2603 assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
2604 }
2605
2606 #[test]
2607 fn a_read_by_another_connection_does_not() {
2608 let mut f = Fixture::new();
2609 f.run(&[b"SET", b"k", b"1"]);
2610 f.run(&[b"WATCH", b"k"]);
2611 f.other(&[b"GET", b"k"]);
2612 f.other(&[b"STRLEN", b"k"]);
2613 f.run(&[b"MULTI"]);
2614 f.run(&[b"GET", b"k"]);
2615 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n1\r\n");
2616 }
2617
2618 #[test]
2619 fn deleting_a_key_that_was_never_there_does_not_fail_a_watch_on_it() {
2620 let mut f = Fixture::new();
2621 f.run(&[b"WATCH", b"k"]);
2622 f.other(&[b"DEL", b"k"]);
2623 f.run(&[b"MULTI"]);
2624 f.run(&[b"PING"]);
2625 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+PONG\r\n");
2626 f.run(&[b"WATCH", b"k"]);
2628 f.other(&[b"SET", b"k", b"1"]);
2629 f.run(&[b"MULTI"]);
2630 assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
2631 }
2632
2633 #[test]
2634 fn a_watched_key_that_expires_fails_the_transaction() {
2635 let mut f = Fixture::new();
2636 f.run(&[b"SET", b"k", b"1", b"PX", b"50"]);
2637 f.run(&[b"WATCH", b"k"]);
2638 f.run(&[b"MULTI"]);
2639 f.advance(100);
2640 assert_eq!(
2641 f.run(&[b"EXEC"]),
2642 "*-1\r\n",
2643 "nothing wrote to the key, so only the liveness check can catch this"
2644 );
2645 }
2646
2647 #[test]
2648 fn every_way_a_transaction_ends_lets_go_of_the_watches() {
2649 for end in [
2650 &[b"EXEC".as_slice()] as &[&[u8]],
2651 &[b"DISCARD".as_slice()],
2652 &[b"UNWATCH".as_slice()],
2653 &[b"RESET".as_slice()],
2654 ] {
2655 let mut f = Fixture::new();
2656 f.run(&[b"SET", b"k", b"1"]);
2657 f.run(&[b"WATCH", b"k"]);
2658 if end[0] != b"UNWATCH" && end[0] != b"RESET" {
2659 f.run(&[b"MULTI"]);
2660 }
2661 f.run(end);
2662 assert!(!f.server.watching(), "{end:?} left a row behind");
2663 f.other(&[b"SET", b"k", b"2"]);
2665 f.run(&[b"MULTI"]);
2666 f.run(&[b"GET", b"k"]);
2667 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n2\r\n");
2668 }
2669 }
2670
2671 #[test]
2672 fn a_connection_going_away_lets_go_of_its_watches() {
2673 let mut f = Fixture::new();
2674 f.run(&[b"SET", b"k", b"1"]);
2675 f.run(&[b"WATCH", b"k"]);
2676 assert!(f.server.watching());
2677 forget_session(&f.server, &mut f.session);
2678 assert!(!f.server.watching());
2679 }
2680
2681 #[test]
2682 fn watching_the_same_key_twice_is_one_watch() {
2683 let mut f = Fixture::new();
2684 f.run(&[b"SET", b"k", b"1"]);
2685 f.run(&[b"WATCH", b"k", b"k"]);
2686 f.run(&[b"UNWATCH"]);
2687 assert!(
2688 !f.server.watching(),
2689 "the row counts watchers, so a doubled watch would leave one behind"
2690 );
2691 }
2692
2693 #[test]
2694 fn two_connections_can_watch_the_same_key() {
2695 let mut f = Fixture::new();
2696 f.run(&[b"SET", b"k", b"1"]);
2697 f.run(&[b"WATCH", b"k"]);
2698 let mut second = Session::new(9);
2699 second.db = f.session.db();
2700 assert_eq!(f.by(&mut second, &[b"WATCH", b"k"]), "+OK\r\n");
2701 forget_session(&f.server, &mut second);
2703 assert!(f.server.watching());
2704 f.other(&[b"SET", b"k", b"2"]);
2705 f.run(&[b"MULTI"]);
2706 assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
2707 }
2708
2709 #[test]
2710 fn flushdb_fails_a_watch_on_a_key_that_was_there() {
2711 let mut f = Fixture::new();
2712 f.run(&[b"SET", b"k", b"1"]);
2713 f.run(&[b"WATCH", b"k"]);
2714 f.other(&[b"FLUSHDB"]);
2715 f.run(&[b"MULTI"]);
2716 assert_eq!(f.run(&[b"EXEC"]), "*-1\r\n");
2717 }
2718
2719 #[test]
2720 fn flushdb_does_not_fail_a_watch_on_a_key_that_was_not() {
2721 let mut f = Fixture::new();
2722 f.run(&[b"WATCH", b"k"]);
2723 f.other(&[b"FLUSHDB"]);
2724 f.run(&[b"MULTI"]);
2725 f.run(&[b"PING"]);
2726 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n+PONG\r\n");
2727 }
2728
2729 #[test]
2730 fn a_watch_is_on_a_database_and_a_key_and_not_on_a_key() {
2731 let mut f = Fixture::new();
2732 f.run(&[b"SET", b"k", b"1"]);
2733 f.run(&[b"WATCH", b"k"]);
2734 let elsewhere = f.session.db() + 1;
2736 f.other_in(elsewhere, &[b"SET", b"k", b"9"]);
2737 f.run(&[b"MULTI"]);
2738 f.run(&[b"GET", b"k"]);
2739 assert_eq!(f.run(&[b"EXEC"]), "*1\r\n$1\r\n1\r\n");
2740 }
2741
2742 #[test]
2743 fn a_write_that_reaches_a_key_it_did_not_name_still_fails_a_watch() {
2744 let mut f = Fixture::new();
2745 f.run(&[b"RPUSH", b"src", b"1"]);
2746 f.run(&[b"WATCH", b"dst"]);
2747 f.other(&[b"SORT", b"src", b"STORE", b"dst"]);
2748 f.run(&[b"MULTI"]);
2749 assert_eq!(
2750 f.run(&[b"EXEC"]),
2751 "*-1\r\n",
2752 "SORT is movablekeys, so every watched key in the database is asked"
2753 );
2754 }
2755
2756 #[test]
2757 fn a_server_nobody_is_watching_says_so() {
2758 let mut f = Fixture::new();
2759 assert!(!f.server.watching());
2760 f.run(&[b"SET", b"k", b"1"]);
2761 assert!(!f.server.watching());
2762 }
2763
2764 #[test]
2770 fn the_count_a_subscribe_answers_covers_channels_and_patterns() {
2771 let mut f = Fixture::new();
2772 assert_eq!(
2773 f.run(&[b"SUBSCRIBE", b"a", b"b"]),
2774 "*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"
2775 );
2776 assert_eq!(
2777 f.run(&[b"PSUBSCRIBE", b"c*"]),
2778 "*3\r\n$10\r\npsubscribe\r\n$2\r\nc*\r\n:3\r\n"
2779 );
2780 assert_eq!(
2781 f.run(&[b"SSUBSCRIBE", b"s"]),
2782 "*3\r\n$10\r\nssubscribe\r\n$1\r\ns\r\n:1\r\n"
2783 );
2784 assert_eq!(
2787 f.run(&[b"SUBSCRIBE", b"a"]),
2788 "*3\r\n$9\r\nsubscribe\r\n$1\r\na\r\n:3\r\n"
2789 );
2790 }
2791
2792 #[test]
2795 fn unsubscribe_answers_for_names_it_was_not_holding_too() {
2796 let mut f = Fixture::new();
2797 f.run(&[b"SUBSCRIBE", b"a"]);
2798
2799 assert_eq!(
2802 f.run(&[b"UNSUBSCRIBE", b"zz"]),
2803 "*3\r\n$11\r\nunsubscribe\r\n$2\r\nzz\r\n:1\r\n"
2804 );
2805 f.run(&[b"SUBSCRIBE", b"b"]);
2807 f.run(&[b"PSUBSCRIBE", b"p*"]);
2808 assert_eq!(
2809 f.run(&[b"UNSUBSCRIBE"]),
2810 "*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"
2811 );
2812 assert_eq!(
2815 f.run(&[b"UNSUBSCRIBE"]),
2816 "*3\r\n$11\r\nunsubscribe\r\n$-1\r\n:1\r\n",
2817 "the pattern is still held, so the count is one"
2818 );
2819 assert_eq!(
2820 f.run(&[b"SUNSUBSCRIBE"]),
2821 "*3\r\n$12\r\nsunsubscribe\r\n$-1\r\n:0\r\n",
2822 "shard channels are counted on their own"
2823 );
2824 }
2825
2826 #[test]
2831 fn the_subscribe_gate_does_not_reach_inside_exec() {
2832 let mut f = Fixture::new();
2833 f.run(&[b"SET", b"k", b"1"]);
2834 f.run(&[b"MULTI"]);
2835 assert_eq!(f.run(&[b"SUBSCRIBE", b"z"]), "+QUEUED\r\n");
2836 assert_eq!(f.run(&[b"GET", b"k"]), "+QUEUED\r\n");
2837 assert_eq!(
2838 f.run(&[b"EXEC"]),
2839 "*2\r\n*3\r\n$9\r\nsubscribe\r\n$1\r\nz\r\n:1\r\n$1\r\n1\r\n"
2840 );
2841 assert_eq!(
2844 f.run(&[b"GET", b"k"]),
2845 "-ERR Can't execute 'get': only (P|S)SUBSCRIBE / (P|S)UNSUBSCRIBE / PING / QUIT / RESET are allowed in this context\r\n"
2846 );
2847 }
2848
2849 #[test]
2852 fn exec_sent_by_a_subscriber_aborts_the_transaction() {
2853 let mut f = Fixture::new();
2854 f.run(&[b"MULTI"]);
2855 f.run(&[b"SET", b"k", b"1"]);
2856 f.run(&[b"SUBSCRIBE", b"z"]);
2857 f.run(&[b"EXEC"]);
2858 f.run(&[b"MULTI"]);
2859 assert_eq!(
2860 f.run(&[b"EXEC"]),
2861 "-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"
2862 );
2863 }
2864
2865 #[test]
2868 fn reset_lets_go_of_every_subscription() {
2869 let mut f = Fixture::new();
2870 f.run(&[b"SUBSCRIBE", b"a"]);
2871 f.run(&[b"PSUBSCRIBE", b"p*"]);
2872 f.run(&[b"SSUBSCRIBE", b"s"]);
2873 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
2874 assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":0\r\n");
2875 assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*0\r\n");
2876 assert_eq!(f.run(&[b"PUBSUB", b"SHARDCHANNELS"]), "*0\r\n");
2877 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
2879 }
2880
2881 #[test]
2884 fn pubsub_reports_channels_patterns_and_shard_channels_apart() {
2885 let mut f = Fixture::new();
2886 let mut sub = Session::new(9);
2887 f.by(&mut sub, &[b"SUBSCRIBE", b"a"]);
2888 f.by(&mut sub, &[b"PSUBSCRIBE", b"a*"]);
2889 f.by(&mut sub, &[b"SSUBSCRIBE", b"a"]);
2890
2891 assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*1\r\n$1\r\na\r\n");
2892 assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS", b"b*"]), "*0\r\n");
2893 assert_eq!(f.run(&[b"PUBSUB", b"SHARDCHANNELS"]), "*1\r\n$1\r\na\r\n");
2894 assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":1\r\n");
2895 assert_eq!(
2896 f.run(&[b"PUBSUB", b"NUMSUB", b"a", b"zz"]),
2897 "*4\r\n$1\r\na\r\n:1\r\n$2\r\nzz\r\n:0\r\n"
2898 );
2899 assert_eq!(
2900 f.run(&[b"PUBSUB", b"SHARDNUMSUB", b"a"]),
2901 "*2\r\n$1\r\na\r\n:1\r\n",
2902 "the shard channel and the channel share a name and not a count"
2903 );
2904 assert_eq!(f.run(&[b"PUBSUB", b"NUMSUB"]), "*0\r\n");
2905
2906 forget_session(&f.server, &mut sub);
2907 assert_eq!(f.run(&[b"PUBSUB", b"NUMPAT"]), ":0\r\n");
2908 assert_eq!(f.run(&[b"PUBSUB", b"CHANNELS"]), "*0\r\n");
2909 }
2910
2911 #[test]
2914 fn the_notification_setting_reads_back_in_the_servers_own_spelling() {
2915 let mut f = Fixture::new();
2916 assert_eq!(
2917 f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
2918 "*2\r\n$22\r\nnotify-keyspace-events\r\n$0\r\n\r\n"
2919 );
2920 assert_eq!(
2921 f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"KEA"]),
2922 "+OK\r\n"
2923 );
2924 assert_eq!(
2927 f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
2928 "*2\r\n$22\r\nnotify-keyspace-events\r\n$3\r\nAKE\r\n"
2929 );
2930 assert_eq!(
2931 f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"Kg"]),
2932 "+OK\r\n"
2933 );
2934 assert_eq!(
2935 f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
2936 "*2\r\n$22\r\nnotify-keyspace-events\r\n$2\r\ngK\r\n"
2937 );
2938 }
2939
2940 #[test]
2941 fn a_letter_the_notification_setting_does_not_know_is_refused() {
2942 let mut f = Fixture::new();
2943 assert_eq!(
2944 f.run(&[b"CONFIG", b"SET", b"notify-keyspace-events", b"KEQ"]),
2945 "-ERR CONFIG SET failed (possibly related to argument 'notify-keyspace-events') \
2946 - Invalid event class character. Use 'Ag$lshzxeKEtmdnocaSTIV'.\r\n"
2947 );
2948 assert_eq!(
2951 f.run(&[b"CONFIG", b"GET", b"notify-keyspace-events"]),
2952 "*2\r\n$22\r\nnotify-keyspace-events\r\n$0\r\n\r\n"
2953 );
2954 }
2955
2956 #[test]
2962 fn pubsub_says_no_two_different_ways() {
2963 let mut f = Fixture::new();
2964 assert_eq!(
2965 f.run(&[b"PUBSUB"]),
2966 "-ERR wrong number of arguments for 'pubsub' command\r\n"
2967 );
2968 assert_eq!(
2969 f.run(&[b"PUBSUB", b"NOPE"]),
2970 "-ERR unknown subcommand 'NOPE'. Try PUBSUB HELP.\r\n"
2971 );
2972 assert_eq!(
2973 f.run(&[b"PUBSUB", b"CHANNELS", b"a*", b"b"]),
2974 "-ERR unknown subcommand or wrong number of arguments for 'CHANNELS'. Try PUBSUB HELP.\r\n"
2975 );
2976 assert_eq!(
2977 f.run(&[b"PUBSUB", b"NUMPAT", b"x"]),
2978 "-ERR wrong number of arguments for 'pubsub|numpat' command\r\n"
2979 );
2980 assert_eq!(
2981 f.run(&[b"PUBSUB", b"HELP", b"x"]),
2982 "-ERR wrong number of arguments for 'pubsub|help' command\r\n"
2983 );
2984 }
2985
2986 #[test]
2989 fn publishing_to_nobody_answers_zero() {
2990 let mut f = Fixture::new();
2991 assert_eq!(f.run(&[b"PUBLISH", b"a", b"hi"]), ":0\r\n");
2992 assert_eq!(f.run(&[b"SPUBLISH", b"a", b"hi"]), ":0\r\n");
2993 assert_eq!(f.run(&[b"PUBLISH", b"", b"hi"]), ":0\r\n");
2995 }
2996
2997 #[test]
3001 fn a_publish_counts_the_deliveries_and_not_the_clients() {
3002 let mut f = Fixture::new();
3003 let mut sub = Session::new(9);
3004 f.by(&mut sub, &[b"SUBSCRIBE", b"news"]);
3005 f.by(&mut sub, &[b"PSUBSCRIBE", b"ne*"]);
3006 f.by(&mut sub, &[b"PSUBSCRIBE", b"n*s"]);
3007 assert_eq!(f.run(&[b"PUBLISH", b"news", b"hi"]), ":3\r\n");
3008 forget_session(&f.server, &mut sub);
3009 }
3010
3011 #[cfg_attr(miri, ignore = "megabytes through the arena")]
3024 #[test]
3025 fn rewriting_the_same_keys_does_not_grow_the_server() {
3026 let mut f = Fixture::new();
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 f.server.compact_step();
3034 let after_first = f.server.memory_bytes();
3035
3036 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 }
3055
3056 #[cfg_attr(miri, ignore = "megabytes through the arena")]
3073 #[test]
3074 fn a_database_nobody_started_on_is_still_collected() {
3075 let mut f = Fixture::new();
3076 assert_eq!(f.run(&[b"SELECT", b"9"]), "+OK\r\n");
3077 let val = vec![b'v'; 1024];
3078 let keys: Vec<Vec<u8>> = (0..64).map(|i| format!("key:{i}").into_bytes()).collect();
3079
3080 for k in &keys {
3081 f.run(&[b"SET", k, &val]);
3082 }
3083 while f.server.compact_step().is_some() {}
3084 assert!(
3085 !f.server.mine().wanted(9),
3086 "database nine was drained and should not be asked again until it is written to"
3087 );
3088 let after_first = f.server.memory_bytes();
3089
3090 for _ in 0..500 {
3091 for k in &keys {
3092 f.run(&[b"SET", k, &val]);
3093 }
3094 f.server.compact_step();
3095 }
3096
3097 assert!(
3098 f.server.memory_bytes() <= after_first * 2,
3099 "held {} after five hundred passes against {after_first} after one",
3100 f.server.memory_bytes()
3101 );
3102 assert_eq!(f.run(&[b"DBSIZE"]), format!(":{}\r\n", keys.len()));
3103 assert_eq!(f.run(&[b"STRLEN", b"key:7"]), ":1024\r\n");
3104 f.run(&[b"SELECT", b"0"]);
3106 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3107 }
3108
3109 #[test]
3110 fn a_command_goes_from_bytes_to_bytes() {
3111 let mut f = Fixture::new();
3112 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
3113 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
3114 assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
3115 assert_eq!(f.run(&[b"STRLEN", b"k"]), ":1\r\n");
3116 assert_eq!(f.run(&[b"set", b"k", b"v2", b"xx"]), "+OK\r\n");
3118 assert_eq!(f.run(&[b"GET", b"k"]), "$2\r\nv2\r\n");
3119 }
3120
3121 #[test]
3122 fn deleting_counts_keys_removed_and_existing_counts_arguments_matched() {
3123 let mut f = Fixture::new();
3124 f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
3125 assert_eq!(f.run(&[b"EXISTS", b"a", b"a", b"nosuch"]), ":2\r\n");
3128 assert_eq!(f.run(&[b"DEL", b"a", b"a", b"nosuch"]), ":1\r\n");
3129 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
3130 assert_eq!(f.run(&[b"UNLINK", b"b", b"c"]), ":2\r\n");
3132 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
3133 }
3134
3135 #[test]
3136 fn type_is_a_simple_string_and_says_none_for_a_key_that_is_not_there() {
3137 let mut f = Fixture::new();
3138 f.run(&[b"SET", b"k", b"v"]);
3139 assert_eq!(f.run(&[b"TYPE", b"k"]), "+string\r\n");
3142 assert_eq!(f.run(&[b"TYPE", b"nosuch"]), "+none\r\n");
3143 }
3144
3145 #[test]
3146 fn touch_counts_the_way_exists_counts() {
3147 let mut f = Fixture::new();
3148 f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
3149 assert_eq!(f.run(&[b"TOUCH", b"a", b"b"]), ":2\r\n");
3150 assert_eq!(
3151 f.run(&[b"TOUCH", b"a", b"a"]),
3152 ":2\r\n",
3153 "twice counts twice"
3154 );
3155 assert_eq!(f.run(&[b"TOUCH", b"a", b"nosuch"]), ":1\r\n");
3156 assert_eq!(f.run(&[b"TOUCH", b"nosuch"]), ":0\r\n");
3157 }
3158
3159 #[test]
3160 fn a_rename_moves_the_deadline_with_the_value_and_drops_the_one_it_lands_on() {
3161 let mut f = Fixture::new();
3162 f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
3163 f.run(&[b"SET", b"b", b"v2", b"EX", b"500"]);
3164
3165 assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "+OK\r\n");
3166 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
3167 assert_eq!(
3168 f.run(&[b"TTL", b"b"]),
3169 ":100\r\n",
3170 "the source's and not b's"
3171 );
3172 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
3173 }
3174
3175 #[test]
3176 fn a_rename_with_no_source_is_an_error_and_not_a_zero() {
3177 let mut f = Fixture::new();
3178 assert_eq!(f.run(&[b"RENAME", b"a", b"b"]), "-ERR no such key\r\n");
3179 assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), "-ERR no such key\r\n");
3182 }
3183
3184 #[test]
3185 fn renamenx_refuses_a_taken_name_including_the_one_it_already_has() {
3186 let mut f = Fixture::new();
3187 f.run(&[b"MSET", b"a", b"v1", b"b", b"v2"]);
3188
3189 assert_eq!(f.run(&[b"RENAMENX", b"a", b"b"]), ":0\r\n");
3190 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
3191 assert_eq!(f.run(&[b"RENAMENX", b"a", b"a"]), ":0\r\n");
3194 assert_eq!(f.run(&[b"RENAME", b"a", b"a"]), "+OK\r\n");
3195 assert_eq!(f.run(&[b"RENAMENX", b"a", b"c"]), ":1\r\n");
3196 assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\nv1\r\n");
3197 }
3198
3199 #[test]
3200 fn renaming_a_set_does_not_touch_a_member() {
3201 let mut f = Fixture::new();
3202 for i in 0..300 {
3203 f.run(&[b"SADD", b"s", format!("m{i}").as_bytes()]);
3204 }
3205 let before = f.server.memory_bytes();
3206
3207 assert_eq!(f.run(&[b"RENAME", b"s", b"t"]), "+OK\r\n");
3208 assert_eq!(f.run(&[b"SCARD", b"t"]), ":300\r\n");
3209 assert_eq!(f.run(&[b"TYPE", b"t"]), "+set\r\n");
3210 assert!(
3211 f.server.memory_bytes().abs_diff(before) < 256,
3212 "the members were copied: {} against {before}",
3213 f.server.memory_bytes()
3214 );
3215 }
3216
3217 #[test]
3218 fn a_copy_is_a_second_value_and_not_a_second_name() {
3219 let mut f = Fixture::new();
3220 f.run(&[b"SADD", b"s", b"m1", b"m2"]);
3221
3222 assert_eq!(f.run(&[b"COPY", b"s", b"t"]), ":1\r\n");
3223 f.run(&[b"SADD", b"t", b"m3"]);
3224 assert_eq!(f.run(&[b"SCARD", b"s"]), ":2\r\n", "the original is intact");
3225 assert_eq!(f.run(&[b"SCARD", b"t"]), ":3\r\n");
3226 }
3227
3228 #[test]
3238 fn every_type_can_be_copied() {
3239 let mut f = Fixture::new();
3240 f.run(&[b"SET", b"str", b"v1"]);
3241 f.run(&[b"SADD", b"set", b"m1"]);
3242 f.run(&[b"HSET", b"hash", b"f", b"v"]);
3243 f.run(&[b"RPUSH", b"list", b"a", b"b"]);
3244 f.run(&[b"ZADD", b"zset", b"1", b"m1"]);
3245
3246 for name in [
3247 &b"str"[..],
3248 &b"set"[..],
3249 &b"hash"[..],
3250 &b"list"[..],
3251 &b"zset"[..],
3252 ] {
3253 let dst = [name, b":copy"].concat();
3254 assert_eq!(
3255 f.run(&[b"COPY", name, &dst]),
3256 ":1\r\n",
3257 "copying {}",
3258 String::from_utf8_lossy(name)
3259 );
3260 assert_eq!(f.run(&[b"TYPE", name]), f.run(&[b"TYPE", &dst]));
3261 }
3262
3263 assert_eq!(f.run(&[b"LRANGE", b"list:copy", b"0", b"-1"]), {
3264 let mut want = String::from("*2\r\n");
3265 want.push_str("$1\r\na\r\n$1\r\nb\r\n");
3266 want
3267 });
3268 assert_eq!(f.run(&[b"ZSCORE", b"zset:copy", b"m1"]), "$1\r\n1\r\n");
3269
3270 f.run(&[b"RPUSH", b"list:copy", b"c"]);
3272 assert_eq!(f.run(&[b"LLEN", b"list"]), ":2\r\n");
3273 assert_eq!(f.run(&[b"LLEN", b"list:copy"]), ":3\r\n");
3274 }
3275
3276 #[test]
3277 fn a_copy_refuses_a_taken_destination_until_it_is_told_it_can_have_it() {
3278 let mut f = Fixture::new();
3279 f.run(&[b"SET", b"a", b"v1", b"EX", b"100"]);
3280 f.run(&[b"SET", b"b", b"v2"]);
3281
3282 assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":0\r\n");
3283 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv2\r\n");
3284 assert_eq!(f.run(&[b"COPY", b"a", b"b", b"REPLACE"]), ":1\r\n");
3285 assert_eq!(f.run(&[b"GET", b"b"]), "$2\r\nv1\r\n");
3286 assert_eq!(f.run(&[b"TTL", b"b"]), ":100\r\n", "the deadline came too");
3287 assert_eq!(f.run(&[b"COPY", b"nosuch", b"z"]), ":0\r\n");
3288 }
3289
3290 #[test]
3291 fn a_copy_into_another_database_is_a_copy_and_onto_itself_there_is_too() {
3292 let mut f = Fixture::new();
3293 f.run(&[b"SET", b"a", b"v1"]);
3294
3295 assert_eq!(f.run(&[b"COPY", b"a", b"a", b"DB", b"1"]), ":1\r\n");
3298 f.run(&[b"SELECT", b"1"]);
3299 assert_eq!(f.run(&[b"GET", b"a"]), "$2\r\nv1\r\n");
3300 assert_eq!(
3301 f.run(&[b"COPY", b"a", b"a", b"DB", b"0"]),
3302 ":0\r\n",
3303 "taken"
3304 );
3305 assert_eq!(
3306 f.run(&[b"COPY", b"a", b"a", b"DB", b"0", b"REPLACE"]),
3307 ":1\r\n"
3308 );
3309 }
3310
3311 #[test]
3312 fn sort_takes_its_options_in_any_order_and_the_last_one_wins() {
3313 let mut f = Fixture::new();
3314 f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
3315 assert_eq!(
3316 f.run(&[b"SORT", b"l"]),
3317 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
3318 );
3319 assert_eq!(
3321 f.run(&[b"SORT", b"l", b"DESC", b"asc"]),
3322 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
3323 );
3324 assert_eq!(
3325 f.run(&[b"sort", b"l", b"LIMIT", b"1", b"1", b"DESC"]),
3326 "*1\r\n$1\r\n2\r\n"
3327 );
3328 }
3329
3330 #[test]
3331 fn sort_reads_a_key_per_element_for_by_and_for_get() {
3332 let mut f = Fixture::new();
3333 f.run(&[b"RPUSH", b"l", b"a", b"b"]);
3334 f.run(&[b"MSET", b"w_a", b"2", b"w_b", b"1", b"d_b", b"bee"]);
3335 assert_eq!(
3338 f.run(&[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"]),
3339 "*4\r\n$1\r\nb\r\n$3\r\nbee\r\n$1\r\na\r\n$-1\r\n"
3340 );
3341 }
3342
3343 #[test]
3344 fn sort_store_writes_a_list_and_answers_its_length() {
3345 let mut f = Fixture::new();
3346 f.run(&[b"RPUSH", b"l", b"3", b"1", b"2"]);
3347 assert_eq!(f.run(&[b"SORT", b"l", b"STORE", b"out"]), ":3\r\n");
3348 assert_eq!(f.run(&[b"TYPE", b"out"]), "+list\r\n");
3349 assert_eq!(
3350 f.run(&[b"LRANGE", b"out", b"0", b"-1"]),
3351 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n"
3352 );
3353 assert_eq!(f.run(&[b"SORT", b"missing", b"STORE", b"out"]), ":0\r\n");
3356 assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
3357 }
3358
3359 #[test]
3360 fn sort_ro_does_not_know_the_word_store() {
3361 let mut f = Fixture::new();
3362 f.run(&[b"RPUSH", b"l", b"2", b"1"]);
3363 assert_eq!(f.run(&[b"SORT_RO", b"l"]), "*2\r\n$1\r\n1\r\n$1\r\n2\r\n");
3364 assert_eq!(
3365 f.run(&[b"SORT_RO", b"l", b"STORE", b"d"]),
3366 "-ERR syntax error\r\n"
3367 );
3368 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
3369 }
3370
3371 #[test]
3372 fn sort_refuses_what_it_cannot_sort() {
3373 let mut f = Fixture::new();
3374 assert_eq!(f.run(&[b"SORT", b"nosuchkey"]), "*0\r\n");
3375 f.run(&[b"SET", b"s", b"x"]);
3376 assert_eq!(
3377 f.run(&[b"SORT", b"s"]),
3378 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
3379 );
3380 f.run(&[b"RPUSH", b"words", b"one", b"two"]);
3381 assert_eq!(
3382 f.run(&[b"SORT", b"words"]),
3383 "-ERR One or more scores can't be converted into double\r\n"
3384 );
3385 assert_eq!(
3386 f.run(&[b"SORT", b"words", b"ALPHA"]),
3387 "*2\r\n$3\r\none\r\n$3\r\ntwo\r\n"
3388 );
3389 assert_eq!(f.run(&[b"SORT", b"words", b"BY"]), "-ERR syntax error\r\n");
3390 }
3391
3392 #[test]
3393 fn move_takes_the_key_out_of_one_database_and_puts_it_in_another() {
3394 let mut f = Fixture::new();
3395 assert_eq!(f.run(&[b"RPUSH", b"l", b"a", b"b"]), ":2\r\n");
3396 assert_eq!(f.run(&[b"MOVE", b"l", b"1"]), ":1\r\n");
3397 assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
3398 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3399 assert_eq!(
3400 f.run(&[b"LRANGE", b"l", b"0", b"-1"]),
3401 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
3402 );
3403 assert_eq!(f.run(&[b"MOVE", b"l", b"0"]), ":1\r\n");
3406 assert_eq!(f.run(&[b"EXISTS", b"l"]), ":0\r\n");
3407 }
3408
3409 #[test]
3410 fn move_answers_zero_when_either_end_says_no() {
3411 let mut f = Fixture::new();
3412 assert_eq!(f.run(&[b"MOVE", b"nope", b"1"]), ":0\r\n");
3413 assert_eq!(f.run(&[b"SET", b"a", b"here"]), "+OK\r\n");
3414 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3415 assert_eq!(f.run(&[b"SET", b"a", b"there"]), "+OK\r\n");
3416 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
3417 assert_eq!(f.run(&[b"MOVE", b"a", b"1"]), ":0\r\n");
3420 assert_eq!(f.run(&[b"GET", b"a"]), "$4\r\nhere\r\n");
3421 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3422 assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nthere\r\n");
3423 }
3424
3425 #[test]
3426 fn move_refuses_a_database_that_is_not_one_and_the_one_it_is_on() {
3427 let mut f = Fixture::new();
3428 assert_eq!(
3429 f.run(&[b"MOVE", b"a", b"0"]),
3430 "-ERR source and destination objects are the same\r\n"
3431 );
3432 assert_eq!(
3433 f.run(&[b"MOVE", b"a", b"99"]),
3434 "-ERR DB index is out of range\r\n"
3435 );
3436 assert_eq!(
3437 f.run(&[b"MOVE", b"a", b"-1"]),
3438 "-ERR DB index is out of range\r\n"
3439 );
3440 assert_eq!(
3441 f.run(&[b"MOVE", b"a", b"x"]),
3442 "-ERR value is not an integer or out of range\r\n"
3443 );
3444 }
3445
3446 #[test]
3447 fn swapdb_swaps_what_two_connections_would_see() {
3448 let mut f = Fixture::new();
3449 assert_eq!(f.run(&[b"SET", b"k", b"zero"]), "+OK\r\n");
3450 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3451 assert_eq!(f.run(&[b"SET", b"k", b"one"]), "+OK\r\n");
3452 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
3453
3454 assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
3455 assert_eq!(f.run(&[b"GET", b"k"]), "$3\r\none\r\n");
3457 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3458 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
3459 assert_eq!(f.run(&[b"SWAPDB", b"1", b"1"]), "+OK\r\n");
3461 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
3462 }
3463
3464 #[test]
3469 fn a_wide_server_puts_its_databases_on_its_own_clock() {
3470 let mut f = Fixture::striped(8);
3471 f.server.set_clock_ms(1_700_000_000_000);
3472 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"100"]), "+OK\r\n");
3473 assert_eq!(f.run(&[b"EXPIRETIME", b"k"]), ":1700000100\r\n");
3474 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
3475 f.server.set_clock_ms(1_700_000_050_000);
3476 assert_eq!(f.run(&[b"TTL", b"k"]), ":50\r\n");
3477 }
3478
3479 #[test]
3484 fn swapdb_swaps_every_stripe_of_a_wide_database() {
3485 let mut f = Fixture::striped(4);
3486 for i in 0..16u32 {
3487 let key = format!("k{i}");
3488 assert_eq!(f.run(&[b"SET", key.as_bytes(), b"zero"]), "+OK\r\n");
3489 }
3490 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3491 assert_eq!(f.run(&[b"SET", b"only", b"one"]), "+OK\r\n");
3492 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
3493
3494 assert_eq!(f.run(&[b"SWAPDB", b"0", b"1"]), "+OK\r\n");
3495 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
3496 assert_eq!(f.run(&[b"GET", b"only"]), "$3\r\none\r\n");
3497 assert_eq!(f.run(&[b"SELECT", b"1"]), "+OK\r\n");
3498 assert_eq!(f.run(&[b"DBSIZE"]), ":16\r\n");
3499 for i in 0..16u32 {
3500 let key = format!("k{i}");
3501 assert_eq!(f.run(&[b"GET", key.as_bytes()]), "$4\r\nzero\r\n");
3502 }
3503 }
3504
3505 #[test]
3506 fn swapdb_says_which_index_it_could_not_read() {
3507 let mut f = Fixture::new();
3508 assert_eq!(
3509 f.run(&[b"SWAPDB", b"x", b"1"]),
3510 "-ERR invalid first DB index\r\n"
3511 );
3512 assert_eq!(
3513 f.run(&[b"SWAPDB", b"0", b"y"]),
3514 "-ERR invalid second DB index\r\n"
3515 );
3516 assert_eq!(
3520 f.run(&[b"SWAPDB", b"99999999999999", b"1"]),
3521 "-ERR invalid first DB index\r\n"
3522 );
3523 assert_eq!(
3524 f.run(&[b"SWAPDB", b"0", b"99"]),
3525 "-ERR DB index is out of range\r\n"
3526 );
3527 assert_eq!(
3528 f.run(&[b"SWAPDB", b"-1", b"0"]),
3529 "-ERR DB index is out of range\r\n"
3530 );
3531 }
3532
3533 #[test]
3534 fn wait_answers_zero_replicas_without_waiting() {
3535 let mut f = Fixture::new();
3536 assert_eq!(f.run(&[b"SET", b"a", b"v"]), "+OK\r\n");
3537 assert_eq!(f.run(&[b"WAIT", b"0", b"0"]), ":0\r\n");
3538 assert_eq!(f.run(&[b"WAIT", b"3", b"1000"]), ":0\r\n");
3541 assert_eq!(f.run(&[b"WAIT", b"-1", b"0"]), ":0\r\n");
3544 assert_eq!(
3545 f.run(&[b"WAIT", b"x", b"0"]),
3546 "-ERR value is not an integer or out of range\r\n"
3547 );
3548 assert_eq!(
3549 f.run(&[b"WAIT", b"0", b"-1"]),
3550 "-ERR timeout is negative\r\n"
3551 );
3552 assert_eq!(
3553 f.run(&[b"WAIT", b"0", b"1.5"]),
3554 "-ERR timeout is not an integer or out of range\r\n"
3555 );
3556 }
3557
3558 #[test]
3559 fn waitaof_answers_two_zeroes_and_refuses_a_local_wait() {
3560 let mut f = Fixture::new();
3561 assert_eq!(f.run(&[b"WAITAOF", b"0", b"0", b"0"]), "*2\r\n:0\r\n:0\r\n");
3562 assert_eq!(
3563 f.run(&[b"WAITAOF", b"1", b"0", b"0"]),
3564 "-ERR WAITAOF cannot be used when numlocal is set but appendonly is disabled.\r\n"
3565 );
3566 assert_eq!(
3567 f.run(&[b"WAITAOF", b"2", b"0", b"0"]),
3568 "-ERR value is out of range, value must between 0 and 1\r\n"
3569 );
3570 assert_eq!(
3571 f.run(&[b"WAITAOF", b"0", b"-1", b"0"]),
3572 "-ERR value is out of range, must be positive\r\n"
3573 );
3574 assert_eq!(
3577 f.run(&[b"WAITAOF", b"1", b"0", b"-5"]),
3578 "-ERR timeout is negative\r\n"
3579 );
3580 }
3581
3582 fn payload(reply: &[u8]) -> Vec<u8> {
3586 let head = reply.windows(2).position(|w| w == b"\r\n").unwrap();
3587 reply[head + 2..reply.len() - 2].to_vec()
3588 }
3589
3590 #[test]
3591 fn a_value_survives_a_dump_and_a_restore() {
3592 let mut f = Fixture::new();
3593 f.run(&[b"SET", b"s", b"hello"]);
3594 f.run(&[b"RPUSH", b"l", b"a", b"b", b"c"]);
3595 f.run(&[b"SADD", b"t", b"1", b"2", b"3"]);
3596 f.run(&[b"SADD", b"u", b"x", b"y"]);
3597 f.run(&[b"HSET", b"h", b"f", b"1", b"g", b"2"]);
3598 f.run(&[b"ZADD", b"z", b"1.5", b"a", b"2.5", b"b"]);
3599
3600 for key in [&b"s"[..], b"l", b"t", b"u", b"h", b"z"] {
3601 let mut copy = key.to_vec();
3602 copy.push(b'2');
3603 let bytes = payload(&f.raw(&[b"DUMP", key]));
3604 assert_eq!(f.run(&[b"RESTORE", ©, b"0", &bytes]), "+OK\r\n");
3605 assert_eq!(f.run(&[b"TYPE", ©]), f.run(&[b"TYPE", key]));
3606 }
3607
3608 assert_eq!(f.run(&[b"GET", b"s2"]), "$5\r\nhello\r\n");
3609 assert_eq!(
3610 f.run(&[b"LRANGE", b"l2", b"0", b"-1"]),
3611 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
3612 );
3613 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"t2"])), ["1", "2", "3"]);
3614 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"u2"])), ["x", "y"]);
3615 assert_eq!(f.run(&[b"HGET", b"h2", b"g"]), "$1\r\n2\r\n");
3616 assert_eq!(f.run(&[b"ZSCORE", b"z2", b"b"]), "$3\r\n2.5\r\n");
3617 assert_eq!(
3620 f.run(&[b"OBJECT", b"ENCODING", b"t2"]),
3621 f.run(&[b"OBJECT", b"ENCODING", b"t"])
3622 );
3623 }
3624
3625 #[test]
3626 fn a_dumped_hash_keeps_its_field_deadlines() {
3627 let mut f = Fixture::new();
3628 f.run(&[b"HSET", b"h", b"keep", b"1", b"go", b"2"]);
3629 assert_eq!(
3630 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"go"]),
3631 "*1\r\n:1\r\n"
3632 );
3633 let bytes = payload(&f.raw(&[b"DUMP", b"h"]));
3634 assert_eq!(f.run(&[b"RESTORE", b"h2", b"0", &bytes]), "+OK\r\n");
3635 assert_eq!(
3636 f.run(&[b"HTTL", b"h2", b"FIELDS", b"2", b"keep", b"go"]),
3637 "*2\r\n:-1\r\n:100\r\n"
3638 );
3639 }
3640
3641 #[test]
3642 fn dump_leaves_the_deadline_behind_and_restore_is_given_a_new_one() {
3643 let mut f = Fixture::new();
3644 f.run(&[b"SET", b"a", b"v", b"EX", b"100"]);
3645 let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
3646 assert_eq!(f.run(&[b"RESTORE", b"b", b"0", &bytes]), "+OK\r\n");
3647 assert_eq!(f.run(&[b"TTL", b"b"]), ":-1\r\n");
3648 assert_eq!(f.run(&[b"RESTORE", b"c", b"5000", &bytes]), "+OK\r\n");
3649 assert_eq!(f.run(&[b"TTL", b"c"]), ":5\r\n");
3650 assert_eq!(
3653 f.run(&[b"RESTORE", b"d", b"1", &bytes, b"ABSTTL"]),
3654 "+OK\r\n"
3655 );
3656 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
3657 }
3658
3659 #[test]
3660 fn dump_answers_nothing_for_a_key_that_is_not_there() {
3661 let mut f = Fixture::new();
3662 assert_eq!(f.run(&[b"DUMP", b"nope"]), "$-1\r\n");
3663 f.run(&[b"SET", b"gone", b"v", b"PX", b"10"]);
3664 f.advance(50);
3665 assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
3666 }
3667
3668 #[test]
3669 fn restore_refuses_a_key_that_is_there_unless_it_is_told_to_replace() {
3670 let mut f = Fixture::new();
3671 f.run(&[b"SET", b"a", b"first"]);
3672 f.run(&[b"SET", b"b", b"second"]);
3673 let bytes = payload(&f.raw(&[b"DUMP", b"b"]));
3674 assert_eq!(
3675 f.run(&[b"RESTORE", b"a", b"0", &bytes]),
3676 "-BUSYKEY Target key name already exists.\r\n"
3677 );
3678 assert_eq!(f.run(&[b"GET", b"a"]), "$5\r\nfirst\r\n");
3679 assert_eq!(
3680 f.run(&[b"RESTORE", b"a", b"0", &bytes, b"REPLACE"]),
3681 "+OK\r\n"
3682 );
3683 assert_eq!(f.run(&[b"GET", b"a"]), "$6\r\nsecond\r\n");
3684 }
3685
3686 #[test]
3690 fn restore_asks_about_the_key_before_it_looks_at_the_bytes() {
3691 let mut f = Fixture::new();
3692 f.run(&[b"SET", b"a", b"v"]);
3693 assert_eq!(
3694 f.run(&[b"RESTORE", b"a", b"0", b"rubbish"]),
3695 "-BUSYKEY Target key name already exists.\r\n"
3696 );
3697 assert_eq!(
3700 f.run(&[b"RESTORE", b"a", b"0", b"rubbish", b"FREQ", b"300"]),
3701 "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
3702 );
3703 }
3704
3705 #[test]
3706 fn restore_can_tell_a_bad_footer_from_bad_bytes() {
3707 let mut f = Fixture::new();
3708 f.run(&[b"SET", b"a", b"hello"]);
3709 let good = payload(&f.raw(&[b"DUMP", b"a"]));
3710
3711 let mut flipped = good.clone();
3712 flipped[2] ^= 0x40;
3713 assert_eq!(
3714 f.run(&[b"RESTORE", b"b", b"0", &flipped]),
3715 "-ERR DUMP payload version or checksum are wrong\r\n"
3716 );
3717 assert_eq!(
3718 f.run(&[b"RESTORE", b"b", b"0", b"short"]),
3719 "-ERR DUMP payload version or checksum are wrong\r\n"
3720 );
3721 let mut truncated = good[..1].to_vec();
3725 truncated.extend_from_slice(&good[good.len() - 10..good.len() - 8]);
3726 let crc = yo_common::crc::crc64(0, &truncated);
3727 truncated.extend_from_slice(&crc.to_le_bytes());
3728 assert_eq!(
3729 f.run(&[b"RESTORE", b"b", b"0", &truncated]),
3730 "-ERR Bad data format\r\n"
3731 );
3732 assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
3733 }
3734
3735 #[test]
3736 fn restore_checks_the_three_numbers_a_client_can_get_wrong() {
3737 let mut f = Fixture::new();
3738 f.run(&[b"SET", b"a", b"v"]);
3739 let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
3740 assert_eq!(
3741 f.run(&[b"RESTORE", b"b", b"-1", &bytes]),
3742 "-ERR Invalid TTL value, must be >= 0\r\n"
3743 );
3744 assert_eq!(
3745 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"-1"]),
3746 "-ERR Invalid IDLETIME value, must be >= 0\r\n"
3747 );
3748 assert_eq!(
3749 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ", b"256"]),
3750 "-ERR Invalid FREQ value, must be >= 0 and <= 255\r\n"
3751 );
3752 assert_eq!(
3754 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"IDLETIME", b"90"]),
3755 "+OK\r\n"
3756 );
3757 assert_eq!(
3758 f.run(&[b"RESTORE", b"c", b"0", &bytes, b"FREQ", b"200", b"REPLACE"]),
3759 "+OK\r\n"
3760 );
3761 }
3762
3763 #[test]
3767 fn restore_takes_idletime_or_freq_and_not_both() {
3768 let mut f = Fixture::new();
3769 f.run(&[b"SET", b"a", b"v"]);
3770 let bytes = payload(&f.raw(&[b"DUMP", b"a"]));
3771 assert_eq!(
3772 f.run(&[
3773 b"RESTORE",
3774 b"b",
3775 b"0",
3776 &bytes,
3777 b"IDLETIME",
3778 b"1",
3779 b"FREQ",
3780 b"2"
3781 ]),
3782 "-ERR syntax error\r\n"
3783 );
3784 assert_eq!(
3785 f.run(&[
3786 b"RESTORE",
3787 b"b",
3788 b"0",
3789 &bytes,
3790 b"FREQ",
3791 b"2",
3792 b"IDLETIME",
3793 b"1"
3794 ]),
3795 "-ERR syntax error\r\n"
3796 );
3797 assert_eq!(
3798 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"FREQ"]),
3799 "-ERR syntax error\r\n"
3800 );
3801 assert_eq!(
3802 f.run(&[b"RESTORE", b"b", b"0", &bytes, b"NOSUCH"]),
3803 "-ERR syntax error\r\n"
3804 );
3805 }
3806
3807 #[test]
3808 fn copy_checks_its_options_before_it_looks_for_anything() {
3809 let mut f = Fixture::new();
3810 assert_eq!(
3813 f.run(&[b"COPY", b"a", b"b", b"DB", b"99"]),
3814 "-ERR DB index is out of range\r\n"
3815 );
3816 assert_eq!(
3817 f.run(&[b"COPY", b"a", b"b", b"DB", b"-1"]),
3818 "-ERR DB index is out of range\r\n"
3819 );
3820 assert_eq!(
3821 f.run(&[b"COPY", b"a", b"b", b"DB", b"x"]),
3822 "-ERR value is not an integer or out of range\r\n"
3823 );
3824 assert_eq!(
3825 f.run(&[b"COPY", b"a", b"b", b"nonsense"]),
3826 "-ERR syntax error\r\n"
3827 );
3828 assert_eq!(
3829 f.run(&[b"COPY", b"a", b"a"]),
3830 "-ERR source and destination objects are the same\r\n"
3831 );
3832 assert_eq!(
3834 f.run(&[b"COPY", b"a", b"b", b"dB", b"1", b"rEpLaCe", b"db", b"2"]),
3835 ":0\r\n"
3836 );
3837 }
3838
3839 #[test]
3840 fn time_is_two_bulk_strings_and_moves() {
3841 let mut f = Fixture::new();
3842 let first = f.run(&[b"TIME"]);
3843 assert!(first.starts_with("*2\r\n$"), "got {first}");
3844 let parts: Vec<&str> = first.split("\r\n").collect();
3845 let secs: i64 = parts[2].parse().expect("seconds as decimal text");
3846 let micros: i64 = parts[4].parse().expect("microseconds as decimal text");
3847 assert!(secs > 1_700_000_000, "a real wall clock, got {secs}");
3848 assert!((0..1_000_000).contains(µs), "got {micros}");
3849 assert_ne!(first, f.run(&[b"TIME"]));
3853 }
3854
3855 #[test]
3856 fn a_keyspace_scan_walks_every_key_once() {
3857 let n = if cfg!(miri) { 96 } else { 500 };
3861 let mut f = Fixture::new();
3862 for i in 0..n {
3863 f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
3864 }
3865
3866 let mut seen: Vec<String> = Vec::new();
3867 let mut cursor = "0".to_owned();
3868 let mut calls = 0;
3869 loop {
3870 let (next, keys) = scan_reply(&f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"32"]));
3871 seen.extend(keys);
3872 cursor = next;
3873 calls += 1;
3874 assert!(calls < 10_000, "the cursor is not advancing");
3875 if cursor == "0" {
3876 break;
3877 }
3878 }
3879
3880 seen.sort();
3881 seen.dedup();
3882 assert_eq!(seen.len(), n, "every key once and only once");
3883 assert!(calls > 1, "{n} keys came back in one batch");
3886 }
3887
3888 #[test]
3889 fn a_scan_narrows_by_pattern_and_by_type() {
3890 let mut f = Fixture::new();
3891 f.run(&[b"SET", b"str", b"v"]);
3892 f.run(&[b"SADD", b"members", b"a"]);
3893 f.run(&[b"HSET", b"fields", b"f", b"v"]);
3894
3895 let all = |f: &mut Fixture, args: &[&[u8]]| {
3896 let mut out: Vec<String> = Vec::new();
3897 let mut cursor = "0".to_owned();
3898 loop {
3899 let mut line: Vec<&[u8]> = vec![b"SCAN", cursor.as_bytes()];
3900 line.extend_from_slice(args);
3901 let (next, keys) = scan_reply(&f.run(&line));
3902 out.extend(keys);
3903 cursor = next;
3904 if cursor == "0" {
3905 break;
3906 }
3907 }
3908 out.sort();
3909 out
3910 };
3911
3912 assert_eq!(all(&mut f, &[]), ["fields", "members", "str"]);
3913 assert_eq!(all(&mut f, &[b"MATCH", b"*e*"]), ["fields", "members"]);
3914 assert_eq!(all(&mut f, &[b"TYPE", b"set"]), ["members"]);
3915 assert_eq!(all(&mut f, &[b"TYPE", b"HASH"]), ["fields"]);
3917 assert!(all(&mut f, &[b"TYPE", b"list"]).is_empty());
3919 assert!(all(&mut f, &[b"TYPE", b"banana"]).is_empty());
3920 assert!(all(&mut f, &[b"MATCH", b"str*", b"TYPE", b"set"]).is_empty());
3922 }
3923
3924 #[test]
3925 fn a_scan_says_what_is_wrong_with_it() {
3926 let mut f = Fixture::new();
3927 assert_eq!(f.run(&[b"SCAN", b"nope"]), "-ERR invalid cursor\r\n");
3928 assert_eq!(f.run(&[b"SCAN", b"-1"]), "-ERR invalid cursor\r\n");
3929 assert_eq!(f.run(&[b"SCAN", b"0", b"MATCH"]), "-ERR syntax error\r\n");
3930 assert_eq!(
3931 f.run(&[b"SCAN", b"0", b"COUNT", b"0"]),
3932 "-ERR syntax error\r\n"
3933 );
3934 assert_eq!(
3935 f.run(&[b"SCAN", b"0", b"COUNT", b"x"]),
3936 "-ERR value is not an integer or out of range\r\n"
3937 );
3938 assert_eq!(
3939 f.run(&[b"SCAN", b"0", b"WAT", b"1"]),
3940 "-ERR syntax error\r\n"
3941 );
3942 assert!(f.run(&[b"SCAN", b"18446744073709551615"]).starts_with("*2"));
3947 }
3948
3949 #[test]
3950 fn keys_and_randomkey_look_at_the_whole_database() {
3951 let mut f = Fixture::new();
3952 assert_eq!(f.run(&[b"KEYS", b"*"]), "*0\r\n");
3953 assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
3954
3955 for name in ["one", "two", "three"] {
3956 f.run(&[b"SET", name.as_bytes(), b"v"]);
3957 }
3958 assert_eq!(sorted(&f.run(&[b"KEYS", b"*"])), ["one", "three", "two"]);
3959 assert_eq!(sorted(&f.run(&[b"KEYS", b"t*"])), ["three", "two"]);
3960 assert_eq!(f.run(&[b"KEYS", b"nothing"]), "*0\r\n");
3961
3962 for _ in 0..50 {
3963 let got = f.run(&[b"RANDOMKEY"]);
3964 assert!(
3965 ["$3\r\none\r\n", "$3\r\ntwo\r\n", "$5\r\nthree\r\n"].contains(&got.as_str()),
3966 "got {got}"
3967 );
3968 }
3969 }
3970
3971 #[test]
3972 fn a_walk_does_not_answer_keys_that_have_expired() {
3973 let mut f = Fixture::new();
3974 f.run(&[b"SET", b"alive", b"v"]);
3975 f.run(&[b"SET", b"dead", b"v", b"PX", b"1"]);
3976 f.server.advance_clock_ms(2);
3977 assert_eq!(
3978 f.run(&[b"DBSIZE"]),
3979 ":2\r\n",
3980 "nothing has collected it yet"
3981 );
3982
3983 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$5\r\nalive\r\n");
3984 let (_, keys) = scan_reply(&f.run(&[b"SCAN", b"0", b"COUNT", b"1000"]));
3985 assert_eq!(keys, ["alive"]);
3986 for _ in 0..20 {
3987 assert_eq!(f.run(&[b"RANDOMKEY"]), "$5\r\nalive\r\n");
3988 }
3989 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
3992 }
3993
3994 #[test]
3995 fn a_key_deadline_goes_on_and_comes_back_in_all_four_units() {
3996 let mut f = Fixture::new();
3997 f.run(&[b"SET", b"k", b"v"]);
3998 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "there and no deadline");
3999 assert_eq!(f.run(&[b"TTL", b"nosuch"]), ":-2\r\n", "not there at all");
4000
4001 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100"]), ":1\r\n");
4002 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
4003 let ms = int(&f.run(&[b"PTTL", b"k"]));
4004 assert!((99_000..=100_000).contains(&ms), "got {ms}");
4005
4006 let at = int(&f.run(&[b"EXPIRETIME", b"k"]));
4008 let at_ms = int(&f.run(&[b"PEXPIRETIME", b"k"]));
4009 assert_eq!(at, (at_ms + 500) / 1000);
4010 assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
4011
4012 assert_eq!(f.run(&[b"PERSIST", b"k"]), ":1\r\n");
4013 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
4014 assert_eq!(
4015 f.run(&[b"PERSIST", b"k"]),
4016 ":0\r\n",
4017 "nothing to take off the second time"
4018 );
4019 assert_eq!(f.run(&[b"PERSIST", b"nosuch"]), ":0\r\n");
4020 assert_eq!(
4021 f.run(&[b"GET", b"k"]),
4022 "$1\r\nv\r\n",
4023 "and the value went through all of that untouched"
4024 );
4025 }
4026
4027 #[test]
4028 fn every_type_can_be_given_a_deadline_and_it_is_the_same_deadline() {
4029 let mut f = Fixture::new();
4030 f.run(&[b"SET", b"str", b"v"]);
4031 f.run(&[b"SADD", b"set", b"a", b"b"]);
4032 f.run(&[b"HSET", b"hash", b"f", b"v"]);
4033
4034 for key in [b"str".as_slice(), b"set", b"hash"] {
4035 assert_eq!(f.run(&[b"EXPIRE", key, b"100"]), ":1\r\n");
4036 assert_eq!(f.run(&[b"TTL", key]), ":100\r\n");
4037 }
4038 assert_eq!(f.run(&[b"SCARD", b"set"]), ":2\r\n");
4041 assert_eq!(f.run(&[b"HGET", b"hash", b"f"]), "$1\r\nv\r\n");
4042 assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
4043 }
4044
4045 #[test]
4046 fn a_deadline_that_has_already_gone_deletes_the_key_now() {
4047 let mut f = Fixture::new();
4048 for key in [b"a".as_slice(), b"b", b"c", b"d"] {
4049 f.run(&[b"SET", key, b"v"]);
4050 }
4051 assert_eq!(f.run(&[b"EXPIRE", b"a", b"0"]), ":1\r\n");
4055 assert_eq!(f.run(&[b"EXPIRE", b"b", b"-1"]), ":1\r\n");
4056 assert_eq!(f.run(&[b"EXPIREAT", b"c", b"1"]), ":1\r\n");
4057 assert_eq!(f.run(&[b"PEXPIREAT", b"d", b"1"]), ":1\r\n");
4058 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4059 assert_eq!(
4060 f.run(&[b"EXPIRE", b"a", b"100"]),
4061 ":0\r\n",
4062 "and the key really went, so there is nothing to put a deadline on"
4063 );
4064 }
4065
4066 #[test]
4067 fn the_four_conditions_decide_whether_the_deadline_moves() {
4068 let mut f = Fixture::new();
4069 f.run(&[b"SET", b"k", b"v"]);
4070
4071 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX"]), ":0\r\n");
4072 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n", "and XX left it alone");
4073 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"GT"]), ":0\r\n");
4074 assert_eq!(
4075 f.run(&[b"EXPIRE", b"k", b"100", b"LT"]),
4076 ":1\r\n",
4077 "no deadline reads as infinitely far away, so LT passes where GT fails"
4078 );
4079
4080 assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"NX"]), ":0\r\n");
4081 assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"GT"]), ":0\r\n");
4082 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
4083 assert_eq!(f.run(&[b"EXPIRE", b"k", b"50", b"LT"]), ":1\r\n");
4084 assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"GT"]), ":1\r\n");
4085 assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
4086
4087 assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"NX"]), ":0\r\n");
4090 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":1\r\n");
4091 assert_eq!(f.run(&[b"EXPIRE", b"k", b"0", b"XX"]), ":1\r\n");
4092 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n", "and XX let it through");
4093 }
4094
4095 #[test]
4096 fn the_conditions_are_a_set_and_not_a_keyword() {
4097 let mut f = Fixture::new();
4098 f.run(&[b"SET", b"k", b"v"]);
4099
4100 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"nx"]), ":1\r\n");
4101 assert_eq!(
4102 f.run(&[b"EXPIRE", b"k", b"100", b"nx", b"nx"]),
4103 ":0\r\n",
4104 "the same keyword twice means it once, and NX now has a deadline to fail on"
4105 );
4106
4107 assert_eq!(f.run(&[b"EXPIRE", b"k", b"200", b"xx", b"gt"]), ":1\r\n");
4110 assert_eq!(f.run(&[b"TTL", b"k"]), ":200\r\n");
4111 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"gt", b"xx"]), ":0\r\n");
4112 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]), ":1\r\n");
4113 assert_eq!(f.run(&[b"TTL", b"k"]), ":100\r\n");
4114 f.run(&[b"PERSIST", b"k"]);
4115 assert_eq!(
4116 f.run(&[b"EXPIRE", b"k", b"100", b"XX", b"LT"]),
4117 ":0\r\n",
4118 "where LT on its own would have taken it"
4119 );
4120 assert_eq!(f.run(&[b"EXPIRE", b"k", b"100", b"LT"]), ":1\r\n");
4121 }
4122
4123 #[test]
4124 fn a_key_is_gone_once_its_moment_passes() {
4125 let mut f = Fixture::new();
4126 f.run(&[b"SET", b"k", b"v"]);
4127 f.run(&[b"EXPIRE", b"k", b"100"]);
4128
4129 let at = int(&f.run(&[b"PEXPIRETIME", b"k"]));
4130 f.server.set_clock_ms(at as u64 + 1);
4131 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
4132 assert_eq!(f.run(&[b"TTL", b"k"]), ":-2\r\n");
4133 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
4134 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4135 }
4136
4137 #[test]
4138 fn the_expiry_commands_refuse_what_a_real_server_refuses() {
4139 let mut f = Fixture::new();
4140 f.run(&[b"SET", b"k", b"v"]);
4141 for (bad, want) in [
4142 (
4143 &[b"EXPIRE".as_slice(), b"k", b"soon"][..],
4144 "-ERR value is not an integer or out of range\r\n",
4145 ),
4146 (
4147 &[b"EXPIRE", b"k", b"100", b"MAYBE"],
4148 "-ERR Unsupported option MAYBE\r\n",
4149 ),
4150 (
4151 &[b"EXPIRE", b"k", b"100", b"NX", b"XX"],
4152 "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
4153 ),
4154 (
4155 &[b"EXPIRE", b"k", b"100", b"NX", b"GT"],
4156 "-ERR NX and XX, GT or LT options at the same time are not compatible\r\n",
4157 ),
4158 (
4159 &[b"EXPIRE", b"k", b"100", b"GT", b"LT", b"GT"],
4160 "-ERR GT and LT options at the same time are not compatible\r\n",
4161 ),
4162 (
4165 &[b"EXPIRE", b"k", b"9223372036854775807"],
4166 "-ERR invalid expire time in 'expire' command\r\n",
4167 ),
4168 (
4169 &[b"EXPIREAT", b"k", b"9223372036854775807"],
4170 "-ERR invalid expire time in 'expireat' command\r\n",
4171 ),
4172 (
4173 &[b"PEXPIRE", b"k", b"9223372036854775807"],
4174 "-ERR invalid expire time in 'pexpire' command\r\n",
4175 ),
4176 ] {
4177 assert_eq!(f.run(bad), want, "for {bad:?}");
4178 }
4179 assert_eq!(
4180 f.run(&[b"TTL", b"k"]),
4181 ":-1\r\n",
4182 "and none of those put a deadline on anything"
4183 );
4184
4185 assert_eq!(
4189 f.run(&[b"PEXPIREAT", b"k", b"9223372036854775807"]),
4190 ":1\r\n"
4191 );
4192 assert_eq!(f.run(&[b"PEXPIRETIME", b"k"]), ":70368744177663\r\n");
4193 }
4194
4195 #[test]
4196 fn flushing_empties_this_database_or_every_one_of_them() {
4197 let mut f = Fixture::new();
4198 f.run(&[b"SELECT", b"0"]);
4199 f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
4200 f.run(&[b"SELECT", b"1"]);
4201 f.run(&[b"SET", b"c", b"3"]);
4202 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
4203 assert_eq!(f.run(&[b"FLUSHDB", b"async"]), "+OK\r\n");
4206 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4207 f.run(&[b"SELECT", b"0"]);
4209 assert_eq!(f.run(&[b"DBSIZE"]), ":2\r\n");
4210 assert_eq!(f.run(&[b"FLUSHALL", b"SYNC"]), "+OK\r\n");
4211 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4212 f.run(&[b"SELECT", b"1"]);
4213 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
4214 assert_eq!(f.run(&[b"FLUSHALL", b"nope"]), "-ERR syntax error\r\n");
4217 assert_eq!(
4218 f.run(&[b"FLUSHDB", b"sync", b"sync"]),
4219 "-ERR syntax error\r\n"
4220 );
4221 }
4222
4223 #[test]
4224 fn the_script_cache_and_the_library_set_answer_for_being_empty() {
4225 let mut f = Fixture::new();
4226 assert_eq!(f.run(&[b"SCRIPT", b"FLUSH"]), "+OK\r\n");
4227 assert_eq!(f.run(&[b"SCRIPT", b"FLUSH", b"async"]), "+OK\r\n");
4228 assert_eq!(f.run(&[b"FUNCTION", b"FLUSH", b"SYNC"]), "+OK\r\n");
4229 assert_eq!(
4232 f.run(&[b"SCRIPT", b"EXISTS", b"aaaa", b"bbbb"]),
4233 "*2\r\n:0\r\n:0\r\n"
4234 );
4235 assert_eq!(f.run(&[b"FUNCTION", b"LIST"]), "*0\r\n");
4236 assert_eq!(
4237 f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"x", b"WITHCODE"]),
4238 "*0\r\n"
4239 );
4240 assert_eq!(
4241 f.run(&[b"FUNCTION", b"DELETE", b"nosuch"]),
4242 "-ERR Library not found\r\n"
4243 );
4244
4245 assert_eq!(
4248 f.run(&[b"SCRIPT", b"FLUSH", b"nope"]),
4249 "-ERR SCRIPT FLUSH only support SYNC|ASYNC option\r\n"
4250 );
4251 assert_eq!(
4252 f.run(&[b"FUNCTION", b"FLUSH", b"nope"]),
4253 "-ERR FUNCTION FLUSH only supports SYNC|ASYNC option\r\n"
4254 );
4255 assert_eq!(
4260 f.run(&[b"FUNCTION", b"FLUSH", b"sync", b"sync"]),
4261 "-ERR unknown subcommand or wrong number of arguments for 'FLUSH'. Try FUNCTION HELP.\r\n"
4262 );
4263 assert_eq!(
4264 f.run(&[b"FUNCTION", b"LIST", b"bogus"]),
4265 "-ERR Unknown argument bogus\r\n"
4266 );
4267 assert_eq!(
4268 f.run(&[b"SCRIPT", b"EXISTS"]),
4269 "-ERR wrong number of arguments for 'script|exists' command\r\n"
4270 );
4271
4272 assert_eq!(
4273 f.run(&[b"FUNCTION", b"NOPE"]),
4274 "-ERR unknown subcommand 'NOPE'. Try FUNCTION HELP.\r\n"
4275 );
4276 }
4277
4278 #[test]
4279 fn the_script_cache_holds_what_was_loaded_into_it() {
4280 let mut f = Fixture::new();
4281 let sha = b"e0e1f9fabfc9d4800c877a703b823ac0578ff8db";
4284 assert_eq!(
4285 f.run(&[b"SCRIPT", b"LOAD", b"return 1"]),
4286 "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
4287 );
4288 assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:1\r\n");
4289 assert_eq!(f.run(&[b"EVALSHA", sha, b"0"]), ":1\r\n");
4290 assert_eq!(
4293 f.run(&[b"SCRIPT", b"LOAD", b"return 1"]),
4294 "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
4295 );
4296 assert!(
4297 f.run(&[b"SCRIPT", b"LOAD", b"this is not lua"])
4298 .starts_with("-ERR Error compiling script"),
4299 );
4300
4301 assert_eq!(f.run(&[b"SCRIPT", b"FLUSH"]), "+OK\r\n");
4302 assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:0\r\n");
4303 assert_eq!(
4304 f.run(&[b"EVALSHA", sha, b"0"]),
4305 "-NOSCRIPT No matching script. Please use EVAL.\r\n"
4306 );
4307
4308 assert_eq!(f.run(&[b"EVAL", b"return 1", b"0"]), ":1\r\n");
4311 assert_eq!(f.run(&[b"SCRIPT", b"EXISTS", sha]), "*1\r\n:1\r\n");
4312
4313 assert_eq!(
4316 f.run(&[b"SCRIPT", b"KILL"]),
4317 "-NOTBUSY No scripts in execution right now.\r\n"
4318 );
4319 assert_eq!(f.run(&[b"SCRIPT", b"DEBUG", b"NO"]), "+OK\r\n");
4320 assert_eq!(f.run(&[b"SCRIPT", b"DEBUG", b"yes"]), "+OK\r\n");
4321 assert_eq!(
4322 f.run(&[b"SCRIPT", b"DEBUG", b"maybe"]),
4323 "-ERR Use SCRIPT DEBUG YES/SYNC/NO\r\n"
4324 );
4325 }
4326
4327 #[test]
4328 fn eval_counts_its_keys_before_it_compiles_anything() {
4329 let mut f = Fixture::new();
4330 assert_eq!(
4331 f.run(&[b"EVAL", b"return 1"]),
4332 "-ERR wrong number of arguments for 'eval' command\r\n"
4333 );
4334 assert_eq!(
4335 f.run(&[b"EVAL", b"return 1", b"abc"]),
4336 "-ERR value is not an integer or out of range\r\n"
4337 );
4338 assert_eq!(
4339 f.run(&[b"EVAL", b"return 1", b"-1"]),
4340 "-ERR Number of keys can't be negative\r\n"
4341 );
4342 assert_eq!(
4343 f.run(&[b"EVAL", b"return 1", b"1"]),
4344 "-ERR Number of keys can't be greater than number of args\r\n"
4345 );
4346 assert_eq!(
4348 f.run(&[
4349 b"EVAL",
4350 b"return {KEYS[1],KEYS[2],ARGV[1]}",
4351 b"2",
4352 b"a",
4353 b"b",
4354 b"c"
4355 ]),
4356 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
4357 );
4358 assert_eq!(
4359 f.run(&[b"EVAL", b"return #KEYS", b"0", b"a", b"b"]),
4360 ":0\r\n"
4361 );
4362 assert_eq!(
4363 f.run(&[b"EVAL", b"return #ARGV", b"0", b"a", b"b"]),
4364 ":2\r\n"
4365 );
4366 }
4367
4368 #[test]
4369 fn a_lua_value_comes_back_as_the_reply_it_maps_to() {
4370 let mut f = Fixture::new();
4371 let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
4372
4373 assert_eq!(eval(&mut f, b"return 3.99"), ":3\r\n");
4376 assert_eq!(eval(&mut f, b"return -3.99"), ":-3\r\n");
4377 assert_eq!(eval(&mut f, b"return 0.5"), ":0\r\n");
4378 assert_eq!(eval(&mut f, b"return 2^63"), ":9223372036854775807\r\n");
4379 assert_eq!(eval(&mut f, b"return -2^63"), ":-9223372036854775808\r\n");
4380 assert_eq!(eval(&mut f, b"return 1/0"), ":9223372036854775807\r\n");
4381 assert_eq!(eval(&mut f, b"return 0/0"), ":0\r\n");
4382
4383 assert_eq!(eval(&mut f, b"return 'hello'"), "$5\r\nhello\r\n");
4384 assert_eq!(eval(&mut f, b"return true"), ":1\r\n");
4385 assert_eq!(eval(&mut f, b"return false"), "$-1\r\n");
4387 assert_eq!(eval(&mut f, b"return nil"), "$-1\r\n");
4388 assert_eq!(eval(&mut f, b"return"), "$-1\r\n");
4389 assert_eq!(eval(&mut f, b""), "$-1\r\n");
4390
4391 assert_eq!(eval(&mut f, b"return {}"), "*0\r\n");
4394 assert_eq!(eval(&mut f, b"return {1,2,nil,4}"), "*2\r\n:1\r\n:2\r\n");
4395 assert_eq!(
4396 eval(&mut f, b"return {1,'a',{2}}"),
4397 "*3\r\n:1\r\n$1\r\na\r\n*1\r\n:2\r\n"
4398 );
4399
4400 assert_eq!(eval(&mut f, b"return {ok='fine'}"), "+fine\r\n");
4402 assert_eq!(eval(&mut f, b"return {err='mine'}"), "-mine\r\n");
4403 assert_eq!(eval(&mut f, b"return {err='a', ok='b'}"), "-a\r\n");
4404 assert_eq!(eval(&mut f, b"return {ok='b', double=1.5}"), "+b\r\n");
4405 assert_eq!(eval(&mut f, b"return {ok='a\\r\\nb'}"), "+a b\r\n");
4408 assert_eq!(eval(&mut f, b"return {ok=1}"), "*0\r\n");
4411 assert_eq!(eval(&mut f, b"return {err={}}"), "*0\r\n");
4412 }
4413
4414 #[test]
4415 fn the_protocol_the_client_asked_for_is_the_one_a_table_answers_in() {
4416 let mut f = Fixture::new();
4417 assert_eq!(
4420 f.run(&[b"EVAL", b"return {double=3.5}", b"0"]),
4421 "$3\r\n3.5\r\n"
4422 );
4423 assert_eq!(
4424 f.run(&[b"EVAL", b"return {big_number='123'}", b"0"]),
4425 "$3\r\n123\r\n"
4426 );
4427 assert_eq!(
4428 f.run(&[b"EVAL", b"return {map={a='b'}}", b"0"]),
4429 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
4430 );
4431 assert_eq!(
4432 f.run(&[b"EVAL", b"return {set={a=true}}", b"0"]),
4433 "*1\r\n$1\r\na\r\n"
4434 );
4435 assert_eq!(f.run(&[b"EVAL", b"return false", b"0"]), "$-1\r\n");
4436
4437 f.out = Out::new(Proto::Resp3);
4438 assert_eq!(f.run(&[b"EVAL", b"return {double=3.5}", b"0"]), ",3.5\r\n");
4439 assert_eq!(
4440 f.run(&[b"EVAL", b"return {big_number='123'}", b"0"]),
4441 "(123\r\n"
4442 );
4443 assert_eq!(
4444 f.run(&[b"EVAL", b"return {map={a='b'}}", b"0"]),
4445 "%1\r\n$1\r\na\r\n$1\r\nb\r\n"
4446 );
4447 assert_eq!(
4448 f.run(&[b"EVAL", b"return {set={a=true}}", b"0"]),
4449 "~1\r\n$1\r\na\r\n"
4450 );
4451 assert_eq!(f.run(&[b"EVAL", b"return false", b"0"]), "_\r\n");
4452 }
4453
4454 #[test]
4455 fn a_reply_comes_back_into_lua_as_the_value_it_maps_to() {
4456 let mut f = Fixture::new();
4457 f.run(&[b"SET", b"s", b"hello"]);
4458 f.run(&[b"RPUSH", b"l", b"a", b"b"]);
4459 let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
4460
4461 assert_eq!(
4462 eval(&mut f, b"return type(redis.call('get','s'))"),
4463 "$6\r\nstring\r\n"
4464 );
4465 assert_eq!(
4466 eval(&mut f, b"return type(redis.call('llen','l'))"),
4467 "$6\r\nnumber\r\n"
4468 );
4469 assert_eq!(
4470 eval(&mut f, b"return type(redis.call('lrange','l',0,-1))"),
4471 "$5\r\ntable\r\n"
4472 );
4473 assert_eq!(
4476 eval(&mut f, b"return redis.call('set','s','v')['ok']"),
4477 "$2\r\nOK\r\n"
4478 );
4479 assert_eq!(
4482 eval(&mut f, b"return tostring(redis.call('get','nosuch'))"),
4483 "$5\r\nfalse\r\n"
4484 );
4485 assert_eq!(
4486 eval(
4487 &mut f,
4488 b"redis.setresp(3) return tostring(redis.call('get','nosuch'))"
4489 ),
4490 "$3\r\nnil\r\n"
4491 );
4492 assert_eq!(
4494 eval(&mut f, b"return tostring(redis.call('get','nosuch'))"),
4495 "$5\r\nfalse\r\n"
4496 );
4497 }
4498
4499 #[test]
4500 fn an_error_from_a_script_names_the_line_it_came_from() {
4501 let mut f = Fixture::new();
4502 assert_eq!(
4505 f.run(&[b"EVAL", b"error('boom')", b"0"]),
4506 "-ERR user_script:1: boom script: \
4507 82903a0434f1503e152f89c03c9acd881a0e8150, on @user_script:1.\r\n"
4508 );
4509 assert_eq!(
4511 f.run(&[b"EVAL", b"error('boom', 0)", b"0"]),
4512 "-ERR boom script: 90724e16396e5864c1184910ba6d7440461cee4f, on @user_script:1.\r\n"
4513 );
4514 assert!(
4516 f.run(&[b"EVAL", b"error({err='structured'})", b"0"])
4517 .starts_with("-structured script: "),
4518 );
4519 assert_eq!(
4522 f.run(&[b"EVAL", b"return this is not lua", b"0"]),
4523 "-ERR Error compiling script (new function): user_script:1: '<eof>' expected near 'is'\r\n"
4524 );
4525
4526 assert_eq!(
4529 f.run(&[
4530 b"EVAL",
4531 b"local a, b = pcall(function() error({err='z'}) end) return type(b) .. ':' .. tostring(b)",
4532 b"0"
4533 ]),
4534 "$8\r\nstring:z\r\n"
4535 );
4536 assert_eq!(
4537 f.run(&[
4538 b"EVAL",
4539 b"local a, b = pcall(function() error({a=1}) end) return type(b)",
4540 b"0"
4541 ]),
4542 "$5\r\ntable\r\n"
4543 );
4544 }
4545
4546 #[test]
4547 fn redis_call_refuses_what_it_cannot_run_and_pcall_hands_it_back() {
4548 let mut f = Fixture::new();
4549 let sentence = |f: &mut Fixture, body: &[u8]| {
4550 let reply = f.run(&[b"EVAL", body, b"0"]);
4551 reply.split(" script: ").next().unwrap().to_owned()
4552 };
4553
4554 assert_eq!(
4555 sentence(&mut f, b"return redis.call()"),
4556 "-ERR Please specify at least one argument for this redis lib call"
4557 );
4558 assert_eq!(
4559 sentence(&mut f, b"return redis.call('get', {})"),
4560 "-ERR Lua redis lib command arguments must be strings or integers"
4561 );
4562 assert_eq!(
4563 sentence(&mut f, b"return redis.call('nosuchcmd')"),
4564 "-ERR Unknown Redis command called from script"
4565 );
4566 assert_eq!(
4567 sentence(&mut f, b"return redis.call('get')"),
4568 "-ERR Wrong number of args calling Redis command from script"
4569 );
4570 for name in [
4574 &b"return redis.call('multi')"[..],
4575 b"return redis.call('exec')",
4576 b"return redis.call('watch','k')",
4577 b"return redis.call('subscribe','c')",
4578 b"return redis.call('debug','jmap')",
4579 b"return redis.call('eval','return 1',0)",
4580 b"return redis.call('config','get','maxmemory')",
4581 ] {
4582 assert_eq!(
4583 sentence(&mut f, name),
4584 "-ERR This Redis command is not allowed from script",
4585 "for {}",
4586 String::from_utf8_lossy(name)
4587 );
4588 }
4589 assert!(
4592 f.run(&[b"EVAL", b"return redis.call('config','help')", b"0"])
4593 .starts_with('*'),
4594 );
4595
4596 assert_eq!(
4599 f.run(&[
4600 b"EVAL",
4601 b"local x = redis.pcall('nosuchcmd') return x.err",
4602 b"0"
4603 ]),
4604 "$44\r\nERR Unknown Redis command called from script\r\n"
4605 );
4606 assert_eq!(
4608 f.run(&[b"EVAL", b"return redis.pcall('nosuchcmd')", b"0"]),
4609 "-ERR Unknown Redis command called from script\r\n"
4610 );
4611 }
4612
4613 #[test]
4614 fn a_read_only_script_is_stopped_at_the_write_and_not_at_the_door() {
4615 let mut f = Fixture::new();
4616 f.run(&[b"SET", b"k", b"v"]);
4617 assert_eq!(
4618 f.run(&[b"EVAL_RO", b"return redis.call('get', KEYS[1])", b"1", b"k"]),
4619 "$1\r\nv\r\n"
4620 );
4621 assert!(
4622 f.run(&[
4623 b"EVAL_RO",
4624 b"return redis.call('set', KEYS[1], 'x')",
4625 b"1",
4626 b"k"
4627 ])
4628 .starts_with("-ERR Write commands are not allowed from read-only scripts."),
4629 );
4630 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nv\r\n");
4632 assert_eq!(
4633 f.run(&[
4634 b"EVAL",
4635 b"return redis.call('set', KEYS[1], 'x')",
4636 b"1",
4637 b"k"
4638 ]),
4639 "+OK\r\n"
4640 );
4641 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\nx\r\n");
4642
4643 let sha = b"e0e1f9fabfc9d4800c877a703b823ac0578ff8db";
4645 f.run(&[b"SCRIPT", b"LOAD", b"return 1"]);
4646 assert_eq!(f.run(&[b"EVALSHA_RO", sha, b"0"]), ":1\r\n");
4647 }
4648
4649 #[test]
4650 fn a_script_cannot_leave_anything_behind_for_the_next_one() {
4651 let mut f = Fixture::new();
4652 for body in [&b"x = 1"[..], b"pcall = 1", b"redis = 1", b"redis.call = 1"] {
4655 let reply = f.run(&[b"EVAL", body, b"0"]);
4656 assert!(
4657 reply
4658 .starts_with("-ERR user_script:1: Attempt to modify a readonly table script: "),
4659 "{body:?} gave {reply}",
4660 );
4661 }
4662 for body in [
4665 &b"rawset(redis, 'call', 1)"[..],
4666 b"rawset(_G, 'zz', 1)",
4667 b"setmetatable(_G, {})",
4668 b"setmetatable(redis, {})",
4669 ] {
4670 let reply = f.run(&[b"EVAL", body, b"0"]);
4671 assert!(
4672 reply.starts_with("-ERR Attempt to modify a readonly table script: "),
4673 "{body:?} gave {reply}",
4674 );
4675 }
4676 assert!(
4679 f.run(&[b"EVAL", b"return nosuchglobal", b"0"])
4680 .contains("Script attempted to access nonexistent global variable 'nosuchglobal'"),
4681 );
4682 assert_eq!(
4685 f.run(&[b"EVAL", b"return tostring(redis.nosuchfield)", b"0"]),
4686 "$3\r\nnil\r\n"
4687 );
4688
4689 assert_eq!(f.run(&[b"EVAL", b"_G.pcall = 1 return 1", b"0"]), ":1\r\n");
4692 assert_eq!(
4693 f.run(&[b"EVAL", b"return type(pcall)", b"0"]),
4694 "$8\r\nfunction\r\n"
4695 );
4696 assert_eq!(
4697 f.run(&[b"EVAL", b"return type(redis.call)", b"0"]),
4698 "$8\r\nfunction\r\n"
4699 );
4700 }
4701
4702 #[test]
4703 fn a_script_can_walk_the_redis_table_it_is_not_allowed_to_write_to() {
4704 let mut f = Fixture::new();
4705 assert_eq!(
4709 f.run(&[
4710 b"EVAL",
4711 b"local n = 0 for k in pairs(redis) do n = n + 1 end return n",
4712 b"0",
4713 ]),
4714 ":23\r\n"
4715 );
4716 assert_eq!(
4717 f.run(&[
4718 b"EVAL",
4719 b"local t = {} for k in pairs(redis) do t[#t+1] = k end \
4720 table.sort(t) return table.concat(t, ' ')",
4721 b"0",
4722 ]),
4723 "$243\r\nLOG_DEBUG LOG_NOTICE LOG_VERBOSE LOG_WARNING REDIS_VERSION \
4724 REDIS_VERSION_NUM REPL_ALL REPL_AOF REPL_NONE REPL_REPLICA REPL_SLAVE \
4725 acl_check_cmd breakpoint call debug error_reply log pcall replicate_commands \
4726 set_repl setresp sha1hex status_reply\r\n"
4727 );
4728 assert_eq!(
4731 f.run(&[
4732 b"EVAL",
4733 b"local n = 0 for k, v in pairs(redis) do \
4734 if type(v) == 'function' then n = n + 1 end end return n",
4735 b"0",
4736 ]),
4737 ":12\r\n"
4738 );
4739 assert_eq!(
4741 f.run(&[b"EVAL", b"return type(next(redis))", b"0"]),
4742 "$6\r\nstring\r\n"
4743 );
4744 assert_eq!(
4745 f.run(&[b"EVAL", b"return type(rawget(redis, 'call'))", b"0"]),
4746 "$8\r\nfunction\r\n"
4747 );
4748 assert_eq!(
4749 f.run(&[
4750 b"EVAL",
4751 b"return tostring(rawget(redis, 'nosuchfield'))",
4752 b"0",
4753 ]),
4754 "$3\r\nnil\r\n"
4755 );
4756 for body in [&b"redis.call = 1"[..], b"rawset(redis, 'call', 1)"] {
4759 assert!(
4760 f.run(&[b"EVAL", body, b"0"])
4761 .contains("Attempt to modify a readonly table script: "),
4762 "{body:?}",
4763 );
4764 }
4765 assert_eq!(
4768 f.run(&[
4769 b"EVAL",
4770 b"local t = {a=1,b=2} local n = 0 for k in pairs(t) do n = n + 1 end return n",
4771 b"0",
4772 ]),
4773 ":2\r\n"
4774 );
4775 assert_eq!(
4776 f.run(&[b"EVAL", b"return tostring(next({}))", b"0"]),
4777 "$3\r\nnil\r\n"
4778 );
4779 assert_eq!(
4780 f.run(&[
4781 b"EVAL",
4782 b"local f for k, v in pairs(string) do if k == 'sub' then f = v end end \
4783 return type(f)",
4784 b"0",
4785 ]),
4786 "$8\r\nfunction\r\n"
4787 );
4788 }
4789
4790 #[test]
4791 fn a_script_gets_the_bit_library_a_real_server_carries() {
4792 let mut f = Fixture::new();
4793 for (body, want) in [
4796 ("bit.tobit(1)", ":1\r\n"),
4797 ("bit.tobit(2^32 + 1)", ":1\r\n"),
4798 ("bit.tobit(2^31)", ":-2147483648\r\n"),
4799 ("bit.tobit(0xffffffff)", ":-1\r\n"),
4800 ("bit.tobit(1.5)", ":2\r\n"),
4802 ("bit.tobit(2.5)", ":2\r\n"),
4803 ("bit.bnot(0)", ":-1\r\n"),
4804 ("bit.band(0xff, 0x0f)", ":15\r\n"),
4805 ("bit.band(1, 2, 3)", ":0\r\n"),
4806 ("bit.bor(1, 2, 4)", ":7\r\n"),
4807 ("bit.bxor(0xff, 0x0f)", ":240\r\n"),
4808 ("bit.lshift(1, 31)", ":-2147483648\r\n"),
4810 ("bit.lshift(1, 32)", ":1\r\n"),
4811 ("bit.lshift(1, 33)", ":2\r\n"),
4812 ("bit.rshift(-1, 1)", ":2147483647\r\n"),
4813 ("bit.arshift(-1, 1)", ":-1\r\n"),
4814 ("bit.rol(0x12345678, 8)", ":878082066\r\n"),
4815 ("bit.ror(0x12345678, 8)", ":2014458966\r\n"),
4816 ("bit.bswap(0x12345678)", ":2018915346\r\n"),
4817 ("bit.tobit('0x10')", ":16\r\n"),
4820 ] {
4821 let script = format!("return {body}");
4822 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
4823 }
4824 for (body, want) in [
4827 ("bit.tohex(1)", "00000001"),
4828 ("bit.tohex(-1)", "ffffffff"),
4829 ("bit.tohex(255, 2)", "ff"),
4830 ("bit.tohex(255, -8)", "000000FF"),
4831 ("bit.tohex(0x87654321, 4)", "4321"),
4832 ("bit.tohex(1, 0)", ""),
4833 ("bit.tohex(1, 9)", "00000001"),
4834 ] {
4835 let script = format!("return {body}");
4836 assert_eq!(
4837 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
4838 format!("${}\r\n{want}\r\n", want.len()),
4839 "{body}",
4840 );
4841 }
4842 for (body, want) in [
4845 (
4846 "return bit.band()",
4847 "bad argument #1 to 'band' (number expected, got no value)",
4848 ),
4849 (
4850 "return bit.band('x')",
4851 "bad argument #1 to 'band' (number expected, got string)",
4852 ),
4853 (
4854 "return bit.tobit(true)",
4855 "bad argument #1 to 'tobit' (number expected, got boolean)",
4856 ),
4857 (
4858 "return bit.lshift(1)",
4859 "bad argument #2 to 'lshift' (number expected, got no value)",
4860 ),
4861 ] {
4862 let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
4863 assert!(
4864 reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
4865 "{body} gave {reply}",
4866 );
4867 }
4868 assert_eq!(
4871 f.run(&[
4872 b"EVAL",
4873 b"local ok, e = pcall(bit.band, 'x') return tostring(e)",
4874 b"0",
4875 ]),
4876 "$52\r\nbad argument #1 to '?' (number expected, got string)\r\n"
4877 );
4878 assert_eq!(
4880 f.run(&[
4881 b"EVAL",
4882 b"local t = {} for k in pairs(bit) do t[#t+1] = k end \
4883 table.sort(t) return table.concat(t, ' ')",
4884 b"0",
4885 ]),
4886 "$66\r\narshift band bnot bor bswap bxor lshift rol ror rshift tobit tohex\r\n"
4887 );
4888 for body in [&b"bit.band = 1"[..], b"rawset(bit, 'zz', 1)"] {
4889 assert!(
4890 f.run(&[b"EVAL", body, b"0"])
4891 .contains("Attempt to modify a readonly table script: "),
4892 "{body:?}",
4893 );
4894 }
4895 }
4896
4897 #[test]
4898 fn a_script_gets_the_cjson_library_a_real_server_carries() {
4899 let mut f = Fixture::new();
4900 for (body, want) in [
4904 ("cjson.encode(nil)", "null"),
4905 ("cjson.encode(true)", "true"),
4906 ("cjson.encode(cjson.null)", "null"),
4907 ("cjson.encode(100)", "100"),
4908 ("cjson.encode(1/3)", "0.33333333333333"),
4909 ("cjson.encode(1e300)", "1e+300"),
4910 ("cjson.encode(2^53)", "9.007199254741e+15"),
4911 ("cjson.encode({})", "{}"),
4912 ("cjson.encode({1,2,3})", "[1,2,3]"),
4913 ("cjson.encode({a=1})", "{\"a\":1}"),
4914 ("cjson.encode({[1]=1,[3]=3})", "[1,null,3]"),
4915 ("cjson.encode({[0]=1})", "{\"0\":1}"),
4916 ("cjson.encode('a\\nb')", "\"a\\nb\""),
4917 ("cjson.encode('\\t\\\\')", "\"\\t\\\\\""),
4919 ("cjson.encode('\\11')", "\"\\u000b\""),
4920 (
4923 "cjson.encode(cjson.decode('[1,[2,{\"a\":null}]]'))",
4924 "[1,[2,{\"a\":null}]]",
4925 ),
4926 ("cjson.encode(cjson.decode('[]'))", "{}"),
4929 ] {
4930 let script = format!("return {body}");
4931 assert_eq!(
4932 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
4933 format!("${}\r\n{want}\r\n", want.len()),
4934 "{body}",
4935 );
4936 }
4937 for (body, want) in [
4940 ("cjson.decode('[1,2,3]')[2]", ":2\r\n"),
4941 ("cjson.decode('{\"a\":41}').a + 1", ":42\r\n"),
4942 ("cjson.decode('0x10')", ":16\r\n"),
4943 ("cjson.decode('+1')", ":1\r\n"),
4944 ("cjson.decode('01')", ":1\r\n"),
4945 ("cjson.decode(1) + 1", ":2\r\n"),
4946 ("cjson.decode([[\"\\u0041\"]]) == 'A' and 1 or 0", ":1\r\n"),
4948 ("cjson.decode('null') == cjson.null and 1 or 0", ":1\r\n"),
4949 ("cjson.decode('null') == nil and 1 or 0", ":0\r\n"),
4950 ] {
4951 let script = format!("return {body}");
4952 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
4953 }
4954 for (body, want) in [
4956 (
4957 "cjson.encode_number_precision(3) return cjson.encode(1/3)",
4958 "0.333",
4959 ),
4960 (
4961 "cjson.encode_invalid_numbers('null') return cjson.encode(1/0)",
4962 "null",
4963 ),
4964 (
4965 "cjson.encode_invalid_numbers(true) return cjson.encode(1/0)",
4966 "inf",
4967 ),
4968 (
4969 "cjson.encode_sparse_array(true) return cjson.encode({[1]=1,[100]=1})",
4970 "{\"1\":1,\"100\":1}",
4971 ),
4972 (
4973 "cjson.decode_array_with_array_mt(true) return cjson.encode(cjson.decode('[]'))",
4974 "[]",
4975 ),
4976 ("return tostring(cjson.encode_max_depth())", "1000"),
4977 ("return tostring(cjson.encode_keep_buffer(false))", "false"),
4978 ("return tostring(cjson.encode_sparse_array())", "false"),
4979 ("return tostring(cjson.encode_number_precision())", "14"),
4982 ] {
4983 assert_eq!(
4984 f.run(&[b"EVAL", body.as_bytes(), b"0"]),
4985 format!("${}\r\n{want}\r\n", want.len()),
4986 "{body}",
4987 );
4988 }
4989 for (body, want) in [
4991 (
4992 "return cjson.encode(1/0)",
4993 "Cannot serialise number: must not be NaN or Inf",
4994 ),
4995 (
4996 "return cjson.encode({[1]=1,[100]=1})",
4997 "Cannot serialise table: excessively sparse array",
4998 ),
4999 (
5000 "return cjson.encode({[true]=1})",
5001 "Cannot serialise boolean: table key must be a number or string",
5002 ),
5003 (
5004 "return cjson.encode(tostring)",
5005 "Cannot serialise function: type not supported",
5006 ),
5007 (
5008 "return cjson.encode()",
5009 "bad argument #1 to 'encode' (expected 1 argument)",
5010 ),
5011 (
5012 "return cjson.decode('[1,2')",
5013 "Expected comma or array end but found T_END at character 5",
5014 ),
5015 (
5016 "return cjson.decode('{\"a\" 1}')",
5017 "Expected colon but found T_NUMBER at character 6",
5018 ),
5019 (
5020 "return cjson.decode('tru')",
5021 "Expected value but found invalid token at character 1",
5022 ),
5023 (
5024 "return cjson.decode('[1] 2')",
5025 "Expected the end but found T_NUMBER at character 5",
5026 ),
5027 (
5028 "return cjson.encode_max_depth(0)",
5029 "bad argument #1 to 'encode_max_depth' (expected integer between 1 and 2147483647)",
5030 ),
5031 (
5032 "return cjson.encode_invalid_numbers('yes')",
5033 "bad argument #1 to 'encode_invalid_numbers' (invalid option 'yes')",
5034 ),
5035 (
5036 "return cjson.encode_max_depth(1, 2)",
5037 "bad argument #2 to 'encode_max_depth' (found too many arguments)",
5038 ),
5039 ] {
5040 let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
5041 assert!(
5042 reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
5043 "{body} gave {reply}",
5044 );
5045 }
5046 assert_eq!(
5049 f.run(&[
5050 b"EVAL",
5051 b"local n = cjson.new() n.encode_number_precision(3) \
5052 return cjson.encode(1/3) .. ' ' .. n.encode(1/3)",
5053 b"0",
5054 ]),
5055 "$22\r\n0.33333333333333 0.333\r\n"
5056 );
5057 let names = "_NAME _VERSION decode decode_array_with_array_mt decode_invalid_numbers \
5059 decode_max_depth encode encode_invalid_numbers encode_keep_buffer \
5060 encode_max_depth encode_number_precision encode_sparse_array new null";
5061 assert_eq!(
5062 f.run(&[
5063 b"EVAL",
5064 b"local t = {} for k in pairs(cjson) do t[#t+1] = k end \
5065 table.sort(t) return table.concat(t, ' ')",
5066 b"0",
5067 ]),
5068 format!("${}\r\n{names}\r\n", names.len())
5069 );
5070 for body in [&b"cjson.encode = 1"[..], b"rawset(cjson, 'zz', 1)"] {
5071 assert!(
5072 f.run(&[b"EVAL", body, b"0"])
5073 .contains("Attempt to modify a readonly table script: "),
5074 "{body:?}",
5075 );
5076 }
5077 }
5078
5079 #[test]
5080 fn a_script_gets_the_struct_library_a_real_server_carries() {
5081 let mut f = Fixture::new();
5082 for (body, want) in [
5085 ("#struct.pack('i4', 1)", ":4\r\n"),
5086 ("#struct.pack('l', 1)", ":8\r\n"),
5087 ("#struct.pack('d', 1)", ":8\r\n"),
5088 ("#struct.pack('f', 1)", ":4\r\n"),
5089 ("#struct.pack('s', 'abc')", ":4\r\n"),
5090 ("#struct.pack('c3', 'abcdef')", ":3\r\n"),
5091 ("#struct.pack('x')", ":1\r\n"),
5092 ("string.byte(struct.pack('i4', 1), 1)", ":1\r\n"),
5093 ("string.byte(struct.pack('>i4', 1), 4)", ":1\r\n"),
5094 ("string.byte(struct.pack('<i4', 1), 1)", ":1\r\n"),
5095 ("string.byte(struct.pack('i16', -1), 9)", ":0\r\n"),
5098 ("string.byte(struct.pack('i8', -1), 8)", ":255\r\n"),
5099 ("#struct.pack('c0', 'abcd')", ":4\r\n"),
5102 ("string.byte(struct.pack('s', 'a'), 2)", ":0\r\n"),
5103 ("string.byte(struct.pack('bxb', 1, 2), 2)", ":0\r\n"),
5104 ] {
5105 let script = format!("return {body}");
5106 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
5107 }
5108 for (body, want) in [
5111 ("struct.size('i')", ":4\r\n"),
5112 ("struct.size('l')", ":8\r\n"),
5113 ("struct.size('T')", ":8\r\n"),
5114 ("struct.size('h')", ":2\r\n"),
5115 ("struct.size('c10')", ":10\r\n"),
5116 ("struct.size('ic')", ":5\r\n"),
5117 ("struct.size('!8ic')", ":5\r\n"),
5118 ("struct.size('!4i')", ":4\r\n"),
5119 ("struct.size('bd')", ":9\r\n"),
5122 ("struct.size('!bd')", ":16\r\n"),
5123 ("struct.size('A')", ":0\r\n"),
5124 ("struct.size('7')", ":0\r\n"),
5125 ] {
5126 let script = format!("return {body}");
5127 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
5128 }
5129 for (body, want) in [
5132 ("select('#', struct.unpack('i4', '\\1\\0\\0\\0'))", ":2\r\n"),
5133 ("select(1, struct.unpack('i4', '\\1\\0\\0\\0'))", ":1\r\n"),
5134 ("select(2, struct.unpack('i4', '\\1\\0\\0\\0'))", ":5\r\n"),
5135 ("select(1, struct.unpack('i1', '\\255'))", ":-1\r\n"),
5136 ("select(1, struct.unpack('I1', '\\255'))", ":255\r\n"),
5137 (
5138 "select(1, struct.unpack('i4', struct.pack('i4', -70000)))",
5139 ":-70000\r\n",
5140 ),
5141 ("select(2, struct.unpack('i1', 'abc', 2))", ":3\r\n"),
5142 ("select('#', struct.unpack('bc0', '\\3abcd'))", ":2\r\n"),
5146 ("select(2, struct.unpack('bc0', '\\3abcd'))", ":5\r\n"),
5147 ] {
5148 let script = format!("return {body}");
5149 assert_eq!(f.run(&[b"EVAL", script.as_bytes(), b"0"]), want, "{body}");
5150 }
5151 for (body, want) in [
5152 ("select(1, struct.unpack('bc0', '\\3abcd'))", "abc"),
5153 ("select(1, struct.unpack('s', 'ab\\0cd'))", "ab"),
5154 ("select(1, struct.unpack('c3', 'abcdef'))", "abc"),
5155 ] {
5156 let script = format!("return {body}");
5157 assert_eq!(
5158 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5159 format!("${}\r\n{want}\r\n", want.len()),
5160 "{body}",
5161 );
5162 }
5163 for (body, want) in [
5166 (
5167 "return struct.pack()",
5168 "bad argument #1 to 'pack' (string expected, got no value)",
5169 ),
5170 (
5173 "return struct.pack('i4')",
5174 "bad argument #2 to 'pack' (number expected, got nil)",
5175 ),
5176 (
5179 "return struct.pack('c6', 'abc')",
5180 "bad argument #3 to 'pack' (string too short)",
5181 ),
5182 (
5183 "return struct.pack('A', 'x')",
5184 "bad argument #1 to 'pack' (invalid format option 'A')",
5185 ),
5186 (
5187 "return struct.pack('i33', 1)",
5188 "integral size 33 is larger than limit of 32",
5189 ),
5190 (
5191 "return struct.pack('!3i', 1)",
5192 "alignment 3 is not a power of 2",
5193 ),
5194 (
5195 "return struct.unpack()",
5196 "bad argument #1 to 'unpack' (string expected, got no value)",
5197 ),
5198 (
5199 "return struct.unpack('i4')",
5200 "bad argument #2 to 'unpack' (string expected, got no value)",
5201 ),
5202 (
5203 "return struct.unpack('i4', 'ab')",
5204 "bad argument #2 to 'unpack' (data string too short)",
5205 ),
5206 (
5207 "return struct.unpack('i1', 'abc', 0)",
5208 "bad argument #3 to 'unpack' (offset must be 1 or greater)",
5209 ),
5210 (
5211 "return struct.unpack('c0', 'abc')",
5212 "format 'c0' needs a previous size",
5213 ),
5214 (
5215 "return struct.unpack('s', 'abc')",
5216 "unfinished string in data",
5217 ),
5218 (
5219 "return struct.size()",
5220 "bad argument #1 to 'size' (string expected, got no value)",
5221 ),
5222 (
5223 "return struct.size('s')",
5224 "bad argument #1 to 'size' (option 's' has no fixed size)",
5225 ),
5226 (
5227 "return struct.size('c0')",
5228 "bad argument #1 to 'size' (option 'c0' has no fixed size)",
5229 ),
5230 ] {
5231 let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
5232 assert!(
5233 reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
5234 "{body} gave {reply}",
5235 );
5236 }
5237 let names = "pack size unpack";
5239 assert_eq!(
5240 f.run(&[
5241 b"EVAL",
5242 b"local t = {} for k in pairs(struct) do t[#t+1] = k end \
5243 table.sort(t) return table.concat(t, ' ')",
5244 b"0",
5245 ]),
5246 format!("${}\r\n{names}\r\n", names.len())
5247 );
5248 for body in [&b"struct.pack = 1"[..], b"rawset(struct, 'zz', 1)"] {
5249 assert!(
5250 f.run(&[b"EVAL", body, b"0"])
5251 .contains("Attempt to modify a readonly table script: "),
5252 "{body:?}",
5253 );
5254 }
5255 }
5256
5257 #[test]
5258 fn a_script_gets_the_cmsgpack_library_a_real_server_carries() {
5259 let mut f = Fixture::new();
5260 let hex = "local function hx(s) return (string.gsub(s, '.', \
5263 function(c) return string.format('%02x', string.byte(c)) end)) end ";
5264 for (body, want) in [
5265 ("cmsgpack.pack(nil)", "c0"),
5266 ("cmsgpack.pack(true)", "c3"),
5267 ("cmsgpack.pack(false)", "c2"),
5268 ("cmsgpack.pack(0)", "00"),
5269 ("cmsgpack.pack(127)", "7f"),
5270 ("cmsgpack.pack(128)", "cc80"),
5271 ("cmsgpack.pack(-1)", "ff"),
5272 ("cmsgpack.pack(-33)", "d0df"),
5273 ("cmsgpack.pack(65535)", "cdffff"),
5274 ("cmsgpack.pack(4294967296)", "cf0000000100000000"),
5275 ("cmsgpack.pack(2^53)", "cf0020000000000000"),
5276 ("cmsgpack.pack(-2^63)", "d38000000000000000"),
5277 ("cmsgpack.pack(2^64)", "ca5f800000"),
5280 ("cmsgpack.pack(1.5)", "ca3fc00000"),
5281 ("cmsgpack.pack(0.1)", "cb3fb999999999999a"),
5282 ("cmsgpack.pack('abc')", "a3616263"),
5283 ("cmsgpack.pack('')", "a0"),
5284 ("cmsgpack.pack({})", "90"),
5285 ("cmsgpack.pack({1, 2})", "920102"),
5286 ("cmsgpack.pack({a = 1})", "81a16101"),
5287 ("cmsgpack.pack(1, 'a', true)", "01a161c3"),
5288 (
5291 "(function() local t = {} local c = t \
5292 for i = 1, 20 do c.n = {} c = c.n end return cmsgpack.pack(t) end)()",
5293 "81a16e81a16e81a16e81a16e81a16e81a16e81a16e81a16e\
5294 81a16e81a16e81a16e81a16e81a16e81a16e81a16e81a16ec0",
5295 ),
5296 ] {
5297 let script = format!("{hex} return hx({body})");
5298 assert_eq!(
5299 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5300 format!("${}\r\n{want}\r\n", want.len()),
5301 "{body}",
5302 );
5303 }
5304 for (body, want) in [
5308 ("cmsgpack.unpack(cmsgpack.pack(42))", 42),
5309 ("select('#', cmsgpack.unpack('\\1\\2\\3'))", 3),
5310 ("select(3, cmsgpack.unpack('\\1\\2\\3'))", 3),
5311 ("select('#', cmsgpack.unpack(''))", 0),
5312 ("select('#', cmsgpack.unpack_one('\\1\\2\\3'))", 2),
5313 ("select(1, cmsgpack.unpack_one('\\1\\2\\3'))", 1),
5314 ("select(2, cmsgpack.unpack_one('\\1\\2\\3'))", 1),
5315 ("select(1, cmsgpack.unpack_one('\\1\\2\\3', 2))", -1),
5316 ("select(1, cmsgpack.unpack_one('\\1'))", -1),
5317 ("select(1, cmsgpack.unpack_one('', 0))", -1),
5318 ("select('#', cmsgpack.unpack_limit('\\1\\2\\3', 2))", 3),
5319 ("select(1, cmsgpack.unpack_limit('\\1\\2\\3', 2))", 2),
5320 ("select('#', cmsgpack.unpack_limit('\\1\\2\\3', 0, 0))", 3),
5323 ("cmsgpack.unpack(cmsgpack.pack({1, 2, 3}))[2]", 2),
5324 ] {
5325 let script = format!("return {body}");
5326 assert_eq!(
5327 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5328 format!(":{want}\r\n"),
5329 "{body}",
5330 );
5331 }
5332 for (body, want) in [
5333 ("cmsgpack.unpack(cmsgpack.pack({a = 'b'})).a", "b"),
5334 ("tostring(cmsgpack.unpack(cmsgpack.pack(1.5)))", "1.5"),
5335 ("tostring(cmsgpack.unpack(cmsgpack.pack(nil)))", "nil"),
5336 (
5337 "tostring(cmsgpack.unpack(string.char(0xcb, 0x7f, 0xf0, 0, 0, 0, 0, 0, 0)))",
5338 "inf",
5339 ),
5340 ("cmsgpack._NAME", "cmsgpack"),
5341 ("cmsgpack._VERSION", "lua-cmsgpack 0.4.0"),
5342 (
5343 "cmsgpack._COPYRIGHT",
5344 "Copyright (C) 2012, Salvatore Sanfilippo",
5345 ),
5346 (
5347 "cmsgpack._DESCRIPTION",
5348 "MessagePack C implementation for Lua",
5349 ),
5350 ] {
5351 let script = format!("return {body}");
5352 assert_eq!(
5353 f.run(&[b"EVAL", script.as_bytes(), b"0"]),
5354 format!("${}\r\n{want}\r\n", want.len()),
5355 "{body}",
5356 );
5357 }
5358 for (body, want) in [
5359 (
5362 "return cmsgpack.pack()",
5363 "bad argument #0 to 'pack' (MessagePack pack needs input.)",
5364 ),
5365 (
5366 "return cmsgpack.unpack()",
5367 "bad argument #1 to 'unpack' (string expected, got no value)",
5368 ),
5369 (
5370 "return cmsgpack.unpack(string.char(193))",
5371 "Bad data format in input.",
5372 ),
5373 (
5374 "return cmsgpack.unpack(string.char(204))",
5375 "Missing bytes in input.",
5376 ),
5377 (
5378 "return cmsgpack.unpack(string.char(146, 1))",
5379 "Missing bytes in input.",
5380 ),
5381 (
5382 "return cmsgpack.unpack_one('\\1', 5)",
5383 "Start offset 5 greater than input length 1.",
5384 ),
5385 (
5386 "return cmsgpack.unpack_limit('\\1\\2', 1, 5)",
5387 "Start offset 5 greater than input length 2.",
5388 ),
5389 (
5392 "return cmsgpack.unpack_one('\\1', -1)",
5393 "Invalid request to unpack with offset of -1 and limit of 1.",
5394 ),
5395 (
5396 "return cmsgpack.unpack_limit('\\1', -1, 0)",
5397 "Invalid request to unpack with offset of 0 and limit of 1.",
5398 ),
5399 ] {
5400 let reply = f.run(&[b"EVAL", body.as_bytes(), b"0"]);
5401 assert!(
5402 reply.starts_with(&format!("-ERR user_script:1: {want} script: ")),
5403 "{body} gave {reply}",
5404 );
5405 }
5406 let names = "_COPYRIGHT _DESCRIPTION _NAME _VERSION pack unpack unpack_limit unpack_one";
5408 assert_eq!(
5409 f.run(&[
5410 b"EVAL",
5411 b"local t = {} for k in pairs(cmsgpack) do t[#t+1] = k end \
5412 table.sort(t) return table.concat(t, ' ')",
5413 b"0",
5414 ]),
5415 format!("${}\r\n{names}\r\n", names.len())
5416 );
5417 for body in [&b"cmsgpack.pack = 1"[..], b"rawset(cmsgpack, 'zz', 1)"] {
5418 assert!(
5419 f.run(&[b"EVAL", body, b"0"])
5420 .contains("Attempt to modify a readonly table script: "),
5421 "{body:?}",
5422 );
5423 }
5424 assert_eq!(
5427 f.run(&[
5428 b"EVAL",
5429 b"return cmsgpack.unpack(cmsgpack.pack(cmsgpack))._NAME",
5430 b"0",
5431 ]),
5432 "$8\r\ncmsgpack\r\n"
5433 );
5434 }
5435
5436 const LIB: &[u8] = b"#!lua name=mylib\n\
5442 local counter = 0\n\
5443 redis.register_function{function_name = 'ping', description = 'says pong',\n\
5444 callback = function(keys, args) return 'pong' end, flags = {'no-writes'}}\n\
5445 redis.register_function('count', function() counter = counter + 1 return counter end)\n\
5446 redis.register_function('echo', function(keys, args) return {keys, args} end)\n\
5447 redis.register_function('setit', function(keys, args) \
5448 return redis.call('SET', keys[1], args[1]) end)\n\
5449 redis.register_function('raise', function() error('boom') end)\n";
5450
5451 const OTHER: &[u8] = b"#!lua name=other\n\
5453 redis.register_function('twice', function(keys, args) return 2 end)\n";
5454
5455 #[test]
5456 fn a_library_is_loaded_once_and_called_by_name_forever_after() {
5457 let mut f = Fixture::new();
5458 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5459 assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
5460 assert_eq!(f.run(&[b"FCALL", b"PiNg", b"0"]), "$4\r\npong\r\n");
5463 assert_eq!(
5467 f.run(&[b"FCALL", b"echo", b"1", b"k", b"a", b"b"]),
5468 "*2\r\n*1\r\n$1\r\nk\r\n*2\r\n$1\r\na\r\n$1\r\nb\r\n"
5469 );
5470 assert_eq!(f.run(&[b"FCALL", b"setit", b"1", b"s", b"v"]), "+OK\r\n");
5471 assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nv\r\n");
5472 assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":1\r\n");
5475 assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":2\r\n");
5476 assert_eq!(
5479 f.run(&[b"FCALL", b"raise", b"0"]),
5480 "-ERR user_function:8: boom script: raise, on @user_function:8.\r\n"
5481 );
5482 assert_eq!(
5485 f.run(&[b"FUNCTION", b"DELETE", b"MYLIB"]),
5486 "-ERR Library not found\r\n"
5487 );
5488 assert_eq!(f.run(&[b"FUNCTION", b"DELETE", b"mylib"]), "+OK\r\n");
5489 assert_eq!(
5490 f.run(&[b"FCALL", b"ping", b"0"]),
5491 "-ERR Function not found\r\n"
5492 );
5493 }
5494
5495 #[test]
5496 fn a_library_that_is_wrong_says_which_way_it_is_wrong() {
5497 let mut f = Fixture::new();
5498 for (code, want) in [
5499 (&b"return 1"[..], "ERR Missing library metadata"),
5500 (b"#!lua name=x", "ERR Invalid library metadata"),
5501 (b"#!\n", "ERR Library name was not given"),
5502 (b"#!lua\nx", "ERR Library name was not given"),
5503 (
5504 b"#!lua name=a name=b\nx",
5505 "ERR Invalid metadata value, name argument was given multiple times",
5506 ),
5507 (
5508 b"#!lua nome=a\nx",
5509 "ERR Invalid metadata value given: nome=a",
5510 ),
5511 (b"#!lua name=\"q\nx", "ERR Invalid library metadata"),
5512 (
5513 b"#!lua name=a-b\nx",
5514 "ERR Library names can only contain letters, numbers, or underscores(_) \
5515 and must be at least one character long",
5516 ),
5517 (b"#!zz name=x\nx", "ERR Engine 'zz' not found"),
5518 (
5519 b"#!lua name=c\nthis is not lua",
5520 "ERR Error compiling function: user_function:2: '=' expected near 'is'",
5521 ),
5522 (
5526 b"#!lua name=r\nerror('boom')",
5527 "ERR Error registering functions: ERR user_function:2: \
5528 Script attempted to access nonexistent global variable 'error'",
5529 ),
5530 (
5533 b"#!lua name=r\nredis.call('PING')",
5534 "ERR Error registering functions: ERR user_function:2: \
5535 Script attempted to access nonexistent global variable 'call'",
5536 ),
5537 (
5538 b"#!lua name=r\nx = 1",
5539 "ERR Error registering functions: ERR user_function:2: \
5540 Attempt to modify a readonly table",
5541 ),
5542 (b"#!lua name=n\nlocal x = 1", "ERR No functions registered"),
5543 ] {
5544 assert_eq!(
5545 f.run(&[b"FUNCTION", b"LOAD", code]),
5546 format!("-{want}\r\n"),
5547 "{}",
5548 String::from_utf8_lossy(code),
5549 );
5550 }
5551 }
5552
5553 #[test]
5554 fn register_function_turns_away_every_call_it_cannot_make_sense_of() {
5555 let mut f = Fixture::new();
5556 for (call, want) in [
5557 (
5558 &b"redis.register_function()"[..],
5559 "wrong number of arguments to redis.register_function",
5560 ),
5561 (
5562 b"redis.register_function('a', function() end, 1)",
5563 "wrong number of arguments to redis.register_function",
5564 ),
5565 (
5566 b"redis.register_function('a')",
5567 "calling redis.register_function with a single argument is only \
5568 applicable to Lua table (representing named arguments).",
5569 ),
5570 (
5571 b"redis.register_function({foo = 'a'})",
5572 "unknown argument given to redis.register_function",
5573 ),
5574 (
5575 b"redis.register_function({callback = function() end})",
5576 "redis.register_function must get a function name argument",
5577 ),
5578 (
5579 b"redis.register_function({function_name = 'a'})",
5580 "redis.register_function must get a callback argument",
5581 ),
5582 (
5583 b"redis.register_function({function_name = {}, callback = function() end})",
5584 "function_name argument given to redis.register_function must be a string",
5585 ),
5586 (
5587 b"redis.register_function({function_name = 'a', description = {}, \
5588 callback = function() end})",
5589 "description argument given to redis.register_function must be a string",
5590 ),
5591 (
5592 b"redis.register_function({function_name = 'a', callback = 1})",
5593 "callback argument given to redis.register_function must be a function",
5594 ),
5595 (
5596 b"redis.register_function({function_name = 'a', callback = function() end, \
5597 flags = 1})",
5598 "flags argument to redis.register_function must be a table \
5599 representing function flags",
5600 ),
5601 (
5602 b"redis.register_function({function_name = 'a', callback = function() end, \
5603 flags = {'zz'}})",
5604 "unknown flag given",
5605 ),
5606 (
5607 b"redis.register_function({}, function() end)",
5608 "first argument to redis.register_function must be a string",
5609 ),
5610 (
5611 b"redis.register_function('a', 1)",
5612 "second argument to redis.register_function must be a function",
5613 ),
5614 (
5615 b"redis.register_function('a-b', function() end)",
5616 "Library names can only contain letters, numbers, or underscores(_) \
5617 and must be at least one character long",
5618 ),
5619 (
5620 b"redis.register_function('d', function() end) \
5621 redis.register_function('d', function() end)",
5622 "Function already exists in the library",
5623 ),
5624 ] {
5625 let mut code = b"#!lua name=e\n".to_vec();
5626 code.extend_from_slice(call);
5627 assert_eq!(
5631 f.run(&[b"FUNCTION", b"LOAD", &code]),
5632 format!("-ERR Error registering functions: ERR {want}\r\n"),
5633 "{}",
5634 String::from_utf8_lossy(call),
5635 );
5636 }
5637 assert_eq!(
5640 f.run(&[
5641 b"FUNCTION",
5642 b"LOAD",
5643 b"#!lua name=n\nredis.register_function(12, function() return 1 end)",
5644 ]),
5645 "$1\r\nn\r\n"
5646 );
5647 assert_eq!(f.run(&[b"FCALL", b"12", b"0"]), ":1\r\n");
5648 assert_eq!(
5651 f.run(&[
5652 b"FUNCTION",
5653 b"LOAD",
5654 b"#!lua name=c\nredis.register_function('d', function() return 1 end) \
5655 redis.register_function('D', function() return 2 end)",
5656 ]),
5657 "$1\r\nc\r\n"
5658 );
5659 }
5660
5661 #[test]
5662 fn a_library_cannot_take_a_name_another_library_already_has() {
5663 let mut f = Fixture::new();
5664 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5665 assert_eq!(
5666 f.run(&[b"FUNCTION", b"LOAD", LIB]),
5667 "-ERR Library 'mylib' already exists\r\n"
5668 );
5669 assert_eq!(
5673 f.run(&[
5674 b"FUNCTION",
5675 b"LOAD",
5676 b"#!lua name=other\nredis.register_function('PING', function() return 1 end)",
5677 ]),
5678 "-ERR Function PING already exists\r\n"
5679 );
5680 assert_eq!(
5683 f.run(&[b"FUNCTION", b"LOAD", b"REPLACE", LIB]),
5684 "$5\r\nmylib\r\n"
5685 );
5686 assert_eq!(f.run(&[b"FCALL", b"count", b"0"]), ":1\r\n");
5689 assert_eq!(
5690 f.run(&[b"FUNCTION", b"LOAD", b"NOPE", LIB]),
5691 "-ERR Unknown option given: NOPE\r\n"
5692 );
5693 assert_eq!(
5696 f.run(&[b"FUNCTION", b"LOAD", b"REPLACE"]),
5697 "-ERR Missing library metadata\r\n"
5698 );
5699 assert_eq!(
5700 f.run(&[b"FUNCTION", b"LOAD"]),
5701 "-ERR wrong number of arguments for 'function|load' command\r\n"
5702 );
5703 }
5704
5705 #[test]
5706 fn fcall_checks_the_name_before_it_looks_at_anything_else() {
5707 let mut f = Fixture::new();
5708 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5709 for (args, want) in [
5710 (&[&b"nosuch"[..], b"x"][..], "ERR Function not found"),
5711 (&[b"ping", b"x"], "ERR Bad number of keys provided"),
5712 (&[b"ping", b"1.5"], "ERR Bad number of keys provided"),
5713 (&[b"ping", b"+1"], "ERR Bad number of keys provided"),
5714 (
5715 &[b"ping", b"99999999999999999999"],
5716 "ERR Bad number of keys provided",
5717 ),
5718 (
5719 &[b"ping", b"3", b"a"],
5720 "ERR Number of keys can't be greater than number of args",
5721 ),
5722 (&[b"ping", b"-1"], "ERR Number of keys can't be negative"),
5723 ] {
5724 let mut wire: Vec<&[u8]> = vec![b"FCALL"];
5725 wire.extend_from_slice(args);
5726 assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
5727 }
5728 assert_eq!(
5731 f.run(&[b"FCALL_RO", b"setit", b"1", b"s", b"v"]),
5732 "-ERR Can not execute a script with write flag using *_ro command.\r\n"
5733 );
5734 assert_eq!(f.run(&[b"FCALL_RO", b"ping", b"0"]), "$4\r\npong\r\n");
5735 assert_eq!(
5736 f.run(&[b"FCALL_RO", b"nosuch", b"0"]),
5737 "-ERR Function not found\r\n"
5738 );
5739 assert_eq!(
5742 f.run(&[
5743 b"FUNCTION",
5744 b"LOAD",
5745 b"#!lua name=w\nredis.register_function{function_name = 'w', \
5746 flags = {'no-writes'}, callback = function(keys) \
5747 return redis.call('SET', keys[1], 'x') end}",
5748 ]),
5749 "$1\r\nw\r\n"
5750 );
5751 assert!(
5752 f.run(&[b"FCALL", b"w", b"1", b"k"])
5753 .starts_with("-ERR Write commands are not allowed from read-only scripts."),
5754 );
5755 }
5756
5757 #[test]
5758 fn a_function_gets_the_globals_a_script_gets_minus_the_ones_only_eval_has() {
5759 let mut f = Fixture::new();
5760 let names = "LOG_DEBUG LOG_NOTICE LOG_VERBOSE LOG_WARNING REDIS_VERSION \
5763 REDIS_VERSION_NUM REPL_ALL REPL_AOF REPL_NONE REPL_REPLICA REPL_SLAVE \
5764 acl_check_cmd call error_reply log pcall set_repl setresp sha1hex \
5765 status_reply";
5766 let globals = "_G _VERSION assert bit cjson cmsgpack collectgarbage coroutine error \
5767 gcinfo getmetatable ipairs load loadstring math next os pairs pcall \
5768 rawequal rawget rawset redis select setmetatable string struct table \
5769 tonumber tostring type unpack xpcall";
5770 assert_eq!(
5771 f.run(&[
5772 b"FUNCTION",
5773 b"LOAD",
5774 b"#!lua name=g\n\
5775 local function sorted(t) local o = {} for k in pairs(t) do o[#o+1] = k end \
5776 table.sort(o) return table.concat(o, ' ') end\n\
5777 redis.register_function('names', function() return sorted(redis) end)\n\
5778 redis.register_function('globals', function() return sorted(_G) end)\n\
5779 redis.register_function('keysg', function() return KEYS[1] end)\n\
5780 redis.register_function('zzz', function() return tostring(redis.zzz) end)\n\
5781 redis.register_function('wr', function() rawset(_G, 'x', 1) end)\n\
5782 redis.register_function('gwr', function() _G.pcall = 1 end)\n",
5783 ]),
5784 "$1\r\ng\r\n"
5785 );
5786 assert_eq!(
5787 f.run(&[b"FCALL", b"names", b"0"]),
5788 format!("${}\r\n{names}\r\n", names.len())
5789 );
5790 assert_eq!(
5791 f.run(&[b"FCALL", b"globals", b"0"]),
5792 format!("${}\r\n{globals}\r\n", globals.len())
5793 );
5794 assert!(
5797 f.run(&[b"FCALL", b"keysg", b"1", b"k"])
5798 .contains("nonexistent global variable 'KEYS'"),
5799 );
5800 assert_eq!(f.run(&[b"FCALL", b"zzz", b"0"]), "$3\r\nnil\r\n");
5803 for name in [&b"wr"[..], b"gwr"] {
5806 assert!(
5807 f.run(&[b"FCALL", name, b"0"])
5808 .contains("Attempt to modify a readonly table"),
5809 "{}",
5810 String::from_utf8_lossy(name),
5811 );
5812 }
5813 }
5814
5815 #[test]
5816 fn function_list_says_what_every_library_registered() {
5817 let mut f = Fixture::new();
5818 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5819 f.out = Out::new(Proto::Resp3);
5822 let listed = f.run(&[b"FUNCTION", b"LIST"]);
5823 assert!(listed.starts_with("*1\r\n%3\r\n$12\r\nlibrary_name\r\n$5\r\nmylib\r\n"));
5824 assert!(listed.contains("$6\r\nengine\r\n$3\r\nLUA\r\n"));
5825 assert!(listed.contains(
5826 "%3\r\n$4\r\nname\r\n$4\r\nping\r\n\
5827 $11\r\ndescription\r\n$9\r\nsays pong\r\n$5\r\nflags\r\n~1\r\n+no-writes\r\n"
5828 ));
5829 assert!(listed.contains(
5832 "$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"
5833 ));
5834 assert!(!listed.contains("library_code"));
5835 assert!(
5836 f.run(&[b"FUNCTION", b"LIST", b"WITHCODE"])
5837 .contains("library_code")
5838 );
5839 assert!(
5842 f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"MY*"])
5843 .starts_with("*1\r\n")
5844 );
5845 assert_eq!(
5846 f.run(&[b"FUNCTION", b"LIST", b"LIBRARYNAME", b"zz*"]),
5847 "*0\r\n"
5848 );
5849 f.out = Out::new(Proto::Resp2);
5852 assert!(f.run(&[b"FUNCTION", b"LIST"]).starts_with("*1\r\n*6\r\n"));
5853 for (args, want) in [
5854 (&[&b"ZZ"[..]][..], "ERR Unknown argument ZZ"),
5855 (&[b"WITHCODE", b"WITHCODE"], "ERR Unknown argument WITHCODE"),
5856 (
5857 &[b"LIBRARYNAME", b"a", b"LIBRARYNAME", b"b"],
5858 "ERR Unknown argument LIBRARYNAME",
5859 ),
5860 (&[b"LIBRARYNAME"], "ERR library name argument was not given"),
5861 ] {
5862 let mut wire: Vec<&[u8]> = vec![b"FUNCTION", b"LIST"];
5863 wire.extend_from_slice(args);
5864 assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
5865 }
5866 }
5867
5868 #[test]
5869 fn function_stats_counts_what_is_loaded_and_says_nothing_is_running() {
5870 let mut f = Fixture::new();
5871 f.out = Out::new(Proto::Resp3);
5872 assert_eq!(
5873 f.run(&[b"FUNCTION", b"STATS"]),
5874 "%2\r\n$14\r\nrunning_script\r\n_\r\n$7\r\nengines\r\n%1\r\n$3\r\nLUA\r\n\
5875 %2\r\n$15\r\nlibraries_count\r\n:0\r\n$15\r\nfunctions_count\r\n:0\r\n"
5876 );
5877 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5878 assert!(
5879 f.run(&[b"FUNCTION", b"STATS"])
5880 .ends_with("libraries_count\r\n:1\r\n$15\r\nfunctions_count\r\n:5\r\n"),
5881 );
5882 assert_eq!(f.run(&[b"FUNCTION", b"FLUSH"]), "+OK\r\n");
5883 assert!(
5884 f.run(&[b"FUNCTION", b"STATS"])
5885 .ends_with(":0\r\n$15\r\nfunctions_count\r\n:0\r\n")
5886 );
5887 }
5888
5889 #[test]
5890 fn every_function_subcommand_complains_about_its_own_arity() {
5891 let mut f = Fixture::new();
5892 for (args, want) in [
5893 (
5894 &[&b"STATS"[..], b"X"][..],
5895 "ERR wrong number of arguments for 'function|stats' command",
5896 ),
5897 (
5898 &[b"KILL", b"X"],
5899 "ERR wrong number of arguments for 'function|kill' command",
5900 ),
5901 (
5902 &[b"HELP", b"X"],
5903 "ERR wrong number of arguments for 'function|help' command",
5904 ),
5905 (
5906 &[b"DELETE"],
5907 "ERR wrong number of arguments for 'function|delete' command",
5908 ),
5909 (
5910 &[b"DELETE", b"a", b"b"],
5911 "ERR wrong number of arguments for 'function|delete' command",
5912 ),
5913 (
5914 &[b"DUMP", b"X"],
5915 "ERR wrong number of arguments for 'function|dump' command",
5916 ),
5917 (
5918 &[b"RESTORE"],
5919 "ERR wrong number of arguments for 'function|restore' command",
5920 ),
5921 (
5924 &[b"RESTORE", b"a", b"FLUSH", b"X"],
5925 "ERR unknown subcommand or wrong number of arguments for 'RESTORE'. \
5926 Try FUNCTION HELP.",
5927 ),
5928 (
5929 &[b"RESTORE", b"a", b"ZZ"],
5930 "ERR Wrong restore policy given, value should be either FLUSH, APPEND \
5931 or REPLACE.",
5932 ),
5933 (
5936 &[b"FLUSH", b"SYNC", b"X"],
5937 "ERR unknown subcommand or wrong number of arguments for 'FLUSH'. \
5938 Try FUNCTION HELP.",
5939 ),
5940 (
5941 &[b"FLUSH", b"ZZ"],
5942 "ERR FUNCTION FLUSH only supports SYNC|ASYNC option",
5943 ),
5944 (&[b"ZZ"], "ERR unknown subcommand 'ZZ'. Try FUNCTION HELP."),
5945 ] {
5946 let mut wire: Vec<&[u8]> = vec![b"FUNCTION"];
5947 wire.extend_from_slice(args);
5948 assert_eq!(f.run(&wire), format!("-{want}\r\n"), "{args:?}");
5949 }
5950 assert_eq!(
5951 f.run(&[b"FUNCTION"]),
5952 "-ERR wrong number of arguments for 'function' command\r\n"
5953 );
5954 assert_eq!(
5955 f.run(&[b"FUNCTION", b"KILL"]),
5956 "-NOTBUSY No scripts in execution right now.\r\n"
5957 );
5958 }
5959
5960 #[test]
5966 fn a_library_survives_a_dump_and_a_restore() {
5967 let mut f = Fixture::new();
5968 let empty = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
5969 assert_eq!(empty.len(), 10);
5970 assert_eq!(f.run(&[b"FUNCTION", b"RESTORE", &empty]), "+OK\r\n");
5971
5972 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
5973 let full = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
5974 assert!(full.len() > empty.len());
5975
5976 assert_eq!(
5979 f.run(&[b"FUNCTION", b"RESTORE", &full]),
5980 "-ERR Library mylib already exists\r\n"
5981 );
5982 assert_eq!(
5983 f.run(&[b"FUNCTION", b"RESTORE", &full, b"REPLACE"]),
5984 "+OK\r\n"
5985 );
5986 assert_eq!(
5987 f.run(&[b"FUNCTION", b"RESTORE", &full, b"FLUSH"]),
5988 "+OK\r\n"
5989 );
5990 assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
5992
5993 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", OTHER]), "$5\r\nother\r\n");
5996 assert_eq!(
5997 f.run(&[b"FUNCTION", b"RESTORE", &full, b"FLUSH"]),
5998 "+OK\r\n"
5999 );
6000 assert_eq!(
6001 f.run(&[b"FUNCTION", b"DELETE", b"other"]),
6002 "-ERR Library not found\r\n"
6003 );
6004 }
6005
6006 #[test]
6013 fn a_restore_that_fails_changes_nothing() {
6014 let mut f = Fixture::new();
6015 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
6016 let good = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
6017
6018 let reseal = |body: &[u8], version: u16| {
6022 let mut out = body.to_vec();
6023 out.extend_from_slice(&version.to_le_bytes());
6024 let crc = yo_common::crc::crc64(0, &out);
6025 out.extend_from_slice(&crc.to_le_bytes());
6026 out
6027 };
6028 let body = &good[..good.len() - 10];
6029
6030 let mut torn = good.clone();
6031 let n = torn.len();
6032 torn[n - 1] ^= 0xff;
6033 let future = reseal(body, 999);
6034 let mut pre_ga = body.to_vec();
6037 pre_ga[0] = 246;
6038 let pre_ga = reseal(&pre_ga, yo_kv::rdb::VERSION);
6039 let mut other = body.to_vec();
6040 other[0] = 0;
6041 let other = reseal(&other, yo_kv::rdb::VERSION);
6042 let mut cut = body.to_vec();
6044 cut.truncate(body.len() - 1);
6045 let cut = reseal(&cut, yo_kv::rdb::VERSION);
6046
6047 for (bytes, want) in [
6048 (vec![], "ERR DUMP payload version or checksum are wrong"),
6049 (
6050 b"0123456789".to_vec(),
6051 "ERR DUMP payload version or checksum are wrong",
6052 ),
6053 (torn, "ERR DUMP payload version or checksum are wrong"),
6054 (future, "ERR DUMP payload version or checksum are wrong"),
6055 (pre_ga, "ERR Pre-GA function format not supported"),
6056 (other, "ERR given type is not a function"),
6057 (cut, "ERR Failed loading library payload"),
6058 ] {
6059 assert_eq!(
6060 f.run(&[b"FUNCTION", b"RESTORE", &bytes]),
6061 format!("-{want}\r\n")
6062 );
6063 }
6064
6065 assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$4\r\npong\r\n");
6067 let again = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
6068 assert_eq!(again, good);
6069 }
6070
6071 #[test]
6074 fn a_restore_will_not_take_a_function_name_off_a_library_it_keeps() {
6075 let mut f = Fixture::new();
6076 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", LIB]), "$5\r\nmylib\r\n");
6077 let full = payload(&f.raw(&[b"FUNCTION", b"DUMP"]));
6078 let clash =
6080 b"#!lua name=cl\nredis.register_function('ping', function() return 'other' end)"
6081 .as_slice();
6082 assert_eq!(f.run(&[b"FUNCTION", b"FLUSH"]), "+OK\r\n");
6083 assert_eq!(f.run(&[b"FUNCTION", b"LOAD", clash]), "$2\r\ncl\r\n");
6084 assert_eq!(
6085 f.run(&[b"FUNCTION", b"RESTORE", &full, b"REPLACE"]),
6086 "-ERR Function ping already exists\r\n"
6087 );
6088 assert_eq!(f.run(&[b"FCALL", b"ping", b"0"]), "$5\r\nother\r\n");
6090 }
6091
6092 #[test]
6093 fn command_getkeys_reads_the_key_count_out_of_a_script_call() {
6094 let mut f = Fixture::new();
6095 assert_eq!(
6096 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"1", b"k"]),
6097 "*1\r\n$1\r\nk\r\n"
6098 );
6099 assert_eq!(
6100 f.run(&[
6101 b"COMMAND", b"GETKEYS", b"EVALSHA", b"abc", b"2", b"k1", b"k2"
6102 ]),
6103 "*2\r\n$2\r\nk1\r\n$2\r\nk2\r\n"
6104 );
6105 assert_eq!(
6108 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL_RO", b"return 1", b"0", b"a"]),
6109 "*0\r\n"
6110 );
6111 assert_eq!(
6114 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"3", b"k"]),
6115 "*0\r\n"
6116 );
6117 assert_eq!(
6118 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"-1"]),
6119 "*0\r\n"
6120 );
6121 assert_eq!(
6122 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1", b"abc"]),
6123 "*0\r\n"
6124 );
6125 assert_eq!(
6127 f.run(&[b"COMMAND", b"GETKEYS", b"EVAL", b"return 1"]),
6128 "-ERR Invalid number of arguments specified for command\r\n"
6129 );
6130 }
6131
6132 #[test]
6133 fn the_helpers_on_the_redis_table_answer_the_way_they_are_documented() {
6134 let mut f = Fixture::new();
6135 let eval = |f: &mut Fixture, body: &[u8]| f.run(&[b"EVAL", body, b"0"]);
6136
6137 assert_eq!(
6138 eval(&mut f, b"return redis.sha1hex('')"),
6139 "$40\r\nda39a3ee5e6b4b0d3255bfef95601890afd80709\r\n"
6140 );
6141 assert_eq!(
6142 eval(&mut f, b"return redis.sha1hex('return 1')"),
6143 "$40\r\ne0e1f9fabfc9d4800c877a703b823ac0578ff8db\r\n"
6144 );
6145 assert_eq!(
6148 eval(&mut f, b"return redis.error_reply('boom')"),
6149 "-ERR boom\r\n"
6150 );
6151 assert_eq!(
6152 eval(&mut f, b"return redis.error_reply('WRONGTYPE nope')"),
6153 "-WRONGTYPE nope\r\n"
6154 );
6155 assert_eq!(
6156 eval(&mut f, b"return redis.status_reply('fine')"),
6157 "+fine\r\n"
6158 );
6159 assert_eq!(
6162 eval(&mut f, b"return redis.error_reply(1)"),
6163 "-ERR wrong number or type of arguments\r\n"
6164 );
6165 assert_eq!(
6166 eval(&mut f, b"local x = redis.status_reply() return x.err"),
6167 "$37\r\nERR wrong number or type of arguments\r\n"
6168 );
6169
6170 assert_eq!(
6172 eval(
6173 &mut f,
6174 b"return redis.LOG_DEBUG .. redis.LOG_VERBOSE .. redis.LOG_NOTICE .. redis.LOG_WARNING"
6175 ),
6176 "$4\r\n0123\r\n"
6177 );
6178 assert_eq!(
6179 eval(
6180 &mut f,
6181 b"return redis.REPL_NONE .. redis.REPL_AOF .. redis.REPL_SLAVE .. redis.REPL_REPLICA .. redis.REPL_ALL"
6182 ),
6183 "$5\r\n01223\r\n"
6184 );
6185 assert_eq!(eval(&mut f, b"return redis.replicate_commands()"), ":1\r\n");
6187 assert_eq!(
6188 eval(&mut f, b"redis.set_repl(redis.REPL_ALL) return 1"),
6189 ":1\r\n"
6190 );
6191 assert_eq!(
6192 eval(&mut f, b"redis.log(redis.LOG_WARNING, 'x') return 1"),
6193 ":1\r\n"
6194 );
6195 assert_eq!(
6196 eval(&mut f, b"return redis.acl_check_cmd('get', 'k')"),
6197 ":1\r\n"
6198 );
6199 assert!(eval(&mut f, b"redis.setresp(4)").contains("RESP version must be 2 or 3."),);
6201 assert!(eval(&mut f, b"redis.set_repl(9)").contains("Invalid replication flags."));
6202 assert!(
6203 eval(&mut f, b"redis.log('x', 'y')")
6204 .contains("First argument must be a number (log level)."),
6205 );
6206 assert!(
6207 eval(&mut f, b"return redis.acl_check_cmd('nosuchcmd')")
6208 .contains("Invalid command passed to redis.acl_check_cmd()"),
6209 );
6210 assert!(
6211 eval(&mut f, b"return redis.acl_check_cmd('get')")
6212 .contains("Wrong number of args for redis.acl_check_cmd()"),
6213 );
6214 }
6215
6216 #[test]
6217 fn a_counter_is_an_integer_and_not_a_string_of_digits() {
6218 let mut f = Fixture::new();
6219 assert_eq!(f.run(&[b"INCR", b"c"]), ":1\r\n");
6220 assert_eq!(f.run(&[b"INCRBY", b"c", b"41"]), ":42\r\n");
6221 assert_eq!(f.run(&[b"DECRBY", b"c", b"2"]), ":40\r\n");
6222 assert_eq!(f.run(&[b"GET", b"c"]), "$2\r\n40\r\n");
6225 assert_eq!(f.run(&[b"INCRBYFLOAT", b"c", b"0.5"]), "$4\r\n40.5\r\n");
6226 f.run(&[b"SET", b"k", b"hello"]);
6229 assert_eq!(
6230 f.run(&[b"INCR", b"k"]),
6231 "-ERR value is not an integer or out of range\r\n"
6232 );
6233 assert_eq!(
6234 f.run(&[b"INCRBYFLOAT", b"c", b"inf"]),
6235 "-ERR increment would produce NaN or Infinity\r\n"
6236 );
6237 }
6238
6239 #[test]
6244 fn the_newer_commands_reply_in_the_shapes_a_real_server_sends() {
6245 let mut f = Fixture::new();
6246 assert_eq!(f.run(&[b"SET", b"k", b"hello"]), "+OK\r\n");
6247 assert_eq!(f.run(&[b"DIGEST", b"k"]), "$16\r\n9555e8555c62dcfd\r\n");
6250 assert_eq!(f.run(&[b"DIGEST", b"nosuch"]), "$-1\r\n");
6251 assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"1"]), ":1\r\n");
6252 assert_eq!(f.run(&[b"MSETEX", b"1", b"a", b"2", b"NX"]), ":0\r\n");
6253 assert_eq!(f.run(&[b"GET", b"a"]), "$1\r\n1\r\n");
6254 assert_eq!(f.run(&[b"INCREX", b"n"]), "*2\r\n:1\r\n:1\r\n");
6255 assert_eq!(
6256 f.run(&[b"INCREX", b"n", b"BYINT", b"5", b"UBOUND", b"3"]),
6257 "*2\r\n:1\r\n:0\r\n",
6258 "a refused increment reports the value it left alone and applied nothing"
6259 );
6260 assert_eq!(
6261 f.run(&[
6262 b"INCREX",
6263 b"n",
6264 b"BYINT",
6265 b"5",
6266 b"UBOUND",
6267 b"3",
6268 b"SATURATE"
6269 ]),
6270 "*2\r\n:3\r\n:2\r\n"
6271 );
6272 assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"2"]), ":0\r\n");
6273 assert_eq!(f.run(&[b"DELEX", b"a", b"IFEQ", b"1"]), ":1\r\n");
6274 }
6275
6276 #[test]
6277 fn the_same_answers_come_out_in_resp3_spelling() {
6278 let mut f = Fixture::new();
6279 assert!(f.run(&[b"HELLO", b"3"]).starts_with("%7\r\n"));
6280 assert_eq!(f.run(&[b"GET", b"nosuch"]), "_\r\n");
6281 assert_eq!(
6284 f.run(&[b"INCREX", b"c", b"BYFLOAT", b"1.5"]),
6285 "*2\r\n,1.5\r\n,1.5\r\n"
6286 );
6287 assert_eq!(f.run(&[b"INCRBYFLOAT", b"f", b"2.5"]), "$3\r\n2.5\r\n");
6288 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
6291 assert_eq!(f.run(&[b"GET", b"nosuch"]), "$-1\r\n");
6292 }
6293
6294 #[test]
6295 fn a_command_nobody_has_heard_of_is_an_error_and_not_a_closed_socket() {
6296 let mut f = Fixture::new();
6297 let (flow, reply) = f.flow(&[b"NOPE", b"a", b"b"]);
6298 assert_eq!(flow, Flow::Continue);
6299 assert_eq!(
6300 reply,
6301 "-ERR unknown command 'NOPE', with args beginning with: 'a' 'b' \r\n"
6302 );
6303 let reply = f.run(&[b"NO\r\n+PONG\r\nPE"]);
6306 assert_eq!(reply.matches("\r\n").count(), 1);
6307 }
6308
6309 #[test]
6310 fn arity_is_checked_before_the_command_is() {
6311 let mut f = Fixture::new();
6312 assert_eq!(
6313 f.run(&[b"GET"]),
6314 "-ERR wrong number of arguments for 'get' command\r\n"
6315 );
6316 assert_eq!(
6317 f.run(&[b"MSET", b"k"]),
6318 "-ERR wrong number of arguments for 'mset' command\r\n"
6319 );
6320 assert_eq!(
6324 f.run(&[b"PING", b"a", b"b"]),
6325 "-ERR wrong number of arguments for 'ping' command\r\n"
6326 );
6327 assert_eq!(f.run(&[b"PING"]), "+PONG\r\n");
6328 assert_eq!(f.run(&[b"PING", b"hi"]), "$2\r\nhi\r\n");
6329 assert_eq!(
6331 f.run(&[b"DELEX", b"k", b"IFEQ"]),
6332 "-ERR wrong number of arguments for 'delex' command\r\n"
6333 );
6334 }
6335
6336 #[test]
6340 fn the_option_combinations_are_the_ones_a_real_server_accepts() {
6341 let mut f = Fixture::new();
6342 let syntax = "-ERR syntax error\r\n";
6343 assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"XX"]), syntax);
6344 assert_eq!(f.run(&[b"SET", b"k", b"v", b"NX", b"IFEQ", b"a"]), syntax);
6345 assert_eq!(
6346 f.run(&[b"SET", b"k", b"v", b"KEEPTTL", b"EX", b"5"]),
6347 syntax
6348 );
6349 assert_eq!(
6350 f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"PX", b"5"]),
6351 syntax
6352 );
6353 assert_eq!(f.run(&[b"SET", b"k", b"v", b"PERSIST"]), syntax);
6354 assert_eq!(
6356 f.run(&[b"SET", b"k", b"v", b"EX", b"5", b"EX", b"100"]),
6357 "+OK\r\n"
6358 );
6359 assert_eq!(f.run(&[b"SET", b"k", b"v", b"XX", b"XX"]), "+OK\r\n");
6360 assert_eq!(f.run(&[b"SET", b"k", b"v", b"GET", b"GET"]), "$1\r\nv\r\n");
6361 assert_eq!(
6363 f.run(&[b"INCREX", b"n", b"BYINT", b"1", b"BYINT", b"2"]),
6364 syntax
6365 );
6366 assert_eq!(
6367 f.run(&[b"INCREX", b"n", b"ENX"]),
6368 "-ERR ENX flag requires an expiration\r\n"
6369 );
6370 assert_eq!(
6371 f.run(&[b"INCREX", b"n", b"UBOUND", b"abc"]),
6372 "-ERR UBOUND is not an integer or out of range\r\n"
6373 );
6374 assert_eq!(
6375 f.run(&[b"INCREX", b"n", b"LBOUND", b"10", b"UBOUND", b"5"]),
6376 "-ERR LBOUND can't be greater than UBOUND\r\n"
6377 );
6378 assert_eq!(
6379 f.run(&[b"LCS", b"a", b"b", b"LEN", b"IDX"]),
6380 "-ERR If you want both the length and indexes, please just use IDX.\r\n"
6381 );
6382 }
6383
6384 #[test]
6388 fn the_expiry_rules_are_redis_own() {
6389 let mut f = Fixture::new();
6390 let bad = "-ERR invalid expire time in 'set' command\r\n";
6391 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"0"]), bad);
6392 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EX", b"-1"]), bad);
6393 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"0"]), bad);
6394 assert_eq!(
6395 f.run(&[b"SET", b"k", b"v", b"EX", b"9999999999999999"]),
6396 bad
6397 );
6398 assert_eq!(
6399 f.run(&[b"SET", b"k", b"v", b"PX", b"99999999999999999999"]),
6400 "-ERR value is not an integer or out of range\r\n"
6401 );
6402 assert_eq!(
6403 f.run(&[b"SETEX", b"k", b"0", b"v"]),
6404 "-ERR invalid expire time in 'setex' command\r\n"
6405 );
6406 assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"0"]), "$-1\r\n");
6407 assert_eq!(f.run(&[b"GETEX", b"nosuch", b"EX", b"abc"]), "$-1\r\n");
6408 assert_eq!(
6409 f.run(&[b"GETEX", b"nosuch", b"KEEPTTL"]),
6410 "-ERR syntax error\r\n",
6411 "the option list is still checked before the key is looked up"
6412 );
6413 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
6415 assert_eq!(f.run(&[b"SET", b"k", b"v", b"EXAT", b"1"]), "+OK\r\n");
6416 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
6417 }
6418
6419 #[test]
6420 fn mset_takes_its_pairs_from_the_read_buffer() {
6421 let mut f = Fixture::new();
6422 assert_eq!(f.run(&[b"MSET", b"a", b"1", b"b", b"2"]), "+OK\r\n");
6423 assert_eq!(
6424 f.run(&[b"MGET", b"a", b"b", b"nosuch"]),
6425 "*3\r\n$1\r\n1\r\n$1\r\n2\r\n$-1\r\n"
6426 );
6427 assert_eq!(f.run(&[b"MSETNX", b"b", b"9", b"c", b"3"]), ":0\r\n");
6428 assert_eq!(f.run(&[b"MSETNX", b"c", b"3", b"d", b"4"]), ":1\r\n");
6429 assert_eq!(
6430 f.run(&[b"MSETEX", b"2", b"e", b"5"]),
6431 "-ERR wrong number of key-value pairs\r\n"
6432 );
6433 assert_eq!(
6434 f.run(&[b"MSETEX", b"0", b"e", b"5"]),
6435 "-ERR invalid numkeys value\r\n"
6436 );
6437 assert_eq!(
6438 f.run(&[b"MSETEX", b"abc", b"e", b"5"]),
6439 "-ERR invalid numkeys value\r\n"
6440 );
6441 }
6442
6443 #[test]
6444 fn lcs_answers_the_length_the_string_and_the_runs() {
6445 let mut f = Fixture::new();
6446 f.run(&[b"MSET", b"a", b"ohmytext", b"b", b"mynewtext"]);
6447 assert_eq!(f.run(&[b"LCS", b"a", b"b"]), "$6\r\nmytext\r\n");
6448 assert_eq!(f.run(&[b"LCS", b"a", b"b", b"LEN"]), ":6\r\n");
6449 assert_eq!(
6450 f.run(&[b"LCS", b"a", b"b", b"IDX", b"MINMATCHLEN", b"4"]),
6451 "*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"
6452 );
6453 assert_eq!(
6456 f.run(&[b"LCS", b"a", b"b", b"MINMATCHLEN", b"4", b"WITHMATCHLEN"]),
6457 "$6\r\nmytext\r\n"
6458 );
6459 }
6460
6461 #[test]
6462 fn select_moves_the_connection_and_the_databases_stay_apart() {
6463 let mut f = Fixture::new();
6464 f.run(&[b"SET", b"k", b"zero"]);
6465 assert_eq!(f.run(&[b"SELECT", b"4"]), "+OK\r\n");
6466 assert_eq!(f.run(&[b"GET", b"k"]), "$-1\r\n");
6467 f.run(&[b"SET", b"k", b"four"]);
6468 assert_eq!(f.run(&[b"SELECT", b"0"]), "+OK\r\n");
6469 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
6470 assert_eq!(
6471 f.run(&[b"SELECT", b"99"]),
6472 "-ERR DB index is out of range\r\n"
6473 );
6474 assert_eq!(
6475 f.run(&[b"SELECT", b"-1"]),
6476 "-ERR DB index is out of range\r\n"
6477 );
6478 assert_eq!(
6479 f.run(&[b"SELECT", b"abc"]),
6480 "-ERR value is not an integer or out of range\r\n"
6481 );
6482 f.run(&[b"SELECT", b"4"]);
6484 f.run(&[b"RESET"]);
6485 assert_eq!(f.run(&[b"GET", b"k"]), "$4\r\nzero\r\n");
6486 }
6487
6488 #[test]
6489 fn hello_agrees_on_a_protocol_and_refuses_the_ones_that_do_not_exist() {
6490 let mut f = Fixture::new();
6491 let reply = f.run(&[b"HELLO"]);
6492 assert!(reply.starts_with("*14\r\n"), "{reply}");
6493 assert!(reply.contains("$5\r\nredis\r\n"), "{reply}");
6494 assert!(reply.contains("$5\r\n8.8.0\r\n"), "{reply}");
6495 assert!(
6496 reply.contains(":7\r\n"),
6497 "the connection id is in there: {reply}"
6498 );
6499 assert_eq!(
6500 f.run(&[b"HELLO", b"4"]),
6501 "-NOPROTO unsupported protocol version\r\n"
6502 );
6503 assert_eq!(
6504 f.run(&[b"HELLO", b"abc"]),
6505 "-ERR Protocol version is not an integer or out of range\r\n"
6506 );
6507 assert_eq!(
6508 f.run(&[b"HELLO", b"3", b"SETNAME"]),
6509 "-ERR Syntax error in HELLO option 'SETNAME'\r\n"
6510 );
6511 assert!(
6512 f.run(&[b"HELLO", b"3", b"SETNAME", b"bob"])
6513 .starts_with("%7\r\n")
6514 );
6515 assert_eq!(f.session.name(), b"bob");
6516 f.run(&[b"RESET"]);
6517 assert_eq!(f.session.name(), b"");
6518 }
6519
6520 #[test]
6521 fn command_describes_this_server_in_the_shape_a_driver_reads() {
6522 let mut f = Fixture::new();
6523 let count = format!(":{}\r\n", COMMANDS.len());
6524 assert_eq!(f.run(&[b"COMMAND", b"COUNT"]), count);
6525 let info = f.run(&[b"COMMAND", b"INFO", b"get"]);
6526 assert_eq!(
6527 info,
6528 "*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\
6529 *3\r\n+@read\r\n+@string\r\n+@fast\r\n*0\r\n*0\r\n*0\r\n"
6530 );
6531 assert_eq!(f.run(&[b"COMMAND", b"INFO", b"nosuch"]), "*1\r\n$-1\r\n");
6533 assert_eq!(
6534 f.run(&[b"COMMAND", b"LIST", b"FILTERBY", b"PATTERN", b"getr*"]),
6535 "*1\r\n$8\r\ngetrange\r\n"
6536 );
6537 assert_eq!(
6538 f.run(&[b"COMMAND", b"NOPE"]),
6539 "-ERR unknown subcommand 'NOPE'. Try COMMAND HELP.\r\n"
6540 );
6541 }
6542
6543 #[test]
6547 fn command_getkeys_finds_the_keys_including_the_hidden_ones() {
6548 let mut f = Fixture::new();
6549 assert_eq!(
6550 f.run(&[b"COMMAND", b"GETKEYS", b"get", b"k"]),
6551 "*1\r\n$1\r\nk\r\n"
6552 );
6553 assert_eq!(
6554 f.run(&[b"COMMAND", b"GETKEYS", b"mset", b"a", b"1", b"b", b"2"]),
6555 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
6556 );
6557 assert_eq!(
6558 f.run(&[
6559 b"COMMAND", b"GETKEYS", b"msetex", b"2", b"a", b"1", b"b", b"2"
6560 ]),
6561 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
6562 );
6563 assert_eq!(
6564 f.run(&[b"COMMAND", b"GETKEYS", b"ping"]),
6565 "-ERR The command has no key arguments\r\n"
6566 );
6567 assert_eq!(
6568 f.run(&[b"COMMAND", b"GETKEYS", b"set"]),
6569 "-ERR Invalid number of arguments specified for command\r\n"
6570 );
6571 }
6572
6573 #[test]
6574 fn config_answers_what_it_can_and_refuses_what_it_cannot() {
6575 let mut f = Fixture::new();
6576 assert_eq!(
6577 f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
6578 "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n"
6579 );
6580 let both = f.run(&[b"CONFIG", b"GET", b"maxmemory*", b"maxmemory"]);
6583 assert!(both.starts_with("*6\r\n"), "{both}");
6584 assert_eq!(f.run(&[b"CONFIG", b"GET", b"nosuch"]), "*0\r\n");
6585 assert_eq!(f.run(&[b"CONFIG", b"SET", b"appendonly", b"no"]), "+OK\r\n");
6586 assert_eq!(
6587 f.run(&[b"CONFIG", b"SET", b"appendonly", b"yes"]),
6588 "-ERR CONFIG SET failed (possibly related to argument 'appendonly') - can't set immutable config\r\n"
6589 );
6590 assert_eq!(
6591 f.run(&[b"CONFIG", b"SET", b"nosuch", b"1"]),
6592 "-ERR Unknown option or number of arguments for CONFIG SET - 'nosuch'\r\n"
6593 );
6594 assert_eq!(
6595 f.run(&[b"CONFIG", b"GET"]),
6596 "-ERR wrong number of arguments for 'config|get' command\r\n"
6597 );
6598 assert_eq!(
6602 f.run(&[b"CONFIG", b"SET", b"appendonly"]),
6603 "-ERR wrong number of arguments for 'config|set' command\r\n"
6604 );
6605 assert_eq!(
6606 f.run(&[b"CONFIG", b"SET", b"appendonly", b"no", b"maxmemory"]),
6607 "-ERR syntax error\r\n"
6608 );
6609 assert_eq!(f.run(&[b"CONFIG", b"RESETSTAT"]), "+OK\r\n");
6610 assert_eq!(
6611 f.run(&[b"CONFIG", b"REWRITE"]),
6612 "-ERR The server is running without a config file\r\n"
6613 );
6614 }
6615
6616 #[test]
6617 fn the_eviction_policy_reads_back_what_was_written_to_it() {
6618 let mut f = Fixture::new();
6619 assert_eq!(
6620 f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
6621 "*2\r\n$16\r\nmaxmemory-policy\r\n$10\r\nnoeviction\r\n"
6622 );
6623 assert_eq!(
6624 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"AllKeys-LFU"]),
6625 "+OK\r\n",
6626 "the name is matched without regard to case, like every other one"
6627 );
6628 assert_eq!(
6629 f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
6630 "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
6631 );
6632 assert!(
6634 f.run(&[b"INFO", b"memory"])
6635 .contains("maxmemory_policy:allkeys-lfu"),
6636 "INFO and CONFIG disagree about the policy"
6637 );
6638 assert_eq!(
6642 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"garbage"]),
6643 "-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"
6644 );
6645 assert_eq!(
6648 f.run(&[b"CONFIG", b"GET", b"maxmemory-policy"]),
6649 "*2\r\n$16\r\nmaxmemory-policy\r\n$11\r\nallkeys-lfu\r\n"
6650 );
6651 f.run(&[
6652 b"CONFIG",
6653 b"SET",
6654 b"hash-max-listpack-entries",
6655 b"7",
6656 b"maxmemory-policy",
6657 b"nonsense",
6658 ]);
6659 assert_eq!(
6660 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
6661 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n"
6662 );
6663 }
6664
6665 #[test]
6666 fn the_three_eviction_numbers_read_back_too() {
6667 let mut f = Fixture::new();
6668 for (name, default, set) in [
6669 ("maxmemory-samples", "5", "12"),
6670 ("lfu-log-factor", "10", "3"),
6671 ("lfu-decay-time", "1", "60"),
6672 ] {
6673 let get = || {
6674 format!(
6675 "*2\r\n${}\r\n{name}\r\n${}\r\n{default}\r\n",
6676 name.len(),
6677 default.len()
6678 )
6679 };
6680 assert_eq!(f.run(&[b"CONFIG", b"GET", name.as_bytes()]), get());
6681 assert_eq!(
6682 f.run(&[b"CONFIG", b"SET", name.as_bytes(), set.as_bytes()]),
6683 "+OK\r\n"
6684 );
6685 assert_eq!(
6686 f.run(&[b"CONFIG", b"GET", name.as_bytes()]),
6687 format!(
6688 "*2\r\n${}\r\n{name}\r\n${}\r\n{set}\r\n",
6689 name.len(),
6690 set.len()
6691 )
6692 );
6693 assert_eq!(
6696 f.run(&[b"CONFIG", b"SET", name.as_bytes(), b"soon"]),
6697 format!(
6698 "-ERR CONFIG SET failed (possibly related to argument '{name}') - argument couldn't be parsed into an integer\r\n"
6699 )
6700 );
6701 }
6702 }
6703
6704 #[test]
6705 fn the_memory_limit_reads_back_in_bytes_whatever_the_unit_was() {
6706 let mut f = Fixture::new();
6707 assert_eq!(
6708 f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
6709 "*2\r\n$9\r\nmaxmemory\r\n$1\r\n0\r\n",
6710 "no limit is the default"
6711 );
6712 for (typed, bytes) in [
6715 (&b"1024"[..], "1024"),
6716 (b"1k", "1000"),
6717 (b"1kb", "1024"),
6718 (b"1M", "1000000"),
6719 (b"1Mb", "1048576"),
6720 (b"1gb", "1073741824"),
6721 (b"100mb", "104857600"),
6722 ] {
6723 assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxmemory", typed]), "+OK\r\n");
6724 assert_eq!(
6725 f.run(&[b"CONFIG", b"GET", b"maxmemory"]),
6726 format!("*2\r\n$9\r\nmaxmemory\r\n${}\r\n{bytes}\r\n", bytes.len()),
6727 "set {}",
6728 String::from_utf8_lossy(typed)
6729 );
6730 }
6731 assert!(
6732 f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
6733 "the report agrees with the setting"
6734 );
6735
6736 for bad in [&b"1tb"[..], b"-1", b"", b"lots"] {
6739 assert_eq!(
6740 f.run(&[b"CONFIG", b"SET", b"maxmemory", bad]),
6741 "-ERR CONFIG SET failed (possibly related to argument 'maxmemory') - argument must be a memory value\r\n",
6742 "refused {}",
6743 String::from_utf8_lossy(bad)
6744 );
6745 }
6746 assert!(
6747 f.run(&[b"INFO", b"memory"]).contains("maxmemory:104857600"),
6748 "and the refusal left the old one alone"
6749 );
6750 }
6751
6752 #[test]
6753 fn a_write_is_refused_when_there_is_no_room_and_nothing_to_evict() {
6754 let mut f = Fixture::new();
6755 f.run(&[b"SET", b"here", b"already"]);
6756 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1"]);
6760 assert_eq!(
6761 f.run(&[b"SET", b"k", b"v"]),
6762 "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
6763 );
6764 assert_eq!(
6765 f.run(&[b"LPUSH", b"l", b"v"]),
6766 "-OOM command not allowed when used memory > 'maxmemory'.\r\n"
6767 );
6768 assert_eq!(f.run(&[b"GET", b"here"]), "$7\r\nalready\r\n");
6770 assert_eq!(f.run(&[b"DEL", b"here"]), ":1\r\n");
6771 assert!(f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"));
6772
6773 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
6775 assert_eq!(f.run(&[b"SET", b"k", b"v"]), "+OK\r\n");
6776 }
6777
6778 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
6782 #[test]
6783 fn an_allkeys_policy_makes_room_instead_of_refusing() {
6784 let mut f = Fixture::new();
6785 let val = vec![b'v'; 256];
6786 for i in 0..24000u32 {
6787 let k = format!("key:{i:08}");
6788 f.run(&[b"SET", k.as_bytes(), &val]);
6789 }
6790 let full = f.server.memory_bytes();
6791 assert!(
6792 full > 3 * 1024 * 1024,
6793 "the arena is several segments: {full}"
6794 );
6795
6796 let limit = full - 2 * 1024 * 1024;
6800 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
6801 f.run(&[
6802 b"CONFIG",
6803 b"SET",
6804 b"maxmemory",
6805 limit.to_string().as_bytes(),
6806 ]);
6807
6808 for i in 0..2000u32 {
6812 let k = format!("new:{i:08}");
6813 assert_eq!(
6814 f.run(&[b"SET", k.as_bytes(), &val]),
6815 "+OK\r\n",
6816 "write {i} was refused"
6817 );
6818 f.server.refresh_memory();
6819 if f.server.memory_bytes() <= limit {
6820 break;
6821 }
6822 }
6823 assert!(
6824 f.server.memory_bytes() <= limit,
6825 "it never got under: {} against {limit}",
6826 f.server.memory_bytes()
6827 );
6828 let info = f.run(&[b"INFO", b"stats"]);
6829 assert!(!info.contains("evicted_keys:0"), "{info}");
6830 assert!(
6831 f.run(&[b"DBSIZE"]) != ":0\r\n",
6832 "and it did not empty the database to get there"
6833 );
6834 }
6835
6836 #[cfg_attr(miri, ignore = "an hour of commands, and they cannot come down")]
6847 #[test]
6848 fn the_running_total_and_the_walk_agree_on_a_mixed_keyspace() {
6849 let mut f = Fixture::new();
6856 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
6857 let big = vec![b'v'; 200];
6858
6859 for i in 0..400u32 {
6860 let n = i.to_string();
6861 let n = n.as_bytes();
6862 f.run(&[b"SADD", b"s", n]);
6863 f.run(&[b"SADD", b"s2", &big]);
6864 f.run(&[b"HSET", b"h", n, &big]);
6865 f.run(&[b"RPUSH", b"l", &big]);
6866 f.run(&[b"ZADD", b"z", n, n]);
6867 f.run(&[b"ARSET", b"a", n, &big]);
6868 if i % 7 == 0 {
6869 f.run(&[b"SREM", b"s", n]);
6870 f.run(&[b"HDEL", b"h", n]);
6871 f.run(&[b"LPOP", b"l"]);
6872 f.run(&[b"ZREM", b"z", n]);
6873 f.run(&[b"ARDEL", b"a", n]);
6874 }
6875 if i % 53 == 0 {
6876 f.run(&[b"DEL", b"s2"]);
6879 }
6880 assert_eq!(
6881 f.server.settled_memory(),
6882 f.server.memory_bytes(),
6883 "after round {i}"
6884 );
6885 }
6886
6887 assert_eq!(f.run(&[b"DBSIZE"]), ":6\r\n");
6890 assert!(
6891 f.server.memory_bytes() > 512 * 1024,
6892 "{}",
6893 f.server.memory_bytes()
6894 );
6895
6896 f.run(&[b"FLUSHALL"]);
6898 assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
6899 }
6900
6901 #[test]
6902 fn taking_the_limit_away_stops_the_counting_and_putting_it_back_starts_again() {
6903 let mut f = Fixture::new();
6908 for i in 0..200u32 {
6909 let n = i.to_string();
6910 f.run(&[b"SADD", b"s", n.as_bytes()]);
6911 f.run(&[b"HSET", b"h", n.as_bytes(), b"value"]);
6912 }
6913 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
6914 assert_eq!(f.server.settled_memory(), f.server.memory_bytes());
6915
6916 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"0"]);
6917 for i in 200..400u32 {
6918 let n = i.to_string();
6919 f.run(&[b"SADD", b"s", n.as_bytes()]);
6920 }
6921 f.run(&[b"CONFIG", b"SET", b"maxmemory", b"1gb"]);
6922 assert_eq!(
6923 f.server.settled_memory(),
6924 f.server.memory_bytes(),
6925 "the writes it was not watching are in the number it started from"
6926 );
6927 }
6928
6929 #[test]
6930 fn evicted_keys_and_expired_keys_are_different_numbers() {
6931 let mut f = Fixture::new();
6932 f.run(&[b"SET", b"gone", b"v", b"PX", b"1"]);
6935 f.server.advance_clock_ms(20);
6936 f.run(&[b"GET", b"gone"]);
6937 let info = f.run(&[b"INFO", b"stats"]);
6938 assert!(info.contains("expired_keys:1"), "{info}");
6939 assert!(info.contains("evicted_keys:0"), "{info}");
6940 }
6941
6942 #[test]
6943 fn the_object_subcommands_follow_the_policy() {
6944 let mut f = Fixture::new();
6945 f.run(&[b"SET", b"s", b"v"]);
6946 assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
6950 assert!(
6951 f.run(&[b"OBJECT", b"FREQ", b"s"])
6952 .starts_with("-ERR An LFU maxmemory policy is not selected"),
6953 );
6954
6955 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lfu"]);
6956 assert!(
6957 f.run(&[b"OBJECT", b"IDLETIME", b"s"])
6958 .starts_with("-ERR An LFU maxmemory policy is selected"),
6959 );
6960 assert!(
6965 f.run(&[b"OBJECT", b"FREQ", b"s"]).starts_with(':'),
6966 "FREQ should answer under an LFU policy"
6967 );
6968 }
6969
6970 #[test]
6971 fn object_says_which_rung_of_the_ladder_a_key_is_on() {
6972 let mut f = Fixture::new();
6973 f.run(&[b"SET", b"s", b"hello"]);
6974 f.run(&[b"SET", b"n", b"123"]);
6975 f.run(&[b"SADD", b"si", b"1", b"2", b"3"]);
6976 f.run(&[b"SADD", b"ss", b"a", b"b"]);
6977 f.run(&[b"HSET", b"h", b"f", b"v"]);
6978 for (key, want) in [
6979 (b"s".as_slice(), "embstr"),
6980 (b"n", "int"),
6981 (b"si", "intset"),
6982 (b"ss", "listpack"),
6983 (b"h", "listpack"),
6984 ] {
6985 let reply = f.run(&[b"OBJECT", b"ENCODING", key]);
6986 assert_eq!(reply, format!("${}\r\n{want}\r\n", want.len()));
6987 }
6988
6989 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"f"]);
6992 assert_eq!(
6993 f.run(&[b"OBJECT", b"ENCODING", b"h"]),
6994 "$10\r\nlistpackex\r\n"
6995 );
6996
6997 assert_eq!(f.run(&[b"OBJECT", b"REFCOUNT", b"s"]), ":1\r\n");
6998 assert_eq!(f.run(&[b"OBJECT", b"IDLETIME", b"s"]), ":0\r\n");
6999 assert!(f.run(&[b"OBJECT", b"HELP"]).starts_with("*14\r\n+OBJECT "));
7000 }
7001
7002 #[test]
7003 fn object_answers_nil_for_a_key_that_is_not_there() {
7004 let mut f = Fixture::new();
7005 for sub in [b"ENCODING".as_slice(), b"REFCOUNT", b"IDLETIME", b"FREQ"] {
7006 assert_eq!(
7007 f.run(&[b"OBJECT", sub, b"nokey"]),
7008 "$-1\r\n",
7009 "a nil and not an error, which is what 8.10.1 does"
7010 );
7011 }
7012 f.run(&[b"SET", b"s", b"v"]);
7015 assert!(
7016 f.run(&[b"OBJECT", b"FREQ", b"s"])
7017 .starts_with("-ERR An LFU maxmemory policy is not"),
7018 );
7019 assert_eq!(
7020 f.run(&[b"OBJECT", b"NOPE", b"s"]),
7021 "-ERR unknown subcommand 'NOPE'. Try OBJECT HELP.\r\n"
7022 );
7023 assert_eq!(
7024 f.run(&[b"OBJECT", b"ENCODING"]),
7025 "-ERR wrong number of arguments for 'object|encoding' command\r\n"
7026 );
7027 assert_eq!(
7028 f.run(&[b"OBJECT", b"ENCODING", b"s", b"extra"]),
7029 "-ERR wrong number of arguments for 'object|encoding' command\r\n"
7030 );
7031 assert_eq!(
7032 f.run(&[b"OBJECT"]),
7033 "-ERR wrong number of arguments for 'object' command\r\n"
7034 );
7035 }
7036
7037 #[test]
7038 fn config_moves_the_ladder_and_object_encoding_agrees() {
7039 let mut f = Fixture::new();
7040 assert_eq!(
7041 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
7042 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
7043 "512 and not the 128 everyone remembers, which is what 8.10.1 says"
7044 );
7045 assert_eq!(
7048 f.run(&[b"CONFIG", b"GET", b"hash-max-ziplist-entries"]),
7049 "*2\r\n$24\r\nhash-max-ziplist-entries\r\n$3\r\n512\r\n"
7050 );
7051 assert!(
7052 f.run(&[b"CONFIG", b"GET", b"hash-max-*"])
7053 .starts_with("*8\r\n")
7054 );
7055 assert!(
7056 f.run(&[b"CONFIG", b"GET", b"set-max-*"])
7057 .starts_with("*6\r\n")
7058 );
7059
7060 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2", b"c", b"3"]);
7061 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$8\r\nlistpack\r\n");
7062
7063 assert_eq!(
7064 f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"2"]),
7065 "+OK\r\n",
7066 "written under the old name and read back under the new one"
7067 );
7068 assert_eq!(
7069 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
7070 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n"
7071 );
7072 assert_eq!(
7073 f.run(&[b"OBJECT", b"ENCODING", b"h"]),
7074 "$8\r\nlistpack\r\n",
7075 "the hash that already exists is left exactly where it was"
7076 );
7077 f.run(&[b"HSET", b"h2", b"a", b"1", b"b", b"2", b"c", b"3"]);
7078 assert_eq!(
7079 f.run(&[b"OBJECT", b"ENCODING", b"h2"]),
7080 "$9\r\nhashtable\r\n",
7081 "and the next one built goes straight to a table"
7082 );
7083
7084 f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"2"]);
7086 f.run(&[b"SADD", b"s", b"1", b"2", b"3"]);
7087 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$8\r\nlistpack\r\n");
7088 f.run(&[b"CONFIG", b"SET", b"set-max-listpack-value", b"2"]);
7089 f.run(&[b"SADD", b"s2", b"abcdefgh"]);
7090 assert_eq!(
7091 f.run(&[b"OBJECT", b"ENCODING", b"s2"]),
7092 "$9\r\nhashtable\r\n"
7093 );
7094 }
7095
7096 #[test]
7097 fn config_set_takes_all_of_the_ladder_or_none_of_it() {
7098 let mut f = Fixture::new();
7099 assert_eq!(
7100 f.run(&[
7101 b"CONFIG",
7102 b"SET",
7103 b"hash-max-listpack-entries",
7104 b"7",
7105 b"set-max-listpack-entries",
7106 b"abc"
7107 ]),
7108 "-ERR CONFIG SET failed (possibly related to argument 'set-max-listpack-entries') - argument couldn't be parsed into an integer\r\n"
7109 );
7110 assert_eq!(
7111 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
7112 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$3\r\n512\r\n",
7113 "the pair in front of the bad one did not go in"
7114 );
7115 assert_eq!(
7118 f.run(&[b"CONFIG", b"SET", b"hash-max-ziplist-entries", b"abc"]),
7119 "-ERR CONFIG SET failed (possibly related to argument 'hash-max-ziplist-entries') - argument couldn't be parsed into an integer\r\n"
7120 );
7121 assert_eq!(
7122 f.run(&[b"CONFIG", b"SET", b"set-max-intset-entries", b"-1"]),
7123 "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument must be between 0 and 9223372036854775807 inclusive\r\n"
7124 );
7125 assert_eq!(
7128 f.run(&[
7129 b"CONFIG",
7130 b"SET",
7131 b"set-max-intset-entries",
7132 b"99999999999999999999"
7133 ]),
7134 "-ERR CONFIG SET failed (possibly related to argument 'set-max-intset-entries') - argument couldn't be parsed into an integer\r\n"
7135 );
7136 assert_eq!(
7137 f.run(&[
7138 b"CONFIG",
7139 b"SET",
7140 b"set-max-intset-entries",
7141 b"9223372036854775807"
7142 ]),
7143 "+OK\r\n"
7144 );
7145 }
7146
7147 #[test]
7148 fn a_setting_moved_on_one_database_moved_on_all_of_them() {
7149 let mut f = Fixture::new();
7150 f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"1"]);
7151 f.run(&[b"SELECT", b"3"]);
7152 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
7153 assert_eq!(
7154 f.run(&[b"OBJECT", b"ENCODING", b"h"]),
7155 "$9\r\nhashtable\r\n",
7156 "these are one server wide number in Redis, whatever a Keyspace carries"
7157 );
7158 }
7159
7160 #[test]
7161 fn info_reports_the_numbers_it_can_stand_behind() {
7162 let mut f = Fixture::new();
7163 f.run(&[b"MSET", b"a", b"1", b"b", b"2"]);
7164 let all = f.run(&[b"INFO"]);
7165 assert!(all.contains("redis_version:8.8.0"), "{all}");
7166 assert!(
7167 all.contains(concat!("yo_version:", env!("CARGO_PKG_VERSION"))),
7168 "{all}"
7169 );
7170 assert!(all.contains("db0:keys=2,expires=0,avg_ttl=0"), "{all}");
7171 assert!(all.contains("role:master"), "{all}");
7172 let clients = f.run(&[b"INFO", b"clients"]);
7174 assert!(clients.contains("connected_clients:0"), "{clients}");
7175 assert!(!clients.contains("redis_version"), "{clients}");
7176 assert_eq!(f.run(&[b"INFO", b"nosuch"]), "$0\r\n\r\n");
7177 }
7178
7179 #[test]
7186 fn commandstats_is_asked_for_and_replication_is_not() {
7187 let mut f = Fixture::new();
7188 for arg in ["", "all", "default", "everything"] {
7189 let info = if arg.is_empty() {
7190 f.run(&[b"INFO"])
7191 } else {
7192 f.run(&[b"INFO", arg.as_bytes()])
7193 };
7194 assert!(info.contains("redis_version"), "{arg}: {info}");
7195 assert!(info.contains("used_cpu_user"), "{arg}: {info}");
7196 assert!(info.contains("used_memory"), "{arg}: {info}");
7197 assert!(!info.contains("sentinel_tilt"), "{arg}: {info}");
7198 let asked = arg == "all" || arg == "everything";
7199 assert_eq!(
7200 info.contains("rejected_calls"),
7201 asked,
7202 "{arg} should{} carry the command counters: {info}",
7203 if asked { "" } else { " not" }
7204 );
7205 }
7206
7207 let cpu = f.run(&[b"INFO", b"cpu"]);
7208 assert!(cpu.contains("used_cpu_user"), "{cpu}");
7209 assert!(!cpu.contains("used_memory"), "{cpu}");
7210
7211 let stats = f.run(&[b"INFO", b"commandSTATS"]);
7214 assert!(!stats.contains("used_memory"), "{stats}");
7215 assert!(stats.contains("rejected_calls"), "{stats}");
7216
7217 let pair = f.run(&[b"INFO", b"cpu", b"sentinel"]);
7219 assert!(pair.contains("used_cpu_user"), "{pair}");
7220 assert!(!pair.contains("master_repl_offset"), "{pair}");
7221
7222 let with_all = f.run(&[b"INFO", b"cpu", b"all"]);
7223 assert!(with_all.contains("used_memory"), "{with_all}");
7224 assert!(with_all.contains("master_repl_offset"), "{with_all}");
7225 assert!(with_all.contains("rejected_calls"), "{with_all}");
7226 assert_eq!(
7228 with_all.matches("used_cpu_user_children").count(),
7229 1,
7230 "{with_all}"
7231 );
7232
7233 let with_default = f.run(&[b"INFO", b"cpu", b"default"]);
7234 assert!(with_default.contains("used_memory"), "{with_default}");
7235 assert!(
7236 with_default.contains("master_repl_offset"),
7237 "{with_default}"
7238 );
7239 assert!(!with_default.contains("rejected_calls"), "{with_default}");
7240 assert_eq!(
7241 with_default.matches("used_cpu_user_children").count(),
7242 1,
7243 "{with_default}"
7244 );
7245 }
7246
7247 #[test]
7256 fn info_memory_reports_the_cap_and_the_quarter_of_it_that_gets_used() {
7257 let mut f = Fixture::new();
7258 let info = f.run(&[b"INFO", b"memory"]);
7259 for field in [
7260 "total_system_memory:",
7261 "mem_cgroup_limit:",
7262 "mem_limit:",
7263 "mem_budget:",
7264 ] {
7265 assert!(info.contains(field), "no {field} in {info}");
7266 }
7267
7268 let field = |name: &str| -> u64 {
7269 info.lines()
7270 .find_map(|l| l.strip_prefix(name))
7271 .unwrap_or_else(|| panic!("no {name} in {info}"))
7272 .trim()
7273 .parse()
7274 .unwrap_or_else(|_| panic!("{name} is not a number in {info}"))
7275 };
7276 let limit = field("mem_limit:");
7277 assert_eq!(field("mem_budget:"), limit / 4, "{info}");
7278 if limit != 0 {
7281 let host = field("total_system_memory:");
7282 let cgroup = field("mem_cgroup_limit:");
7283 assert!(
7284 limit == host || limit == cgroup,
7285 "the limit came from neither number: {info}"
7286 );
7287 }
7288 }
7289
7290 #[test]
7299 fn a_command_counts_what_it_did_separately_from_what_it_refused() {
7300 let mut f = Fixture::new();
7301 f.run(&[b"SET", b"k", b"v"]);
7302 f.run(&[b"SET", b"k", b"w"]);
7303 f.run(&[b"LPUSH", b"k", b"x"]);
7305 f.run(&[b"LPUSH", b"k"]);
7307
7308 let stats = f.run(&[b"INFO", b"commandstats"]);
7309 assert!(
7310 stats.contains("cmdstat_set:calls=2,rejected_calls=0,failed_calls=0"),
7311 "{stats}"
7312 );
7313 assert!(
7314 stats.contains("cmdstat_lpush:calls=1,rejected_calls=1,failed_calls=1"),
7315 "{stats}"
7316 );
7317 assert!(
7318 !stats.contains("cmdstat_zadd"),
7319 "a command nobody has sent has no row: {stats}"
7320 );
7321 }
7322
7323 #[test]
7327 fn the_active_sweep_reclaims_keys_no_client_comes_back_for() {
7328 let (dead, live) = if cfg!(miri) {
7335 (375, 125)
7336 } else {
7337 (3_000, 1_000)
7338 };
7339 let mut f = Fixture::new();
7340 for i in 0..dead {
7341 f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
7342 }
7343 for i in 0..live {
7344 f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
7345 }
7346 let all = format!(":{}\r\n", dead + live);
7347 assert_eq!(f.run(&[b"DBSIZE"]), all);
7348 f.advance(100);
7349 assert_eq!(
7350 f.run(&[b"DBSIZE"]),
7351 all,
7352 "DBSIZE counts records and nothing has read past the dead ones yet"
7353 );
7354
7355 let rest = format!(":{live}\r\n");
7357 let mut spent = 0;
7358 for _ in 0..2_000 {
7359 spent += f.server.expire_step(4096);
7360 if f.run(&[b"DBSIZE"]) == rest {
7361 break;
7362 }
7363 }
7364 assert_eq!(f.run(&[b"DBSIZE"]), rest, "spent {spent} looks");
7365 assert!(
7366 f.run(&[b"INFO", b"stats"])
7367 .contains(&format!("expired_keys:{dead}"))
7368 );
7369 for i in 0..live {
7370 assert_eq!(
7371 f.run(&[b"GET", format!("k{i}").as_bytes()]),
7372 "$1\r\nv\r\n",
7373 "it took a key that had no deadline"
7374 );
7375 }
7376 }
7377
7378 #[test]
7379 fn a_sweep_of_a_server_with_no_deadlines_anywhere_costs_nothing() {
7380 let n = if cfg!(miri) { 200 } else { 2_000 };
7384 let mut f = Fixture::new();
7385 for i in 0..n {
7386 f.run(&[b"SET", format!("k{i}").as_bytes(), b"v"]);
7387 }
7388 assert_eq!(f.server.expire_step(4096), 0);
7389 f.run(&[b"SELECT", b"3"]);
7391 f.run(&[b"SET", b"x", b"v", b"PX", b"50"]);
7392 f.advance(100);
7393 for _ in 0..64 {
7394 f.server.expire_step(4096);
7395 }
7396 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
7397 f.run(&[b"SELECT", b"0"]);
7398 assert_eq!(f.run(&[b"DBSIZE"]), format!(":{n}\r\n"));
7399 assert_eq!(f.server.expire_step(4096), 0, "and it is quiet again");
7400 }
7401
7402 #[test]
7405 fn the_sweep_the_loop_calls_runs_at_most_once_a_millisecond() {
7406 let mut f = Fixture::new();
7407 for i in 0..500u32 {
7408 f.run(&[b"SET", format!("d{i}").as_bytes(), b"v", b"PX", b"50"]);
7409 }
7410 f.advance(100);
7411 let at = f.server.striped(0).now_ms();
7412 f.server.set_clock_ms(at);
7413 assert!(f.server.expire_slice(8) > 0, "the first one works");
7417 for _ in 0..1_000 {
7418 assert_eq!(
7419 f.server.expire_slice(8),
7420 0,
7421 "the millisecond has not moved and neither should this"
7422 );
7423 }
7424 assert!(
7425 f.server.striped(0).expires() > 400,
7426 "there is plenty left to take"
7427 );
7428 f.server.set_clock_ms(at + 1);
7429 assert!(f.server.expire_slice(8) > 0, "and then it goes again");
7430 }
7431
7432 #[test]
7435 fn info_keyspace_counts_the_keys_that_have_a_deadline() {
7436 let mut f = Fixture::new();
7437 f.run(&[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"]);
7438 assert!(
7439 f.run(&[b"INFO", b"keyspace"])
7440 .contains("db0:keys=3,expires=0"),
7441 "none of them has one yet"
7442 );
7443 f.run(&[b"EXPIRE", b"a", b"1000"]);
7444 f.run(&[b"EXPIRE", b"b", b"1000"]);
7445 let two = f.run(&[b"INFO", b"keyspace"]);
7446 assert!(two.contains("db0:keys=3,expires=2"), "{two}");
7447 f.run(&[b"PERSIST", b"a"]);
7448 f.run(&[b"DEL", b"b"]);
7449 let none = f.run(&[b"INFO", b"keyspace"]);
7450 assert!(none.contains("db0:keys=2,expires=0"), "{none}");
7451
7452 f.run(&[b"SELECT", b"1"]);
7454 f.run(&[b"SET", b"x", b"1", b"EX", b"1000"]);
7455 let both = f.run(&[b"INFO", b"keyspace"]);
7456 assert!(both.contains("db0:keys=2,expires=0"), "{both}");
7457 assert!(both.contains("db1:keys=1,expires=1"), "{both}");
7458 }
7459
7460 #[cfg(unix)]
7467 #[cfg_attr(miri, ignore = "no getrusage under Miri, so the number is fixed")]
7468 #[test]
7469 fn info_cpu_reports_processor_time_that_was_really_measured() {
7470 let mut f = Fixture::new();
7471 let cpu = f.run(&[b"INFO", b"cpu"]);
7472 assert!(cpu.contains("# CPU"), "{cpu}");
7473 assert!(cpu.contains("used_cpu_user:"), "{cpu}");
7475 assert!(cpu.contains("used_cpu_sys:"), "{cpu}");
7476 assert!(cpu.contains("used_cpu_user_children:0.000000"), "{cpu}");
7477 assert!(!cpu.contains("redis_version"), "{cpu}");
7478
7479 let before = used_cpu_user(&cpu);
7483 let mut n = 0u64;
7484 let mut rounds = 0;
7485 while used_cpu_user(&f.run(&[b"INFO", b"cpu"])) <= before {
7486 for i in 0..1_000_000u64 {
7487 n = n.wrapping_add(i.wrapping_mul(i));
7488 }
7489 rounds += 1;
7490 assert!(rounds < 1_000, "cpu time never moved, n is {n}");
7494 }
7495 }
7496
7497 #[cfg(unix)]
7499 fn used_cpu_user(info: &str) -> f64 {
7500 info.lines()
7501 .find_map(|l| l.strip_prefix("used_cpu_user:"))
7502 .expect("no used_cpu_user in the reply")
7503 .trim()
7504 .parse()
7505 .expect("used_cpu_user is not a number")
7506 }
7507
7508 #[test]
7514 fn a_command_that_fails_leaves_nothing_half_written() {
7515 let mut f = Fixture::new();
7516 let reply = f.run(&[b"SETRANGE", b"k", b"-1", b"x"]);
7517 assert_eq!(reply, "-ERR offset is out of range\r\n");
7518 assert!(!reply.contains(':'), "no integer went out in front of it");
7519 }
7520
7521 #[test]
7522 fn quit_answers_first_and_closes_after() {
7523 let mut f = Fixture::new();
7524 let (flow, reply) = f.flow(&[b"QUIT"]);
7525 assert_eq!(reply, "+OK\r\n");
7526 assert_eq!(flow, Flow::Close);
7527 }
7528
7529 #[test]
7536 fn shutdown_writes_nothing_and_sets_the_flag() {
7537 let mut f = Fixture::new();
7538 assert!(!f.server.stopping(), "nobody has asked yet");
7539
7540 let (flow, reply) = f.flow(&[b"SHUTDOWN"]);
7541 assert_eq!(reply, "");
7542 assert_eq!(flow, Flow::Close);
7543 assert!(f.server.stopping());
7544 }
7545
7546 #[test]
7554 fn shutdown_takes_the_flags_redis_takes() {
7555 for flags in [
7556 &[b"NOSAVE".as_slice()][..],
7557 &[b"SAVE"],
7558 &[b"NOW"],
7559 &[b"FORCE"],
7560 &[b"nosave"],
7561 &[b"NOW", b"NOW"],
7562 &[b"SAVE", b"SAVE"],
7563 &[b"NOSAVE", b"NOW", b"FORCE"],
7564 ] {
7565 let mut f = Fixture::new();
7566 let mut parts = vec![b"SHUTDOWN".as_slice()];
7567 parts.extend_from_slice(flags);
7568 let (flow, reply) = f.flow(&parts);
7569 assert_eq!(reply, "", "SHUTDOWN {flags:?} answered something");
7570 assert_eq!(flow, Flow::Close, "SHUTDOWN {flags:?} did not close");
7571 assert!(f.server.stopping(), "SHUTDOWN {flags:?} did not stop");
7572 }
7573
7574 for flags in [
7575 &[b"BOGUS".as_slice()][..],
7576 &[b"SAVE", b"NOSAVE"],
7577 &[b"NOSAVE", b"SAVE"],
7578 &[b"ABORT", b"NOW"],
7579 &[b"NOSAVE", b"ABORT"],
7580 &[b"NOW", b"FORCE", b"ABORT"],
7581 ] {
7582 let mut f = Fixture::new();
7583 let mut parts = vec![b"SHUTDOWN".as_slice()];
7584 parts.extend_from_slice(flags);
7585 assert_eq!(
7586 f.run(&parts),
7587 "-ERR syntax error\r\n",
7588 "SHUTDOWN {flags:?} was accepted"
7589 );
7590 assert!(!f.server.stopping(), "SHUTDOWN {flags:?} stopped anyway");
7591 }
7592 }
7593
7594 #[test]
7602 fn shutdown_abort_never_has_anything_to_abort() {
7603 let mut f = Fixture::new();
7604 for parts in [
7605 &[b"SHUTDOWN".as_slice(), b"ABORT"][..],
7606 &[b"SHUTDOWN", b"ABORT", b"ABORT"],
7607 ] {
7608 assert_eq!(f.run(parts), "-ERR No shutdown in progress.\r\n");
7609 assert!(!f.server.stopping(), "an abort stopped the server");
7610 }
7611 }
7612
7613 struct Backups {
7621 f: Fixture,
7622 dir: PathBuf,
7623 }
7624
7625 impl Backups {
7626 fn new(name: &str) -> Backups {
7627 let dir = std::env::temp_dir().join(format!("yo-backup-{name}-{}", std::process::id()));
7628 let _ = std::fs::remove_dir_all(&dir);
7629 std::fs::create_dir_all(&dir).expect("could not make a temporary directory");
7630 let mut f = Fixture::new();
7631 f.server.set_dir(dir.clone());
7632 Backups { f, dir }
7633 }
7634
7635 fn run(&mut self, parts: &[&[u8]]) -> String {
7636 self.f.run(parts)
7637 }
7638
7639 fn files(&self) -> Vec<String> {
7641 let mut names: Vec<String> = match std::fs::read_dir(self.dir.join("backupdir")) {
7642 Ok(entries) => entries
7643 .filter_map(|e| e.ok())
7644 .map(|e| e.file_name().to_string_lossy().into_owned())
7645 .collect(),
7646 Err(_) => Vec::new(),
7647 };
7648 names.sort();
7649 names
7650 }
7651
7652 fn read(&self, name: &str) -> Vec<u8> {
7653 std::fs::read(self.dir.join("backupdir").join(name)).expect("could not read")
7654 }
7655 }
7656
7657 impl Drop for Backups {
7658 fn drop(&mut self) {
7659 let _ = std::fs::remove_dir_all(&self.dir);
7660 }
7661 }
7662
7663 #[test]
7666 fn backup_walks_the_states_the_reference_walks() {
7667 let mut b = Backups::new("states");
7668 let status = |b: &mut Backups| b.run(&[b"BACKUP", b"STATUS"]);
7669
7670 assert!(status(&mut b).contains("idle"));
7671 assert!(b.files().is_empty(), "an idle server has written a backup");
7672
7673 assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
7674 assert!(status(&mut b).contains("incrementing"));
7675 assert_eq!(b.files(), ["appendonly.aof.1.base.rdb"]);
7676
7677 assert_eq!(b.run(&[b"BACKUP", b"SEAL"]), "+OK\r\n");
7678 assert!(status(&mut b).contains("sealed"));
7679 assert_eq!(
7680 b.files(),
7681 [
7682 "appendonly.aof.1.base.rdb",
7683 "appendonly.aof.1.incr.aof",
7684 "appendonly.aof.manifest",
7685 ]
7686 );
7687
7688 assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
7689 assert!(status(&mut b).contains("idle"));
7690 assert!(b.files().is_empty(), "cleanup left something behind");
7691 }
7692
7693 #[test]
7695 fn backup_refuses_the_moves_the_reference_refuses() {
7696 let mut b = Backups::new("refusals");
7697
7698 assert_eq!(
7699 b.run(&[b"BACKUP", b"SEAL"]),
7700 "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
7701 );
7702 assert_eq!(
7703 b.run(&[b"BACKUP", b"ABORT"]),
7704 "-ERR No backup in progress\r\n"
7705 );
7706 assert_eq!(b.run(&[b"BACKUP", b"CLEANUP"]), "+OK\r\n");
7709
7710 b.run(&[b"BACKUP", b"START"]);
7711 assert_eq!(
7712 b.run(&[b"BACKUP", b"START"]),
7713 "-ERR A backup is already in progress, ABORT it first\r\n"
7714 );
7715 assert_eq!(
7716 b.run(&[b"BACKUP", b"CLEANUP"]),
7717 "-ERR Backup is in progress\r\n"
7718 );
7719
7720 b.run(&[b"BACKUP", b"SEAL"]);
7721 assert_eq!(
7722 b.run(&[b"BACKUP", b"START"]),
7723 "-ERR A sealed backup exists, CLEANUP it first\r\n"
7724 );
7725 assert_eq!(
7726 b.run(&[b"BACKUP", b"SEAL"]),
7727 "-ERR No backup ready to seal (must be in the incrementing state)\r\n"
7728 );
7729 assert_eq!(
7730 b.run(&[b"BACKUP", b"ABORT"]),
7731 "-ERR No backup in progress\r\n"
7732 );
7733 }
7734
7735 #[test]
7742 fn backup_abort_removes_the_file_and_says_who_did_it() {
7743 let mut b = Backups::new("abort");
7744 b.run(&[b"BACKUP", b"START"]);
7745 assert_eq!(b.run(&[b"BACKUP", b"ABORT"]), "+OK\r\n");
7746
7747 let status = b.run(&[b"BACKUP", b"STATUS"]);
7748 assert!(status.contains("failed"), "{status}");
7749 assert!(status.contains("aborted by user"), "{status}");
7750 assert!(b.files().is_empty(), "abort left the base file behind");
7751 assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
7752
7753 assert_eq!(b.run(&[b"BACKUP", b"START"]), "+OK\r\n");
7755 assert_eq!(b.files(), ["appendonly.aof.2.base.rdb"]);
7756 let status = b.run(&[b"BACKUP", b"STATUS"]);
7757 assert!(status.contains("incrementing"), "{status}");
7758 assert!(!status.contains("aborted"), "the old error was kept");
7759 }
7760
7761 #[test]
7763 fn backup_list_names_the_files_that_are_pinned_so_far() {
7764 let mut b = Backups::new("list");
7765 assert_eq!(b.run(&[b"BACKUP", b"LIST"]), "*0\r\n");
7766
7767 b.run(&[b"BACKUP", b"START"]);
7768 let base = b.dir.join("backupdir").join("appendonly.aof.1.base.rdb");
7769 let base = base.to_string_lossy().into_owned();
7770 assert_eq!(
7771 b.run(&[b"BACKUP", b"LIST"]),
7772 format!("*1\r\n${}\r\n{base}\r\n", base.len())
7773 );
7774
7775 b.run(&[b"BACKUP", b"SEAL"]);
7776 let listed = b.run(&[b"BACKUP", b"LIST"]);
7777 assert!(listed.starts_with("*3\r\n"), "{listed}");
7778 let names: Vec<&str> = listed
7781 .lines()
7782 .filter(|l| l.starts_with('/') || l.contains(":\\"))
7783 .collect();
7784 assert_eq!(names.len(), 3, "{listed}");
7785 assert!(names[0].ends_with("appendonly.aof.1.base.rdb"), "{listed}");
7786 assert!(names[1].ends_with("appendonly.aof.1.incr.aof"), "{listed}");
7787 assert!(names[2].ends_with("appendonly.aof.manifest"), "{listed}");
7788 }
7789
7790 #[test]
7798 fn a_backup_holds_the_dataset_as_it_was_at_start() {
7799 let mut b = Backups::new("contents");
7800 b.run(&[b"SET", b"bk", b"v1"]);
7801 b.run(&[b"BACKUP", b"START"]);
7802 b.run(&[b"SET", b"bk", b"v2"]);
7803 b.run(&[b"BACKUP", b"SEAL"]);
7804
7805 let base = b.read("appendonly.aof.1.base.rdb");
7806 assert!(base.starts_with(b"REDIS"), "not an RDB file");
7807 assert!(base.windows(2).any(|w| w == b"v1"), "the value is missing");
7808 assert!(
7809 !base.windows(2).any(|w| w == b"v2"),
7810 "the base file moved on after START"
7811 );
7812 let at = base
7817 .windows(8)
7818 .position(|w| w == b"aof-base")
7819 .expect("no aof-base aux field");
7820 assert_eq!(&base[at + 8..at + 10], b"\xc0\x01", "{:?}", &base[at..]);
7821
7822 assert!(b.read("appendonly.aof.1.incr.aof").is_empty());
7823 assert_eq!(
7824 String::from_utf8(b.read("appendonly.aof.manifest")).expect("the manifest is text"),
7825 "file appendonly.aof.1.base.rdb seq 1 type b\n\
7826 file appendonly.aof.1.incr.aof seq 1 type i startoffset 0 endoffset 0\n"
7827 );
7828 }
7829
7830 #[test]
7833 fn backup_status_is_a_map_on_resp3_and_a_flat_array_on_resp2() {
7834 let mut b = Backups::new("status");
7835 b.f.server.set_clock_ms(1_700_000_000_000);
7836
7837 assert_eq!(
7838 b.run(&[b"BACKUP", b"STATUS"]),
7839 "*8\r\n$5\r\nstate\r\n$4\r\nidle\r\n$5\r\nerror\r\n$0\r\n\r\n\
7840 $10\r\nstart_time\r\n:0\r\n$8\r\nend_time\r\n:0\r\n"
7841 );
7842
7843 b.f.out = Out::new(Proto::Resp3);
7844 b.run(&[b"BACKUP", b"START"]);
7845 assert_eq!(
7846 b.run(&[b"BACKUP", b"STATUS"]),
7847 "%4\r\n$5\r\nstate\r\n$12\r\nincrementing\r\n$5\r\nerror\r\n$0\r\n\r\n\
7848 $10\r\nstart_time\r\n:1700000000\r\n$8\r\nend_time\r\n:0\r\n"
7849 );
7850
7851 b.run(&[b"BACKUP", b"SEAL"]);
7852 let sealed = b.run(&[b"BACKUP", b"STATUS"]);
7853 assert!(sealed.contains("end_time\r\n:1700000000"), "{sealed}");
7854 }
7855
7856 #[test]
7859 fn a_sealed_backup_is_swept_away_after_the_timeout() {
7860 let mut b = Backups::new("ttl");
7861 b.f.server.set_clock_ms(1_000_000);
7862 assert_eq!(
7863 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"60"]),
7864 "+OK\r\n"
7865 );
7866 b.run(&[b"BACKUP", b"START"]);
7867 b.run(&[b"BACKUP", b"SEAL"]);
7868
7869 b.f.server.set_clock_ms(1_000_000 + 59_000);
7871 b.f.server.backup_expire();
7872 assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
7873 assert_eq!(b.files().len(), 3);
7874
7875 b.f.server.set_clock_ms(1_000_000 + 60_000);
7876 b.f.server.backup_expire();
7877 let status = b.run(&[b"BACKUP", b"STATUS"]);
7878 assert!(status.contains("idle"), "{status}");
7879 assert!(b.files().is_empty(), "the timeout left the files behind");
7880
7881 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"0"]);
7883 b.run(&[b"BACKUP", b"START"]);
7884 b.run(&[b"BACKUP", b"SEAL"]);
7885 b.f.server.set_clock_ms(9_000_000_000);
7886 b.f.server.backup_expire();
7887 assert!(b.run(&[b"BACKUP", b"STATUS"]).contains("sealed"));
7888 }
7889
7890 #[test]
7893 fn the_backup_settings_behave_the_way_the_reference_does() {
7894 let mut b = Backups::new("config");
7895 let dir = b.dir.to_string_lossy().into_owned();
7896
7897 assert_eq!(
7898 b.run(&[b"CONFIG", b"GET", b"dir"]),
7899 format!("*2\r\n$3\r\ndir\r\n${}\r\n{dir}\r\n", dir.len())
7900 );
7901 assert_eq!(
7902 b.run(&[b"CONFIG", b"GET", b"backupdirname"]),
7903 "*2\r\n$13\r\nbackupdirname\r\n$9\r\nbackupdir\r\n"
7904 );
7905 assert_eq!(
7906 b.run(&[b"CONFIG", b"GET", b"backup-sealed-ttl"]),
7907 "*2\r\n$17\r\nbackup-sealed-ttl\r\n$1\r\n0\r\n"
7908 );
7909
7910 assert_eq!(
7913 b.run(&[b"CONFIG", b"SET", b"dir", dir.as_bytes()]),
7914 "-ERR CONFIG SET failed (possibly related to argument 'dir') - can't set protected config\r\n"
7915 );
7916 assert_eq!(
7917 b.run(&[b"CONFIG", b"SET", b"backupdirname", b"other"]),
7918 "-ERR CONFIG SET failed (possibly related to argument 'backupdirname') - can't set immutable config\r\n"
7919 );
7920 assert!(
7921 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"abc"])
7922 .contains("argument couldn't be parsed into an integer")
7923 );
7924 assert!(
7925 b.run(&[b"CONFIG", b"SET", b"backup-sealed-ttl", b"-1"])
7926 .contains("argument must be between 0 and 9223372036854775807 inclusive")
7927 );
7928 }
7929
7930 #[test]
7932 fn backup_help_is_the_text_the_reference_sends() {
7933 let mut f = Fixture::new();
7934 let help = f.run(&[b"BACKUP", b"HELP"]);
7935 assert!(help.starts_with("*17\r\n"), "{help}");
7936 assert!(
7937 help.contains("+BACKUP <subcommand> [<arg> [value] [opt] ...]. Subcommands are:\r\n")
7938 );
7939 assert!(help.contains("+ Start a new backup into the configured 'backupdirname'.\r\n"));
7940 assert!(help.contains("+ Freeze the current backup (BASE + INCR + manifest).\r\n"));
7941 assert!(help.contains("+ Return this help.\r\n+HELP\r\n+ Print this help.\r\n"));
7942 }
7943
7944 #[test]
7952 fn backup_refuses_what_it_cannot_read() {
7953 let mut f = Fixture::new();
7954 assert_eq!(
7955 f.run(&[b"BACKUP"]),
7956 "-ERR wrong number of arguments for 'backup' command\r\n"
7957 );
7958 assert_eq!(
7959 f.run(&[b"BACKUP", b"START", b"x"]),
7960 "-ERR wrong number of arguments for 'backup' command\r\n"
7961 );
7962 assert_eq!(
7963 f.run(&[b"BACKUP", b"NOPE"]),
7964 "-ERR unknown subcommand 'NOPE'. Try BACKUP HELP.\r\n"
7965 );
7966 }
7967
7968 #[test]
7969 fn the_command_counter_counts_every_command_including_the_bad_ones() {
7970 let mut f = Fixture::new();
7971 f.run(&[b"PING"]);
7972 f.run(&[b"NOPE"]);
7973 f.run(&[b"GET"]);
7974 assert_eq!(f.server.totals().commands, 3);
7975 }
7976
7977 #[test]
7978 fn what_a_thread_marked_is_taken_by_the_maintenance_turn() {
7979 let mut server = Server::new();
7980 server.set_threads(2);
7981 server.mine().turn.store(0, Relaxed);
7984 server.locals[1].mark(1 << 9);
7985 server.collect_marks();
7986 assert!(server.mine().wanted(9));
7987 assert_eq!(server.locals[1].dirty.load(Relaxed), 0);
7989 }
7990
7991 #[test]
7992 fn what_two_threads_counted_is_added_up_when_info_asks() {
7993 let mut server = Server::new();
7994 server.set_threads(2);
7995 let ping = lookup(b"PING").expect("PING is a command");
7999 for (at, calls) in [(0, 2), (1, 3)] {
8000 let counters = &server.locals[at];
8001 for _ in 0..calls {
8002 counters.stats.commands.bump();
8003 counters.cmdstats.at(ping).calls.bump();
8004 }
8005 counters.stats.opened();
8006 }
8007 assert_eq!(server.totals().commands, 5);
8008 assert_eq!(server.totals().clients, 2);
8009 assert_eq!(server.totals().connections, 2);
8010 let rows: Vec<_> = server.command_stats().collect();
8011 assert_eq!(rows.len(), 1);
8012 assert_eq!(rows[0].0, "ping");
8013 assert_eq!(rows[0].1.calls, 5);
8014 server.reset_stats();
8017 assert_eq!(server.totals().commands, 0);
8018 assert_eq!(server.totals().connections, 0);
8019 assert_eq!(server.totals().clients, 2);
8020 }
8021
8022 #[test]
8023 fn the_parked_count_says_what_the_waiter_list_says() {
8024 let mut f = Fixture::new();
8025 assert_eq!(f.server.parked(), 0);
8026 for client in 1..=3u64 {
8027 f.session = Session::new(client);
8028 assert_eq!(f.flow(&[b"BLPOP", b"q", b"0"]).0, Flow::Block);
8029 }
8030 assert_eq!(f.server.parked(), 3);
8031 assert_eq!(f.server.waiters().len(), 3);
8032
8033 f.server.forget_waiters(2);
8037 assert_eq!(f.server.parked(), f.server.waiters().len());
8038 f.server.forget_waiters(1);
8039 assert_eq!(f.server.parked(), f.server.waiters().len());
8040 f.run(&[b"RPUSH", b"q", b"v"]);
8041 let mut out = Out::new(Proto::Resp2);
8042 assert!(f.server.serve_waiter(3, 0, &mut out));
8043 f.server.forget_waiters(3);
8044 assert_eq!(f.server.parked(), 0);
8045 assert!(f.server.waiters().is_empty());
8046 }
8047
8048 #[test]
8049 fn a_set_goes_from_bytes_to_bytes() {
8050 let mut f = Fixture::new();
8051 assert_eq!(f.run(&[b"SADD", b"s", b"a", b"b", b"c"]), ":3\r\n");
8052 assert_eq!(f.run(&[b"SADD", b"s", b"b", b"d"]), ":1\r\n");
8053 assert_eq!(f.run(&[b"SCARD", b"s"]), ":4\r\n");
8054 assert_eq!(f.run(&[b"SISMEMBER", b"s", b"a"]), ":1\r\n");
8055 assert_eq!(f.run(&[b"SISMEMBER", b"s", b"z"]), ":0\r\n");
8056 assert_eq!(f.run(&[b"TYPE", b"s"]), "+set\r\n");
8057 assert_eq!(
8058 f.run(&[b"SMISMEMBER", b"s", b"a", b"z", b"d"]),
8059 "*3\r\n:1\r\n:0\r\n:1\r\n"
8060 );
8061 assert_eq!(f.run(&[b"SREM", b"s", b"a", b"z"]), ":1\r\n");
8062 assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
8063 }
8064
8065 #[test]
8066 fn a_set_command_at_a_key_that_is_not_there_answers_empty() {
8067 let mut f = Fixture::new();
8068 assert_eq!(f.run(&[b"SCARD", b"nope"]), ":0\r\n");
8069 assert_eq!(f.run(&[b"SISMEMBER", b"nope", b"a"]), ":0\r\n");
8070 assert_eq!(f.run(&[b"SREM", b"nope", b"a"]), ":0\r\n");
8071 assert_eq!(f.run(&[b"SMEMBERS", b"nope"]), "*0\r\n");
8072 assert_eq!(
8073 f.run(&[b"SMISMEMBER", b"nope", b"a", b"b"]),
8074 "*2\r\n:0\r\n:0\r\n"
8075 );
8076 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n", "and made nothing");
8077 }
8078
8079 #[test]
8080 fn smembers_answers_a_set_on_resp3_and_an_array_on_resp2() {
8081 let mut f = Fixture::new();
8085 f.run(&[b"SADD", b"s", b"one"]);
8086 assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$3\r\none\r\n");
8087
8088 f.run(&[b"HELLO", b"3"]);
8089 assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "~1\r\n$3\r\none\r\n");
8090 }
8091
8092 #[test]
8093 fn an_integer_member_comes_back_as_the_digits_it_never_stored() {
8094 let mut f = Fixture::new();
8097 f.run(&[b"SADD", b"s", b"42"]);
8098 assert_eq!(f.run(&[b"SMEMBERS", b"s"]), "*1\r\n$2\r\n42\r\n");
8099 assert_eq!(f.run(&[b"SISMEMBER", b"s", b"42"]), ":1\r\n");
8100 assert_eq!(
8101 f.run(&[b"SISMEMBER", b"s", b"042"]),
8102 ":0\r\n",
8103 "the member is the bytes and not the number they parse to"
8104 );
8105 }
8106
8107 #[test]
8108 fn the_wrong_command_at_the_wrong_type_says_so_both_ways() {
8109 let mut f = Fixture::new();
8110 f.run(&[b"SET", b"str", b"v"]);
8111 f.run(&[b"SADD", b"set", b"a"]);
8112
8113 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8114 assert_eq!(f.run(&[b"SADD", b"str", b"a"]), wrong);
8115 assert_eq!(f.run(&[b"SCARD", b"str"]), wrong);
8116 assert_eq!(f.run(&[b"SMEMBERS", b"str"]), wrong);
8117 assert_eq!(f.run(&[b"SMISMEMBER", b"str", b"a"]), wrong);
8118 assert_eq!(f.run(&[b"GET", b"set"]), wrong);
8119 assert_eq!(f.run(&[b"APPEND", b"set", b"x"]), wrong);
8120 assert_eq!(f.run(&[b"INCR", b"set"]), wrong);
8121 assert_eq!(f.run(&[b"STRLEN", b"set"]), wrong);
8122
8123 assert_eq!(
8126 f.run(&[b"MGET", b"str", b"set", b"nope"]),
8127 "*3\r\n$1\r\nv\r\n$-1\r\n$-1\r\n"
8128 );
8129 assert_eq!(f.run(&[b"SET", b"set", b"now a string"]), "+OK\r\n");
8131 assert_eq!(f.run(&[b"TYPE", b"set"]), "+string\r\n");
8132 }
8133
8134 #[test]
8135 fn a_wrongtype_leaves_nothing_half_written() {
8136 let mut f = Fixture::new();
8140 f.run(&[b"SET", b"k", b"v"]);
8141 let reply = f.run(&[b"SMISMEMBER", b"k", b"a", b"b"]);
8142 assert!(reply.starts_with("-WRONGTYPE"), "got {reply}");
8143 assert!(!reply.contains('*'), "an array header went out in front");
8144 }
8145
8146 #[test]
8147 fn emptying_a_set_takes_the_key_with_it() {
8148 let mut f = Fixture::new();
8149 f.run(&[b"SADD", b"s", b"a", b"b"]);
8150 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
8151 assert_eq!(f.run(&[b"SREM", b"s", b"a", b"b"]), ":2\r\n");
8152 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
8153 assert_eq!(f.run(&[b"TYPE", b"s"]), "+none\r\n");
8154 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
8155 }
8156
8157 fn split_scan(reply: &str) -> (String, Vec<String>) {
8163 let mut lines = reply.split("\r\n");
8164 assert_eq!(lines.next(), Some("*2"), "got {reply}");
8165 lines.next().expect("the cursor header");
8166 let cursor = lines.next().expect("the cursor").to_owned();
8167 let header = lines.next().expect("the member header");
8168 let n: usize = header[1..].parse().expect("a member count");
8169 let mut members = Vec::with_capacity(n);
8170 for _ in 0..n {
8171 lines.next().expect("a member header");
8172 members.push(lines.next().expect("a member").to_owned());
8173 }
8174 (cursor, members)
8175 }
8176
8177 #[test]
8178 fn popping_takes_a_member_off_the_set_and_hands_it_back() {
8179 let mut f = Fixture::new();
8180 f.run(&[b"SADD", b"s", b"a", b"b", b"c", b"d"]);
8181
8182 let one = f.run(&[b"SPOP", b"s"]);
8183 assert!(
8184 ["$1\r\na\r\n", "$1\r\nb\r\n", "$1\r\nc\r\n", "$1\r\nd\r\n"].contains(&one.as_str()),
8185 "got {one}"
8186 );
8187 assert_eq!(f.run(&[b"SCARD", b"s"]), ":3\r\n");
8188
8189 let (_, rest) = ("", f.run(&[b"SPOP", b"s", b"3"]));
8191 assert!(rest.starts_with("*3\r\n"), "got {rest}");
8192 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
8193 assert_eq!(f.run(&[b"SPOP", b"s"]), "$-1\r\n");
8195 assert_eq!(f.run(&[b"SPOP", b"s", b"2"]), "*0\r\n");
8196 }
8197
8198 #[test]
8199 fn the_two_draws_disagree_about_the_reply_type_and_they_are_right_to() {
8200 let mut f = Fixture::new();
8205 f.run(&[b"HELLO", b"3"]);
8206 f.run(&[b"SADD", b"s", b"a", b"b", b"c"]);
8207
8208 assert!(f.run(&[b"SPOP", b"s", b"2"]).starts_with("~2\r\n"));
8209 assert!(f.run(&[b"SRANDMEMBER", b"s", b"1"]).starts_with("*1\r\n"));
8211
8212 f.run(&[b"SADD", b"one", b"z"]);
8216 assert_eq!(
8217 f.run(&[b"SRANDMEMBER", b"one", b"-3"]),
8218 "*3\r\n$1\r\nz\r\n$1\r\nz\r\n$1\r\nz\r\n"
8219 );
8220 }
8221
8222 #[test]
8223 fn drawing_a_member_removes_nothing_and_says_nil_at_a_missing_key() {
8224 let mut f = Fixture::new();
8225 f.run(&[b"SADD", b"s", b"only"]);
8226 assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
8227 assert_eq!(f.run(&[b"SRANDMEMBER", b"s"]), "$4\r\nonly\r\n");
8228 assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n");
8229
8230 assert_eq!(f.run(&[b"SRANDMEMBER", b"nope"]), "$-1\r\n");
8231 assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"3"]), "*0\r\n");
8234 assert_eq!(f.run(&[b"SRANDMEMBER", b"nope", b"-3"]), "*0\r\n");
8235 assert_eq!(f.run(&[b"SRANDMEMBER", b"s", b"9"]), "*1\r\n$4\r\nonly\r\n");
8237 }
8238
8239 #[test]
8240 fn a_pop_count_that_is_not_a_positive_number_says_so() {
8241 let mut f = Fixture::new();
8242 f.run(&[b"SADD", b"s", b"a"]);
8243 let bad = "-ERR value is out of range, must be positive\r\n";
8244 assert_eq!(f.run(&[b"SPOP", b"s", b"-1"]), bad);
8245 assert_eq!(f.run(&[b"SPOP", b"s", b"abc"]), bad);
8246 assert_eq!(f.run(&[b"SCARD", b"s"]), ":1\r\n", "and took nothing");
8247 assert_eq!(f.run(&[b"SPOP", b"s", b"0"]), "*0\r\n");
8249 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
8250 }
8251
8252 #[test]
8253 fn a_scan_walks_a_set_of_any_size_exactly_once() {
8254 let mut f = Fixture::new();
8255 let members: Vec<Vec<u8>> = (0..300).map(|i| format!("m{i}").into_bytes()).collect();
8256 let args: Vec<&[u8]> = [&b"SADD"[..], &b"s"[..]]
8257 .into_iter()
8258 .chain(members.iter().map(Vec::as_slice))
8259 .collect();
8260 f.run(&args);
8261
8262 let mut seen = Vec::new();
8263 let mut cursor = "0".to_owned();
8264 loop {
8265 let reply = f.run(&[b"SSCAN", b"s", cursor.as_bytes()]);
8266 let (next, got) = split_scan(&reply);
8267 seen.extend(got);
8268 cursor = next;
8269 if cursor == "0" {
8270 break;
8271 }
8272 }
8273 seen.sort();
8274 seen.dedup();
8275 assert_eq!(seen.len(), 300, "a walk saw a member twice or missed one");
8276
8277 f.run(&[b"SADD", b"small", b"a", b"b", b"c"]);
8280 let (cursor, got) = split_scan(&f.run(&[b"SSCAN", b"small", b"0", b"COUNT", b"1"]));
8281 assert_eq!(cursor, "0");
8282 assert_eq!(got.len(), 3);
8283 assert_eq!(f.run(&[b"SSCAN", b"nope", b"0"]), "*2\r\n$1\r\n0\r\n*0\r\n");
8285 }
8286
8287 #[test]
8288 fn a_scan_takes_match_and_count_and_refuses_anything_else() {
8289 let mut f = Fixture::new();
8290 f.run(&[b"SADD", b"s", b"aa", b"ab", b"ba", b"12", b"13"]);
8291
8292 let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"a*"]));
8293 let mut got = got;
8294 got.sort();
8295 assert_eq!(got, ["aa", "ab"]);
8296
8297 let (_, got) = split_scan(&f.run(&[b"SSCAN", b"s", b"0", b"MATCH", b"1?"]));
8300 let mut got = got;
8301 got.sort();
8302 assert_eq!(got, ["12", "13"]);
8303
8304 assert_eq!(f.run(&[b"SSCAN", b"s", b"abc"]), "-ERR invalid cursor\r\n");
8305 assert_eq!(f.run(&[b"SSCAN", b"s", b"-1"]), "-ERR invalid cursor\r\n");
8306 assert_eq!(
8307 f.run(&[b"SSCAN", b"s", b"0", b"NOPE", b"1"]),
8308 "-ERR syntax error\r\n"
8309 );
8310 assert_eq!(
8313 f.run(&[b"SSCAN", b"s", b"0", b"COUNT", b"0"]),
8314 "-ERR syntax error\r\n"
8315 );
8316 }
8317
8318 #[test]
8319 fn moving_a_member_takes_it_off_one_set_and_puts_it_on_another() {
8320 let mut f = Fixture::new();
8321 f.run(&[b"SADD", b"src", b"a", b"b"]);
8322 f.run(&[b"SADD", b"dst", b"c"]);
8323
8324 assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"a"]), ":1\r\n");
8325 assert_eq!(f.run(&[b"SISMEMBER", b"src", b"a"]), ":0\r\n");
8326 assert_eq!(f.run(&[b"SISMEMBER", b"dst", b"a"]), ":1\r\n");
8327 assert_eq!(f.run(&[b"SMOVE", b"src", b"dst", b"zz"]), ":0\r\n");
8329 assert_eq!(f.run(&[b"SCARD", b"dst"]), ":2\r\n");
8330
8331 assert_eq!(f.run(&[b"SMOVE", b"src", b"fresh", b"b"]), ":1\r\n");
8334 assert_eq!(f.run(&[b"EXISTS", b"src"]), ":0\r\n");
8335 assert_eq!(f.run(&[b"SMEMBERS", b"fresh"]), "*1\r\n$1\r\nb\r\n");
8336 }
8337
8338 #[test]
8339 fn moving_checks_the_types_in_the_order_redis_checks_them() {
8340 let mut f = Fixture::new();
8344 f.run(&[b"SET", b"str", b"v"]);
8345 f.run(&[b"SADD", b"set", b"a"]);
8346
8347 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8348 assert_eq!(f.run(&[b"SMOVE", b"nope", b"str", b"a"]), ":0\r\n");
8349 assert_eq!(f.run(&[b"SMOVE", b"str", b"set", b"a"]), wrong);
8350 assert_eq!(f.run(&[b"SMOVE", b"set", b"str", b"a"]), wrong);
8351 assert_eq!(f.run(&[b"SPOP", b"str"]), wrong);
8352 assert_eq!(f.run(&[b"SRANDMEMBER", b"str"]), wrong);
8353 assert_eq!(f.run(&[b"SSCAN", b"str", b"0"]), wrong);
8354 assert_eq!(
8355 f.run(&[b"SISMEMBER", b"set", b"a"]),
8356 ":1\r\n",
8357 "and none of that moved anything"
8358 );
8359 }
8360
8361 #[test]
8362 fn a_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
8363 let mut f = Fixture::new();
8366 f.run(&[b"SADD", b"s", b"a"]);
8367 for bad in [
8368 &[b"SSCAN".as_slice(), b"s", b"abc"][..],
8369 &[b"SSCAN".as_slice(), b"s", b"0", b"COUNT", b"nope"][..],
8370 &[b"SSCAN".as_slice(), b"s", b"0", b"MATCH"][..],
8371 ] {
8372 let reply = f.run(bad);
8373 assert!(reply.starts_with("-ERR"), "got {reply}");
8374 assert!(!reply.contains('*'), "an array header went out in front");
8375 }
8376 }
8377
8378 #[test]
8379 fn a_hash_writes_reads_and_deletes_its_fields() {
8380 let mut f = Fixture::new();
8381 assert_eq!(f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]), ":2\r\n");
8382 assert_eq!(f.run(&[b"HSET", b"h", b"a", b"9"]), ":0\r\n", "a was there");
8383 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
8384 assert_eq!(f.run(&[b"HGET", b"h", b"nope"]), "$-1\r\n");
8385 assert_eq!(f.run(&[b"HGET", b"nokey", b"a"]), "$-1\r\n");
8386 assert_eq!(f.run(&[b"HLEN", b"h"]), ":2\r\n");
8387 assert_eq!(f.run(&[b"HEXISTS", b"h", b"a"]), ":1\r\n");
8388 assert_eq!(f.run(&[b"HEXISTS", b"h", b"nope"]), ":0\r\n");
8389 assert_eq!(f.run(&[b"HSTRLEN", b"h", b"a"]), ":1\r\n");
8390 assert_eq!(f.run(&[b"HSTRLEN", b"h", b"nope"]), ":0\r\n");
8391
8392 assert_eq!(f.run(&[b"HGET", b"h", b"2"]), "$-1\r\n");
8395
8396 assert_eq!(f.run(&[b"HDEL", b"h", b"a", b"nope"]), ":1\r\n");
8397 assert_eq!(f.run(&[b"HDEL", b"h", b"b"]), ":1\r\n");
8398 assert_eq!(
8399 f.run(&[b"EXISTS", b"h"]),
8400 ":0\r\n",
8401 "and losing the last field lost the key"
8402 );
8403 }
8404
8405 #[test]
8406 fn hgetall_answers_a_map_on_resp3_and_the_same_pairs_flat_on_resp2() {
8407 let mut f = Fixture::new();
8408 f.run(&[b"HSET", b"h", b"a", b"1"]);
8409 assert_eq!(f.run(&[b"HGETALL", b"h"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
8410 assert_eq!(f.run(&[b"HGETALL", b"nokey"]), "*0\r\n");
8411 assert_eq!(f.run(&[b"HKEYS", b"h"]), "*1\r\n$1\r\na\r\n");
8412 assert_eq!(f.run(&[b"HVALS", b"h"]), "*1\r\n$1\r\n1\r\n");
8413 assert_eq!(f.run(&[b"HKEYS", b"nokey"]), "*0\r\n");
8414
8415 f.run(&[b"HELLO", b"3"]);
8416 assert_eq!(f.run(&[b"HGETALL", b"h"]), "%1\r\n$1\r\na\r\n$1\r\n1\r\n");
8417 assert_eq!(
8418 f.run(&[b"HGETALL", b"nokey"]),
8419 "%0\r\n",
8420 "a missing key is the empty hash and never a nil"
8421 );
8422 assert_eq!(
8423 f.run(&[b"HKEYS", b"h"]),
8424 "*1\r\n$1\r\na\r\n",
8425 "and the two that answer one side stay arrays"
8426 );
8427 }
8428
8429 #[test]
8430 fn hmget_answers_once_per_field_and_hmset_answers_ok() {
8431 let mut f = Fixture::new();
8432 assert_eq!(f.run(&[b"HMSET", b"h", b"a", b"1", b"c", b"3"]), "+OK\r\n");
8433 assert_eq!(
8434 f.run(&[b"HMGET", b"h", b"a", b"b", b"c"]),
8435 "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n",
8436 "the reply is positional, so b is a nil and not a gap"
8437 );
8438 assert_eq!(
8439 f.run(&[b"HMGET", b"nokey", b"a", b"b"]),
8440 "*2\r\n$-1\r\n$-1\r\n",
8441 "and a missing key is all nils rather than an empty array"
8442 );
8443
8444 assert_eq!(f.run(&[b"HSETNX", b"h", b"a", b"9"]), ":0\r\n");
8445 assert_eq!(f.run(&[b"HSETNX", b"h", b"z", b"9"]), ":1\r\n");
8446 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
8447 }
8448
8449 #[test]
8450 fn a_hash_counts_up_and_says_so_when_it_cannot() {
8451 let mut f = Fixture::new();
8452 assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"5"]), ":5\r\n");
8453 assert_eq!(f.run(&[b"HINCRBY", b"h", b"n", b"-7"]), ":-2\r\n");
8454 assert_eq!(f.run(&[b"HGET", b"h", b"n"]), "$2\r\n-2\r\n");
8455 assert_eq!(
8456 f.run(&[b"HINCRBYFLOAT", b"h", b"f", b"10.5"]),
8457 "$4\r\n10.5\r\n",
8458 "a bulk string and not a double, on both protocols"
8459 );
8460
8461 f.run(&[b"HSET", b"h", b"s", b"words"]);
8462 let bad = f.run(&[b"HINCRBY", b"h", b"s", b"1"]);
8463 assert!(
8464 bad.starts_with("-ERR hash value is not an integer"),
8465 "{bad}"
8466 );
8467 let bad = f.run(&[b"HINCRBY", b"h", b"n", b"nope"]);
8468 assert!(
8469 bad.starts_with("-ERR value is not an integer"),
8470 "a bad argument is not yet a hash value, {bad}"
8471 );
8472 assert_eq!(
8473 f.run(&[b"HGET", b"h", b"s"]),
8474 "$5\r\nwords\r\n",
8475 "and neither of them wrote anything"
8476 );
8477 }
8478
8479 #[test]
8480 fn a_hash_scan_walks_every_pair_once_and_novalues_drops_half_of_it() {
8481 let fields = if cfg!(miri) { 96 } else { 500 };
8486 let mut f = Fixture::new();
8487 for i in 0..fields {
8488 let field = format!("field-{i}");
8489 let value = format!("value-{i}");
8490 f.run(&[b"HSET", b"h", field.as_bytes(), value.as_bytes()]);
8491 }
8492
8493 let mut seen: Vec<String> = Vec::new();
8494 let mut cursor = "0".to_owned();
8495 loop {
8496 let reply = f.run(&[b"HSCAN", b"h", cursor.as_bytes(), b"COUNT", b"32"]);
8497 let (next, items) = scan_reply(&reply);
8498 assert_eq!(items.len() % 2, 0, "a pair went out half written");
8499 for pair in items.chunks(2) {
8500 assert_eq!(
8501 pair[0].strip_prefix("field-"),
8502 pair[1].strip_prefix("value-"),
8503 "a field came back with someone else's value"
8504 );
8505 seen.push(pair[0].clone());
8506 }
8507 cursor = next;
8508 if cursor == "0" {
8509 break;
8510 }
8511 }
8512 seen.sort();
8513 seen.dedup();
8514 assert_eq!(seen.len(), fields, "every field once and only once");
8515
8516 let (_, items) = scan_reply(&f.run(&[b"HSCAN", b"h", b"0", b"NOVALUES", b"COUNT", b"32"]));
8517 assert!(
8518 items.iter().all(|s| s.starts_with("field-")),
8519 "NOVALUES still sent the values"
8520 );
8521
8522 let last = fields - 1;
8523 let (_, one) = scan_reply(&f.run(&[
8524 b"HSCAN",
8525 b"h",
8526 b"0",
8527 b"MATCH",
8528 format!("field-{last}").as_bytes(),
8529 b"COUNT",
8530 b"1000",
8531 ]));
8532 assert_eq!(
8533 one,
8534 [format!("field-{last}"), format!("value-{last}")],
8535 "MATCH is on the field"
8536 );
8537 }
8538
8539 #[test]
8540 fn hrandfield_draws_what_it_was_asked_for_and_nests_values_on_resp3() {
8541 let mut f = Fixture::new();
8542 f.run(&[b"HSET", b"h", b"a", b"1"]);
8543 assert_eq!(f.run(&[b"HRANDFIELD", b"h"]), "$1\r\na\r\n");
8544 assert_eq!(f.run(&[b"HRANDFIELD", b"nokey"]), "$-1\r\n");
8545 assert_eq!(f.run(&[b"HRANDFIELD", b"nokey", b"3"]), "*0\r\n");
8546 assert_eq!(
8547 f.run(&[b"HRANDFIELD", b"h", b"3"]),
8548 "*1\r\n$1\r\na\r\n",
8549 "a positive count is capped at the size of the hash"
8550 );
8551 assert_eq!(
8552 f.run(&[b"HRANDFIELD", b"h", b"-3"]),
8553 "*3\r\n$1\r\na\r\n$1\r\na\r\n$1\r\na\r\n",
8554 "and a negative one repeats itself"
8555 );
8556 assert_eq!(
8557 f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
8558 "*2\r\n$1\r\na\r\n$1\r\n1\r\n",
8559 "flat on RESP2"
8560 );
8561
8562 f.run(&[b"HELLO", b"3"]);
8563 assert_eq!(
8564 f.run(&[b"HRANDFIELD", b"h", b"1", b"WITHVALUES"]),
8565 "*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n",
8566 "and nested on RESP3, but still an array and never a map"
8567 );
8568 }
8569
8570 #[test]
8571 fn every_hash_command_says_wrongtype_and_writes_nothing() {
8572 let mut f = Fixture::new();
8573 f.run(&[b"SET", b"str", b"v"]);
8574 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8575
8576 for cmd in [
8577 &[b"HSET".as_slice(), b"str", b"f", b"v"][..],
8578 &[b"HMSET".as_slice(), b"str", b"f", b"v"][..],
8579 &[b"HSETNX".as_slice(), b"str", b"f", b"v"][..],
8580 &[b"HGET".as_slice(), b"str", b"f"][..],
8581 &[b"HMGET".as_slice(), b"str", b"f"][..],
8582 &[b"HDEL".as_slice(), b"str", b"f"][..],
8583 &[b"HLEN".as_slice(), b"str"][..],
8584 &[b"HEXISTS".as_slice(), b"str", b"f"][..],
8585 &[b"HSTRLEN".as_slice(), b"str", b"f"][..],
8586 &[b"HGETALL".as_slice(), b"str"][..],
8587 &[b"HKEYS".as_slice(), b"str"][..],
8588 &[b"HVALS".as_slice(), b"str"][..],
8589 &[b"HINCRBY".as_slice(), b"str", b"f", b"1"][..],
8590 &[b"HINCRBYFLOAT".as_slice(), b"str", b"f", b"1"][..],
8591 &[b"HRANDFIELD".as_slice(), b"str"][..],
8592 &[b"HRANDFIELD".as_slice(), b"str", b"2"][..],
8593 &[b"HSCAN".as_slice(), b"str", b"0"][..],
8594 ] {
8595 let reply = f.run(cmd);
8596 assert_eq!(reply, wrong, "{:?}", cmd[0]);
8597 }
8598 assert_eq!(
8599 f.run(&[b"GET", b"str"]),
8600 "$1\r\nv\r\n",
8601 "and none of them touched the value"
8602 );
8603 }
8604
8605 #[test]
8606 fn a_hash_scan_leaves_nothing_half_written_when_its_arguments_are_wrong() {
8607 let mut f = Fixture::new();
8608 f.run(&[b"HSET", b"h", b"f", b"v"]);
8609 for bad in [
8610 &[b"HSCAN".as_slice(), b"h", b"abc"][..],
8611 &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"nope"][..],
8612 &[b"HSCAN".as_slice(), b"h", b"0", b"COUNT", b"0"][..],
8613 &[b"HSCAN".as_slice(), b"h", b"0", b"MATCH"][..],
8614 ] {
8615 let reply = f.run(bad);
8616 assert!(reply.starts_with("-ERR"), "got {reply}");
8617 assert!(!reply.contains('*'), "an array header went out in front");
8618 }
8619 }
8620
8621 #[test]
8622 fn a_field_deadline_goes_on_and_comes_back_in_all_four_units() {
8623 let mut f = Fixture::new();
8624 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
8625 assert_eq!(
8626 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]),
8627 "*1\r\n:1\r\n"
8628 );
8629 assert_eq!(
8630 f.run(&[b"HTTL", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
8631 "*3\r\n:100\r\n:-1\r\n:-2\r\n",
8632 "one answer per field, and the two sentinels are TTL's own"
8633 );
8634
8635 let ms = int_reply(&f.run(&[b"HPTTL", b"h", b"FIELDS", b"1", b"a"]));
8638 assert!((99_000..=100_000).contains(&ms), "got {ms}");
8639 let at = int_reply(&f.run(&[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
8640 let at_ms = int_reply(&f.run(&[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"]));
8641 assert_eq!(at, at_ms.div_euclid(1000) + i64::from(at_ms % 1000 != 0));
8642 assert!(at_ms > 1_700_000_000_000, "an absolute moment, got {at_ms}");
8643
8644 assert_eq!(
8645 f.run(&[b"HPERSIST", b"h", b"FIELDS", b"3", b"a", b"b", b"nope"]),
8646 "*3\r\n:1\r\n:-1\r\n:-2\r\n",
8647 "one for the deadline taken off, and it does not say what it was"
8648 );
8649 assert_eq!(
8650 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8651 "*1\r\n:-1\r\n"
8652 );
8653 assert_eq!(
8654 f.run(&[b"HGET", b"h", b"a"]),
8655 "$1\r\n1\r\n",
8656 "and the field is still there with the value it had"
8657 );
8658 }
8659
8660 #[test]
8661 fn a_deadline_that_has_already_gone_deletes_the_field_now() {
8662 let mut f = Fixture::new();
8663 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
8664 assert_eq!(
8665 f.run(&[b"HEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"a"]),
8666 "*1\r\n:2\r\n",
8667 "two, and not one, because nothing was stored"
8668 );
8669 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
8670 assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
8671
8672 assert_eq!(
8673 f.run(&[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"]),
8674 "*1\r\n:2\r\n"
8675 );
8676 assert_eq!(
8677 f.run(&[b"EXISTS", b"h"]),
8678 ":0\r\n",
8679 "and the last field going took the key with it"
8680 );
8681
8682 f.run(&[b"HSET", b"h", b"a", b"1"]);
8685 assert_eq!(
8686 f.run(&[b"HEXPIRE", b"h", b"0", b"FIELDS", b"1", b"a"]),
8687 "*1\r\n:2\r\n"
8688 );
8689 }
8690
8691 #[test]
8692 fn a_field_is_gone_once_its_moment_passes() {
8693 let mut f = Fixture::new();
8694 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
8695 assert_eq!(
8696 f.run(&[b"HPEXPIRE", b"h", b"20", b"FIELDS", b"1", b"a"]),
8697 "*1\r\n:1\r\n"
8698 );
8699 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n", "not yet");
8700
8701 f.server.advance_clock_ms(60);
8705 assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
8706 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$-1\r\n");
8707 assert_eq!(
8708 f.run(&[b"HGETALL", b"h"]),
8709 "*2\r\n$1\r\nb\r\n$1\r\n2\r\n",
8710 "and the walks do not hand back a field that has expired"
8711 );
8712 }
8713
8714 #[test]
8715 fn a_missing_key_answers_the_no_field_sentinel_for_every_field() {
8716 let mut f = Fixture::new();
8717 for cmd in [
8718 &[
8719 b"HEXPIRE".as_slice(),
8720 b"nokey",
8721 b"100",
8722 b"FIELDS",
8723 b"2",
8724 b"a",
8725 b"b",
8726 ][..],
8727 &[b"HTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
8728 &[b"HPTTL".as_slice(), b"nokey", b"FIELDS", b"2", b"a", b"b"][..],
8729 &[
8730 b"HEXPIRETIME".as_slice(),
8731 b"nokey",
8732 b"FIELDS",
8733 b"2",
8734 b"a",
8735 b"b",
8736 ][..],
8737 &[
8738 b"HPERSIST".as_slice(),
8739 b"nokey",
8740 b"FIELDS",
8741 b"2",
8742 b"a",
8743 b"b",
8744 ][..],
8745 ] {
8746 assert_eq!(f.run(cmd), "*2\r\n:-2\r\n:-2\r\n", "{:?}", cmd[0]);
8747 }
8748 }
8749
8750 #[test]
8751 fn writing_a_field_clears_the_deadline_that_was_on_it() {
8752 let mut f = Fixture::new();
8753 f.run(&[b"HSET", b"h", b"a", b"1"]);
8754 f.run(&[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"]);
8755 f.run(&[b"HSET", b"h", b"a", b"2"]);
8756 assert_eq!(
8757 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8758 "*1\r\n:-1\r\n",
8759 "Redis has done this since 7.4, and it is why HGETEX exists"
8760 );
8761 }
8762
8763 #[test]
8764 fn the_four_conditions_reach_the_store_the_way_they_were_written() {
8765 let mut f = Fixture::new();
8766 f.run(&[b"HSET", b"h", b"a", b"1"]);
8767 assert_eq!(
8768 f.run(&[b"HEXPIRE", b"h", b"100", b"XX", b"FIELDS", b"1", b"a"]),
8769 "*1\r\n:0\r\n",
8770 "XX on a field with no deadline changes nothing"
8771 );
8772 assert_eq!(
8773 f.run(&[b"HEXPIRE", b"h", b"100", b"NX", b"FIELDS", b"1", b"a"]),
8774 "*1\r\n:1\r\n"
8775 );
8776 assert_eq!(
8777 f.run(&[b"HEXPIRE", b"h", b"200", b"NX", b"FIELDS", b"1", b"a"]),
8778 "*1\r\n:0\r\n",
8779 "and NX will not move one that is already there"
8780 );
8781 assert_eq!(
8782 f.run(&[b"HEXPIRE", b"h", b"50", b"GT", b"FIELDS", b"1", b"a"]),
8783 "*1\r\n:0\r\n"
8784 );
8785 assert_eq!(
8786 f.run(&[b"HEXPIRE", b"h", b"500", b"GT", b"FIELDS", b"1", b"a"]),
8787 "*1\r\n:1\r\n"
8788 );
8789 assert_eq!(
8790 f.run(&[b"HEXPIRE", b"h", b"50", b"LT", b"FIELDS", b"1", b"a"]),
8791 "*1\r\n:1\r\n"
8792 );
8793 assert_eq!(
8794 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8795 "*1\r\n:50\r\n"
8796 );
8797 }
8798
8799 #[test]
8800 fn the_field_ttl_family_leaves_nothing_half_written_on_a_bad_argument() {
8801 let mut f = Fixture::new();
8802 f.run(&[b"HSET", b"h", b"a", b"1"]);
8803 for (bad, want) in [
8804 (
8805 &[b"HEXPIRE".as_slice(), b"h", b"-1", b"FIELDS", b"1", b"a"][..],
8806 "-ERR invalid expire time, must be >= 0",
8807 ),
8808 (
8809 &[
8810 b"HEXPIRE".as_slice(),
8811 b"h",
8812 b"9999999999999999",
8813 b"FIELDS",
8814 b"1",
8815 b"a",
8816 ][..],
8817 "-ERR invalid expire time in 'hexpire' command",
8818 ),
8819 (
8820 &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELD", b"1", b"a"][..],
8821 "-ERR wrong number of arguments for 'hexpire' command",
8822 ),
8823 (
8824 &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"0", b"a"][..],
8825 "-ERR Parameter `numFields` should be greater than 0",
8826 ),
8827 (
8828 &[b"HEXPIRE".as_slice(), b"h", b"100", b"FIELDS", b"2", b"a"][..],
8829 "-ERR wrong number of arguments",
8830 ),
8831 (
8832 &[b"HTTL".as_slice(), b"h", b"FIELDS", b"3", b"a", b"b"][..],
8833 "-ERR wrong number of arguments",
8834 ),
8835 ] {
8836 let reply = f.run(bad);
8837 assert!(reply.starts_with(want), "wanted {want}, got {reply}");
8838 assert!(!reply.contains('*'), "an array header went out in front");
8839 }
8840 assert_eq!(
8841 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8842 "*1\r\n:-1\r\n",
8843 "and not one of them put a deadline on anything"
8844 );
8845 }
8846
8847 #[test]
8848 fn every_field_ttl_command_says_wrongtype_and_writes_nothing() {
8849 let mut f = Fixture::new();
8850 f.run(&[b"SET", b"str", b"v"]);
8851 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
8852
8853 for cmd in [
8854 &[b"HEXPIRE".as_slice(), b"str", b"100", b"FIELDS", b"1", b"f"][..],
8855 &[
8856 b"HPEXPIRE".as_slice(),
8857 b"str",
8858 b"100",
8859 b"FIELDS",
8860 b"1",
8861 b"f",
8862 ][..],
8863 &[
8864 b"HEXPIREAT".as_slice(),
8865 b"str",
8866 b"9999999999",
8867 b"FIELDS",
8868 b"1",
8869 b"f",
8870 ][..],
8871 &[
8872 b"HPEXPIREAT".as_slice(),
8873 b"str",
8874 b"9999999999999",
8875 b"FIELDS",
8876 b"1",
8877 b"f",
8878 ][..],
8879 &[b"HTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8880 &[b"HPTTL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8881 &[b"HEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8882 &[b"HPEXPIRETIME".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8883 &[b"HPERSIST".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
8884 ] {
8885 assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
8886 }
8887 assert_eq!(
8888 f.run(&[b"GET", b"str"]),
8889 "$1\r\nv\r\n",
8890 "and none of them touched the value"
8891 );
8892 }
8893
8894 #[test]
8895 fn hgetdel_hands_the_value_out_and_then_takes_the_field() {
8896 let mut f = Fixture::new();
8897 f.run(&[b"HSET", b"h", b"a", b"1", b"b", b"2"]);
8898 assert_eq!(
8899 f.run(&[b"HGETDEL", b"h", b"FIELDS", b"2", b"a", b"nope"]),
8900 "*2\r\n$1\r\n1\r\n$-1\r\n",
8901 "positional, so the field that was not there is a nil in its place"
8902 );
8903 assert_eq!(f.run(&[b"HLEN", b"h"]), ":1\r\n");
8904 assert_eq!(
8905 f.run(&[b"HGETDEL", b"nokey", b"FIELDS", b"1", b"a"]),
8906 "*1\r\n$-1\r\n"
8907 );
8908 assert_eq!(
8909 f.run(&[b"HGETDEL", b"h", b"FIELDS", b"1", b"b"]),
8910 "*1\r\n$1\r\n2\r\n"
8911 );
8912 assert_eq!(
8913 f.run(&[b"EXISTS", b"h"]),
8914 ":0\r\n",
8915 "and the last field took the key"
8916 );
8917 }
8918
8919 #[test]
8920 fn hgetex_reads_and_moves_the_deadline_in_one_command() {
8921 let mut f = Fixture::new();
8922 f.run(&[b"HSET", b"h", b"a", b"1"]);
8923 assert_eq!(
8924 f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]),
8925 "*1\r\n$1\r\n1\r\n"
8926 );
8927 assert_eq!(
8928 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8929 "*1\r\n:-1\r\n",
8930 "no option means leave it alone, which is the one place this is not GETEX"
8931 );
8932
8933 f.run(&[b"HGETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a"]);
8934 assert_eq!(
8935 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8936 "*1\r\n:100\r\n"
8937 );
8938 f.run(&[b"HGETEX", b"h", b"FIELDS", b"1", b"a"]);
8939 assert_eq!(
8940 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8941 "*1\r\n:100\r\n",
8942 "and a plain read really does leave it alone"
8943 );
8944 assert_eq!(
8945 f.run(&[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"a"]),
8946 "*1\r\n$1\r\n1\r\n"
8947 );
8948 assert_eq!(
8949 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
8950 "*1\r\n:-1\r\n"
8951 );
8952
8953 assert_eq!(
8954 f.run(&[b"HGETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a"]),
8955 "*1\r\n$1\r\n1\r\n",
8956 "the value goes out before the deadline that has already gone is applied"
8957 );
8958 assert_eq!(f.run(&[b"EXISTS", b"h"]), ":0\r\n");
8959 assert_eq!(
8960 f.run(&[b"HGETEX", b"nokey", b"EX", b"100", b"FIELDS", b"1", b"a"]),
8961 "*1\r\n$-1\r\n"
8962 );
8963 }
8964
8965 #[test]
8966 fn hsetex_writes_all_of_it_or_none_of_it() {
8967 let mut f = Fixture::new();
8968 assert_eq!(
8969 f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"1"]),
8970 ":1\r\n"
8971 );
8972 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
8973 assert_eq!(
8974 f.run(&[
8975 b"HSETEX", b"h", b"FNX", b"FIELDS", b"2", b"a", b"9", b"new", b"9"
8976 ]),
8977 ":0\r\n",
8978 "FNX wants every field named to be missing"
8979 );
8980 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
8981 assert_eq!(
8982 f.run(&[b"HEXISTS", b"h", b"new"]),
8983 ":0\r\n",
8984 "and none of the list was written"
8985 );
8986 assert_eq!(
8987 f.run(&[
8988 b"HSETEX", b"h", b"FXX", b"FIELDS", b"2", b"a", b"9", b"nope", b"9"
8989 ]),
8990 ":0\r\n",
8991 "and FXX wants every one of them to be there"
8992 );
8993 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n1\r\n");
8994 assert_eq!(
8995 f.run(&[b"HSETEX", b"h", b"FXX", b"FIELDS", b"1", b"a", b"9"]),
8996 ":1\r\n"
8997 );
8998 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n9\r\n");
8999
9000 assert_eq!(
9001 f.run(&[b"HSETEX", b"gone", b"FXX", b"FIELDS", b"1", b"a", b"1"]),
9002 ":0\r\n"
9003 );
9004 assert_eq!(
9005 f.run(&[b"EXISTS", b"gone"]),
9006 ":0\r\n",
9007 "a key with no fields cannot meet FXX and is not created trying"
9008 );
9009 }
9010
9011 #[test]
9012 fn hsetex_clears_the_deadline_unless_it_is_told_to_keep_it() {
9013 let mut f = Fixture::new();
9014 f.run(&[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"a", b"1"]);
9015 assert_eq!(
9016 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9017 "*1\r\n:100\r\n"
9018 );
9019
9020 f.run(&[b"HSETEX", b"h", b"KEEPTTL", b"FIELDS", b"1", b"a", b"2"]);
9021 assert_eq!(f.run(&[b"HGET", b"h", b"a"]), "$1\r\n2\r\n");
9022 assert_eq!(
9023 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9024 "*1\r\n:100\r\n",
9025 "KEEPTTL put back what the write cleared"
9026 );
9027
9028 f.run(&[b"HSETEX", b"h", b"FIELDS", b"1", b"a", b"3"]);
9029 assert_eq!(
9030 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9031 "*1\r\n:-1\r\n",
9032 "and without it a write clears the deadline the way HSET does"
9033 );
9034
9035 assert_eq!(
9038 f.run(&[
9039 b"HSETEX", b"h", b"PX", b"100000", b"FXX", b"FIELDS", b"1", b"a", b"4"
9040 ]),
9041 ":1\r\n"
9042 );
9043 assert_eq!(
9044 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9045 "*1\r\n:100\r\n"
9046 );
9047
9048 assert_eq!(
9049 f.run(&[b"HSETEX", b"h", b"EXAT", b"1", b"FIELDS", b"1", b"a", b"5"]),
9050 ":1\r\n",
9051 "written, and not the separate code the HEXPIRE family has for this"
9052 );
9053 assert_eq!(
9054 f.run(&[b"EXISTS", b"h"]),
9055 ":0\r\n",
9056 "and storing it and then removing it emptied the hash"
9057 );
9058 }
9059
9060 #[test]
9061 fn the_last_three_hash_commands_word_their_mistakes_their_own_way() {
9062 let mut f = Fixture::new();
9063 f.run(&[b"HSET", b"h", b"a", b"1"]);
9064 for (bad, want) in [
9065 (
9067 &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
9068 "-ERR Number of fields must be a positive integer",
9069 ),
9070 (
9071 &[b"HGETDEL".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
9072 "-ERR The `numfields` parameter must match the number of arguments",
9073 ),
9074 (
9075 &[b"HGETDEL".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
9076 "-ERR Mandatory argument FIELDS is missing or not at the right position",
9077 ),
9078 (
9080 &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"0", b"a"][..],
9081 "-ERR invalid number of fields",
9082 ),
9083 (
9084 &[b"HGETEX".as_slice(), b"h", b"FIELDS", b"2", b"a"][..],
9085 "-ERR wrong number of arguments",
9086 ),
9087 (
9088 &[b"HGETEX".as_slice(), b"h", b"FIELD", b"1", b"a"][..],
9089 "-ERR unknown argument: FIELD",
9090 ),
9091 (
9092 &[
9093 b"HGETEX".as_slice(),
9094 b"h",
9095 b"KEEPTTL",
9096 b"FIELDS",
9097 b"1",
9098 b"a",
9099 ][..],
9100 "-ERR unknown argument: KEEPTTL",
9101 ),
9102 (
9103 &[
9104 b"HGETEX".as_slice(),
9105 b"h",
9106 b"EX",
9107 b"100",
9108 b"PERSIST",
9109 b"FIELDS",
9110 b"1",
9111 b"a",
9112 ][..],
9113 "-ERR Only one of EX, PX, EXAT, PXAT or PERSIST arguments can be specified",
9114 ),
9115 (
9116 &[
9117 b"HSETEX".as_slice(),
9118 b"h",
9119 b"EX",
9120 b"1",
9121 b"KEEPTTL",
9122 b"FIELDS",
9123 b"1",
9124 b"a",
9125 b"1",
9126 ][..],
9127 "-ERR Only one of EX, PX, EXAT, PXAT or KEEPTTL arguments can be specified",
9128 ),
9129 (
9130 &[
9131 b"HSETEX".as_slice(),
9132 b"h",
9133 b"FNX",
9134 b"FXX",
9135 b"FIELDS",
9136 b"1",
9137 b"a",
9138 b"1",
9139 ][..],
9140 "-ERR Only one of FXX or FNX arguments can be specified",
9141 ),
9142 (
9143 &[
9144 b"HSETEX".as_slice(),
9145 b"h",
9146 b"FIELDS",
9147 b"2",
9148 b"a",
9149 b"1",
9150 b"b",
9151 ][..],
9152 "-ERR wrong number of arguments",
9153 ),
9154 (
9155 &[
9156 b"HGETEX".as_slice(),
9157 b"h",
9158 b"EX",
9159 b"-1",
9160 b"FIELDS",
9161 b"1",
9162 b"a",
9163 ][..],
9164 "-ERR invalid expire time, must be >= 0",
9165 ),
9166 (
9167 &[
9168 b"HGETEX".as_slice(),
9169 b"h",
9170 b"PXAT",
9171 b"99999999999999",
9172 b"FIELDS",
9173 b"1",
9174 b"a",
9175 ][..],
9176 "-ERR invalid expire time in 'hgetex' command",
9177 ),
9178 (
9179 &[
9180 b"HSETEX".as_slice(),
9181 b"h",
9182 b"EX",
9183 b"abc",
9184 b"FIELDS",
9185 b"1",
9186 b"a",
9187 b"1",
9188 ][..],
9189 "-ERR value is not an integer or out of range",
9190 ),
9191 ] {
9192 let reply = f.run(bad);
9193 assert!(reply.starts_with(want), "wanted {want}, got {reply}");
9194 assert!(!reply.contains('*'), "an array header went out in front");
9195 }
9196 assert_eq!(
9197 f.run(&[b"HGET", b"h", b"a"]),
9198 "$1\r\n1\r\n",
9199 "and not one of them wrote anything"
9200 );
9201 assert_eq!(
9202 f.run(&[b"HTTL", b"h", b"FIELDS", b"1", b"a"]),
9203 "*1\r\n:-1\r\n"
9204 );
9205 }
9206
9207 #[test]
9208 fn the_last_three_hash_commands_say_wrongtype_and_write_nothing() {
9209 let mut f = Fixture::new();
9210 f.run(&[b"SET", b"str", b"v"]);
9211 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9212 for cmd in [
9213 &[b"HGETDEL".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
9214 &[b"HGETEX".as_slice(), b"str", b"FIELDS", b"1", b"f"][..],
9215 &[
9216 b"HGETEX".as_slice(),
9217 b"str",
9218 b"EX",
9219 b"100",
9220 b"FIELDS",
9221 b"1",
9222 b"f",
9223 ][..],
9224 &[b"HSETEX".as_slice(), b"str", b"FIELDS", b"1", b"f", b"v"][..],
9225 ] {
9226 assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
9227 }
9228 assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
9229 }
9230
9231 #[test]
9239 fn himport_writes_declared_values_into_sorted_fields() {
9240 let mut f = Fixture::new();
9241 assert_eq!(
9242 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"b", b"aa", b"a"]),
9243 "+OK\r\n"
9244 );
9245 assert_eq!(
9246 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2", b"3"]),
9247 "+OK\r\n"
9248 );
9249 assert_eq!(f.run(&[b"HKEYS", b"k"]), bulks(&["a", "b", "aa"]));
9250 assert_eq!(
9251 f.run(&[b"HGETALL", b"k"]),
9252 bulks(&["a", "3", "b", "1", "aa", "2"])
9253 );
9254 }
9255
9256 #[test]
9259 fn himport_set_replaces_the_whole_key() {
9260 let mut f = Fixture::new();
9261 f.run(&[b"HSET", b"k", b"gone", b"old", b"a", b"old"]);
9262 f.run(&[b"EXPIRE", b"k", b"100"]);
9263 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9264 assert_eq!(
9265 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
9266 "+OK\r\n"
9267 );
9268 assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
9269 assert_eq!(f.run(&[b"TTL", b"k"]), ":-1\r\n");
9270 }
9271
9272 #[test]
9275 fn himport_fieldsets_belong_to_the_connection_and_not_to_the_keyspace() {
9276 let mut f = Fixture::new();
9277 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a"]);
9278 f.run(&[b"SELECT", b"1"]);
9279 assert_eq!(
9280 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
9281 "+OK\r\n"
9282 );
9283 f.run(&[b"SELECT", b"0"]);
9284 assert_eq!(f.run(&[b"RESET"]), "+RESET\r\n");
9285 assert_eq!(
9286 f.run(&[b"HIMPORT", b"SET", b"k2", b"shape", b"1"]),
9287 "-ERR no such fieldset\r\n"
9288 );
9289 }
9290
9291 #[test]
9298 fn himport_complains_in_the_order_a_real_server_does() {
9299 let mut f = Fixture::new();
9300 f.run(&[b"SET", b"str", b"v"]);
9301 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9302 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9303 assert_eq!(
9304 f.run(&[b"HIMPORT", b"SET", b"str", b"nope", b"1"]),
9305 wrong,
9306 "the type beats a missing fieldset"
9307 );
9308 assert_eq!(
9309 f.run(&[b"HIMPORT", b"SET", b"str", b"shape", b"1"]),
9310 wrong,
9311 "and it beats a value count that does not fit"
9312 );
9313 assert_eq!(
9314 f.run(&[b"HIMPORT", b"SET", b"k", b"nope", b"1"]),
9315 "-ERR no such fieldset\r\n"
9316 );
9317 for values in [&[b"1".as_slice()][..], &[b"1".as_slice(), b"2", b"3"][..]] {
9319 let mut line: Vec<&[u8]> = vec![b"HIMPORT", b"SET", b"k", b"shape"];
9320 line.extend_from_slice(values);
9321 assert_eq!(
9322 f.run(&line),
9323 "-ERR value count does not match fieldset field count\r\n",
9324 "{} values into two fields",
9325 values.len()
9326 );
9327 }
9328 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
9329 }
9330
9331 #[test]
9333 fn himport_checks_each_subcommand_count_under_its_own_name() {
9334 let mut f = Fixture::new();
9335 assert_eq!(
9336 f.run(&[b"HIMPORT"]),
9337 "-ERR wrong number of arguments for 'himport' command\r\n"
9338 );
9339 for (rest, name) in [
9340 (&["PREPARE"][..], "prepare"),
9341 (&["PREPARE", "fs"][..], "prepare"),
9342 (&["SET"][..], "set"),
9343 (&["SET", "k"][..], "set"),
9344 (&["SET", "k", "fs"][..], "set"),
9345 (&["DISCARD"][..], "discard"),
9346 (&["DISCARD", "a", "b"][..], "discard"),
9347 (&["DISCARDALL", "x"][..], "discardall"),
9348 ] {
9349 let mut line: Vec<&[u8]> = vec![b"HIMPORT"];
9350 line.extend(rest.iter().map(|a| a.as_bytes()));
9351 assert_eq!(
9352 f.run(&line),
9353 format!("-ERR wrong number of arguments for 'himport|{name}' command\r\n"),
9354 "HIMPORT {}",
9355 rest.join(" ")
9356 );
9357 }
9358 assert_eq!(
9359 f.run(&[b"HIMPORT", b"NOPE", b"x"]),
9360 "-ERR unknown subcommand 'NOPE'. Try HIMPORT HELP.\r\n"
9361 );
9362 }
9363
9364 #[test]
9367 fn a_failed_himport_prepare_leaves_the_old_fieldset_alone() {
9368 let mut f = Fixture::new();
9369 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9370 assert_eq!(
9371 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"c", b"c"]),
9372 "-ERR duplicate field name in fieldset\r\n"
9373 );
9374 assert_eq!(
9375 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1", b"2"]),
9376 "+OK\r\n"
9377 );
9378 assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["a", "1", "b", "2"]));
9379 }
9380
9381 #[test]
9384 fn himport_prepare_replaces_and_the_discards_count() {
9385 let mut f = Fixture::new();
9386 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"a", b"b"]);
9387 f.run(&[b"HIMPORT", b"PREPARE", b"shape", b"z"]);
9388 assert_eq!(
9389 f.run(&[b"HIMPORT", b"SET", b"k", b"shape", b"1"]),
9390 "+OK\r\n"
9391 );
9392 assert_eq!(f.run(&[b"HGETALL", b"k"]), bulks(&["z", "1"]));
9393
9394 assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":1\r\n");
9395 assert_eq!(f.run(&[b"HIMPORT", b"DISCARD", b"shape"]), ":0\r\n");
9396 f.run(&[b"HIMPORT", b"PREPARE", b"one", b"a"]);
9397 f.run(&[b"HIMPORT", b"PREPARE", b"two", b"a"]);
9398 assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":2\r\n");
9399 assert_eq!(f.run(&[b"HIMPORT", b"DISCARDALL"]), ":0\r\n");
9400 }
9401
9402 fn int(reply: &str) -> i64 {
9408 let body = reply
9409 .strip_prefix(':')
9410 .and_then(|s| s.strip_suffix("\r\n"))
9411 .unwrap_or_else(|| panic!("wanted an integer, got {reply}"));
9412 body.parse().expect("an integer")
9413 }
9414
9415 fn int_reply(reply: &str) -> i64 {
9416 let body = reply
9417 .strip_prefix("*1\r\n:")
9418 .and_then(|s| s.strip_suffix("\r\n"))
9419 .unwrap_or_else(|| panic!("wanted one integer, got {reply}"));
9420 body.parse().expect("an integer")
9421 }
9422
9423 fn scan_reply(reply: &str) -> (String, Vec<String>) {
9425 let mut lines = reply.split("\r\n");
9426 assert_eq!(lines.next(), Some("*2"), "got {reply}");
9427 lines.next().expect("the cursor header");
9428 let cursor = lines.next().expect("a cursor").to_owned();
9429 let header = lines.next().expect("an item count");
9430 let n: usize = header[1..].parse().expect("a count");
9431 let mut items = Vec::with_capacity(n);
9432 for _ in 0..n {
9433 lines.next().expect("an item header");
9434 items.push(lines.next().expect("an item").to_owned());
9435 }
9436 (cursor, items)
9437 }
9438
9439 fn sorted(reply: &str) -> Vec<String> {
9442 let mut lines = reply.split("\r\n");
9443 let header = lines.next().expect("a header");
9444 assert!(
9445 header.starts_with('*') || header.starts_with('~'),
9446 "got {reply}"
9447 );
9448 let n: usize = header[1..].parse().expect("a member count");
9449 let mut got = Vec::with_capacity(n);
9450 for _ in 0..n {
9451 lines.next().expect("a member header");
9452 got.push(lines.next().expect("a member").to_owned());
9453 }
9454 got.sort();
9455 got
9456 }
9457
9458 #[test]
9459 fn the_algebra_answers_what_the_sets_share_and_do_not() {
9460 let mut f = Fixture::new();
9461 f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
9462 f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
9463 f.run(&[b"SADD", b"c", b"3", b"4", b"5"]);
9464
9465 assert_eq!(sorted(&f.run(&[b"SINTER", b"a", b"b", b"c"])), ["3"]);
9466 assert_eq!(
9467 sorted(&f.run(&[b"SUNION", b"a", b"b", b"c"])),
9468 ["1", "2", "3", "4", "5"]
9469 );
9470 assert_eq!(sorted(&f.run(&[b"SDIFF", b"a", b"b"])), ["1"]);
9471 assert_eq!(sorted(&f.run(&[b"SINTER", b"a"])), ["1", "2", "3"]);
9472
9473 assert_eq!(f.run(&[b"SINTER", b"a", b"nope"]), "*0\r\n");
9476 assert_eq!(sorted(&f.run(&[b"SUNION", b"a", b"nope"])), ["1", "2", "3"]);
9477 assert_eq!(f.run(&[b"SDIFF", b"nope", b"a"]), "*0\r\n");
9478 assert_eq!(f.run(&[b"DBSIZE"]), ":3\r\n", "and none of it made a key");
9479 }
9480
9481 #[test]
9482 fn the_algebra_answers_a_set_on_resp3_and_an_array_on_resp2() {
9483 let mut f = Fixture::new();
9484 f.run(&[b"SADD", b"a", b"x"]);
9485 assert_eq!(f.run(&[b"SINTER", b"a"]), "*1\r\n$1\r\nx\r\n");
9486 assert_eq!(f.run(&[b"SUNION", b"a"]), "*1\r\n$1\r\nx\r\n");
9487 assert_eq!(f.run(&[b"SDIFF", b"a"]), "*1\r\n$1\r\nx\r\n");
9488
9489 f.run(&[b"HELLO", b"3"]);
9490 assert_eq!(f.run(&[b"SINTER", b"a"]), "~1\r\n$1\r\nx\r\n");
9491 assert_eq!(f.run(&[b"SUNION", b"a"]), "~1\r\n$1\r\nx\r\n");
9492 assert_eq!(f.run(&[b"SDIFF", b"a"]), "~1\r\n$1\r\nx\r\n");
9493 assert_eq!(f.run(&[b"SINTER", b"nope"]), "~0\r\n");
9494 }
9495
9496 #[test]
9497 fn a_store_form_writes_a_key_and_answers_how_big_it_is() {
9498 let mut f = Fixture::new();
9499 f.run(&[b"SADD", b"a", b"1", b"2", b"3"]);
9500 f.run(&[b"SADD", b"b", b"2", b"3", b"4"]);
9501
9502 assert_eq!(f.run(&[b"SINTERSTORE", b"d", b"a", b"b"]), ":2\r\n");
9503 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["2", "3"]);
9504 assert_eq!(f.run(&[b"SUNIONSTORE", b"d", b"a", b"b"]), ":4\r\n");
9505 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"d"])), ["1", "2", "3", "4"]);
9506 assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"b"]), ":1\r\n");
9507 assert_eq!(f.run(&[b"SMEMBERS", b"d"]), "*1\r\n$1\r\n1\r\n");
9508
9509 assert_eq!(f.run(&[b"SDIFFSTORE", b"d", b"a", b"a"]), ":0\r\n");
9512 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
9513 assert_eq!(f.run(&[b"SINTERSTORE", b"a", b"a", b"b"]), ":2\r\n");
9514 assert_eq!(sorted(&f.run(&[b"SMEMBERS", b"a"])), ["2", "3"]);
9515
9516 f.run(&[b"SET", b"str", b"v"]);
9519 assert_eq!(f.run(&[b"SUNIONSTORE", b"str", b"b"]), ":3\r\n");
9520 assert_eq!(f.run(&[b"TYPE", b"str"]), "+set\r\n");
9521 }
9522
9523 #[test]
9524 fn sintercard_counts_without_building_and_stops_at_a_limit() {
9525 let mut f = Fixture::new();
9526 f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
9527 f.run(&[b"SADD", b"b", b"2", b"3", b"4", b"5"]);
9528
9529 assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"b"]), ":3\r\n");
9530 assert_eq!(
9531 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
9532 ":2\r\n"
9533 );
9534 assert_eq!(
9535 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
9536 ":3\r\n",
9537 "a limit of zero is no limit"
9538 );
9539 assert_eq!(f.run(&[b"SINTERCARD", b"1", b"a"]), ":4\r\n");
9540 assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"nope"]), ":0\r\n");
9541
9542 assert_eq!(
9544 f.run(&[b"SINTERCARD", b"0", b"a"]),
9545 "-ERR numkeys should be greater than 0\r\n"
9546 );
9547 assert_eq!(
9548 f.run(&[b"SINTERCARD", b"abc", b"a"]),
9549 "-ERR numkeys should be greater than 0\r\n"
9550 );
9551 assert_eq!(
9552 f.run(&[b"SINTERCARD", b"3", b"a", b"b"]),
9553 "-ERR Number of keys can't be greater than number of args\r\n"
9554 );
9555 assert_eq!(
9556 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"LIMIT", b"-1"]),
9557 "-ERR LIMIT can't be negative\r\n"
9558 );
9559 assert_eq!(
9560 f.run(&[b"SINTERCARD", b"2", b"a", b"b", b"NOPE", b"1"]),
9561 "-ERR syntax error\r\n"
9562 );
9563 f.run(&[b"SADD", b"LIMIT", b"2"]);
9565 assert_eq!(f.run(&[b"SINTERCARD", b"2", b"a", b"LIMIT"]), ":1\r\n");
9566 }
9567
9568 #[test]
9571 fn sunioncard_and_sdiffcard_count_without_building() {
9572 let mut f = Fixture::new();
9573 f.run(&[b"SADD", b"a", b"1", b"2", b"3", b"4"]);
9574 f.run(&[b"SADD", b"b", b"3", b"4", b"5", b"6"]);
9575
9576 assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"b"]), ":6\r\n");
9577 assert_eq!(
9578 f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"2"]),
9579 ":2\r\n"
9580 );
9581 assert_eq!(
9582 f.run(&[b"SUNIONCARD", b"2", b"a", b"b", b"LIMIT", b"0"]),
9583 ":6\r\n",
9584 "a limit of zero is no limit"
9585 );
9586 assert_eq!(f.run(&[b"SUNIONCARD", b"1", b"a"]), ":4\r\n");
9587 assert_eq!(
9588 f.run(&[b"SUNIONCARD", b"2", b"a", b"nope"]),
9589 ":4\r\n",
9590 "a missing key adds nothing to a union"
9591 );
9592
9593 assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"b"]), ":2\r\n");
9594 assert_eq!(
9595 f.run(&[b"SDIFFCARD", b"2", b"a", b"b", b"LIMIT", b"1"]),
9596 ":1\r\n"
9597 );
9598 assert_eq!(
9599 f.run(&[b"SDIFFCARD", b"2", b"b", b"a"]),
9600 ":2\r\n",
9601 "a difference is not symmetric"
9602 );
9603 assert_eq!(f.run(&[b"SDIFFCARD", b"1", b"a"]), ":4\r\n");
9604 assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"nope"]), ":4\r\n");
9605 assert_eq!(
9606 f.run(&[b"SDIFFCARD", b"2", b"nope", b"a"]),
9607 ":0\r\n",
9608 "nothing taken away from nothing"
9609 );
9610
9611 for name in [b"SUNIONCARD".as_slice(), b"SDIFFCARD".as_slice()] {
9614 assert_eq!(
9615 f.run(&[name, b"0", b"a"]),
9616 "-ERR numkeys should be greater than 0\r\n"
9617 );
9618 assert_eq!(
9619 f.run(&[name, b"abc", b"a"]),
9620 "-ERR numkeys should be greater than 0\r\n"
9621 );
9622 assert_eq!(
9623 f.run(&[name, b"-1", b"a"]),
9624 "-ERR numkeys should be greater than 0\r\n"
9625 );
9626 assert_eq!(
9627 f.run(&[name, b"3", b"a", b"b"]),
9628 "-ERR Number of keys can't be greater than number of args\r\n"
9629 );
9630 assert_eq!(
9631 f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"-1"]),
9632 "-ERR LIMIT can't be negative\r\n"
9633 );
9634 assert_eq!(
9635 f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"abc"]),
9636 "-ERR LIMIT can't be negative\r\n",
9637 "a LIMIT that is not a number gets the negative message too"
9638 );
9639 assert_eq!(
9640 f.run(&[name, b"2", b"a", b"b", b"NOPE", b"1"]),
9641 "-ERR syntax error\r\n"
9642 );
9643 assert_eq!(
9644 f.run(&[name, b"2", b"a", b"b", b"LIMIT"]),
9645 "-ERR syntax error\r\n"
9646 );
9647 assert_eq!(
9648 f.run(&[name, b"2", b"a", b"b", b"LIMIT", b"1", b"X"]),
9649 "-ERR syntax error\r\n"
9650 );
9651 }
9652
9653 f.run(&[b"SADD", b"LIMIT", b"2"]);
9655 assert_eq!(f.run(&[b"SUNIONCARD", b"2", b"a", b"LIMIT"]), ":4\r\n");
9656 assert_eq!(f.run(&[b"SDIFFCARD", b"2", b"a", b"LIMIT"]), ":3\r\n");
9657 }
9658
9659 #[test]
9660 fn the_algebra_answers_wrongtype_before_it_writes_anything() {
9661 let mut f = Fixture::new();
9662 f.run(&[b"SADD", b"a", b"1"]);
9663 f.run(&[b"SADD", b"d", b"old"]);
9664 f.run(&[b"SET", b"str", b"v"]);
9665
9666 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
9667 for bad in [
9668 &[b"SINTER".as_slice(), b"a", b"str"][..],
9669 &[b"SUNION".as_slice(), b"str"][..],
9670 &[b"SDIFF".as_slice(), b"a", b"str"][..],
9671 &[b"SINTERCARD".as_slice(), b"2", b"a", b"str"][..],
9672 &[b"SINTERSTORE".as_slice(), b"d", b"a", b"str"][..],
9673 &[b"SUNIONSTORE".as_slice(), b"d", b"str"][..],
9674 &[b"SDIFFSTORE".as_slice(), b"d", b"a", b"str"][..],
9675 ] {
9676 let reply = f.run(bad);
9677 assert_eq!(reply, wrong, "for {:?}", bad[0]);
9678 }
9679 assert_eq!(
9680 f.run(&[b"SMEMBERS", b"d"]),
9681 "*1\r\n$3\r\nold\r\n",
9682 "and the destination was left alone every time"
9683 );
9684 }
9685
9686 #[cfg_attr(miri, ignore = "the volume is the claim")]
9698 #[test]
9699 fn churning_sets_does_not_grow_the_server() {
9700 let mut f = Fixture::new();
9701 let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
9702 let args: Vec<&[u8]> = std::iter::once(&b"SADD"[..])
9703 .chain(std::iter::once(&b"s"[..]))
9704 .chain(members.iter().map(Vec::as_slice))
9705 .collect();
9706
9707 f.run(&args);
9708 f.run(&[b"DEL", b"s"]);
9709 f.server.compact_step();
9710 let after_first = f.server.memory_bytes();
9711
9712 for _ in 0..200 {
9713 f.run(&args);
9714 f.run(&[b"DEL", b"s"]);
9715 f.server.compact_step();
9716 }
9717 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
9718 assert!(
9719 f.server.memory_bytes() <= after_first * 2,
9720 "held {} after two hundred passes against {after_first} after one",
9721 f.server.memory_bytes()
9722 );
9723 }
9724
9725 #[test]
9733 fn a_bit_is_written_and_read_back_and_a_write_unpacks_an_int() {
9734 let mut f = Fixture::new();
9735 assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"1"]), ":0\r\n");
9736 assert_eq!(f.run(&[b"GET", b"k"]), "$1\r\n\u{1}\r\n");
9737 assert_eq!(f.run(&[b"GETBIT", b"k", b"7"]), ":1\r\n");
9738 assert_eq!(f.run(&[b"GETBIT", b"k", b"6"]), ":0\r\n");
9739 assert_eq!(f.run(&[b"GETBIT", b"k", b"100"]), ":0\r\n");
9740 assert_eq!(f.run(&[b"SETBIT", b"k", b"7", b"0"]), ":1\r\n");
9741
9742 assert_eq!(f.run(&[b"SETBIT", b"nk", b"0", b"0"]), ":0\r\n");
9744 assert_eq!(f.run(&[b"STRLEN", b"nk"]), ":1\r\n");
9745 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"nk"]), "$3\r\nraw\r\n");
9746
9747 f.run(&[b"SET", b"num", b"12345"]);
9748 assert_eq!(f.run(&[b"GETBIT", b"num", b"1"]), ":0\r\n");
9749 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nint\r\n");
9750 assert_eq!(f.run(&[b"SETBIT", b"num", b"1", b"1"]), ":0\r\n");
9751 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"num"]), "$3\r\nraw\r\n");
9752 assert_eq!(f.run(&[b"GET", b"num"]), "$5\r\nq2345\r\n");
9753 }
9754
9755 #[test]
9760 fn bits_are_counted_over_a_range_of_bytes_or_of_bits() {
9761 let mut f = Fixture::new();
9762 f.run(&[b"SET", b"mykey", b"foobar"]);
9763 assert_eq!(f.run(&[b"BITCOUNT", b"mykey"]), ":26\r\n");
9764 assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"0", b"0"]), ":4\r\n");
9765 assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"1", b"1"]), ":6\r\n");
9766 assert_eq!(
9767 f.run(&[b"BITCOUNT", b"mykey", b"1", b"1", b"BYTE"]),
9768 ":6\r\n"
9769 );
9770 assert_eq!(
9771 f.run(&[b"BITCOUNT", b"mykey", b"0", b"-5", b"BIT"]),
9772 ":25\r\n"
9773 );
9774 assert_eq!(
9775 f.run(&[b"BITCOUNT", b"mykey", b"5", b"30", b"BIT"]),
9776 ":17\r\n"
9777 );
9778 assert_eq!(f.run(&[b"BITCOUNT", b"nokey"]), ":0\r\n");
9779
9780 assert_eq!(f.run(&[b"BITCOUNT", b"mykey", b"10", b"20"]), ":0\r\n");
9783
9784 assert_eq!(
9786 f.run(&[b"BITCOUNT", b"mykey", b"0"]),
9787 "-ERR syntax error\r\n"
9788 );
9789 assert_eq!(
9790 f.run(&[b"BITCOUNT", b"mykey", b"0", b"1", b"NIB"]),
9791 "-ERR syntax error\r\n"
9792 );
9793 }
9794
9795 #[test]
9801 fn a_search_for_a_nought_past_the_end_answers_the_length_in_bits() {
9802 let mut f = Fixture::new();
9803 f.run(&[b"SET", b"ones", b"\xff\xff\xff"]);
9804 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0"]), ":24\r\n");
9805 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0"]), ":24\r\n");
9806 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"-1"]), ":-1\r\n");
9807 assert_eq!(f.run(&[b"BITPOS", b"ones", b"0", b"0", b"3"]), ":-1\r\n");
9808 assert_eq!(f.run(&[b"BITPOS", b"ones", b"1"]), ":0\r\n");
9809
9810 f.run(&[b"SET", b"mid", b"\x00\xff\xf0"]);
9811 assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"0"]), ":8\r\n");
9812 assert_eq!(f.run(&[b"BITPOS", b"mid", b"1", b"2"]), ":16\r\n");
9813 assert_eq!(
9814 f.run(&[b"BITPOS", b"mid", b"1", b"0", b"-1", b"BIT"]),
9815 ":8\r\n"
9816 );
9817
9818 assert_eq!(f.run(&[b"BITPOS", b"gone", b"1"]), ":-1\r\n");
9821 assert_eq!(f.run(&[b"BITPOS", b"gone", b"0"]), ":0\r\n");
9822 }
9823
9824 #[test]
9826 fn the_eight_combinations_write_what_a_real_server_writes() {
9827 let mut f = Fixture::new();
9828 f.run(&[b"SET", b"a", b"abc"]);
9829 f.run(&[b"SET", b"b", b"abd"]);
9830 let cases: &[(&[u8], &str)] = &[
9831 (b"AND", "ab`"),
9832 (b"OR", "abg"),
9833 (b"XOR", "\u{0}\u{0}\u{7}"),
9834 (b"DIFF", "\u{0}\u{0}\u{3}"),
9835 (b"DIFF1", "\u{0}\u{0}\u{4}"),
9836 (b"ANDOR", "ab`"),
9837 (b"ONE", "\u{0}\u{0}\u{7}"),
9838 ];
9839 for (op, want) in cases {
9840 assert_eq!(f.run(&[b"BITOP", op, b"d", b"a", b"b"]), ":3\r\n", "{op:?}");
9841 assert_eq!(
9842 f.run(&[b"GET", b"d"]),
9843 format!("$3\r\n{want}\r\n"),
9844 "{op:?}"
9845 );
9846 }
9847 assert_eq!(f.run(&[b"BITOP", b"NOT", b"d", b"a"]), ":3\r\n");
9849 assert_eq!(f.raw(&[b"GET", b"d"]), b"$3\r\n\x9e\x9d\x9c\r\n".to_vec());
9850
9851 assert_eq!(f.run(&[b"BITOP", b"AND", b"d", b"a", b"gone"]), ":3\r\n");
9854 assert_eq!(f.run(&[b"GET", b"d"]), "$3\r\n\u{0}\u{0}\u{0}\r\n");
9855
9856 f.run(&[b"SET", b"dest", b"x"]);
9859 assert_eq!(f.run(&[b"BITOP", b"AND", b"dest", b"g1", b"g2"]), ":0\r\n");
9860 assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
9861 }
9862
9863 #[test]
9865 fn bitop_names_the_operation_in_its_own_complaints() {
9866 let mut f = Fixture::new();
9867 f.run(&[b"SET", b"a", b"abc"]);
9868 assert_eq!(
9869 f.run(&[b"BITOP", b"nope", b"d", b"a"]),
9870 "-ERR syntax error\r\n"
9871 );
9872 assert_eq!(
9873 f.run(&[b"BITOP", b"NOT", b"d", b"a", b"a"]),
9874 "-ERR BITOP NOT must be called with a single source key.\r\n"
9875 );
9876 for op in [&b"DIFF"[..], b"DIFF1", b"ANDOR"] {
9877 assert_eq!(
9878 f.run(&[b"BITOP", op, b"d", b"a"]),
9879 format!(
9880 "-ERR BITOP {} must be called with at least two source keys.\r\n",
9881 String::from_utf8_lossy(op)
9882 )
9883 );
9884 }
9885 f.run(&[b"LPUSH", b"l", b"x"]);
9886 assert_eq!(
9887 f.run(&[b"BITOP", b"AND", b"d", b"a", b"l"]),
9888 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
9889 );
9890 }
9891
9892 #[test]
9894 fn bitfield_reads_and_writes_packed_fields() {
9895 let mut f = Fixture::new();
9896 assert_eq!(f.run(&[b"BITFIELD", b"bf"]), "*0\r\n");
9897 assert_eq!(f.run(&[b"EXISTS", b"bf"]), ":0\r\n");
9898
9899 assert_eq!(
9900 f.run(&[
9901 b"BITFIELD",
9902 b"bf",
9903 b"INCRBY",
9904 b"u2",
9905 b"100",
9906 b"1",
9907 b"GET",
9908 b"u4",
9909 b"0"
9910 ]),
9911 "*2\r\n:1\r\n:0\r\n"
9912 );
9913 assert_eq!(f.run(&[b"STRLEN", b"bf"]), ":13\r\n");
9916
9917 assert_eq!(
9919 f.run(&[
9920 b"BITFIELD",
9921 b"bf",
9922 b"SET",
9923 b"u8",
9924 b"#0",
9925 b"255",
9926 b"GET",
9927 b"u8",
9928 b"#0"
9929 ]),
9930 "*2\r\n:0\r\n:255\r\n"
9931 );
9932
9933 assert_eq!(
9934 f.run(&[
9935 b"BITFIELD",
9936 b"bf",
9937 b"OVERFLOW",
9938 b"SAT",
9939 b"INCRBY",
9940 b"i8",
9941 b"0",
9942 b"120",
9943 b"INCRBY",
9944 b"i8",
9945 b"0",
9946 b"120"
9947 ]),
9948 "*2\r\n:119\r\n:127\r\n"
9949 );
9950 assert_eq!(
9951 f.run(&[
9952 b"BITFIELD",
9953 b"bf2",
9954 b"OVERFLOW",
9955 b"FAIL",
9956 b"INCRBY",
9957 b"u2",
9958 b"0",
9959 b"5"
9960 ]),
9961 "*1\r\n$-1\r\n"
9962 );
9963 assert_eq!(
9964 f.run(&[
9965 b"BITFIELD",
9966 b"bf3",
9967 b"OVERFLOW",
9968 b"WRAP",
9969 b"INCRBY",
9970 b"u2",
9971 b"0",
9972 b"5"
9973 ]),
9974 "*1\r\n:1\r\n"
9975 );
9976 assert_eq!(
9977 f.run(&[b"BITFIELD", b"bf3", b"GET", b"i64", b"0"]),
9978 "*1\r\n:4611686018427387904\r\n"
9979 );
9980 }
9981
9982 #[test]
9988 fn a_bad_bitfield_subcommand_leaves_the_key_alone() {
9989 let mut f = Fixture::new();
9990 let bad_type = "-ERR Invalid bitfield type. Use something like i16 u8. Note that u64 is not supported but i64 is.\r\n";
9991 assert_eq!(
9992 f.run(&[
9993 b"BITFIELD",
9994 b"bad",
9995 b"SET",
9996 b"u8",
9997 b"0",
9998 b"1",
9999 b"GET",
10000 b"u99",
10001 b"0"
10002 ]),
10003 bad_type
10004 );
10005 assert_eq!(f.run(&[b"EXISTS", b"bad"]), ":0\r\n");
10006 assert_eq!(
10007 f.run(&[b"BITFIELD", b"bad", b"GET", b"u64", b"0"]),
10008 bad_type
10009 );
10010 assert_eq!(
10011 f.run(&[b"BITFIELD", b"bad", b"GET"]),
10012 "-ERR syntax error\r\n"
10013 );
10014 assert_eq!(
10015 f.run(&[b"BITFIELD", b"bad", b"NOPE", b"u8", b"0"]),
10016 "-ERR syntax error\r\n"
10017 );
10018 assert_eq!(
10019 f.run(&[b"BITFIELD", b"bad", b"OVERFLOW"]),
10020 "-ERR syntax error\r\n"
10021 );
10022 assert_eq!(
10023 f.run(&[
10024 b"BITFIELD",
10025 b"bad",
10026 b"OVERFLOW",
10027 b"NOPE",
10028 b"GET",
10029 b"u8",
10030 b"0"
10031 ]),
10032 "-ERR Invalid OVERFLOW type specified\r\n"
10033 );
10034 assert_eq!(
10035 f.run(&[b"BITFIELD", b"bad", b"SET", b"u8", b"0", b"notanum"]),
10036 "-ERR value is not an integer or out of range\r\n"
10037 );
10038 for at in [&b"#-1"[..], b"abc"] {
10039 assert_eq!(
10040 f.run(&[b"BITFIELD", b"bad", b"GET", b"u8", at]),
10041 "-ERR bit offset is not an integer or out of range\r\n"
10042 );
10043 }
10044 }
10045
10046 #[test]
10048 fn bitfield_ro_answers_gets_and_refuses_the_rest() {
10049 let mut f = Fixture::new();
10050 f.run(&[b"SET", b"n", b"123"]);
10051 assert_eq!(
10052 f.run(&[b"BITFIELD_RO", b"n", b"GET", b"u8", b"0"]),
10053 "*1\r\n:49\r\n"
10054 );
10055 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"n"]), "$3\r\nint\r\n");
10057
10058 assert_eq!(
10060 f.run(&[
10061 b"BITFIELD_RO",
10062 b"n",
10063 b"OVERFLOW",
10064 b"SAT",
10065 b"GET",
10066 b"u8",
10067 b"0"
10068 ]),
10069 "*1\r\n:49\r\n"
10070 );
10071 for sub in [&b"SET"[..], b"INCRBY"] {
10072 assert_eq!(
10073 f.run(&[b"BITFIELD_RO", b"n", sub, b"u8", b"0", b"1"]),
10074 "-ERR BITFIELD_RO only supports the GET subcommand\r\n"
10075 );
10076 }
10077
10078 assert_eq!(
10079 f.run(&[b"BITFIELD_RO", b"gone", b"GET", b"u8", b"100"]),
10080 "*1\r\n:0\r\n"
10081 );
10082 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
10083 }
10084
10085 #[test]
10087 fn an_offset_off_the_end_of_the_world_is_refused() {
10088 let mut f = Fixture::new();
10089 let bad = "-ERR bit offset is not an integer or out of range\r\n";
10090 for arg in [&b"abc"[..], b"-1", b"4294967296"] {
10091 assert_eq!(f.run(&[b"SETBIT", b"k", arg, b"1"]), bad);
10092 assert_eq!(f.run(&[b"GETBIT", b"k", arg]), bad);
10093 }
10094 for arg in [&b"2"[..], b"-1"] {
10095 assert_eq!(
10096 f.run(&[b"BITPOS", b"k", arg]),
10097 "-ERR The bit argument must be 1 or 0.\r\n"
10098 );
10099 }
10100 assert_eq!(
10101 f.run(&[b"BITPOS", b"k", b"abc"]),
10102 "-ERR value is not an integer or out of range\r\n"
10103 );
10104 assert_eq!(
10105 f.run(&[b"BITPOS", b"k", b"0", b"5", b"BIT"]),
10106 "-ERR value is not an integer or out of range\r\n"
10107 );
10108 let bad_bit = "-ERR bit is not an integer or out of range\r\n";
10109 assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"2"]), bad_bit);
10110 assert_eq!(f.run(&[b"SETBIT", b"k", b"0", b"abc"]), bad_bit);
10111 }
10112
10113 #[test]
10115 fn every_bitmap_command_says_wrongtype() {
10116 let mut f = Fixture::new();
10117 f.run(&[b"LPUSH", b"l", b"x"]);
10118 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10119 let cases: &[&[&[u8]]] = &[
10120 &[b"SETBIT", b"l", b"0", b"1"],
10121 &[b"GETBIT", b"l", b"0"],
10122 &[b"BITCOUNT", b"l"],
10123 &[b"BITPOS", b"l", b"1"],
10124 &[b"BITOP", b"AND", b"d", b"l"],
10125 &[b"BITFIELD", b"l", b"GET", b"u8", b"0"],
10126 &[b"BITFIELD_RO", b"l", b"GET", b"u8", b"0"],
10127 ];
10128 for case in cases {
10129 assert_eq!(f.run(case), wrong, "{:?}", case[0]);
10130 }
10131 }
10132
10133 #[test]
10136 fn a_sketch_is_added_to_and_counted() {
10137 let mut f = Fixture::new();
10138 assert_eq!(f.run(&[b"PFADD", b"h"]), ":1\r\n");
10140 assert_eq!(f.run(&[b"PFADD", b"h"]), ":0\r\n");
10141 assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":0\r\n");
10142 assert_eq!(f.run(&[b"STRLEN", b"h"]), ":18\r\n");
10143 assert_eq!(f.run(&[b"TYPE", b"h"]), "+string\r\n");
10146 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"h"]), "$3\r\nraw\r\n");
10147
10148 assert_eq!(f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]), ":1\r\n");
10149 assert_eq!(f.run(&[b"PFADD", b"h", b"a"]), ":0\r\n");
10150 assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
10151 }
10152
10153 #[test]
10154 fn the_bytes_of_a_sketch_are_the_ones_a_real_server_writes() {
10155 let mut f = Fixture::new();
10156 f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
10157 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";
10159 let mut reply = b"$27\r\n".to_vec();
10160 reply.extend_from_slice(want);
10161 reply.extend_from_slice(b"\r\n");
10162 assert_eq!(f.raw(&[b"GET", b"h"]), reply);
10163 }
10164
10165 #[test]
10166 fn counting_several_keys_counts_their_union() {
10167 let mut f = Fixture::new();
10168 f.run(&[b"PFADD", b"a", b"x", b"y"]);
10169 f.run(&[b"PFADD", b"b", b"y", b"z"]);
10170 assert_eq!(f.run(&[b"PFCOUNT", b"a"]), ":2\r\n");
10171 assert_eq!(f.run(&[b"PFCOUNT", b"a", b"b"]), ":3\r\n");
10172 assert_eq!(f.run(&[b"PFCOUNT", b"gone"]), ":0\r\n");
10175 assert_eq!(f.run(&[b"PFCOUNT", b"a", b"gone"]), ":2\r\n");
10176 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
10177 }
10178
10179 #[test]
10180 fn a_merge_keeps_what_the_destination_had() {
10181 let mut f = Fixture::new();
10182 f.run(&[b"PFADD", b"a", b"x", b"y"]);
10183 f.run(&[b"PFADD", b"b", b"z"]);
10184 assert_eq!(f.run(&[b"PFMERGE", b"d", b"a", b"b"]), "+OK\r\n");
10185 assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":3\r\n");
10186 f.run(&[b"PFADD", b"c", b"w"]);
10188 assert_eq!(f.run(&[b"PFMERGE", b"d", b"c"]), "+OK\r\n");
10189 assert_eq!(f.run(&[b"PFCOUNT", b"d"]), ":4\r\n");
10190 assert_eq!(f.run(&[b"PFMERGE", b"fresh"]), "+OK\r\n");
10193 assert_eq!(f.run(&[b"PFCOUNT", b"fresh"]), ":0\r\n");
10194 }
10195
10196 #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
10205 #[test]
10206 fn the_debug_forms_answer_four_different_shapes() {
10207 let mut f = Fixture::new();
10208 f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
10209 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+sparse\r\n");
10210 assert_eq!(
10211 f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
10212 "$44\r\nZ:8436 v:1,1 Z:4274 v:2,1 Z:3068 v:1,1 Z:603\r\n"
10213 );
10214 assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":1\r\n");
10215 assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"h"]), ":0\r\n");
10216 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"h"]), "+dense\r\n");
10217 assert_eq!(f.run(&[b"STRLEN", b"h"]), ":12304\r\n");
10218 assert_eq!(f.run(&[b"PFCOUNT", b"h"]), ":3\r\n");
10219 assert_eq!(
10221 f.run(&[b"PFDEBUG", b"DECODE", b"h"]),
10222 "-ERR HLL encoding is not sparse\r\n"
10223 );
10224
10225 let reply = f.run(&[b"PFDEBUG", b"GETREG", b"h"]);
10227 assert!(reply.starts_with("*16384\r\n"), "{}", &reply[..16]);
10228 assert_eq!(reply.matches(":0\r\n").count(), 16381);
10229 assert_eq!(reply.matches(":1\r\n").count(), 2);
10230 assert_eq!(reply.matches(":2\r\n").count(), 1);
10231
10232 assert_eq!(f.run(&[b"PFSELFTEST"]), "+OK\r\n");
10233 }
10234
10235 #[test]
10236 fn a_string_that_is_not_a_sketch_is_refused_with_its_own_sentence() {
10237 let mut f = Fixture::new();
10238 f.run(&[b"SET", b"plain", b"not a sketch"]);
10239 let not_hll = "-WRONGTYPE Key is not a valid HyperLogLog string value.\r\n";
10240 assert_eq!(f.run(&[b"PFADD", b"plain", b"a"]), not_hll);
10241 assert_eq!(f.run(&[b"PFCOUNT", b"plain"]), not_hll);
10242 assert_eq!(f.run(&[b"PFMERGE", b"plain"]), not_hll);
10243 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"plain"]), not_hll);
10244
10245 f.run(&[b"RPUSH", b"l", b"x"]);
10248 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10249 assert_eq!(f.run(&[b"PFADD", b"l", b"a"]), wrong);
10250 assert_eq!(f.run(&[b"PFCOUNT", b"l"]), wrong);
10251 assert_eq!(f.run(&[b"PFMERGE", b"dest", b"l"]), wrong);
10252 assert_eq!(f.run(&[b"EXISTS", b"dest"]), ":0\r\n");
10253 assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"l"]), wrong);
10254 }
10255
10256 #[test]
10257 fn pfdebug_has_its_own_complaints() {
10258 let mut f = Fixture::new();
10259 f.run(&[b"PFADD", b"h", b"a"]);
10260 assert_eq!(
10263 f.run(&[b"PFDEBUG", b"NOPE", b"h"]),
10264 "-ERR Unknown PFDEBUG subcommand 'NOPE'\r\n"
10265 );
10266 let gone = "-ERR The specified key does not exist\r\n";
10268 assert_eq!(f.run(&[b"PFDEBUG", b"GETREG", b"missing"]), gone);
10269 assert_eq!(f.run(&[b"PFDEBUG", b"DECODE", b"missing"]), gone);
10270 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"missing"]), gone);
10271 assert_eq!(f.run(&[b"PFDEBUG", b"TODENSE", b"missing"]), gone);
10272 assert_eq!(
10273 f.run(&[b"PFDEBUG"]),
10274 "-ERR wrong number of arguments for 'pfdebug' command\r\n"
10275 );
10276 assert_eq!(
10277 f.run(&[b"PFSELFTEST", b"x"]),
10278 "-ERR wrong number of arguments for 'pfselftest' command\r\n"
10279 );
10280 }
10281
10282 #[test]
10283 fn a_sketch_whose_opcodes_do_not_add_up_says_so() {
10284 let mut f = Fixture::new();
10285 f.run(&[b"PFADD", b"h", b"a", b"b", b"c"]);
10286 let reply = f.raw(&[b"GET", b"h"]);
10289 let short = reply[5..reply.len() - 3].to_vec();
10290 f.run(&[b"SET", b"h", &short]);
10291 assert_eq!(
10292 f.run(&[b"PFCOUNT", b"h"]),
10293 "-INVALIDOBJ Corrupted HLL object detected\r\n"
10294 );
10295 }
10296
10297 #[test]
10298 fn a_sketch_survives_a_dump_and_a_restore_in_both_encodings() {
10299 let mut f = Fixture::new();
10300 f.run(&[b"PFADD", b"small", b"a", b"b", b"c"]);
10303 if cfg!(miri) {
10310 f.run(&[b"PFADD", b"big", b"a", b"b", b"c"]);
10311 f.run(&[b"PFDEBUG", b"TODENSE", b"big"]);
10312 } else {
10313 for i in 0..10_000u32 {
10314 let ele = format!("e{i}");
10315 f.run(&[b"PFADD", b"big", ele.as_bytes()]);
10316 }
10317 }
10318 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"small"]), "+sparse\r\n");
10319 assert_eq!(f.run(&[b"PFDEBUG", b"ENCODING", b"big"]), "+dense\r\n");
10320
10321 for key in [&b"small"[..], b"big"] {
10322 let mut copy = key.to_vec();
10323 copy.push(b'2');
10324 let bytes = payload(&f.raw(&[b"DUMP", key]));
10325 assert_eq!(f.run(&[b"RESTORE", ©, b"0", &bytes]), "+OK\r\n");
10326 assert_eq!(f.raw(&[b"GET", ©]), f.raw(&[b"GET", key]));
10329 assert_eq!(
10330 f.run(&[b"PFDEBUG", b"ENCODING", ©]),
10331 f.run(&[b"PFDEBUG", b"ENCODING", key])
10332 );
10333 assert_eq!(f.run(&[b"PFCOUNT", ©]), f.run(&[b"PFCOUNT", key]));
10334 }
10335 assert_eq!(f.run(&[b"PFCOUNT", b"small2"]), ":3\r\n");
10336 assert_eq!(f.run(&[b"STRLEN", b"big2"]), ":12304\r\n");
10337 }
10338
10339 fn bulk(s: &str) -> String {
10343 format!("${}\r\n{s}\r\n", s.len())
10344 }
10345
10346 fn bulks(parts: &[&str]) -> String {
10349 let mut s = format!("*{}\r\n", parts.len());
10350 for p in parts {
10351 s.push_str(&format!("${}\r\n{p}\r\n", p.len()));
10352 }
10353 s
10354 }
10355
10356 #[test]
10357 fn a_list_is_pushed_from_both_ends_and_the_left_one_reverses() {
10358 let mut f = Fixture::new();
10359 assert_eq!(f.run(&[b"LPUSH", b"k", b"a", b"b", b"c"]), ":3\r\n");
10363 assert_eq!(
10364 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10365 bulks(&["c", "b", "a"])
10366 );
10367 assert_eq!(f.run(&[b"RPUSH", b"k", b"d"]), ":4\r\n");
10368 assert_eq!(f.run(&[b"LLEN", b"k"]), ":4\r\n");
10369 assert_eq!(f.run(&[b"LPOP", b"k"]), "$1\r\nc\r\n");
10370 assert_eq!(f.run(&[b"RPOP", b"k"]), "$1\r\nd\r\n");
10371 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "a"]));
10372 assert_eq!(f.run(&[b"TYPE", b"k"]), "+list\r\n");
10373 }
10374
10375 #[test]
10376 fn the_x_pushes_refuse_to_bring_a_list_back_to_life() {
10377 let mut f = Fixture::new();
10378 assert_eq!(f.run(&[b"LPUSHX", b"k", b"a"]), ":0\r\n");
10379 assert_eq!(f.run(&[b"RPUSHX", b"k", b"a"]), ":0\r\n");
10380 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
10381 f.run(&[b"RPUSH", b"k", b"a"]);
10382 assert_eq!(f.run(&[b"LPUSHX", b"k", b"z"]), ":2\r\n");
10383 assert_eq!(f.run(&[b"RPUSHX", b"k", b"y"]), ":3\r\n");
10384 assert_eq!(
10385 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10386 bulks(&["z", "a", "y"])
10387 );
10388 }
10389
10390 #[test]
10393 fn an_empty_pop_is_a_different_nothing_with_a_count_and_without() {
10394 let mut f = Fixture::new();
10395 assert_eq!(f.run(&[b"LPOP", b"nope"]), "$-1\r\n");
10396 assert_eq!(f.run(&[b"LPOP", b"nope", b"2"]), "*-1\r\n");
10397 assert_eq!(f.run(&[b"RPOP", b"nope"]), "$-1\r\n");
10398 assert_eq!(f.run(&[b"RPOP", b"nope", b"2"]), "*-1\r\n");
10399 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
10400 assert_eq!(f.run(&[b"LPOP", b"k", b"0"]), "*0\r\n");
10403 assert_eq!(f.run(&[b"LPOP", b"k", b"1"]), bulks(&["a"]));
10404 assert_eq!(f.run(&[b"RPOP", b"k", b"9"]), bulks(&["c", "b"]));
10406 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
10407 }
10408
10409 #[test]
10410 fn a_pop_count_has_its_own_sentence_and_a_third_argument_is_an_arity_error() {
10411 let mut f = Fixture::new();
10412 f.run(&[b"RPUSH", b"k", b"a"]);
10413 let range = "-ERR value is out of range, must be positive\r\n";
10414 assert_eq!(f.run(&[b"LPOP", b"k", b"-1"]), range);
10415 assert_eq!(f.run(&[b"LPOP", b"k", b"abc"]), range);
10416 assert_eq!(f.run(&[b"RPOP", b"k", b"-1"]), range);
10417 assert_eq!(
10420 f.run(&[b"LPOP", b"k", b"1", b"2"]),
10421 "-ERR wrong number of arguments for 'lpop' command\r\n"
10422 );
10423 assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
10424 }
10425
10426 #[test]
10427 fn a_range_takes_negative_ends_and_clamps_the_ones_that_run_off() {
10428 let mut f = Fixture::new();
10429 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
10430 assert_eq!(
10431 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10432 bulks(&["a", "b", "c"])
10433 );
10434 assert_eq!(f.run(&[b"LRANGE", b"k", b"-2", b"-1"]), bulks(&["b", "c"]));
10435 assert_eq!(f.run(&[b"LRANGE", b"k", b"1", b"1"]), bulks(&["b"]));
10436 assert_eq!(f.run(&[b"LRANGE", b"k", b"5", b"10"]), "*0\r\n");
10437 assert_eq!(f.run(&[b"LRANGE", b"k", b"2", b"1"]), "*0\r\n");
10438 assert_eq!(
10439 f.run(&[b"LRANGE", b"k", b"-100", b"100"]),
10440 bulks(&["a", "b", "c"])
10441 );
10442 assert_eq!(f.run(&[b"LRANGE", b"nope", b"0", b"-1"]), "*0\r\n");
10445 assert_eq!(
10446 f.run(&[b"LRANGE", b"k", b"a", b"b"]),
10447 "-ERR value is not an integer or out of range\r\n"
10448 );
10449 }
10450
10451 #[test]
10452 fn an_index_reads_and_writes_from_whichever_end_is_nearer() {
10453 let mut f = Fixture::new();
10454 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
10455 assert_eq!(f.run(&[b"LINDEX", b"k", b"0"]), "$1\r\na\r\n");
10456 assert_eq!(f.run(&[b"LINDEX", b"k", b"-1"]), "$1\r\nc\r\n");
10457 assert_eq!(f.run(&[b"LINDEX", b"k", b"99"]), "$-1\r\n");
10458 assert_eq!(f.run(&[b"LINDEX", b"nope", b"0"]), "$-1\r\n");
10459 assert_eq!(f.run(&[b"LSET", b"k", b"-1", b"z"]), "+OK\r\n");
10460 assert_eq!(
10461 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10462 bulks(&["a", "b", "z"])
10463 );
10464 assert_eq!(
10467 f.run(&[b"LSET", b"k", b"99", b"z"]),
10468 "-ERR index out of range\r\n"
10469 );
10470 assert_eq!(
10471 f.run(&[b"LSET", b"nope", b"0", b"z"]),
10472 "-ERR no such key\r\n"
10473 );
10474 }
10475
10476 #[test]
10477 fn linsert_says_three_things_with_one_signed_number() {
10478 let mut f = Fixture::new();
10479 assert_eq!(
10482 f.run(&[b"LINSERT", b"nope", b"BEFORE", b"a", b"x"]),
10483 ":0\r\n"
10484 );
10485 f.run(&[b"RPUSH", b"k", b"a", b"b"]);
10486 assert_eq!(f.run(&[b"LINSERT", b"k", b"before", b"a", b"X"]), ":3\r\n");
10487 assert_eq!(f.run(&[b"LINSERT", b"k", b"AFTER", b"b", b"Y"]), ":4\r\n");
10488 assert_eq!(
10489 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10490 bulks(&["X", "a", "b", "Y"])
10491 );
10492 assert_eq!(
10493 f.run(&[b"LINSERT", b"k", b"BEFORE", b"zz", b"x"]),
10494 ":-1\r\n"
10495 );
10496 assert_eq!(
10497 f.run(&[b"LINSERT", b"k", b"SIDEWAYS", b"a", b"x"]),
10498 "-ERR syntax error\r\n"
10499 );
10500 }
10501
10502 #[test]
10503 fn lrem_counts_in_three_directions_and_takes_the_key_when_it_empties() {
10504 let mut f = Fixture::new();
10505 f.run(&[b"RPUSH", b"k", b"a", b"b", b"a", b"c", b"a"]);
10506 assert_eq!(f.run(&[b"LREM", b"k", b"2", b"a"]), ":2\r\n");
10507 assert_eq!(
10508 f.run(&[b"LRANGE", b"k", b"0", b"-1"]),
10509 bulks(&["b", "c", "a"])
10510 );
10511 assert_eq!(f.run(&[b"LREM", b"k", b"-1", b"a"]), ":1\r\n");
10512 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
10513 assert_eq!(f.run(&[b"LREM", b"k", b"0", b"b"]), ":1\r\n");
10514 assert_eq!(f.run(&[b"LREM", b"k", b"0", b"c"]), ":1\r\n");
10515 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
10516 assert_eq!(f.run(&[b"LREM", b"nope", b"0", b"a"]), ":0\r\n");
10517 }
10518
10519 #[test]
10520 fn ltrim_keeps_a_window_and_an_empty_one_deletes_the_key() {
10521 let mut f = Fixture::new();
10522 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c", b"d"]);
10523 assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"-2"]), "+OK\r\n");
10524 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["b", "c"]));
10525 assert_eq!(f.run(&[b"LTRIM", b"k", b"1", b"0"]), "+OK\r\n");
10528 assert_eq!(f.run(&[b"EXISTS", b"k"]), ":0\r\n");
10529 assert_eq!(f.run(&[b"LTRIM", b"nope", b"0", b"-1"]), "+OK\r\n");
10530 }
10531
10532 #[test]
10533 fn lpos_walks_from_either_end_and_stops_where_it_is_told() {
10534 let mut f = Fixture::new();
10535 f.run(&[b"RPUSH", b"p", b"a", b"b", b"c", b"a", b"b", b"c", b"a"]);
10536 assert_eq!(f.run(&[b"LPOS", b"p", b"a"]), ":0\r\n");
10537 assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1"]), ":6\r\n");
10538 assert_eq!(f.run(&[b"LPOS", b"p", b"a", b"RANK", b"2"]), ":3\r\n");
10539 assert_eq!(
10540 f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"2"]),
10541 "*2\r\n:0\r\n:3\r\n"
10542 );
10543 assert_eq!(
10544 f.run(&[b"LPOS", b"p", b"a", b"RANK", b"-1", b"COUNT", b"0"]),
10545 "*3\r\n:6\r\n:3\r\n:0\r\n"
10546 );
10547 assert_eq!(
10550 f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"0", b"MAXLEN", b"3"]),
10551 "*1\r\n:0\r\n"
10552 );
10553 assert_eq!(f.run(&[b"LPOS", b"p", b"zz"]), "$-1\r\n");
10556 assert_eq!(f.run(&[b"LPOS", b"p", b"zz", b"COUNT", b"0"]), "*0\r\n");
10557 assert_eq!(f.run(&[b"LPOS", b"nope", b"a"]), "$-1\r\n");
10558 assert_eq!(f.run(&[b"LPOS", b"nope", b"a", b"COUNT", b"2"]), "*0\r\n");
10559 }
10560
10561 #[test]
10562 fn lpos_words_its_three_mistakes_the_way_redis_does() {
10563 let mut f = Fixture::new();
10564 f.run(&[b"RPUSH", b"p", b"a"]);
10565 assert_eq!(
10568 f.run(&[b"LPOS", b"p", b"a", b"RANK", b"0"]),
10569 "-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"
10570 );
10571 assert_eq!(
10572 f.run(&[b"LPOS", b"p", b"a", b"COUNT", b"-1"]),
10573 "-ERR COUNT can't be negative\r\n"
10574 );
10575 assert_eq!(
10576 f.run(&[b"LPOS", b"p", b"a", b"MAXLEN", b"-1"]),
10577 "-ERR MAXLEN can't be negative\r\n"
10578 );
10579 assert_eq!(
10580 f.run(&[b"LPOS", b"p", b"a", b"RANK"]),
10581 "-ERR syntax error\r\n"
10582 );
10583 assert_eq!(
10584 f.run(&[b"LPOS", b"p", b"a", b"FOO", b"1"]),
10585 "-ERR syntax error\r\n"
10586 );
10587 }
10588
10589 #[test]
10590 fn a_move_takes_from_one_end_and_gives_to_another_even_on_one_key() {
10591 let mut f = Fixture::new();
10592 f.run(&[b"RPUSH", b"k", b"a", b"b", b"c"]);
10593 assert_eq!(f.run(&[b"RPOPLPUSH", b"k", b"d"]), "$1\r\nc\r\n");
10594 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
10595 assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c"]));
10596 assert_eq!(
10597 f.run(&[b"LMOVE", b"k", b"d", b"LEFT", b"RIGHT"]),
10598 "$1\r\na\r\n"
10599 );
10600 assert_eq!(f.run(&[b"LRANGE", b"d", b"0", b"-1"]), bulks(&["c", "a"]));
10601 f.run(&[b"DEL", b"r"]);
10604 f.run(&[b"RPUSH", b"r", b"1", b"2", b"3"]);
10605 assert_eq!(f.run(&[b"RPOPLPUSH", b"r", b"r"]), "$1\r\n3\r\n");
10606 assert_eq!(
10607 f.run(&[b"LRANGE", b"r", b"0", b"-1"]),
10608 bulks(&["3", "1", "2"])
10609 );
10610 assert_eq!(
10611 f.run(&[b"LMOVE", b"nope", b"d", b"LEFT", b"LEFT"]),
10612 "$-1\r\n"
10613 );
10614 assert_eq!(
10615 f.run(&[b"LMOVE", b"r", b"d", b"LEFT", b"SIDEWAYS"]),
10616 "-ERR syntax error\r\n"
10617 );
10618 }
10619
10620 #[test]
10621 fn a_move_checks_the_destination_before_it_takes_anything() {
10622 let mut f = Fixture::new();
10623 f.run(&[b"RPUSH", b"k", b"a", b"b"]);
10624 f.run(&[b"SET", b"str", b"v"]);
10625 assert_eq!(
10626 f.run(&[b"LMOVE", b"k", b"str", b"LEFT", b"LEFT"]),
10627 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
10628 );
10629 assert_eq!(f.run(&[b"LRANGE", b"k", b"0", b"-1"]), bulks(&["a", "b"]));
10631 }
10632
10633 #[test]
10634 fn a_block_move_orders_the_block_by_the_ends_and_the_ordering_word() {
10635 for (from, to, order, want) in [
10639 ("LEFT", "RIGHT", "OBO", ["a", "b"]),
10640 ("LEFT", "RIGHT", "BULK", ["a", "b"]),
10641 ("LEFT", "LEFT", "OBO", ["b", "a"]),
10642 ("LEFT", "LEFT", "BULK", ["a", "b"]),
10643 ("RIGHT", "LEFT", "OBO", ["d", "e"]),
10644 ("RIGHT", "LEFT", "BULK", ["d", "e"]),
10645 ("RIGHT", "RIGHT", "OBO", ["e", "d"]),
10646 ("RIGHT", "RIGHT", "BULK", ["d", "e"]),
10647 ] {
10648 let mut f = Fixture::new();
10649 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c", b"d", b"e"]);
10650 let how = format!("{from} {to} {order}");
10651 let reply = f.run(&[
10652 b"LMOVEM",
10653 b"s",
10654 b"d",
10655 from.as_bytes(),
10656 to.as_bytes(),
10657 b"COUNT",
10658 b"2",
10659 order.as_bytes(),
10660 ]);
10661 assert_eq!(reply, bulks(&want), "the reply for {how}");
10662 assert_eq!(
10663 f.run(&[b"LRANGE", b"d", b"0", b"-1"]),
10664 bulks(&want),
10665 "the destination for {how}"
10666 );
10667 }
10668 }
10669
10670 #[test]
10671 fn a_block_move_of_one_needs_no_count_at_all() {
10672 let mut f = Fixture::new();
10673 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
10674 assert_eq!(
10675 f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT"]),
10676 bulks(&["a"])
10677 );
10678 assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["b", "c"]));
10679 assert_eq!(
10682 f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT"]),
10683 "-ERR syntax error\r\n"
10684 );
10685 assert_eq!(
10686 f.run(&[b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"2"]),
10687 "-ERR syntax error\r\n"
10688 );
10689 }
10690
10691 #[test]
10692 fn a_block_move_with_exactly_takes_all_of_them_or_none() {
10693 let mut f = Fixture::new();
10694 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
10695 assert_eq!(
10699 f.run(&[
10700 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"EXACTLY", b"99", b"BULK"
10701 ]),
10702 "*-1\r\n"
10703 );
10704 assert_eq!(
10705 f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
10706 bulks(&["a", "b", "c"])
10707 );
10708 assert_eq!(
10710 f.run(&[
10711 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"99", b"BULK"
10712 ]),
10713 bulks(&["a", "b", "c"])
10714 );
10715 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
10716 assert_eq!(
10717 f.run(&[
10718 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
10719 ]),
10720 "*-1\r\n"
10721 );
10722 }
10723
10724 #[test]
10725 fn a_block_move_onto_itself_rotates_by_the_count() {
10726 let mut f = Fixture::new();
10727 f.run(&[b"RPUSH", b"s", b"a", b"b", b"c"]);
10728 assert_eq!(
10729 f.run(&[
10730 b"LMOVEM", b"s", b"s", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
10731 ]),
10732 bulks(&["a", "b"])
10733 );
10734 assert_eq!(
10735 f.run(&[b"LRANGE", b"s", b"0", b"-1"]),
10736 bulks(&["c", "a", "b"])
10737 );
10738 }
10739
10740 #[test]
10741 fn a_block_move_reads_the_count_before_the_ordering_word() {
10742 let mut f = Fixture::new();
10743 f.run(&[b"RPUSH", b"s", b"a", b"b"]);
10744 f.run(&[b"SET", b"str", b"v"]);
10745 let count = "-ERR count should be greater than 0\r\n";
10746 assert_eq!(
10747 f.run(&[
10748 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"NOPE"
10749 ]),
10750 count
10751 );
10752 assert_eq!(
10753 f.run(&[
10754 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"0", b"BULK"
10755 ]),
10756 count
10757 );
10758 assert_eq!(
10759 f.run(&[
10760 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"COUNT", b"1", b"NOPE"
10761 ]),
10762 "-ERR syntax error\r\n"
10763 );
10764 assert_eq!(
10765 f.run(&[
10766 b"LMOVEM", b"s", b"d", b"LEFT", b"RIGHT", b"NOPE", b"abc", b"BULK"
10767 ]),
10768 "-ERR syntax error\r\n"
10769 );
10770 assert_eq!(
10773 f.run(&[
10774 b"LMOVEM", b"str", b"d", b"LEFT", b"RIGHT", b"COUNT", b"abc", b"BULK"
10775 ]),
10776 count
10777 );
10778 assert_eq!(
10779 f.run(&[
10780 b"LMOVEM", b"s", b"str", b"LEFT", b"RIGHT", b"COUNT", b"1", b"BULK"
10781 ]),
10782 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
10783 );
10784 assert_eq!(f.run(&[b"LRANGE", b"s", b"0", b"-1"]), bulks(&["a", "b"]));
10785 }
10786
10787 #[test]
10788 fn lmpop_answers_from_the_first_key_that_has_anything() {
10789 let mut f = Fixture::new();
10790 f.run(&[b"RPUSH", b"b", b"1", b"2", b"3"]);
10791 assert_eq!(
10794 f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT", b"COUNT", b"2"]),
10795 "*2\r\n$1\r\nb\r\n*2\r\n$1\r\n1\r\n$1\r\n2\r\n"
10796 );
10797 assert_eq!(
10798 f.run(&[b"LMPOP", b"2", b"a", b"b", b"RIGHT"]),
10799 "*2\r\n$1\r\nb\r\n*1\r\n$1\r\n3\r\n"
10800 );
10801 assert_eq!(f.run(&[b"EXISTS", b"b"]), ":0\r\n");
10802 assert_eq!(f.run(&[b"LMPOP", b"2", b"a", b"b", b"LEFT"]), "*-1\r\n");
10805 }
10806
10807 #[test]
10808 fn lmpop_has_its_own_words_for_a_count_and_for_a_key_count() {
10809 let mut f = Fixture::new();
10810 f.run(&[b"RPUSH", b"k", b"a"]);
10811 assert_eq!(
10812 f.run(&[b"LMPOP", b"0", b"k", b"LEFT"]),
10813 "-ERR numkeys should be greater than 0\r\n"
10814 );
10815 assert_eq!(
10816 f.run(&[b"LMPOP", b"-1", b"k", b"LEFT"]),
10817 "-ERR numkeys should be greater than 0\r\n"
10818 );
10819 assert_eq!(
10820 f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"0"]),
10821 "-ERR count should be greater than 0\r\n"
10822 );
10823 assert_eq!(
10826 f.run(&[b"LMPOP", b"3", b"k", b"LEFT"]),
10827 "-ERR syntax error\r\n"
10828 );
10829 assert_eq!(
10830 f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"COUNT", b"1", b"x"]),
10831 "-ERR syntax error\r\n"
10832 );
10833 assert_eq!(
10834 f.run(&[b"LMPOP", b"1", b"k", b"LEFT", b"FOO", b"1"]),
10835 "-ERR syntax error\r\n"
10836 );
10837 assert_eq!(
10838 f.run(&[b"LMPOP", b"1", b"k", b"SIDEWAYS"]),
10839 "-ERR syntax error\r\n"
10840 );
10841 assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n");
10842 }
10843
10844 #[test]
10845 fn every_list_command_says_wrongtype_and_writes_nothing() {
10846 let mut f = Fixture::new();
10847 f.run(&[b"SET", b"str", b"v"]);
10848 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
10849 for cmd in [
10850 &[b"LPUSH".as_slice(), b"str", b"a"][..],
10851 &[b"RPUSH", b"str", b"a"],
10852 &[b"LPUSHX", b"str", b"a"],
10853 &[b"RPUSHX", b"str", b"a"],
10854 &[b"LPOP", b"str"],
10855 &[b"LPOP", b"str", b"2"],
10856 &[b"RPOP", b"str"],
10857 &[b"LLEN", b"str"],
10858 &[b"LRANGE", b"str", b"0", b"-1"],
10859 &[b"LINDEX", b"str", b"0"],
10860 &[b"LSET", b"str", b"0", b"a"],
10861 &[b"LINSERT", b"str", b"BEFORE", b"a", b"b"],
10862 &[b"LREM", b"str", b"0", b"a"],
10863 &[b"LTRIM", b"str", b"0", b"-1"],
10864 &[b"LPOS", b"str", b"a"],
10865 &[b"LPOS", b"str", b"a", b"COUNT", b"0"],
10866 &[b"RPOPLPUSH", b"str", b"d"],
10867 &[b"LMOVE", b"str", b"d", b"LEFT", b"LEFT"],
10868 &[b"LMPOP", b"1", b"str", b"LEFT"],
10869 ] {
10870 assert_eq!(f.run(cmd), wrong, "{:?}", String::from_utf8_lossy(cmd[0]));
10871 }
10872 assert_eq!(f.run(&[b"GET", b"str"]), "$1\r\nv\r\n");
10873 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
10874 }
10875
10876 #[test]
10880 fn a_timeout_has_three_ways_of_being_wrong() {
10881 let mut f = Fixture::new();
10882 let not_float = "-ERR timeout is not a float or out of range\r\n";
10883 let range = "-ERR timeout is out of range\r\n";
10884 for (bad, want) in [
10885 (&[b"BLPOP".as_slice(), b"k", b"abc"][..], not_float),
10886 (&[b"BLPOP", b"k", b"nan"], not_float),
10887 (&[b"BLPOP", b"k", b""], not_float),
10888 (&[b"BLPOP", b"k", b" 1"], not_float),
10891 (&[b"BLPOP", b"k", b"1 "], not_float),
10892 (&[b"BLPOP", b"k", b"-1"], "-ERR timeout is negative\r\n"),
10893 (&[b"BLPOP", b"k", b"-0.1"], "-ERR timeout is negative\r\n"),
10894 (&[b"BLPOP", b"k", b"1e400"], range),
10897 (&[b"BLPOP", b"k", b"inf"], range),
10898 (&[b"BLPOP", b"k", b"9999999999999999"], range),
10899 (&[b"BRPOP", b"k", b"abc"], not_float),
10900 (
10901 &[b"BLMOVE", b"a", b"b", b"LEFT", b"RIGHT", b"abc"],
10902 not_float,
10903 ),
10904 (
10905 &[b"BRPOPLPUSH", b"a", b"b", b"-1"],
10906 "-ERR timeout is negative\r\n",
10907 ),
10908 (&[b"BLMPOP", b"abc", b"1", b"k", b"LEFT"], not_float),
10909 ] {
10910 assert_eq!(f.run(bad), want, "for {bad:?}");
10911 }
10912 }
10913
10914 #[test]
10917 fn a_zero_timeout_waits_and_the_smallest_positive_one_does_not() {
10918 let mut f = Fixture::new();
10919 for timeout in [b"0".as_slice(), b"0.0", b"-0.0"] {
10920 let (flow, out) = f.flow(&[b"BLPOP", b"k", timeout]);
10921 assert_eq!(flow, Flow::Block, "for {timeout:?}");
10922 assert!(out.is_empty(), "for {timeout:?}");
10923 }
10924 let (flow, out) = f.flow(&[b"BLPOP", b"k", b"0.0000001"]);
10928 assert_eq!(flow, Flow::Block);
10929 assert!(out.is_empty());
10930 }
10931
10932 #[test]
10933 fn a_blocking_command_that_can_be_answered_answers_like_the_one_it_wraps() {
10934 let mut f = Fixture::new();
10935 f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
10936
10937 assert_eq!(
10940 f.flow(&[b"BLPOP", b"nope", b"L", b"0"]),
10941 (Flow::Continue, "*2\r\n$1\r\nL\r\n$1\r\na\r\n".to_owned())
10942 );
10943 assert_eq!(
10944 f.run(&[b"BRPOP", b"L", b"0"]),
10945 "*2\r\n$1\r\nL\r\n$1\r\ne\r\n"
10946 );
10947 assert_eq!(
10948 f.run(&[
10949 b"BLMPOP", b"0", b"2", b"nope", b"L", b"LEFT", b"COUNT", b"2"
10950 ]),
10951 "*2\r\n$1\r\nL\r\n*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
10952 );
10953 assert_eq!(
10954 f.run(&[b"BLMOVE", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
10955 "$1\r\nd\r\n"
10956 );
10957 assert_eq!(
10958 f.run(&[b"EXISTS", b"L"]),
10959 ":0\r\n",
10960 "and the key went with it"
10961 );
10962 assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nd\r\n");
10963 f.run(&[b"RPUSH", b"D", b"x"]);
10966 assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"D", b"0"]), "$1\r\nx\r\n");
10967 assert_eq!(
10968 f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
10969 "*2\r\n$1\r\nx\r\n$1\r\nd\r\n"
10970 );
10971 }
10972
10973 #[test]
10974 fn blmpop_reads_its_count_and_its_key_count_the_way_lmpop_does() {
10975 let mut f = Fixture::new();
10976 f.run(&[b"RPUSH", b"k", b"a"]);
10977 for (bad, want) in [
10978 (
10979 &[b"BLMPOP".as_slice(), b"0", b"0", b"k", b"LEFT"][..],
10980 "-ERR numkeys should be greater than 0\r\n",
10981 ),
10982 (
10983 &[b"BLMPOP", b"0", b"-1", b"k", b"LEFT"],
10984 "-ERR numkeys should be greater than 0\r\n",
10985 ),
10986 (
10989 &[b"BLMPOP", b"0", b"2", b"k", b"LEFT"],
10990 "-ERR syntax error\r\n",
10991 ),
10992 (
10993 &[b"BLMPOP", b"0", b"1", b"k", b"SIDEWAYS"],
10994 "-ERR syntax error\r\n",
10995 ),
10996 (
10997 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT"],
10998 "-ERR syntax error\r\n",
10999 ),
11000 (
11001 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"2", b"x"],
11002 "-ERR syntax error\r\n",
11003 ),
11004 (
11007 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"0"],
11008 "-ERR count should be greater than 0\r\n",
11009 ),
11010 (
11011 &[b"BLMPOP", b"0", b"1", b"k", b"LEFT", b"COUNT", b"abc"],
11012 "-ERR count should be greater than 0\r\n",
11013 ),
11014 ] {
11015 assert_eq!(f.run(bad), want, "for {bad:?}");
11016 }
11017 assert_eq!(f.run(&[b"LLEN", b"k"]), ":1\r\n", "and none of them popped");
11018 }
11019
11020 #[test]
11021 fn a_blocking_move_reads_its_directions_before_its_timeout() {
11022 let mut f = Fixture::new();
11023 assert_eq!(
11026 f.run(&[b"BLMOVE", b"a", b"b", b"UP", b"DOWN", b"abc"]),
11027 "-ERR syntax error\r\n"
11028 );
11029 assert_eq!(
11030 f.run(&[b"BLMOVE", b"a", b"b", b"LEFT", b"DOWN", b"0.05"]),
11031 "-ERR syntax error\r\n"
11032 );
11033 }
11034
11035 #[test]
11039 fn a_blocking_block_move_that_can_be_answered_answers_like_lmovem() {
11040 let mut f = Fixture::new();
11041 f.run(&[b"RPUSH", b"L", b"a", b"b", b"c", b"d", b"e"]);
11042 assert_eq!(
11043 f.flow(&[b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0"]),
11044 (Flow::Continue, "*1\r\n$1\r\na\r\n".to_owned())
11045 );
11046 assert_eq!(
11047 f.run(&[
11048 b"BLMOVEM", b"L", b"D", b"RIGHT", b"RIGHT", b"0", b"COUNT", b"2", b"OBO"
11049 ]),
11050 bulks(&["e", "d"])
11051 );
11052 assert_eq!(
11053 f.run(&[b"LRANGE", b"D", b"0", b"-1"]),
11054 bulks(&["a", "e", "d"])
11055 );
11056 assert_eq!(
11058 f.run(&[
11059 b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"2", b"BULK"
11060 ]),
11061 bulks(&["b", "c"])
11062 );
11063 assert_eq!(f.run(&[b"EXISTS", b"L"]), ":0\r\n", "and the key went");
11064 }
11065
11066 #[test]
11070 fn a_blocking_block_move_waits_for_the_whole_block_only_under_exactly() {
11071 let mut f = Fixture::new();
11072 f.run(&[b"RPUSH", b"L", b"a", b"b"]);
11073 assert_eq!(
11075 f.flow(&[
11076 b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"COUNT", b"3", b"BULK"
11077 ]),
11078 (Flow::Continue, bulks(&["a", "b"]))
11079 );
11080
11081 f.run(&[b"RPUSH", b"L", b"a", b"b"]);
11082 assert_eq!(
11085 f.flow(&[
11086 b"BLMOVEM", b"L", b"D", b"LEFT", b"RIGHT", b"0", b"EXACTLY", b"3", b"BULK"
11087 ])
11088 .0,
11089 Flow::Block
11090 );
11091 assert_eq!(f.run(&[b"LRANGE", b"L", b"0", b"-1"]), bulks(&["a", "b"]));
11092 }
11093
11094 #[test]
11095 fn a_blocking_block_move_reads_its_directions_then_its_timeout_then_its_count() {
11096 let mut f = Fixture::new();
11097 let syntax = "-ERR syntax error\r\n";
11098 assert_eq!(
11100 f.run(&[
11101 b"BLMOVEM", b"a", b"b", b"UP", b"DOWN", b"abc", b"NOPE", b"x", b"y"
11102 ]),
11103 syntax
11104 );
11105 assert_eq!(
11107 f.run(&[
11108 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"abc", b"COUNT", b"abc", b"BULK"
11109 ]),
11110 "-ERR timeout is not a float or out of range\r\n"
11111 );
11112 assert_eq!(
11113 f.run(&[
11114 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"-1", b"COUNT", b"1", b"BULK"
11115 ]),
11116 "-ERR timeout is negative\r\n"
11117 );
11118 assert_eq!(
11120 f.run(&[
11121 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"abc", b"NOPE"
11122 ]),
11123 "-ERR count should be greater than 0\r\n"
11124 );
11125 assert_eq!(
11126 f.run(&[
11127 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"1", b"NOPE"
11128 ]),
11129 syntax
11130 );
11131 assert_eq!(
11134 f.run(&[b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT"]),
11135 syntax
11136 );
11137 assert_eq!(
11138 f.run(&[
11139 b"BLMOVEM", b"a", b"b", b"LEFT", b"RIGHT", b"0", b"COUNT", b"2"
11140 ]),
11141 syntax
11142 );
11143 }
11144
11145 #[test]
11148 fn a_blocking_command_errors_on_a_wrong_type_rather_than_waiting_on_it() {
11149 let mut f = Fixture::new();
11150 f.run(&[b"SET", b"S", b"v"]);
11151 f.run(&[b"RPUSH", b"D", b"x"]);
11152 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
11153
11154 assert_eq!(f.run(&[b"BLPOP", b"S", b"0"]), wrong);
11155 assert_eq!(f.run(&[b"BLPOP", b"E", b"S", b"0"]), wrong);
11158 assert_eq!(f.run(&[b"BRPOP", b"S", b"0"]), wrong);
11159 assert_eq!(f.run(&[b"BLMPOP", b"0", b"1", b"S", b"LEFT"]), wrong);
11160 assert_eq!(f.run(&[b"BRPOPLPUSH", b"S", b"D", b"0"]), wrong);
11161 assert_eq!(f.run(&[b"BRPOPLPUSH", b"D", b"S", b"0"]), wrong);
11164 assert_eq!(f.run(&[b"LRANGE", b"D", b"0", b"-1"]), "*1\r\n$1\r\nx\r\n");
11165 assert_eq!(
11166 f.run(&[b"BLMOVEM", b"S", b"D", b"LEFT", b"RIGHT", b"0"]),
11167 wrong
11168 );
11169 assert_eq!(
11170 f.run(&[b"BLMOVEM", b"D", b"S", b"LEFT", b"RIGHT", b"0"]),
11171 wrong
11172 );
11173
11174 assert_eq!(
11178 f.flow(&[b"BLMOVE", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
11179 .0,
11180 Flow::Block
11181 );
11182 assert_eq!(
11186 f.flow(&[b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1"])
11187 .0,
11188 Flow::Block
11189 );
11190 f.run(&[b"RPUSH", b"E", b"1", b"2"]);
11191 assert_eq!(
11192 f.flow(&[
11193 b"BLMOVEM", b"E", b"S", b"LEFT", b"RIGHT", b"0.1", b"EXACTLY", b"3", b"BULK"
11194 ])
11195 .0,
11196 Flow::Block
11197 );
11198 }
11199
11200 #[cfg_attr(miri, ignore = "the volume is the claim")]
11205 #[test]
11206 fn churning_lists_does_not_grow_the_server() {
11207 let mut f = Fixture::new();
11208 let vals: Vec<Vec<u8>> = (0..200).map(|i| format!("v{i}").into_bytes()).collect();
11209 let args: Vec<&[u8]> = [&b"RPUSH"[..], &b"k"[..]]
11210 .into_iter()
11211 .chain(vals.iter().map(Vec::as_slice))
11212 .collect();
11213
11214 f.run(&args);
11215 f.run(&[b"DEL", b"k"]);
11216 f.server.compact_step();
11217 let after_first = f.server.memory_bytes();
11218
11219 for _ in 0..200 {
11220 f.run(&args);
11221 f.run(&[b"LTRIM", b"k", b"1", b"0"]);
11222 f.server.compact_step();
11223 }
11224 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
11225 assert!(
11226 f.server.memory_bytes() <= after_first * 2,
11227 "held {} after two hundred passes against {after_first} after one",
11228 f.server.memory_bytes()
11229 );
11230 }
11231
11232 #[test]
11235 fn a_sorted_set_takes_scores_and_gives_them_back() {
11236 let mut f = Fixture::new();
11237 assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]), ":2\r\n");
11238 assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]), ":1\r\n");
11239 assert_eq!(f.run(&[b"ZCARD", b"z"]), ":3\r\n");
11240 assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$1\r\n2\r\n");
11241 assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "$-1\r\n");
11242 assert_eq!(f.run(&[b"ZSCORE", b"nokey", b"b"]), "$-1\r\n");
11243 assert_eq!(
11244 f.run(&[b"ZMSCORE", b"z", b"a", b"nope", b"c"]),
11245 "*3\r\n$1\r\n1\r\n$-1\r\n$1\r\n3\r\n"
11246 );
11247 assert_eq!(f.run(&[b"ZREM", b"z", b"a", b"nope"]), ":1\r\n");
11248 assert_eq!(f.run(&[b"ZCARD", b"z"]), ":2\r\n");
11249 assert_eq!(f.run(&[b"ZREM", b"z", b"b", b"c"]), ":2\r\n");
11251 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
11252 }
11253
11254 #[test]
11255 fn a_score_is_a_double_on_resp3_and_digits_on_resp2() {
11256 let mut f = Fixture::new();
11257 f.run(&[b"ZADD", b"z", b"1.5", b"a", b"inf", b"b", b"-inf", b"c"]);
11258 assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$3\r\n1.5\r\n");
11259 assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), "$3\r\ninf\r\n");
11260 assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), "$4\r\n-inf\r\n");
11261
11262 f.out = Out::new(Proto::Resp3);
11263 assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), ",1.5\r\n");
11264 assert_eq!(f.run(&[b"ZSCORE", b"z", b"b"]), ",inf\r\n");
11265 assert_eq!(f.run(&[b"ZSCORE", b"z", b"c"]), ",-inf\r\n");
11266 assert_eq!(f.run(&[b"ZSCORE", b"z", b"nope"]), "_\r\n");
11267 }
11268
11269 #[test]
11270 fn the_zadd_options_gate_what_gets_written() {
11271 let mut f = Fixture::new();
11272 f.run(&[b"ZADD", b"z", b"5", b"a"]);
11273 assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"9", b"a"]), ":0\r\n");
11275 assert_eq!(f.run(&[b"ZSCORE", b"z", b"a"]), "$1\r\n5\r\n");
11276 assert_eq!(f.run(&[b"ZADD", b"z", b"XX", b"9", b"new"]), ":0\r\n");
11277 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":1\r\n");
11278 assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"3", b"a"]), ":0\r\n");
11280 assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"CH", b"7", b"a"]), ":1\r\n");
11281 assert_eq!(f.run(&[b"ZADD", b"z", b"LT", b"CH", b"9", b"a"]), ":0\r\n");
11282 assert_eq!(f.run(&[b"ZADD", b"z", b"1", b"a", b"1", b"b"]), ":1\r\n");
11284 assert_eq!(
11285 f.run(&[b"ZADD", b"z", b"CH", b"2", b"a", b"2", b"c"]),
11286 ":2\r\n"
11287 );
11288 }
11289
11290 #[test]
11291 fn zadd_incr_answers_a_score_or_nothing_at_all() {
11292 let mut f = Fixture::new();
11293 assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"5", b"m"]), "$1\r\n5\r\n");
11294 assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"2", b"m"]), "$1\r\n7\r\n");
11295 assert_eq!(
11298 f.run(&[b"ZADD", b"z", b"NX", b"INCR", b"2", b"m"]),
11299 "$-1\r\n"
11300 );
11301 assert_eq!(
11302 f.run(&[b"ZADD", b"z", b"XX", b"INCR", b"2", b"gone"]),
11303 "$-1\r\n"
11304 );
11305 assert_eq!(
11306 f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"-1", b"m"]),
11307 "$-1\r\n"
11308 );
11309 assert_eq!(
11310 f.run(&[b"ZADD", b"z", b"GT", b"INCR", b"1", b"m"]),
11311 "$1\r\n8\r\n"
11312 );
11313 assert_eq!(f.run(&[b"ZINCRBY", b"z", b"2", b"m"]), "$2\r\n10\r\n");
11314 assert_eq!(f.run(&[b"ZINCRBY", b"z", b"1", b"fresh"]), "$1\r\n1\r\n");
11315 }
11316
11317 #[test]
11318 fn the_two_infinities_will_not_be_added_together() {
11319 let mut f = Fixture::new();
11320 f.run(&[b"ZADD", b"z", b"inf", b"m"]);
11321 let nan = "-ERR resulting score is not a number (NaN)\r\n";
11322 assert_eq!(f.run(&[b"ZINCRBY", b"z", b"-inf", b"m"]), nan);
11323 assert_eq!(f.run(&[b"ZADD", b"z", b"INCR", b"-inf", b"m"]), nan);
11324 assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\ninf\r\n");
11325 assert_eq!(f.run(&[b"ZINCRBY", b"gone", b"1", b"m"]), "$1\r\n1\r\n");
11327 }
11328
11329 #[test]
11330 fn zadd_says_its_mistakes_the_way_redis_says_them() {
11331 let mut f = Fixture::new();
11332 assert_eq!(
11335 f.run(&[b"ZADD", b"z", b"NX", b"XX"]),
11336 "-ERR syntax error\r\n"
11337 );
11338 assert_eq!(
11339 f.run(&[b"ZADD", b"z", b"NX", b"XX", b"1", b"a"]),
11340 "-ERR XX and NX options at the same time are not compatible\r\n"
11341 );
11342 let gtlt = "-ERR GT, LT, and/or NX options at the same time are not compatible\r\n";
11343 assert_eq!(f.run(&[b"ZADD", b"z", b"NX", b"GT", b"1", b"a"]), gtlt);
11344 assert_eq!(f.run(&[b"ZADD", b"z", b"GT", b"LT", b"1", b"a"]), gtlt);
11345 assert_eq!(
11346 f.run(&[b"ZADD", b"z", b"INCR", b"1", b"a", b"2", b"b"]),
11347 "-ERR INCR option supports a single increment-element pair\r\n"
11348 );
11349 assert_eq!(
11351 f.run(&[b"ZADD", b"z", b"1", b"a", b"2"]),
11352 "-ERR syntax error\r\n"
11353 );
11354 assert_eq!(
11356 f.run(&[b"ZADD", b"z", b"1", b"a", b"nonsense", b"b"]),
11357 "-ERR value is not a valid float\r\n"
11358 );
11359 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
11360 }
11361
11362 #[test]
11363 fn a_rank_says_where_a_member_sits_from_either_end() {
11364 let mut f = Fixture::new();
11365 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11366 assert_eq!(f.run(&[b"ZRANK", b"z", b"a"]), ":0\r\n");
11367 assert_eq!(f.run(&[b"ZRANK", b"z", b"c"]), ":2\r\n");
11368 assert_eq!(f.run(&[b"ZREVRANK", b"z", b"c"]), ":0\r\n");
11369 assert_eq!(f.run(&[b"ZREVRANK", b"z", b"a"]), ":2\r\n");
11370 assert_eq!(
11372 f.run(&[b"ZRANK", b"z", b"b", b"WITHSCORE"]),
11373 "*2\r\n:1\r\n$1\r\n2\r\n"
11374 );
11375 assert_eq!(f.run(&[b"ZRANK", b"z", b"nope"]), "$-1\r\n");
11376 assert_eq!(f.run(&[b"ZRANK", b"z", b"nope", b"WITHSCORE"]), "*-1\r\n");
11377 assert_eq!(f.run(&[b"ZRANK", b"nokey", b"a", b"WITHSCORE"]), "*-1\r\n");
11378 assert_eq!(
11381 f.run(&[b"ZRANK", b"z", b"b", b"bogus"]),
11382 "-ERR syntax error\r\n"
11383 );
11384 assert_eq!(
11385 f.run(&[b"ZREVRANK", b"z", b"b", b"WITHSCORE", b"more"]),
11386 "-ERR wrong number of arguments for 'zrevrank' command\r\n"
11387 );
11388 }
11389
11390 #[test]
11391 fn the_two_counts_read_their_two_kinds_of_bound() {
11392 let mut f = Fixture::new();
11393 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11394 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"-inf", b"+inf"]), ":3\r\n");
11395 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"2", b"3"]), ":2\r\n");
11396 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"3"]), ":2\r\n");
11397 assert_eq!(f.run(&[b"ZCOUNT", b"z", b"(1", b"(3"]), ":1\r\n");
11398 assert_eq!(f.run(&[b"ZCOUNT", b"nokey", b"-inf", b"+inf"]), ":0\r\n");
11399 assert_eq!(
11400 f.run(&[b"ZCOUNT", b"z", b"bogus", b"3"]),
11401 "-ERR min or max is not a float\r\n"
11402 );
11403
11404 f.run(&[b"ZADD", b"l", b"0", b"a", b"0", b"b", b"0", b"c"]);
11405 assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"-", b"+"]), ":3\r\n");
11406 assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"[a", b"(c"]), ":2\r\n");
11407 assert_eq!(f.run(&[b"ZLEXCOUNT", b"l", b"(a", b"+"]), ":2\r\n");
11408 assert_eq!(
11411 f.run(&[b"ZLEXCOUNT", b"l", b"a", b"c"]),
11412 "-ERR min or max not valid string range item\r\n"
11413 );
11414 }
11415
11416 #[test]
11422 fn one_range_command_selects_by_rank_or_score_or_name() {
11423 let mut f = Fixture::new();
11424 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11425 assert_eq!(
11426 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
11427 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
11428 );
11429 assert_eq!(
11430 f.run(&[b"ZRANGE", b"z", b"-2", b"-1"]),
11431 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11432 );
11433 assert_eq!(f.run(&[b"ZRANGE", b"z", b"5", b"9"]), "*0\r\n");
11434 assert_eq!(f.run(&[b"ZRANGE", b"nokey", b"0", b"-1"]), "*0\r\n");
11435 assert_eq!(
11438 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"REV"]),
11439 "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
11440 );
11441 assert_eq!(
11442 f.run(&[b"ZRANGE", b"z", b"(1", b"+inf", b"BYSCORE"]),
11443 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11444 );
11445 assert_eq!(
11448 f.run(&[b"ZRANGE", b"z", b"+inf", b"(1", b"BYSCORE", b"REV"]),
11449 "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
11450 );
11451 assert_eq!(
11452 f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX"]),
11453 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
11454 );
11455 assert_eq!(
11456 f.run(&[b"ZRANGE", b"z", b"+", b"-", b"BYLEX", b"REV"]),
11457 "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
11458 );
11459 }
11460
11461 #[test]
11464 fn the_older_range_spellings_name_their_high_end_first() {
11465 let mut f = Fixture::new();
11466 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11467 assert_eq!(
11468 f.run(&[b"ZREVRANGE", b"z", b"0", b"-1"]),
11469 "*3\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n"
11470 );
11471 assert_eq!(
11472 f.run(&[b"ZREVRANGE", b"z", b"0", b"0", b"WITHSCORES"]),
11473 "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
11474 );
11475 assert_eq!(
11476 f.run(&[b"ZRANGEBYSCORE", b"z", b"(1", b"3"]),
11477 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11478 );
11479 assert_eq!(
11480 f.run(&[b"ZREVRANGEBYSCORE", b"z", b"3", b"(1"]),
11481 "*2\r\n$1\r\nc\r\n$1\r\nb\r\n"
11482 );
11483 assert_eq!(f.run(&[b"ZREVRANGEBYSCORE", b"z", b"(1", b"3"]), "*0\r\n");
11487 assert_eq!(
11488 f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"(c"]),
11489 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
11490 );
11491 assert_eq!(
11492 f.run(&[b"ZREVRANGEBYLEX", b"z", b"(c", b"[a"]),
11493 "*2\r\n$1\r\nb\r\n$1\r\na\r\n"
11494 );
11495 for cmd in [
11498 &[b"ZREVRANGE".as_slice(), b"z", b"0", b"-1", b"BYSCORE"][..],
11499 &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"REV"],
11500 &[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"BYLEX"],
11501 ] {
11502 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{:?}", cmd[0]);
11503 }
11504 }
11505
11506 #[test]
11509 fn limit_and_withscores_are_read_by_all_of_them_and_refused_afterwards() {
11510 let mut f = Fixture::new();
11511 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11512 assert_eq!(
11513 f.run(&[
11514 b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"1", b"1"
11515 ]),
11516 "*1\r\n$1\r\nb\r\n"
11517 );
11518 assert_eq!(
11520 f.run(&[
11521 b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"-1", b"2"
11522 ]),
11523 "*0\r\n"
11524 );
11525 assert_eq!(
11526 f.run(&[
11527 b"ZRANGE", b"z", b"-inf", b"+inf", b"BYSCORE", b"LIMIT", b"0", b"-1"
11528 ]),
11529 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
11530 );
11531 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";
11533 assert_eq!(
11534 f.run(&[
11535 b"ZRANGEBYSCORE",
11536 b"z",
11537 b"1",
11538 b"3",
11539 b"WITHSCORES",
11540 b"LIMIT",
11541 b"0",
11542 b"2"
11543 ]),
11544 both
11545 );
11546 assert_eq!(
11547 f.run(&[
11548 b"ZRANGEBYSCORE",
11549 b"z",
11550 b"1",
11551 b"3",
11552 b"LIMIT",
11553 b"0",
11554 b"2",
11555 b"WITHSCORES"
11556 ]),
11557 both
11558 );
11559 let needs_by = "-ERR syntax error, LIMIT is only supported in combination with either BYSCORE or BYLEX\r\n";
11562 assert_eq!(
11563 f.run(&[
11564 b"ZREVRANGE",
11565 b"z",
11566 b"0",
11567 b"-1",
11568 b"WITHSCORES",
11569 b"LIMIT",
11570 b"0",
11571 b"1"
11572 ]),
11573 needs_by
11574 );
11575 assert_eq!(
11576 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"LIMIT", b"0", b"1"]),
11577 needs_by
11578 );
11579 let not_bylex = "-ERR syntax error, WITHSCORES not supported in combination with BYLEX\r\n";
11580 assert_eq!(
11581 f.run(&[b"ZRANGE", b"z", b"-", b"+", b"BYLEX", b"WITHSCORES"]),
11582 not_bylex
11583 );
11584 assert_eq!(
11585 f.run(&[b"ZRANGEBYLEX", b"z", b"[a", b"[c", b"WITHSCORES"]),
11586 not_bylex
11587 );
11588 for cmd in [
11591 &[
11592 b"ZRANGE".as_slice(),
11593 b"z",
11594 b"0",
11595 b"-1",
11596 b"BYSCORE",
11597 b"BYLEX",
11598 ][..],
11599 &[b"ZRANGE", b"z", b"0", b"-1", b"junk"],
11600 &[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"0"],
11601 ] {
11602 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
11603 }
11604 assert_eq!(
11605 f.run(&[b"ZRANGEBYSCORE", b"z", b"bad", b"3"]),
11606 "-ERR min or max is not a float\r\n"
11607 );
11608 assert_eq!(
11609 f.run(&[b"ZRANGEBYLEX", b"z", b"a", b"[c"]),
11610 "-ERR min or max not valid string range item\r\n"
11611 );
11612 assert_eq!(
11613 f.run(&[b"ZRANGEBYSCORE", b"z", b"1", b"3", b"LIMIT", b"a", b"2"]),
11614 "-ERR value is not an integer or out of range\r\n"
11615 );
11616 }
11617
11618 #[test]
11621 fn withscores_nests_on_resp3_and_flattens_on_resp2() {
11622 let mut f = Fixture::new();
11623 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11624 assert_eq!(
11625 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
11626 "*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"
11627 );
11628 f.out = Out::new(Proto::Resp3);
11629 assert_eq!(
11630 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
11631 "*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"
11632 );
11633 assert_eq!(
11634 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
11635 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
11636 );
11637 }
11638
11639 #[test]
11641 fn a_range_store_writes_the_window_into_another_key() {
11642 let mut f = Fixture::new();
11643 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11644 assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1"]), ":3\r\n");
11645 assert_eq!(f.run(&[b"ZRANGESTORE", b"d", b"z", b"5", b"9"]), ":0\r\n");
11648 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
11649 assert_eq!(
11650 f.run(&[b"ZRANGESTORE", b"d", b"z", b"(1", b"+inf", b"BYSCORE"]),
11651 ":2\r\n"
11652 );
11653 assert_eq!(
11654 f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
11655 "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
11656 );
11657 assert_eq!(f.run(&[b"ZRANGESTORE", b"z", b"z", b"1", b"2"]), ":2\r\n");
11660 assert_eq!(
11661 f.run(&[b"ZRANGE", b"z", b"0", b"-1", b"WITHSCORES"]),
11662 "*4\r\n$1\r\nb\r\n$1\r\n2\r\n$1\r\nc\r\n$1\r\n3\r\n"
11663 );
11664 assert_eq!(
11667 f.run(&[b"ZRANGESTORE", b"d", b"z", b"0", b"-1", b"WITHSCORES"]),
11668 "-ERR syntax error\r\n"
11669 );
11670 }
11671
11672 #[test]
11675 fn the_three_removals_share_their_window_with_the_reads() {
11676 let mut f = Fixture::new();
11677 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11678 assert_eq!(f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"0"]), ":1\r\n");
11679 assert_eq!(
11680 f.run(&[b"ZRANGE", b"z", b"0", b"-1"]),
11681 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
11682 );
11683 assert_eq!(
11684 f.run(&[b"ZREMRANGEBYSCORE", b"z", b"(2", b"+inf"]),
11685 ":1\r\n"
11686 );
11687 assert_eq!(f.run(&[b"ZRANGE", b"z", b"0", b"-1"]), "*1\r\n$1\r\nb\r\n");
11688 assert_eq!(f.run(&[b"ZREMRANGEBYLEX", b"z", b"-", b"+"]), ":1\r\n");
11690 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
11691 assert_eq!(
11692 f.run(&[b"ZREMRANGEBYRANK", b"nokey", b"0", b"-1"]),
11693 ":0\r\n"
11694 );
11695 assert_eq!(
11696 f.run(&[b"ZREMRANGEBYRANK", b"z", b"0", b"x"]),
11697 "-ERR value is not an integer or out of range\r\n"
11698 );
11699 }
11700
11701 #[test]
11703 fn the_three_algebra_commands_combine_scores_and_order_the_answer_once() {
11704 let mut f = Fixture::new();
11705 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11706 f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
11707 assert_eq!(
11708 f.run(&[b"ZUNION", b"2", b"z", b"y"]),
11709 "*4\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\nd\r\n"
11710 );
11711 assert_eq!(
11714 f.run(&[b"ZUNION", b"2", b"z", b"y", b"WITHSCORES"]),
11715 "*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"
11716 );
11717 assert_eq!(
11718 f.run(&[
11719 b"ZUNION",
11720 b"2",
11721 b"z",
11722 b"y",
11723 b"WEIGHTS",
11724 b"2",
11725 b"3",
11726 b"WITHSCORES"
11727 ]),
11728 "*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"
11729 );
11730 assert_eq!(
11731 f.run(&[
11732 b"ZUNION",
11733 b"2",
11734 b"z",
11735 b"y",
11736 b"AGGREGATE",
11737 b"MIN",
11738 b"WITHSCORES"
11739 ]),
11740 "*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"
11741 );
11742 assert_eq!(
11743 f.run(&[
11744 b"ZUNION",
11745 b"2",
11746 b"z",
11747 b"y",
11748 b"AGGREGATE",
11749 b"MAX",
11750 b"WITHSCORES"
11751 ]),
11752 "*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"
11753 );
11754 assert_eq!(
11755 f.run(&[b"ZINTER", b"2", b"z", b"y", b"WITHSCORES"]),
11756 "*2\r\n$1\r\nb\r\n$2\r\n12\r\n"
11757 );
11758 assert_eq!(
11759 f.run(&[b"ZDIFF", b"2", b"z", b"y", b"WITHSCORES"]),
11760 "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nc\r\n$1\r\n3\r\n"
11761 );
11762 assert_eq!(f.run(&[b"ZUNION", b"1", b"nokey"]), "*0\r\n");
11763 f.run(&[b"SADD", b"p", b"a", b"d"]);
11766 assert_eq!(
11767 f.run(&[b"ZUNION", b"2", b"z", b"p", b"WITHSCORES"]),
11768 "*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"
11769 );
11770 for cmd in [
11773 &[
11774 b"ZDIFF".as_slice(),
11775 b"2",
11776 b"z",
11777 b"y",
11778 b"WEIGHTS",
11779 b"1",
11780 b"1",
11781 ][..],
11782 &[b"ZDIFF", b"2", b"z", b"y", b"AGGREGATE", b"MIN"],
11783 ] {
11784 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
11785 }
11786 }
11787
11788 #[test]
11790 fn the_algebra_counts_its_keys_and_says_so_when_the_count_is_wrong() {
11791 let mut f = Fixture::new();
11792 f.run(&[b"ZADD", b"z", b"1", b"a"]);
11793 f.run(&[b"ZADD", b"y", b"2", b"b"]);
11794 assert_eq!(
11796 f.run(&[b"ZUNION", b"0", b"z"]),
11797 "-ERR at least 1 input key is needed for 'zunion' command\r\n"
11798 );
11799 assert_eq!(
11800 f.run(&[b"ZUNION", b"-1", b"z"]),
11801 "-ERR at least 1 input key is needed for 'zunion' command\r\n"
11802 );
11803 assert_eq!(
11804 f.run(&[b"ZINTERCARD", b"0", b"z"]),
11805 "-ERR at least 1 input key is needed for 'zintercard' command\r\n"
11806 );
11807 assert_eq!(
11810 f.run(&[b"ZUNION", b"3", b"z", b"y"]),
11811 "-ERR syntax error\r\n"
11812 );
11813 assert_eq!(
11814 f.run(&[b"ZUNION", b"x", b"z"]),
11815 "-ERR value is not an integer or out of range\r\n"
11816 );
11817 assert_eq!(
11820 f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"1"]),
11821 "-ERR syntax error\r\n"
11822 );
11823 assert_eq!(
11824 f.run(&[b"ZUNION", b"2", b"z", b"y", b"WEIGHTS", b"a", b"b"]),
11825 "-ERR weight value is not a float\r\n"
11826 );
11827 assert_eq!(
11828 f.run(&[b"ZUNION", b"2", b"z", b"y", b"AGGREGATE", b"NOPE"]),
11829 "-ERR syntax error\r\n"
11830 );
11831 }
11832
11833 #[test]
11835 fn the_algebra_stores_answer_a_count_and_delete_an_empty_destination() {
11836 let mut f = Fixture::new();
11837 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11838 f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"d"]);
11839 assert_eq!(f.run(&[b"ZUNIONSTORE", b"d", b"2", b"z", b"y"]), ":4\r\n");
11840 assert_eq!(
11841 f.run(&[b"ZRANGE", b"d", b"0", b"-1", b"WITHSCORES"]),
11842 "*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"
11843 );
11844 assert_eq!(f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"y"]), ":1\r\n");
11845 assert_eq!(f.run(&[b"ZDIFFSTORE", b"d", b"2", b"z", b"y"]), ":2\r\n");
11846 assert_eq!(
11849 f.run(&[b"ZINTERSTORE", b"d", b"2", b"z", b"nokey"]),
11850 ":0\r\n"
11851 );
11852 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
11853 assert_eq!(f.run(&[b"ZUNIONSTORE", b"z", b"2", b"z", b"y"]), ":4\r\n");
11855 assert_eq!(f.run(&[b"ZCARD", b"z"]), ":4\r\n");
11856 for cmd in [
11857 &[
11858 b"ZUNIONSTORE".as_slice(),
11859 b"d",
11860 b"2",
11861 b"z",
11862 b"y",
11863 b"WITHSCORES",
11864 ][..],
11865 &[
11866 b"ZDIFFSTORE",
11867 b"d",
11868 b"2",
11869 b"z",
11870 b"y",
11871 b"WEIGHTS",
11872 b"1",
11873 b"1",
11874 ],
11875 ] {
11876 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
11877 }
11878 }
11879
11880 #[test]
11882 fn intercard_counts_and_stops_at_its_limit() {
11883 let mut f = Fixture::new();
11884 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11885 f.run(&[b"ZADD", b"y", b"10", b"b", b"20", b"c", b"30", b"d"]);
11886 assert_eq!(f.run(&[b"ZINTERCARD", b"2", b"z", b"y"]), ":2\r\n");
11887 assert_eq!(
11889 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"0"]),
11890 ":2\r\n"
11891 );
11892 assert_eq!(
11893 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"1"]),
11894 ":1\r\n"
11895 );
11896 let bad = "-ERR LIMIT can't be negative\r\n";
11899 assert_eq!(
11900 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"-1"]),
11901 bad
11902 );
11903 assert_eq!(
11904 f.run(&[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT", b"x"]),
11905 bad
11906 );
11907 for cmd in [
11908 &[b"ZINTERCARD".as_slice(), b"3", b"z", b"y"][..],
11909 &[b"ZINTERCARD", b"2", b"z", b"y", b"LIMIT"],
11910 &[b"ZINTERCARD", b"2", b"z", b"y", b"junk", b"1"],
11911 ] {
11912 assert_eq!(f.run(cmd), "-ERR syntax error\r\n", "{cmd:?}");
11913 }
11914 }
11915
11916 #[test]
11918 fn a_draw_answers_one_member_or_an_array_of_them() {
11919 let mut f = Fixture::new();
11920 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11921 assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "$-1\r\n");
11924 assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
11925 assert_eq!(f.run(&[b"ZRANDMEMBER", b"z", b"0"]), "*0\r\n");
11926 assert!(f.run(&[b"ZRANDMEMBER", b"z"]).starts_with("$1\r\n"));
11927 let all = f.run(&[b"ZRANDMEMBER", b"z", b"10"]);
11930 assert!(all.starts_with("*3\r\n"), "{all}");
11931 for m in ["a", "b", "c"] {
11932 assert!(all.contains(m), "{all}");
11933 }
11934 assert!(
11937 f.run(&[b"ZRANDMEMBER", b"z", b"-5"]).starts_with("*5\r\n"),
11938 "five draws with replacement"
11939 );
11940 assert!(
11941 f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"])
11942 .starts_with("*4\r\n"),
11943 "two pairs, flat on RESP2"
11944 );
11945 f.out = Out::new(Proto::Resp3);
11946 let got = f.run(&[b"ZRANDMEMBER", b"z", b"2", b"WITHSCORES"]);
11947 assert!(got.starts_with("*2\r\n*2\r\n"), "{got}");
11948 assert_eq!(f.run(&[b"ZRANDMEMBER", b"nokey"]), "_\r\n");
11949 f.out = Out::new(Proto::Resp2);
11950 assert_eq!(
11951 f.run(&[b"ZRANDMEMBER", b"z", b"2", b"junk"]),
11952 "-ERR syntax error\r\n"
11953 );
11954 assert_eq!(
11955 f.run(&[b"ZRANDMEMBER", b"z", b"x"]),
11956 "-ERR value is not an integer or out of range\r\n"
11957 );
11958 }
11959
11960 #[test]
11962 fn a_sorted_set_scan_answers_pairs_of_strings_on_both_protocols() {
11963 let mut f = Fixture::new();
11964 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11965 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";
11966 assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
11967 assert_eq!(f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"10"]), all);
11968 assert_eq!(
11969 f.run(&[b"ZSCAN", b"z", b"0", b"MATCH", b"a*"]),
11970 "*2\r\n$1\r\n0\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
11971 );
11972 assert_eq!(
11973 f.run(&[b"ZSCAN", b"nokey", b"0"]),
11974 "*2\r\n$1\r\n0\r\n*0\r\n"
11975 );
11976 f.out = Out::new(Proto::Resp3);
11979 assert_eq!(f.run(&[b"ZSCAN", b"z", b"0"]), all);
11980 f.out = Out::new(Proto::Resp2);
11981 assert_eq!(
11982 f.run(&[b"ZSCAN", b"z", b"0", b"NOVALUES"]),
11983 "-ERR NOVALUES option can only be used in HSCAN\r\n"
11984 );
11985 assert_eq!(f.run(&[b"ZSCAN", b"z", b"-1"]), "-ERR invalid cursor\r\n");
11986 assert_eq!(
11987 f.run(&[b"ZSCAN", b"z", b"0", b"COUNT", b"0"]),
11988 "-ERR syntax error\r\n"
11989 );
11990 }
11991
11992 #[test]
11994 fn a_sorted_set_pop_changes_shape_when_it_is_given_a_count() {
11995 let mut f = Fixture::new();
11996 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
11997 assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n$1\r\n1\r\n");
11999 assert_eq!(f.run(&[b"ZPOPMAX", b"z"]), "*2\r\n$1\r\nc\r\n$1\r\n3\r\n");
12000 f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
12001 assert_eq!(
12003 f.run(&[b"ZPOPMIN", b"z", b"2"]),
12004 "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
12005 );
12006 assert_eq!(f.run(&[b"ZPOPMIN", b"nokey"]), "*0\r\n");
12009 assert_eq!(f.run(&[b"ZPOPMIN", b"nokey", b"2"]), "*0\r\n");
12010 assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"0"]), "*0\r\n");
12011 assert_eq!(
12013 f.run(&[b"ZPOPMIN", b"z", b"9"]),
12014 "*2\r\n$1\r\nc\r\n$1\r\n3\r\n"
12015 );
12016 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
12017
12018 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b"]);
12019 f.out = Out::new(Proto::Resp3);
12020 assert_eq!(f.run(&[b"ZPOPMIN", b"z"]), "*2\r\n$1\r\na\r\n,1\r\n");
12021 assert_eq!(
12022 f.run(&[b"ZPOPMIN", b"z", b"1"]),
12023 "*1\r\n*2\r\n$1\r\nb\r\n,2\r\n"
12024 );
12025 f.out = Out::new(Proto::Resp2);
12026 let bad = "-ERR value is out of range, must be positive\r\n";
12029 assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"x"]), bad);
12030 assert_eq!(f.run(&[b"ZPOPMIN", b"z", b"-1"]), bad);
12031 assert_eq!(
12032 f.run(&[b"ZPOPMIN", b"z", b"1", b"2"]),
12033 "-ERR syntax error\r\n"
12034 );
12035 }
12036
12037 #[test]
12039 fn a_multi_key_pop_names_the_key_that_answered_and_nests_its_pairs() {
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.run(&[b"ZMPOP", b"2", b"nokey", b"z", b"MIN"]),
12044 "*2\r\n$1\r\nz\r\n*1\r\n*2\r\n$1\r\na\r\n$1\r\n1\r\n"
12045 );
12046 assert_eq!(
12049 f.run(&[b"ZMPOP", b"1", b"z", b"MAX", b"COUNT", b"2"]),
12050 "*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"
12051 );
12052 assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "*-1\r\n");
12054 f.out = Out::new(Proto::Resp3);
12055 assert_eq!(f.run(&[b"ZMPOP", b"1", b"nokey", b"MIN"]), "_\r\n");
12056 f.out = Out::new(Proto::Resp2);
12057 let numkeys = "-ERR numkeys should be greater than 0\r\n";
12058 for bad in [
12059 &[b"ZMPOP".as_slice(), b"0", b"z", b"MIN"][..],
12060 &[b"ZMPOP", b"-1", b"z", b"MIN"],
12061 &[b"ZMPOP", b"x", b"z", b"MIN"],
12062 ] {
12063 assert_eq!(f.run(bad), numkeys, "{:?}", bad[1]);
12064 }
12065 let count = "-ERR count should be greater than 0\r\n";
12066 for bad in [
12067 &[b"ZMPOP".as_slice(), b"1", b"z", b"MIN", b"COUNT", b"0"][..],
12068 &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"-1"],
12069 &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"x"],
12070 ] {
12071 assert_eq!(f.run(bad), count, "{:?}", bad[5]);
12072 }
12073 let syntax = "-ERR syntax error\r\n";
12074 for bad in [
12075 &[b"ZMPOP".as_slice(), b"2", b"z", b"MIN"][..],
12078 &[b"ZMPOP", b"1", b"z", b"SIDEWAYS"],
12079 &[b"ZMPOP", b"1", b"z", b"MIN", b"junk"],
12080 &[b"ZMPOP", b"1", b"z", b"MIN", b"COUNT", b"1", b"junk"],
12081 ] {
12082 assert_eq!(f.run(bad), syntax, "{bad:?}");
12083 }
12084 }
12085
12086 #[test]
12089 fn the_sorted_set_pops_that_wait_answer_like_the_ones_they_wrap() {
12090 let mut f = Fixture::new();
12091 f.run(&[b"ZADD", b"z", b"1", b"a", b"2", b"b", b"3", b"c"]);
12092 assert_eq!(
12093 f.flow(&[b"BZPOPMIN", b"nokey", b"z", b"0"]),
12094 (
12095 Flow::Continue,
12096 "*3\r\n$1\r\nz\r\n$1\r\na\r\n$1\r\n1\r\n".to_owned()
12097 )
12098 );
12099 assert_eq!(
12100 f.run(&[b"BZPOPMAX", b"z", b"0"]),
12101 "*3\r\n$1\r\nz\r\n$1\r\nc\r\n$1\r\n3\r\n"
12102 );
12103 f.run(&[b"ZADD", b"z", b"1", b"a", b"3", b"c"]);
12104 assert_eq!(
12105 f.run(&[
12106 b"BZMPOP", b"0", b"2", b"nokey", b"z", b"MIN", b"COUNT", b"2"
12107 ]),
12108 "*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"
12109 );
12110 f.out = Out::new(Proto::Resp3);
12111 assert_eq!(
12112 f.run(&[b"BZPOPMIN", b"z", b"0"]),
12113 "*3\r\n$1\r\nz\r\n$1\r\nc\r\n,3\r\n"
12114 );
12115 f.out = Out::new(Proto::Resp2);
12116 assert_eq!(
12118 f.flow(&[b"BZPOPMIN", b"z", b"0"]),
12119 (Flow::Block, String::new())
12120 );
12121 assert_eq!(
12122 f.flow(&[b"BZMPOP", b"0", b"1", b"z", b"MIN"]),
12123 (Flow::Block, String::new())
12124 );
12125 assert_eq!(
12128 f.run(&[b"BZMPOP", b"abc", b"0", b"z", b"MIN"]),
12129 "-ERR timeout is not a float or out of range\r\n"
12130 );
12131 assert_eq!(
12132 f.run(&[b"BZMPOP", b"0", b"0", b"z", b"MIN"]),
12133 "-ERR numkeys should be greater than 0\r\n"
12134 );
12135 assert_eq!(
12136 f.run(&[b"BZPOPMIN", b"z", b"-1"]),
12137 "-ERR timeout is negative\r\n"
12138 );
12139 }
12140
12141 #[test]
12145 fn a_parked_sorted_set_client_waits_for_a_member_and_not_for_a_key() {
12146 let mut f = Fixture::new();
12147 assert_eq!(f.flow(&[b"BZPOPMIN", b"z", b"0"]).0, Flow::Block);
12148 assert_eq!(f.server.parked(), 1);
12149 f.run(&[b"SET", b"z", b"v"]);
12152 let mut out = Out::new(Proto::Resp2);
12153 assert!(!f.server.serve_waiter(7, 0, &mut out));
12154 assert!(out.as_slice().is_empty());
12155 f.run(&[b"DEL", b"z"]);
12156 f.run(&[b"ZADD", b"z", b"5", b"m"]);
12157 assert!(f.server.serve_waiter(7, 0, &mut out));
12158 assert_eq!(
12159 core::str::from_utf8(out.as_slice()).expect("ascii"),
12160 "*3\r\n$1\r\nz\r\n$1\r\nm\r\n$1\r\n5\r\n"
12161 );
12162 assert_eq!(f.run(&[b"EXISTS", b"z"]), ":0\r\n");
12165 }
12166
12167 #[test]
12168 fn every_sorted_set_command_says_wrongtype_and_writes_nothing() {
12169 let mut f = Fixture::new();
12170 f.run(&[b"SET", b"s", b"v"]);
12171 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12172 for cmd in [
12173 &[b"ZADD".as_slice(), b"s", b"1", b"a"][..],
12174 &[b"ZINCRBY", b"s", b"1", b"a"],
12175 &[b"ZCARD", b"s"],
12176 &[b"ZSCORE", b"s", b"a"],
12177 &[b"ZMSCORE", b"s", b"a"],
12178 &[b"ZREM", b"s", b"a"],
12179 &[b"ZRANK", b"s", b"a"],
12180 &[b"ZREVRANK", b"s", b"a"],
12181 &[b"ZCOUNT", b"s", b"1", b"2"],
12182 &[b"ZLEXCOUNT", b"s", b"-", b"+"],
12183 &[b"ZRANGE", b"s", b"0", b"-1"],
12184 &[b"ZREVRANGE", b"s", b"0", b"-1"],
12185 &[b"ZRANGEBYSCORE", b"s", b"1", b"2"],
12186 &[b"ZREVRANGEBYSCORE", b"s", b"2", b"1"],
12187 &[b"ZRANGEBYLEX", b"s", b"-", b"+"],
12188 &[b"ZREVRANGEBYLEX", b"s", b"+", b"-"],
12189 &[b"ZRANGESTORE", b"d", b"s", b"0", b"-1"],
12190 &[b"ZREMRANGEBYRANK", b"s", b"0", b"-1"],
12191 &[b"ZREMRANGEBYSCORE", b"s", b"1", b"2"],
12192 &[b"ZREMRANGEBYLEX", b"s", b"-", b"+"],
12193 &[b"ZUNION", b"1", b"s"],
12194 &[b"ZINTER", b"1", b"s"],
12195 &[b"ZDIFF", b"1", b"s"],
12196 &[b"ZUNIONSTORE", b"d", b"1", b"s"],
12197 &[b"ZINTERSTORE", b"d", b"1", b"s"],
12198 &[b"ZDIFFSTORE", b"d", b"1", b"s"],
12199 &[b"ZINTERCARD", b"1", b"s"],
12200 &[b"ZRANDMEMBER", b"s"],
12201 &[b"ZSCAN", b"s", b"0"],
12202 &[b"ZPOPMIN", b"s"],
12203 &[b"ZPOPMAX", b"s", b"2"],
12204 &[b"ZMPOP", b"1", b"s", b"MIN"],
12205 &[b"BZPOPMIN", b"s", b"0"],
12206 &[b"BZPOPMAX", b"s", b"0"],
12207 &[b"BZMPOP", b"0", b"1", b"s", b"MIN"],
12208 ] {
12209 assert_eq!(f.run(cmd), wrong, "{:?}", cmd[0]);
12210 }
12211 assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nv\r\n");
12212 }
12213
12214 #[cfg_attr(miri, ignore = "the volume is the claim")]
12219 #[test]
12220 fn churning_sorted_sets_does_not_grow_the_server() {
12221 let mut f = Fixture::new();
12222 let members: Vec<Vec<u8>> = (0..200).map(|i| format!("m{i}").into_bytes()).collect();
12223 let scores: Vec<Vec<u8>> = (0..200).map(|i| format!("{i}").into_bytes()).collect();
12224 let mut args: Vec<&[u8]> = vec![b"ZADD", b"z"];
12225 for i in 0..200 {
12226 args.push(&scores[i]);
12227 args.push(&members[i]);
12228 }
12229
12230 f.run(&args);
12231 f.run(&[b"DEL", b"z"]);
12232 f.server.compact_step();
12233 let after_first = f.server.memory_bytes();
12234
12235 for _ in 0..200 {
12236 f.run(&args);
12237 f.run(&[b"DEL", b"z"]);
12238 f.server.compact_step();
12239 }
12240 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
12241 assert!(
12242 f.server.memory_bytes() <= after_first * 2,
12243 "held {} after two hundred passes against {after_first} after one",
12244 f.server.memory_bytes()
12245 );
12246 }
12247
12248 fn sicily(f: &mut Fixture) {
12256 f.run(&[
12257 b"GEOADD",
12258 b"Sicily",
12259 b"13.361389",
12260 b"38.115556",
12261 b"Palermo",
12262 b"15.087269",
12263 b"37.502669",
12264 b"Catania",
12265 ]);
12266 f.run(&[
12267 b"GEOADD",
12268 b"Sicily",
12269 b"13.583333",
12270 b"37.316667",
12271 b"Agrigento",
12272 ]);
12273 }
12274
12275 #[test]
12276 fn places_go_in_as_scores_and_come_back_as_positions() {
12277 let mut f = Fixture::new();
12278 assert_eq!(
12279 f.run(&[
12280 b"GEOADD",
12281 b"Sicily",
12282 b"13.361389",
12283 b"38.115556",
12284 b"Palermo",
12285 b"15.087269",
12286 b"37.502669",
12287 b"Catania"
12288 ]),
12289 ":2\r\n"
12290 );
12291 assert_eq!(f.run(&[b"TYPE", b"Sicily"]), "+zset\r\n");
12295 assert_eq!(
12296 f.run(&[b"ZSCORE", b"Sicily", b"Palermo"]),
12297 "$16\r\n3479099956230698\r\n"
12298 );
12299 assert_eq!(
12300 f.run(&[b"GEOPOS", b"Sicily", b"Palermo", b"NonExisting"]),
12301 "*2\r\n*2\r\n$18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n*-1\r\n"
12302 );
12303 assert_eq!(
12304 f.run(&[
12305 b"GEOHASH",
12306 b"Sicily",
12307 b"Palermo",
12308 b"Catania",
12309 b"NonExisting"
12310 ]),
12311 "*3\r\n$11\r\nsqc8b49rny0\r\n$11\r\nsqdtr74hyu0\r\n$-1\r\n"
12312 );
12313 assert_eq!(f.run(&[b"GEOPOS", b"nokey", b"a"]), "*1\r\n*-1\r\n");
12317 assert_eq!(f.run(&[b"GEOHASH", b"nokey", b"a"]), "*1\r\n$-1\r\n");
12318 }
12319
12320 #[test]
12321 fn a_distance_comes_back_with_four_places_in_whatever_unit_was_asked_for() {
12322 let mut f = Fixture::new();
12323 sicily(&mut f);
12324 assert_eq!(
12325 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania"]),
12326 "$11\r\n166274.1516\r\n"
12327 );
12328 assert_eq!(
12329 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"km"]),
12330 "$8\r\n166.2742\r\n"
12331 );
12332 assert_eq!(
12333 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Catania", b"mi"]),
12334 "$8\r\n103.3182\r\n"
12335 );
12336 assert_eq!(
12340 f.run(&[b"GEODIST", b"Sicily", b"Palermo", b"Foo"]),
12341 "$-1\r\n"
12342 );
12343 assert_eq!(f.run(&[b"GEODIST", b"nokey", b"a", b"b"]), "$-1\r\n");
12344 assert_eq!(
12345 f.run(&[b"GEODIST", b"nokey", b"a", b"b", b"parsecs"]),
12346 "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n"
12347 );
12348 assert_eq!(
12349 f.run(&[b"GEODIST", b"Sicily", b"a", b"b", b"km", b"extra"]),
12350 "-ERR syntax error\r\n"
12351 );
12352 }
12353
12354 #[test]
12355 fn a_search_finds_what_is_inside_it_nearest_first() {
12356 let mut f = Fixture::new();
12357 sicily(&mut f);
12358 let all = "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n";
12359 assert_eq!(
12360 f.run(&[
12361 b"GEOSEARCH",
12362 b"Sicily",
12363 b"FROMLONLAT",
12364 b"15",
12365 b"37",
12366 b"BYRADIUS",
12367 b"200",
12368 b"km",
12369 b"ASC"
12370 ]),
12371 all
12372 );
12373 assert_eq!(
12376 f.run(&[b"GEORADIUS", b"Sicily", b"15", b"37", b"200", b"km", b"ASC"]),
12377 all
12378 );
12379 assert_eq!(
12380 f.run(&[
12381 b"GEORADIUS_RO",
12382 b"Sicily",
12383 b"15",
12384 b"37",
12385 b"200",
12386 b"km",
12387 b"ASC"
12388 ]),
12389 all
12390 );
12391 assert_eq!(
12394 f.run(&[
12395 b"GEORADIUS",
12396 b"Sicily",
12397 b"15",
12398 b"37",
12399 b"200",
12400 b"km",
12401 b"DESC",
12402 b"COUNT",
12403 b"1"
12404 ]),
12405 "*1\r\n$7\r\nPalermo\r\n"
12406 );
12407 assert_eq!(
12408 f.run(&[
12409 b"GEORADIUS",
12410 b"Sicily",
12411 b"15",
12412 b"37",
12413 b"200",
12414 b"km",
12415 b"COUNT",
12416 b"1"
12417 ]),
12418 "*1\r\n$7\r\nCatania\r\n"
12419 );
12420 let empty = "*0\r\n";
12423 assert_eq!(
12424 f.run(&[
12425 b"GEOSEARCH",
12426 b"Sicily",
12427 b"FROMLONLAT",
12428 b"15",
12429 b"37",
12430 b"BYRADIUS",
12431 b"1",
12432 b"km"
12433 ]),
12434 empty
12435 );
12436 assert_eq!(
12437 f.run(&[
12438 b"GEOSEARCH",
12439 b"nokey",
12440 b"FROMLONLAT",
12441 b"15",
12442 b"37",
12443 b"BYRADIUS",
12444 b"1",
12445 b"km"
12446 ]),
12447 empty
12448 );
12449 assert_eq!(
12450 f.run(&[b"GEORADIUSBYMEMBER", b"nokey", b"m", b"1", b"km"]),
12451 empty
12452 );
12453 }
12454
12455 #[test]
12456 fn a_search_centred_on_a_member_starts_from_where_that_member_is() {
12457 let mut f = Fixture::new();
12458 sicily(&mut f);
12459 assert_eq!(
12460 f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Agrigento", b"100", b"km"]),
12461 "*2\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
12462 );
12463 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";
12466 assert_eq!(
12467 f.run(&[
12468 b"GEORADIUSBYMEMBER_RO",
12469 b"Sicily",
12470 b"Agrigento",
12471 b"100",
12472 b"km",
12473 b"WITHDIST"
12474 ]),
12475 with_dist
12476 );
12477 assert_eq!(
12478 f.run(&[
12479 b"GEOSEARCH",
12480 b"Sicily",
12481 b"FROMMEMBER",
12482 b"Agrigento",
12483 b"BYRADIUS",
12484 b"100",
12485 b"km",
12486 b"ASC",
12487 b"WITHDIST"
12488 ]),
12489 with_dist
12490 );
12491 assert_eq!(
12492 f.run(&[b"GEORADIUSBYMEMBER", b"Sicily", b"Nowhere", b"100", b"km"]),
12493 "-ERR could not decode requested zset member\r\n"
12494 );
12495 }
12496
12497 #[test]
12498 fn a_box_search_reports_the_distance_the_hash_and_the_coordinates() {
12499 let mut f = Fixture::new();
12500 sicily(&mut f);
12501 assert_eq!(
12505 f.run(&[
12506 b"GEOSEARCH",
12507 b"Sicily",
12508 b"FROMLONLAT",
12509 b"15",
12510 b"37",
12511 b"BYBOX",
12512 b"400",
12513 b"400",
12514 b"km",
12515 b"ASC",
12516 b"WITHCOORD",
12517 b"WITHDIST",
12518 b"WITHHASH"
12519 ]),
12520 "*3\r\n*4\r\n$7\r\nCatania\r\n$7\r\n56.4413\r\n:3479447370796909\r\n*2\r\n\
12521 $18\r\n15.087267458438873\r\n$17\r\n37.50266842333162\r\n\
12522 *4\r\n$9\r\nAgrigento\r\n$8\r\n130.4235\r\n:3479030013248308\r\n*2\r\n\
12523 $18\r\n13.583331406116486\r\n$18\r\n37.316668049938166\r\n\
12524 *4\r\n$7\r\nPalermo\r\n$8\r\n190.4424\r\n:3479099956230698\r\n*2\r\n\
12525 $18\r\n13.361389338970184\r\n$16\r\n38.1155563954963\r\n"
12526 );
12527 }
12528
12529 #[test]
12530 fn a_store_writes_the_hashes_and_a_storedist_writes_the_distances() {
12531 let mut f = Fixture::new();
12532 sicily(&mut f);
12533 let hashes = "*6\r\n$9\r\nAgrigento\r\n$16\r\n3479030013248308\r\n\
12534 $7\r\nPalermo\r\n$16\r\n3479099956230698\r\n\
12535 $7\r\nCatania\r\n$16\r\n3479447370796909\r\n";
12536 assert_eq!(
12537 f.run(&[
12538 b"GEOSEARCHSTORE",
12539 b"dst",
12540 b"Sicily",
12541 b"FROMLONLAT",
12542 b"15",
12543 b"37",
12544 b"BYRADIUS",
12545 b"200",
12546 b"km",
12547 b"ASC"
12548 ]),
12549 ":3\r\n"
12550 );
12551 assert_eq!(
12552 f.run(&[b"ZRANGE", b"dst", b"0", b"-1", b"WITHSCORES"]),
12553 hashes
12554 );
12555 assert_eq!(
12558 f.run(&[
12559 b"GEORADIUS",
12560 b"Sicily",
12561 b"15",
12562 b"37",
12563 b"200",
12564 b"km",
12565 b"STORE",
12566 b"dst3"
12567 ]),
12568 ":3\r\n"
12569 );
12570 assert_eq!(
12571 f.run(&[b"ZRANGE", b"dst3", b"0", b"-1", b"WITHSCORES"]),
12572 hashes
12573 );
12574 assert_eq!(
12583 f.run(&[
12584 b"GEOSEARCHSTORE",
12585 b"dst2",
12586 b"Sicily",
12587 b"FROMLONLAT",
12588 b"15",
12589 b"37",
12590 b"BYRADIUS",
12591 b"200",
12592 b"km",
12593 b"ASC",
12594 b"STOREDIST"
12595 ]),
12596 ":3\r\n"
12597 );
12598 for (member, want) in [
12599 ("Catania", 56.441_257_870_158_19),
12600 ("Agrigento", 130.423_487_067_147_14),
12601 ("Palermo", 190.442_429_847_757_92),
12602 ] {
12603 let reply = f.run(&[b"ZSCORE", b"dst2", member.as_bytes()]);
12604 let got: f64 = reply
12605 .trim_start_matches(|c: char| c != '\n')
12606 .trim()
12607 .parse()
12608 .unwrap_or_else(|_| panic!("{member} scored {reply:?}"));
12609 assert!(
12610 (got - want).abs() < 1e-9,
12611 "{member} scored {got} not {want}"
12612 );
12613 }
12614 assert_eq!(
12617 f.run(&[b"ZRANGE", b"dst2", b"0", b"-1"]),
12618 "*3\r\n$7\r\nCatania\r\n$9\r\nAgrigento\r\n$7\r\nPalermo\r\n"
12619 );
12620 assert_eq!(
12624 f.run(&[
12625 b"GEOSEARCHSTORE",
12626 b"dst",
12627 b"nokey",
12628 b"FROMLONLAT",
12629 b"15",
12630 b"37",
12631 b"BYRADIUS",
12632 b"200",
12633 b"km"
12634 ]),
12635 ":0\r\n"
12636 );
12637 assert_eq!(f.run(&[b"EXISTS", b"dst"]), ":0\r\n");
12638 }
12639
12640 #[test]
12641 fn the_gates_on_geoadd_are_the_ones_zadd_has() {
12642 let mut f = Fixture::new();
12643 sicily(&mut f);
12644 assert_eq!(
12647 f.run(&[
12648 b"GEOADD",
12649 b"Sicily",
12650 b"XX",
12651 b"CH",
12652 b"13.361389",
12653 b"38.115556",
12654 b"Palermo"
12655 ]),
12656 ":0\r\n"
12657 );
12658 assert_eq!(
12659 f.run(&[
12660 b"GEOADD",
12661 b"Sicily",
12662 b"NX",
12663 b"13.361389",
12664 b"38.9",
12665 b"Palermo"
12666 ]),
12667 ":0\r\n"
12668 );
12669 assert_eq!(
12670 f.run(&[
12671 b"GEOADD",
12672 b"Sicily",
12673 b"CH",
12674 b"13.361389",
12675 b"38.9",
12676 b"Palermo"
12677 ]),
12678 ":1\r\n"
12679 );
12680 assert_eq!(
12683 f.run(&[
12684 b"GEOADD",
12685 b"new",
12686 b"13.361389",
12687 b"38.115556",
12688 b"here",
12689 b"181",
12690 b"38",
12691 b"there"
12692 ]),
12693 "-ERR invalid longitude,latitude pair 181.000000,38.000000\r\n"
12694 );
12695 assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
12696 assert_eq!(
12697 f.run(&[b"GEOADD", b"new", b"x", b"38", b"here"]),
12698 "-ERR value is not a valid float\r\n"
12699 );
12700 assert_eq!(
12703 f.run(&[b"GEOADD", b"new", b"13", b"38", b"here", b"and"]),
12704 "-ERR syntax error\r\n"
12705 );
12706 assert_eq!(
12707 f.run(&[b"GEOADD", b"new", b"NX", b"XX", b"CH"]),
12708 "-ERR syntax error\r\n"
12709 );
12710 assert_eq!(
12711 f.run(&[b"GEOADD", b"new", b"CH", b"CH", b"CH", b"CH"]),
12712 "-ERR syntax error\r\n"
12713 );
12714 assert_eq!(
12715 f.run(&[b"GEOADD", b"new", b"NX", b"CH"]),
12716 "-ERR wrong number of arguments for 'geoadd' command\r\n"
12717 );
12718 }
12719
12720 #[test]
12723 fn every_way_a_search_can_be_written_wrong_has_its_own_sentence() {
12724 let mut f = Fixture::new();
12725 sicily(&mut f);
12726 let cases: &[(&[&[u8]], &str)] = &[
12727 (
12728 &[b"GEORADIUS", b"Sicily", b"15", b"37", b"x", b"km"],
12729 "-ERR need numeric radius\r\n",
12730 ),
12731 (
12732 &[b"GEORADIUS", b"Sicily", b"15", b"37", b"-1", b"km"],
12733 "-ERR radius cannot be negative\r\n",
12734 ),
12735 (
12736 &[b"GEORADIUS", b"Sicily", b"15", b"37", b"1", b"parsecs"],
12737 "-ERR unsupported unit provided. please use M, KM, FT, MI\r\n",
12738 ),
12739 (
12740 &[b"GEORADIUS", b"Sicily", b"181", b"37", b"1", b"km"],
12741 "-ERR invalid longitude,latitude pair 181.000000,37.000000\r\n",
12742 ),
12743 (
12744 &[
12745 b"GEOSEARCH",
12746 b"Sicily",
12747 b"FROMLONLAT",
12748 b"15",
12749 b"37",
12750 b"BYBOX",
12751 b"x",
12752 b"1",
12753 b"km",
12754 ],
12755 "-ERR need numeric width\r\n",
12756 ),
12757 (
12758 &[
12759 b"GEOSEARCH",
12760 b"Sicily",
12761 b"FROMLONLAT",
12762 b"15",
12763 b"37",
12764 b"BYBOX",
12765 b"1",
12766 b"y",
12767 b"km",
12768 ],
12769 "-ERR need numeric height\r\n",
12770 ),
12771 (
12772 &[
12773 b"GEOSEARCH",
12774 b"Sicily",
12775 b"FROMLONLAT",
12776 b"15",
12777 b"37",
12778 b"BYBOX",
12779 b"-1",
12780 b"1",
12781 b"km",
12782 ],
12783 "-ERR height or width cannot be negative\r\n",
12784 ),
12785 (
12786 &[
12787 b"GEOSEARCH",
12788 b"Sicily",
12789 b"FROMLONLAT",
12790 b"15",
12791 b"37",
12792 b"BYRADIUS",
12793 b"1",
12794 b"km",
12795 b"ANY",
12796 ],
12797 "-ERR the ANY argument requires COUNT argument\r\n",
12798 ),
12799 (
12800 &[
12801 b"GEOSEARCH",
12802 b"Sicily",
12803 b"FROMLONLAT",
12804 b"15",
12805 b"37",
12806 b"BYRADIUS",
12807 b"1",
12808 b"km",
12809 b"COUNT",
12810 b"0",
12811 ],
12812 "-ERR COUNT must be > 0\r\n",
12813 ),
12814 (
12815 &[
12816 b"GEOSEARCH",
12817 b"Sicily",
12818 b"BYRADIUS",
12819 b"1",
12820 b"km",
12821 b"BYBOX",
12822 b"1",
12823 b"1",
12824 b"km",
12825 ],
12826 "-ERR syntax error\r\n",
12827 ),
12828 (
12829 &[
12830 b"GEOSEARCH",
12831 b"Sicily",
12832 b"FROMMEMBER",
12833 b"Palermo",
12834 b"FROMLONLAT",
12835 b"1",
12836 b"2",
12837 b"BYRADIUS",
12838 b"1",
12839 b"km",
12840 ],
12841 "-ERR syntax error\r\n",
12842 ),
12843 (
12846 &[
12847 b"geosearch",
12848 b"Sicily",
12849 b"BYRADIUS",
12850 b"1",
12851 b"km",
12852 b"ASC",
12853 b"WITHDIST",
12854 ],
12855 "-ERR exactly one of FROMMEMBER or FROMLONLAT can be specified for geosearch\r\n",
12856 ),
12857 (
12858 &[
12859 b"GEOSEARCH",
12860 b"Sicily",
12861 b"FROMLONLAT",
12862 b"15",
12863 b"37",
12864 b"ASC",
12865 b"WITHDIST",
12866 ],
12867 "-ERR exactly one of BYRADIUS and BYBOX can be specified for GEOSEARCH\r\n",
12868 ),
12869 (
12872 &[
12873 b"GEOSEARCHSTORE",
12874 b"d",
12875 b"Sicily",
12876 b"FROMLONLAT",
12877 b"15",
12878 b"37",
12879 b"BYRADIUS",
12880 b"1",
12881 b"km",
12882 b"WITHCOORD",
12883 ],
12884 "-ERR GEOSEARCHSTORE is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
12885 ),
12886 (
12887 &[
12888 b"GEORADIUS",
12889 b"Sicily",
12890 b"15",
12891 b"37",
12892 b"1",
12893 b"km",
12894 b"WITHDIST",
12895 b"STORE",
12896 b"d",
12897 ],
12898 "-ERR STORE option in GEORADIUS is not compatible with WITHDIST, WITHHASH and WITHCOORD options\r\n",
12899 ),
12900 (
12903 &[
12904 b"GEORADIUS_RO",
12905 b"Sicily",
12906 b"15",
12907 b"37",
12908 b"1",
12909 b"km",
12910 b"STORE",
12911 b"d",
12912 ],
12913 "-ERR syntax error\r\n",
12914 ),
12915 (
12916 &[
12917 b"GEOSEARCH",
12918 b"Sicily",
12919 b"FROMLONLAT",
12920 b"15",
12921 b"37",
12922 b"BYRADIUS",
12923 b"1",
12924 b"km",
12925 b"STOREDIST",
12926 ],
12927 "-ERR syntax error\r\n",
12928 ),
12929 ];
12930 for (parts, want) in cases {
12931 assert_eq!(&f.run(parts), want, "{:?}", parts[0]);
12932 }
12933 }
12934
12935 #[test]
12938 fn every_geo_command_says_wrongtype() {
12939 let mut f = Fixture::new();
12940 f.run(&[b"SET", b"s", b"v"]);
12941 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
12942 let cases: &[&[&[u8]]] = &[
12943 &[b"GEOADD", b"s", b"13", b"38", b"m"],
12944 &[b"GEOPOS", b"s", b"m"],
12945 &[b"GEOHASH", b"s", b"m"],
12946 &[b"GEODIST", b"s", b"a", b"b"],
12947 &[
12948 b"GEOSEARCH",
12949 b"s",
12950 b"FROMLONLAT",
12951 b"15",
12952 b"37",
12953 b"BYRADIUS",
12954 b"1",
12955 b"km",
12956 ],
12957 &[
12958 b"GEOSEARCHSTORE",
12959 b"d",
12960 b"s",
12961 b"FROMLONLAT",
12962 b"15",
12963 b"37",
12964 b"BYRADIUS",
12965 b"1",
12966 b"km",
12967 ],
12968 &[b"GEORADIUS", b"s", b"15", b"37", b"1", b"km"],
12969 &[b"GEORADIUS_RO", b"s", b"15", b"37", b"1", b"km"],
12970 &[b"GEORADIUSBYMEMBER", b"s", b"m", b"1", b"km"],
12971 &[b"GEORADIUSBYMEMBER_RO", b"s", b"m", b"1", b"km"],
12972 ];
12973 for case in cases {
12974 assert_eq!(f.run(case), wrong, "{:?}", case[0]);
12975 }
12976 assert_eq!(
12979 f.run(&[b"GEORADIUS", b"s", b"15", b"37", b"x", b"km"]),
12980 wrong
12981 );
12982 }
12983
12984 #[test]
12987 fn an_array_writes_at_any_index_and_reads_back_what_it_sent() {
12988 let mut f = Fixture::new();
12989 assert_eq!(
12992 f.run(&[b"ARSET", b"a", b"1000", b"x", b"y", b"z"]),
12993 ":3\r\n"
12994 );
12995 assert_eq!(f.run(&[b"ARSET", b"a", b"1000", b"X", b"Y"]), ":0\r\n");
12996 assert_eq!(f.run(&[b"ARGET", b"a", b"1000"]), "$1\r\nX\r\n");
12997 assert_eq!(f.run(&[b"ARGET", b"a", b"1002"]), "$1\r\nz\r\n");
12998 assert_eq!(f.run(&[b"ARGET", b"a", b"999"]), "$-1\r\n");
13000 assert_eq!(f.run(&[b"ARGET", b"nope", b"0"]), "$-1\r\n");
13001 assert_eq!(
13002 f.run(&[b"ARMGET", b"a", b"1002", b"999", b"1000"]),
13003 "*3\r\n$1\r\nz\r\n$-1\r\n$1\r\nX\r\n"
13004 );
13005 assert_eq!(f.run(&[b"ARMSET", b"a", b"5", b"p", b"5", b"q"]), ":1\r\n");
13007 assert_eq!(f.run(&[b"ARGET", b"a", b"5"]), "$1\r\nq\r\n");
13008 }
13009
13010 #[test]
13013 fn the_length_is_the_high_water_mark_and_the_count_is_the_population() {
13014 let mut f = Fixture::new();
13015 assert_eq!(f.run(&[b"ARLEN", b"nope"]), ":0\r\n");
13016 assert_eq!(f.run(&[b"ARCOUNT", b"nope"]), ":0\r\n");
13017 f.run(&[b"ARMSET", b"a", b"0", b"x", b"9", b"y"]);
13018 assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
13019 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
13020 assert_eq!(f.run(&[b"ARDEL", b"a", b"0"]), ":1\r\n");
13022 assert_eq!(f.run(&[b"ARLEN", b"a"]), ":10\r\n");
13023 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":1\r\n");
13024
13025 f.run(&[b"ARSET", b"top", b"18446744073709551614", b"z"]);
13029 assert_eq!(f.run(&[b"ARLEN", b"top"]), ":18446744073709551615\r\n");
13030 assert_eq!(f.run(&[b"ARCOUNT", b"top"]), ":1\r\n");
13031 assert_eq!(
13034 f.run(&[b"ARSET", b"over", b"18446744073709551614", b"a", b"b"]),
13035 "-ERR array index overflow\r\n"
13036 );
13037 assert_eq!(f.run(&[b"EXISTS", b"over"]), ":0\r\n");
13038 }
13039
13040 #[test]
13043 fn a_range_read_answers_for_the_holes_too_and_is_capped_at_a_million() {
13044 let mut f = Fixture::new();
13045 f.run(&[b"ARSET", b"a", b"1", b"x"]);
13046 assert_eq!(
13047 f.run(&[b"ARGETRANGE", b"a", b"0", b"3"]),
13048 "*4\r\n$-1\r\n$1\r\nx\r\n$-1\r\n$-1\r\n"
13049 );
13050 assert_eq!(
13053 f.run(&[b"ARGETRANGE", b"a", b"3", b"0"]),
13054 "*4\r\n$-1\r\n$-1\r\n$1\r\nx\r\n$-1\r\n"
13055 );
13056 assert_eq!(
13058 f.run(&[b"ARGETRANGE", b"nope", b"0", b"1"]),
13059 "*2\r\n$-1\r\n$-1\r\n"
13060 );
13061 assert_eq!(
13065 f.run(&[b"ARGETRANGE", b"nope", b"0", b"18446744073709551614"]),
13066 "-ERR range exceeds maximum of 1000000 items\r\n"
13067 );
13068 }
13069
13070 #[test]
13073 fn a_bad_index_late_in_the_line_writes_none_of_the_earlier_ones() {
13074 let mut f = Fixture::new();
13075 assert_eq!(
13076 f.run(&[b"ARMSET", b"a", b"0", b"x", b"-1", b"y"]),
13077 "-ERR invalid array index\r\n"
13078 );
13079 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
13080 f.run(&[b"ARSET", b"a", b"0", b"x", b"y", b"z"]);
13081 assert_eq!(
13082 f.run(&[b"ARDEL", b"a", b"0", b"01"]),
13083 "-ERR invalid array index\r\n"
13084 );
13085 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":3\r\n");
13086 assert_eq!(
13089 f.run(&[b"ARGET", b"a", b"-1"]),
13090 "-ERR invalid array index\r\n"
13091 );
13092 assert_eq!(
13095 f.run(&[b"ARMSET", b"a", b"0", b"x", b"1"]),
13096 "-ERR wrong number of arguments for 'armset' command\r\n"
13097 );
13098 assert_eq!(
13099 f.run(&[b"ARDELRANGE", b"a", b"0", b"1", b"2"]),
13100 "-ERR wrong number of arguments for 'ardelrange' command\r\n"
13101 );
13102 }
13103
13104 #[test]
13105 fn a_range_delete_costs_the_elements_and_takes_the_key_when_it_empties() {
13106 let mut f = Fixture::new();
13107 f.run(&[b"ARSET", b"a", b"0", b"0", b"1", b"2", b"3", b"4"]);
13108 assert_eq!(f.run(&[b"ARDELRANGE", b"a", b"3", b"1"]), ":3\r\n");
13109 assert_eq!(f.run(&[b"ARCOUNT", b"a"]), ":2\r\n");
13110 assert_eq!(
13113 f.run(&[
13114 b"ARDELRANGE",
13115 b"a",
13116 b"100",
13117 b"200",
13118 b"0",
13119 b"18446744073709551614"
13120 ]),
13121 ":2\r\n"
13122 );
13123 assert_eq!(f.run(&[b"EXISTS", b"a"]), ":0\r\n");
13124 assert_eq!(f.run(&[b"ARDELRANGE", b"nope", b"0", b"1"]), ":0\r\n");
13125 assert_eq!(f.run(&[b"ARDEL", b"nope", b"0"]), ":0\r\n");
13126 }
13127
13128 #[test]
13131 fn a_value_comes_back_byte_for_byte_however_it_was_packed() {
13132 let mut f = Fixture::new();
13133 let long = vec![b'v'; 200];
13134 f.run(&[
13135 b"ARMSET", b"a", b"0", b"42", b"1", b"007", b"2", b"3.5", b"3", b"3.14", b"4",
13136 b"short", b"5", &long, b"6", b"-0",
13137 ]);
13138 assert_eq!(
13142 f.run(&[b"ARGETRANGE", b"a", b"0", b"6"]),
13143 format!(
13144 "*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",
13145 String::from_utf8_lossy(&long)
13146 )
13147 );
13148 }
13149
13150 #[test]
13151 fn an_array_is_a_type_and_an_encoding_a_client_can_see() {
13152 let mut f = Fixture::new();
13153 f.run(&[b"ARSET", b"a", b"0", b"x"]);
13154 assert_eq!(f.run(&[b"TYPE", b"a"]), "+array\r\n");
13155 assert_eq!(
13156 f.run(&[b"OBJECT", b"ENCODING", b"a"]),
13157 "$12\r\nsliced-array\r\n"
13158 );
13159 assert_eq!(f.run(&[b"EXPIRE", b"a", b"100"]), ":1\r\n");
13161 assert_eq!(f.run(&[b"PERSIST", b"a"]), ":1\r\n");
13162 assert_eq!(f.run(&[b"COPY", b"a", b"b"]), ":1\r\n");
13163 assert_eq!(f.run(&[b"ARGET", b"b", b"0"]), "$1\r\nx\r\n");
13164 assert_eq!(f.run(&[b"RENAME", b"a", b"c"]), "+OK\r\n");
13165 assert_eq!(f.run(&[b"ARCOUNT", b"c"]), ":1\r\n");
13166 }
13167
13168 #[test]
13169 fn every_array_command_refuses_a_key_holding_something_else() {
13170 let mut f = Fixture::new();
13171 f.run(&[b"SET", b"s", b"v"]);
13172 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13173 for cmd in [
13174 &[b"ARSET".as_ref(), b"s", b"0", b"x"][..],
13175 &[b"ARMSET".as_ref(), b"s", b"0", b"x"][..],
13176 &[b"ARGET".as_ref(), b"s", b"0"][..],
13177 &[b"ARMGET".as_ref(), b"s", b"0"][..],
13178 &[b"ARGETRANGE".as_ref(), b"s", b"0", b"1"][..],
13179 &[b"ARLEN".as_ref(), b"s"][..],
13180 &[b"ARCOUNT".as_ref(), b"s"][..],
13181 &[b"ARDEL".as_ref(), b"s", b"0"][..],
13182 &[b"ARDELRANGE".as_ref(), b"s", b"0", b"1"][..],
13183 &[b"ARINSERT".as_ref(), b"s", b"x"][..],
13184 &[b"ARRING".as_ref(), b"s", b"4", b"x"][..],
13185 &[b"ARNEXT".as_ref(), b"s"][..],
13186 &[b"ARSEEK".as_ref(), b"s", b"1"][..],
13187 &[b"ARLASTITEMS".as_ref(), b"s", b"1"][..],
13188 &[b"ARSCAN".as_ref(), b"s", b"0", b"1"][..],
13189 &[b"ARGREP".as_ref(), b"s", b"0", b"1", b"EXACT", b"v"][..],
13190 &[b"AROP".as_ref(), b"s", b"0", b"1", b"SUM"][..],
13191 &[b"ARINFO".as_ref(), b"s"][..],
13192 ] {
13193 assert_eq!(f.run(cmd), wrong, "{}", String::from_utf8_lossy(cmd[0]));
13194 }
13195 }
13196
13197 #[test]
13201 fn a_bad_index_reports_the_type_only_where_redis_reports_it() {
13202 let mut f = Fixture::new();
13203 f.run(&[b"SET", b"s", b"v"]);
13204 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
13205 let bad = "-ERR invalid array index\r\n";
13206 assert_eq!(f.run(&[b"ARGET", b"s", b"-1"]), wrong);
13207 assert_eq!(f.run(&[b"ARMGET", b"s", b"0", b"-1"]), wrong);
13208 assert_eq!(f.run(&[b"ARSET", b"s", b"-1", b"x"]), bad);
13209 assert_eq!(f.run(&[b"ARDEL", b"s", b"-1"]), bad);
13210 assert_eq!(f.run(&[b"ARSCAN", b"s", b"-1", b"0"]), bad);
13211 assert_eq!(f.run(&[b"ARGREP", b"s", b"-1", b"0", b"EXACT", b"v"]), bad);
13212 f.run(&[b"ARSET", b"a", b"0", b"x"]);
13214 assert_eq!(f.run(&[b"ARGET", b"a", b"-1"]), bad);
13215 assert_eq!(f.run(&[b"ARGET", b"nope", b"-1"]), bad);
13216 }
13217
13218 #[test]
13219 fn an_append_follows_a_cursor_the_client_can_move() {
13220 let mut f = Fixture::new();
13221 assert_eq!(f.run(&[b"ARNEXT", b"nope"]), ":0\r\n");
13222 assert_eq!(f.run(&[b"ARINSERT", b"a", b"x", b"y"]), ":1\r\n");
13223 assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":2\r\n");
13224 assert_eq!(f.run(&[b"ARINSERT", b"a", b"z"]), ":2\r\n");
13225 assert_eq!(f.run(&[b"ARGET", b"a", b"2"]), "$1\r\nz\r\n");
13226
13227 assert_eq!(f.run(&[b"ARSEEK", b"nope", b"5"]), ":0\r\n");
13230 assert_eq!(f.run(&[b"EXISTS", b"nope"]), ":0\r\n");
13231 assert_eq!(f.run(&[b"ARSEEK", b"a", b"100"]), ":1\r\n");
13232 assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":100\r\n");
13233 assert_eq!(f.run(&[b"ARINSERT", b"a", b"far"]), ":100\r\n");
13234 assert_eq!(f.run(&[b"ARSEEK", b"a", b"0"]), ":1\r\n");
13235 assert_eq!(f.run(&[b"ARNEXT", b"a"]), ":0\r\n");
13236
13237 assert_eq!(f.run(&[b"ARSEEK", b"a", b"18446744073709551615"]), ":1\r\n");
13240 assert_eq!(f.run(&[b"ARNEXT", b"a"]), "$-1\r\n");
13241 assert_eq!(
13242 f.run(&[b"ARINSERT", b"a", b"x"]),
13243 "-ERR insert index overflow\r\n"
13244 );
13245 assert_eq!(
13246 f.run(&[b"ARSET", b"a", b"18446744073709551615", b"x"]),
13247 "-ERR invalid array index\r\n"
13248 );
13249 }
13250
13251 #[test]
13252 fn a_ring_keeps_the_newest_and_renumbers_them_when_it_is_resized() {
13253 let mut f = Fixture::new();
13254 assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"a", b"b", b"c"]), ":2\r\n");
13255 assert_eq!(f.run(&[b"ARRING", b"r", b"3", b"d", b"e"]), ":1\r\n");
13256 assert_eq!(f.run(&[b"ARLEN", b"r"]), ":3\r\n");
13257 assert_eq!(
13258 f.run(&[b"ARGETRANGE", b"r", b"0", b"2"]),
13259 "*3\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nc\r\n"
13260 );
13261 assert_eq!(f.run(&[b"ARRING", b"r", b"5", b"f"]), ":3\r\n");
13264 assert_eq!(
13265 f.run(&[b"ARGETRANGE", b"r", b"0", b"3"]),
13266 "*4\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n$1\r\nf\r\n"
13267 );
13268 assert_eq!(
13271 f.run(&[b"ARRING", b"r", b"0", b"x"]),
13272 "-ERR size must be positive\r\n"
13273 );
13274 assert_eq!(
13275 f.run(&[b"ARRING", b"r", b"big", b"x"]),
13276 "-ERR invalid size\r\n"
13277 );
13278 }
13279
13280 #[test]
13281 fn the_last_items_walk_back_from_the_cursor_and_report_the_holes() {
13282 let mut f = Fixture::new();
13283 assert_eq!(f.run(&[b"ARLASTITEMS", b"nope", b"5"]), "*0\r\n");
13284 f.run(&[b"ARRING", b"r", b"4", b"a", b"b", b"c", b"d", b"e"]);
13285 assert_eq!(
13286 f.run(&[b"ARLASTITEMS", b"r", b"3"]),
13287 "*3\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n"
13288 );
13289 assert_eq!(
13290 f.run(&[b"ARLASTITEMS", b"r", b"3", b"rev"]),
13291 "*3\r\n$1\r\ne\r\n$1\r\nd\r\n$1\r\nc\r\n"
13292 );
13293 assert_eq!(
13294 f.run(&[b"ARLASTITEMS", b"r", b"99"]),
13295 "*4\r\n$1\r\nb\r\n$1\r\nc\r\n$1\r\nd\r\n$1\r\ne\r\n",
13296 "more than there is gets what there is"
13297 );
13298 assert_eq!(f.run(&[b"ARLASTITEMS", b"r", b"0", b"junk"]), "*0\r\n");
13301 assert_eq!(
13302 f.run(&[b"ARLASTITEMS", b"r", b"1", b"junk"]),
13303 "-ERR syntax error\r\n"
13304 );
13305 assert_eq!(
13306 f.run(&[b"ARLASTITEMS", b"r", b"nine"]),
13307 "-ERR invalid COUNT\r\n"
13308 );
13309
13310 f.run(&[b"ARMSET", b"h", b"0", b"x", b"2", b"z"]);
13313 assert_eq!(
13314 f.run(&[b"ARLASTITEMS", b"h", b"5"]),
13315 "*2\r\n$-1\r\n$1\r\nz\r\n"
13316 );
13317 }
13318
13319 #[test]
13320 fn a_scan_answers_pairs_for_what_is_there_and_skips_what_is_not() {
13321 let mut f = Fixture::new();
13322 assert_eq!(f.run(&[b"ARSCAN", b"nope", b"0", b"10"]), "*0\r\n");
13323 f.run(&[b"ARMSET", b"a", b"0", b"x", b"7", b"y", b"1000000", b"z"]);
13324 assert_eq!(
13327 f.run(&[b"ARSCAN", b"a", b"0", b"18446744073709551614"]),
13328 "*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"
13329 );
13330 assert_eq!(
13331 f.run(&[
13332 b"ARSCAN",
13333 b"a",
13334 b"18446744073709551614",
13335 b"0",
13336 b"LIMIT",
13337 b"1"
13338 ]),
13339 "*1\r\n*2\r\n:1000000\r\n$1\r\nz\r\n"
13340 );
13341 assert_eq!(f.run(&[b"ARSCAN", b"a", b"1", b"6"]), "*0\r\n");
13342 assert_eq!(
13343 f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT", b"0"]),
13344 "-ERR LIMIT must be positive\r\n"
13345 );
13346 assert_eq!(
13347 f.run(&[b"ARSCAN", b"a", b"0", b"10", b"NOPE", b"1"]),
13348 "-ERR syntax error\r\n"
13349 );
13350 assert_eq!(
13351 f.run(&[b"ARSCAN", b"a", b"0", b"10", b"LIMIT"]),
13352 "-ERR wrong number of arguments for 'arscan' command\r\n"
13353 );
13354 }
13355
13356 #[test]
13357 fn a_grep_answers_the_indexes_whose_elements_match() {
13358 let mut f = Fixture::new();
13359 assert_eq!(
13360 f.run(&[b"ARGREP", b"nope", b"0", b"10", b"EXACT", b"x"]),
13361 "*0\r\n"
13362 );
13363 f.run(&[b"ARSET", b"a", b"0", b"alpha", b"beta", b"gamma", b"ALPHA"]);
13364
13365 assert_eq!(
13368 f.run(&[b"ARGREP", b"a", b"-", b"+", b"GLOB", b"*a"]),
13369 "*3\r\n:0\r\n:1\r\n:2\r\n"
13370 );
13371 assert_eq!(
13372 f.run(&[b"ARGREP", b"a", b"+", b"-", b"GLOB", b"*a"]),
13373 "*3\r\n:2\r\n:1\r\n:0\r\n"
13374 );
13375 assert_eq!(
13376 f.run(&[b"ARGREP", b"a", b"1", b"2", b"GLOB", b"*a"]),
13377 "*2\r\n:1\r\n:2\r\n"
13378 );
13379
13380 assert_eq!(
13383 f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha"]),
13384 "*1\r\n:0\r\n"
13385 );
13386 assert_eq!(
13387 f.run(&[b"ARGREP", b"a", b"-", b"+", b"EXACT", b"alpha", b"NOCASE"]),
13388 "*2\r\n:0\r\n:3\r\n"
13389 );
13390 assert_eq!(
13391 f.run(&[b"ARGREP", b"a", b"-", b"+", b"MATCH", b"mm"]),
13392 "*1\r\n:2\r\n"
13393 );
13394 assert_eq!(
13395 f.run(&[b"ARGREP", b"a", b"-", b"+", b"RE", b"^[bg]"]),
13396 "*2\r\n:1\r\n:2\r\n"
13397 );
13398
13399 let both: &[&[u8]] = &[
13402 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al",
13403 ];
13404 assert_eq!(f.run(both), "*2\r\n:0\r\n:1\r\n");
13405 assert_eq!(
13406 f.run(&[
13407 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND"
13408 ]),
13409 "*0\r\n"
13410 );
13411 assert_eq!(
13412 f.run(&[
13413 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"beta", b"MATCH", b"al", b"AND", b"OR"
13414 ]),
13415 "*2\r\n:0\r\n:1\r\n"
13416 );
13417
13418 assert_eq!(
13421 f.run(&[
13422 b"ARGREP",
13423 b"a",
13424 b"-",
13425 b"+",
13426 b"MATCH",
13427 b"a",
13428 b"WITHVALUES",
13429 b"LIMIT",
13430 b"2"
13431 ]),
13432 "*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"
13433 );
13434 assert_eq!(
13435 f.run(&[
13436 b"ARGREP", b"a", b"-", b"+", b"EXACT", b"ALPHA", b"LIMIT", b"1"
13437 ]),
13438 "*1\r\n:3\r\n"
13439 );
13440 }
13441
13442 #[test]
13444 fn a_grep_reports_a_broken_command_the_way_redis_does() {
13445 let mut f = Fixture::new();
13446 f.run(&[b"ARSET", b"a", b"0", b"alpha"]);
13447 let syntax = "-ERR syntax error\r\n";
13448
13449 assert_eq!(
13452 f.run(&[b"ARGREP", b"a", b"-1", b"0", b"NOPE", b"x"]),
13453 "-ERR invalid array index\r\n"
13454 );
13455 assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOPE", b"x"]), syntax);
13456 assert_eq!(
13458 f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"EXACT"]),
13459 syntax
13460 );
13461 assert_eq!(
13462 f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT"]),
13463 syntax
13464 );
13465 assert_eq!(
13466 f.run(&[b"ARGREP", b"a", b"0", b"1", b"NOCASE", b"WITHVALUES"]),
13467 syntax,
13468 "a command with no predicate in it at all"
13469 );
13470 assert_eq!(
13471 f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"0"]),
13472 "-ERR LIMIT must be positive\r\n"
13473 );
13474 assert_eq!(
13475 f.run(&[
13476 b"ARGREP", b"a", b"0", b"1", b"EXACT", b"x", b"LIMIT", b"nine"
13477 ]),
13478 "-ERR value is not an integer or out of range\r\n"
13479 );
13480 assert_eq!(
13481 f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b""]),
13482 "-ERR regular expression is empty\r\n"
13483 );
13484 assert_eq!(
13485 f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", b"(a"]),
13486 "-ERR invalid regular expression: Missing ')'\r\n"
13487 );
13488 assert_eq!(
13489 f.run(&[b"ARGREP", b"a", b"0", b"1", b"RE", br"(a)\1"]),
13490 "-ERR regular expression backreferences are not supported\r\n"
13491 );
13492 let arity = "-ERR wrong number of arguments for 'argrep' command\r\n";
13495 assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1", b"EXACT"]), arity);
13496 assert_eq!(f.run(&[b"ARGREP", b"a", b"0", b"1"]), arity);
13497 }
13498
13499 #[test]
13500 fn an_op_reduces_a_range_to_one_number() {
13501 let mut f = Fixture::new();
13502 f.run(&[b"ARSET", b"a", b"0", b"1", b"2.5", b"word", b"-4"]);
13503 assert_eq!(
13504 f.run(&[b"AROP", b"a", b"0", b"10", b"SUM"]),
13505 "$4\r\n-0.5\r\n"
13506 );
13507 assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"min"]), "$2\r\n-4\r\n");
13508 assert_eq!(
13509 f.run(&[b"AROP", b"a", b"0", b"10", b"MAX"]),
13510 "$3\r\n2.5\r\n"
13511 );
13512 assert_eq!(f.run(&[b"AROP", b"a", b"0", b"10", b"USED"]), ":4\r\n");
13513 assert_eq!(
13514 f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH", b"word"]),
13515 ":1\r\n"
13516 );
13517 f.run(&[b"ARSET", b"t", b"0", b"0.1", b"0.2"]);
13520 assert_eq!(
13521 f.run(&[b"AROP", b"t", b"0", b"10", b"SUM"]),
13522 "$19\r\n0.30000000000000004\r\n"
13523 );
13524 assert_eq!(f.run(&[b"ZADD", b"z", b"0.3", b"m"]), ":1\r\n");
13525 assert_eq!(f.run(&[b"ZSCORE", b"z", b"m"]), "$3\r\n0.3\r\n");
13526
13527 f.run(&[b"ARSET", b"w", b"0", b"word"]);
13530 assert_eq!(f.run(&[b"AROP", b"w", b"0", b"10", b"SUM"]), "$-1\r\n");
13531 assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"SUM"]), "$-1\r\n");
13532 assert_eq!(f.run(&[b"AROP", b"nope", b"0", b"10", b"USED"]), ":0\r\n");
13533
13534 assert_eq!(
13535 f.run(&[b"AROP", b"a", b"0", b"10", b"NOPE"]),
13536 "-ERR unknown operation\r\n"
13537 );
13538 assert_eq!(
13539 f.run(&[b"AROP", b"a", b"0", b"10", b"MATCH"]),
13540 "-ERR MATCH requires a value argument\r\n"
13541 );
13542 assert_eq!(
13543 f.run(&[b"AROP", b"a", b"0", b"10", b"SUM", b"extra"]),
13544 "-ERR wrong number of arguments for 'arop' command\r\n"
13545 );
13546 }
13547
13548 #[test]
13549 fn the_info_is_a_map_and_a_missing_key_is_an_error() {
13550 let mut f = Fixture::new();
13551 assert_eq!(f.run(&[b"ARINFO", b"nope"]), "-ERR no such key\r\n");
13552 f.run(&[b"ARINSERT", b"a", b"x", b"y"]);
13553 let short = f.run(&[b"ARINFO", b"a"]);
13554 assert!(
13555 short.starts_with("*14\r\n"),
13556 "seven pairs on RESP2: {short}"
13557 );
13558 assert!(short.contains("$5\r\ncount\r\n:2\r\n"), "{short}");
13559 assert!(
13560 short.contains("$17\r\nnext-insert-index\r\n:2\r\n"),
13561 "{short}"
13562 );
13563 assert!(short.contains("$10\r\nslice-size\r\n:4096\r\n"), "{short}");
13564 let full = f.run(&[b"ARINFO", b"a", b"full"]);
13565 assert!(full.starts_with("*24\r\n"), "twelve pairs: {full}");
13566 assert!(full.contains("$12\r\ndense-slices\r\n:0\r\n"), "{full}");
13569 assert!(full.contains("$13\r\nsparse-slices\r\n:1\r\n"), "{full}");
13570 assert!(
13571 full.contains("$14\r\navg-dense-size\r\n$1\r\n0\r\n"),
13572 "{full}"
13573 );
13574 assert_eq!(f.run(&[b"ARINFO", b"a", b"nope"]), "-ERR syntax error\r\n");
13575
13576 let mut g = Fixture::new();
13578 g.run(&[b"HELLO", b"3"]);
13579 g.run(&[b"ARINSERT", b"a", b"x"]);
13580 let map = g.run(&[b"ARINFO", b"a", b"FULL"]);
13581 assert!(map.starts_with("%12\r\n"), "{map}");
13582 assert!(map.contains("$5\r\ncount\r\n:1\r\n"), "{map}");
13583 assert!(map.contains("$14\r\navg-dense-size\r\n,0\r\n"), "{map}");
13584 }
13585
13586 #[test]
13587 fn a_double_on_the_wire_is_written_the_way_redis_writes_one() {
13588 let mut f = Fixture::new();
13589 for (score, want) in [
13592 ("3", "3"),
13593 ("3.5", "3.5"),
13594 ("0.3", "0.3"),
13595 ("1e30", "1e+30"),
13596 ("1e19", "1e+19"),
13597 ("1e-7", "1e-7"),
13598 ("0.000001", "0.000001"),
13599 ("4611686018427387904", "4611686018427387904"),
13600 ("-0", "-0"),
13601 ] {
13602 f.run(&[b"ZADD", b"z", score.as_bytes(), b"m"]);
13603 assert_eq!(
13604 f.run(&[b"ZSCORE", b"z", b"m"]),
13605 format!("${}\r\n{want}\r\n", want.len()),
13606 "score {score}"
13607 );
13608 }
13609
13610 let mut g = Fixture::new();
13613 g.run(&[b"HELLO", b"3"]);
13614 g.run(&[b"ZADD", b"z", b"1e30", b"m"]);
13615 assert_eq!(g.run(&[b"ZSCORE", b"z", b"m"]), ",1e+30\r\n");
13616 assert_eq!(
13621 g.run(&[b"INCRBYFLOAT", b"s", b"1e30"]),
13622 "$31\r\n1000000000000000000000000000000\r\n"
13623 );
13624 assert_eq!(g.run(&[b"INCRBYFLOAT", b"t", b"0.1"]), "$3\r\n0.1\r\n");
13625 assert_eq!(
13626 g.run(&[b"HINCRBYFLOAT", b"h", b"f", b"1e19"]),
13627 "$20\r\n10000000000000000000\r\n"
13628 );
13629 }
13630
13631 #[test]
13634 fn a_node_comes_back_with_the_fields_it_went_in_with() {
13635 let mut f = Fixture::new();
13636 assert_eq!(
13637 f.run(&[
13638 b"G.NADD", b"social", b"ada", b"name", b"Ada", b"born", b"1815"
13639 ]),
13640 ":1\r\n"
13641 );
13642 assert_eq!(
13648 f.run(&[b"G.NGET", b"social", b"ada"]),
13649 "*4\r\n$4\r\nborn\r\n$4\r\n1815\r\n$4\r\nname\r\n$3\r\nAda\r\n"
13650 );
13651 assert_eq!(
13654 f.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada Lovelace"]),
13655 ":0\r\n"
13656 );
13657 assert_eq!(
13658 f.run(&[b"G.NGET", b"social", b"ada"]),
13659 "*2\r\n$4\r\nname\r\n$12\r\nAda Lovelace\r\n"
13660 );
13661 assert_eq!(f.run(&[b"G.NADD", b"social", b"grace"]), ":1\r\n");
13664 assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
13665 assert_eq!(f.run(&[b"G.NGET", b"social", b"nobody"]), "$-1\r\n");
13666 assert_eq!(f.run(&[b"G.NGET", b"nokey", b"ada"]), "$-1\r\n");
13667
13668 assert_eq!(
13671 f.run(&[b"G.NADD", b"fresh", b"n", b"lonely"]),
13672 "-ERR syntax error\r\n"
13673 );
13674 assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
13675
13676 let mut g = Fixture::new();
13678 g.run(&[b"HELLO", b"3"]);
13679 g.run(&[b"G.NADD", b"social", b"ada", b"name", b"Ada"]);
13680 assert_eq!(
13681 g.run(&[b"G.NGET", b"social", b"ada"]),
13682 "%1\r\n$4\r\nname\r\n$3\r\nAda\r\n"
13683 );
13684 }
13685
13686 #[test]
13687 fn an_edge_creates_the_ends_it_needs() {
13688 let mut f = Fixture::new();
13689 assert_eq!(
13690 f.run(&[
13691 b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1843"
13692 ]),
13693 ":1\r\n"
13694 );
13695 assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "*0\r\n");
13697 assert_eq!(f.run(&[b"G.NGET", b"social", b"grace"]), "*0\r\n");
13698 assert_eq!(
13699 f.run(&[b"G.OUT", b"social", b"ada", b"FOLLOWS"]),
13700 "*2\r\n$1\r\n0\r\n*1\r\n$5\r\ngrace\r\n"
13701 );
13702 assert_eq!(
13703 f.run(&[b"G.IN", b"social", b"grace", b"FOLLOWS"]),
13704 "*2\r\n$1\r\n0\r\n*1\r\n$3\r\nada\r\n"
13705 );
13706 assert_eq!(
13709 f.run(&[
13710 b"G.EADD", b"social", b"ada", b"grace", b"FOLLOWS", b"since", b"1844"
13711 ]),
13712 ":0\r\n"
13713 );
13714 assert_eq!(f.run(&[b"G.DEG", b"social", b"ada", b"FOLLOWS"]), ":1\r\n");
13715 assert_eq!(
13717 f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"WORKS_WITH"]),
13718 ":1\r\n"
13719 );
13720 assert_eq!(
13721 f.run(&[b"G.DEG", b"social", b"ada", b"WORKS_WITH"]),
13722 ":1\r\n"
13723 );
13724
13725 assert_eq!(
13726 f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
13727 ":1\r\n"
13728 );
13729 assert_eq!(
13730 f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"FOLLOWS"]),
13731 ":0\r\n"
13732 );
13733 assert_eq!(
13736 f.run(&[b"G.EDEL", b"social", b"ada", b"grace", b"NEVER"]),
13737 ":0\r\n"
13738 );
13739 assert_eq!(
13740 f.run(&[b"G.EDEL", b"social", b"ada", b"nobody", b"FOLLOWS"]),
13741 ":0\r\n"
13742 );
13743 assert_eq!(
13744 f.run(&[b"G.EDEL", b"nokey", b"ada", b"grace", b"FOLLOWS"]),
13745 ":0\r\n"
13746 );
13747 }
13748
13749 #[test]
13752 fn a_hop_answers_a_cursor_and_a_page() {
13753 let mut f = Fixture::new();
13754 for i in 0..25u32 {
13755 let dst = format!("n{i}");
13756 f.run(&[b"G.EADD", b"social", b"hub", dst.as_bytes(), b"FOLLOWS"]);
13757 }
13758 let first = f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS"]);
13760 assert!(first.starts_with("*2\r\n$2\r\n10\r\n*10\r\n"), "{first}");
13761
13762 let mut seen = 0;
13763 let mut cursor = String::from("0");
13764 loop {
13765 let page = f.run(&[
13766 b"G.OUT",
13767 b"social",
13768 b"hub",
13769 b"FOLLOWS",
13770 b"COUNT",
13771 b"7",
13772 b"CURSOR",
13773 cursor.as_bytes(),
13774 ]);
13775 let (head, rest) = page.split_once("\r\n*").expect("a cursor and a page");
13776 cursor = head
13777 .rsplit("\r\n")
13778 .next()
13779 .expect("the cursor line")
13780 .to_string();
13781 seen += rest
13782 .split_once("\r\n")
13783 .expect("the page length")
13784 .0
13785 .parse::<usize>()
13786 .expect("a length");
13787 if cursor == "0" {
13788 break;
13789 }
13790 }
13791 assert_eq!(seen, 25, "every neighbour once across the pages");
13792
13793 assert_eq!(
13796 f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"CURSOR", b"900"]),
13797 "*2\r\n$1\r\n0\r\n*0\r\n"
13798 );
13799 assert_eq!(
13800 f.run(&[b"G.OUT", b"social", b"hub", b"NEVER"]),
13801 "*2\r\n$1\r\n0\r\n*0\r\n"
13802 );
13803 assert_eq!(
13804 f.run(&[b"G.OUT", b"nokey", b"hub", b"FOLLOWS"]),
13805 "*2\r\n$1\r\n0\r\n*0\r\n"
13806 );
13807 assert_eq!(
13808 f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"COUNT", b"0"]),
13809 "-ERR COUNT must be a positive integer\r\n"
13810 );
13811 assert_eq!(
13812 f.run(&[b"G.OUT", b"social", b"hub", b"FOLLOWS", b"NOPE", b"1"]),
13813 "-ERR syntax error\r\n"
13814 );
13815 }
13816
13817 #[test]
13818 fn a_degree_counts_one_way_or_both() {
13819 let mut f = Fixture::new();
13820 f.run(&[b"G.EADD", b"social", b"a", b"b", b"F"]);
13821 f.run(&[b"G.EADD", b"social", b"a", b"c", b"F"]);
13822 f.run(&[b"G.EADD", b"social", b"d", b"a", b"F"]);
13823 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F"]), ":2\r\n");
13824 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"OUT"]), ":2\r\n");
13825 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"IN"]), ":1\r\n");
13826 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"F", b"BOTH"]), ":3\r\n");
13827 assert_eq!(f.run(&[b"G.DEG", b"social", b"nobody", b"F"]), ":0\r\n");
13828 assert_eq!(f.run(&[b"G.DEG", b"social", b"a", b"NEVER"]), ":0\r\n");
13829 assert_eq!(f.run(&[b"G.DEG", b"nokey", b"a", b"F"]), ":0\r\n");
13830 assert_eq!(
13831 f.run(&[b"G.DEG", b"social", b"a", b"F", b"SIDEWAYS"]),
13832 "-ERR syntax error\r\n"
13833 );
13834 }
13835
13836 #[test]
13839 fn a_walk_reaches_each_node_once_however_many_ways_there_are() {
13840 let mut f = Fixture::new();
13841 for (src, dst) in [
13842 ("ada", "grace"),
13843 ("ada", "alan"),
13844 ("grace", "edsger"),
13845 ("alan", "edsger"),
13846 ("edsger", "barbara"),
13847 ] {
13848 f.run(&[b"G.EADD", b"social", src.as_bytes(), dst.as_bytes(), b"F"]);
13849 }
13850 assert_eq!(
13853 f.run(&[b"G.NEIGH", b"social", b"ada", b"F"]),
13854 "*3\r\n$5\r\ngrace\r\n$4\r\nalan\r\n$6\r\nedsger\r\n"
13855 );
13856 assert_eq!(
13857 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"1"]),
13858 "*2\r\n$5\r\ngrace\r\n$4\r\nalan\r\n"
13859 );
13860 let deep = f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"9"]);
13861 assert!(deep.starts_with("*4\r\n"), "the whole component: {deep}");
13862 assert!(deep.contains("$7\r\nbarbara\r\n"), "{deep}");
13863 assert_eq!(
13865 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"COUNT", b"1"]),
13866 "*1\r\n$5\r\ngrace\r\n"
13867 );
13868 assert_eq!(f.run(&[b"G.NEIGH", b"social", b"barbara", b"F"]), "*0\r\n");
13870 assert_eq!(f.run(&[b"G.NEIGH", b"social", b"ada", b"NEVER"]), "*0\r\n");
13871 assert_eq!(f.run(&[b"G.NEIGH", b"nokey", b"ada", b"F"]), "*0\r\n");
13872 assert_eq!(
13873 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"DEPTH", b"0"]),
13874 "-ERR DEPTH must be a positive integer\r\n"
13875 );
13876 assert_eq!(
13877 f.run(&[b"G.NEIGH", b"social", b"ada", b"F", b"NOPE", b"1"]),
13878 "-ERR syntax error\r\n"
13879 );
13880 }
13881
13882 #[test]
13885 fn a_path_is_the_shortest_one_and_goes_over_any_label() {
13886 let mut f = Fixture::new();
13887 for i in 0..6u32 {
13890 let src = format!("n{i}");
13891 let dst = format!("n{}", i + 1);
13892 f.run(&[b"G.EADD", b"road", src.as_bytes(), dst.as_bytes(), b"STEP"]);
13893 }
13894 assert_eq!(
13895 f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
13896 "*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"
13897 );
13898 f.run(&[b"G.EADD", b"road", b"n0", b"n5", b"JUMP"]);
13899 assert_eq!(
13900 f.run(&[b"G.PATH", b"road", b"n0", b"n6"]),
13901 "*3\r\n$2\r\nn0\r\n$2\r\nn5\r\n$2\r\nn6\r\n"
13902 );
13903 assert_eq!(
13906 f.run(&[b"G.PATH", b"road", b"n2", b"n2"]),
13907 "*1\r\n$2\r\nn2\r\n"
13908 );
13909 assert_eq!(
13910 f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"MAXDEPTH", b"1"]),
13911 "*0\r\n"
13912 );
13913 assert_eq!(f.run(&[b"G.PATH", b"road", b"n6", b"n0"]), "*0\r\n");
13915 f.run(&[b"G.NADD", b"road", b"island"]);
13918 assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"island"]), "*0\r\n");
13919 assert_eq!(f.run(&[b"G.PATH", b"road", b"n0", b"nobody"]), "*0\r\n");
13920 assert_eq!(f.run(&[b"G.PATH", b"nokey", b"n0", b"n6"]), "*0\r\n");
13921 assert_eq!(
13922 f.run(&[b"G.PATH", b"road", b"n0", b"n6", b"NOPE", b"3"]),
13923 "-ERR syntax error\r\n"
13924 );
13925 }
13926
13927 #[test]
13931 fn the_keyspace_sees_a_graph_key_like_any_other() {
13932 let mut f = Fixture::new();
13933 f.run(&[b"G.EADD", b"social", b"ada", b"grace", b"F"]);
13934 assert_eq!(f.run(&[b"TYPE", b"social"]), "+graph\r\n");
13935 assert_eq!(
13936 f.run(&[b"OBJECT", b"ENCODING", b"social"]),
13937 "$9\r\nadjacency\r\n"
13938 );
13939 assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
13940 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
13941 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$6\r\nsocial\r\n");
13942 let held = f.server.memory_bytes();
13946 for i in 0..200u32 {
13947 let dst = format!("n{i}");
13948 f.run(&[b"G.EADD", b"big", b"hub", dst.as_bytes(), b"F"]);
13949 }
13950 assert!(
13951 f.server.memory_bytes() > held,
13952 "two hundred edges cost something: {held} then {}",
13953 f.server.memory_bytes()
13954 );
13955 f.run(&[b"DEL", b"big"]);
13956
13957 assert_eq!(f.run(&[b"EXPIRE", b"social", b"100"]), ":1\r\n");
13960 assert_eq!(f.run(&[b"PERSIST", b"social"]), ":1\r\n");
13961 assert_eq!(f.run(&[b"RENAME", b"social", b"net"]), "+OK\r\n");
13962 assert_eq!(f.run(&[b"MOVE", b"net", b"1"]), ":1\r\n");
13963 assert_eq!(f.run(&[b"EXISTS", b"net"]), ":0\r\n");
13964 f.run(&[b"SELECT", b"1"]);
13965 assert_eq!(f.run(&[b"G.DEG", b"net", b"ada", b"F"]), ":1\r\n");
13966
13967 assert_eq!(f.run(&[b"DEL", b"net"]), ":1\r\n");
13968 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
13969 f.run(&[b"G.NADD", b"g", b"n"]);
13970 assert_eq!(f.run(&[b"FLUSHDB"]), "+OK\r\n");
13971 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
13972 }
13973
13974 #[test]
13977 fn a_graph_cannot_be_copied_or_dumped() {
13978 let mut f = Fixture::new();
13979 f.run(&[b"G.NADD", b"social", b"ada"]);
13980 assert_eq!(
13981 f.run(&[b"COPY", b"social", b"other"]),
13982 "-ERR COPY is not supported for a graph\r\n"
13983 );
13984 assert_eq!(
13985 f.run(&[b"COPY", b"social", b"other", b"DB", b"1"]),
13986 "-ERR COPY is not supported for a graph\r\n"
13987 );
13988 assert_eq!(
13989 f.run(&[b"DUMP", b"social"]),
13990 "-ERR DUMP is not supported for a graph\r\n"
13991 );
13992 assert_eq!(f.run(&[b"EXISTS", b"social", b"other"]), ":1\r\n");
13994 }
13995
13996 #[test]
13999 fn a_graph_and_a_string_are_the_wrong_type_for_each_other() {
14000 let wrong = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
14001 let mut f = Fixture::new();
14002 f.run(&[b"G.NADD", b"social", b"ada"]);
14003 assert_eq!(f.run(&[b"GET", b"social"]), wrong);
14004 assert_eq!(f.run(&[b"LPUSH", b"social", b"x"]), wrong);
14005 assert_eq!(f.run(&[b"SADD", b"social", b"x"]), wrong);
14006
14007 f.run(&[b"SET", b"str", b"v"]);
14008 for cmd in [
14009 vec![b"G.NADD".as_ref(), b"str", b"n"],
14010 vec![b"G.NGET".as_ref(), b"str", b"n"],
14011 vec![b"G.NDEL".as_ref(), b"str", b"n"],
14012 vec![b"G.EADD".as_ref(), b"str", b"a", b"b", b"F"],
14013 vec![b"G.EDEL".as_ref(), b"str", b"a", b"b", b"F"],
14014 vec![b"G.OUT".as_ref(), b"str", b"a", b"F"],
14015 vec![b"G.IN".as_ref(), b"str", b"a", b"F"],
14016 vec![b"G.DEG".as_ref(), b"str", b"a", b"F"],
14017 vec![b"G.NEIGH".as_ref(), b"str", b"a", b"F"],
14018 vec![b"G.PATH".as_ref(), b"str", b"a", b"b"],
14019 ] {
14020 assert_eq!(f.run(&cmd), wrong, "{:?}", cmd[0]);
14021 }
14022 }
14023
14024 #[test]
14027 fn a_graph_goes_when_its_last_node_does() {
14028 let mut f = Fixture::new();
14029 f.run(&[
14030 b"G.EADD", b"social", b"ada", b"grace", b"F", b"since", b"1843",
14031 ]);
14032 assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":1\r\n");
14033 assert_eq!(f.run(&[b"G.NGET", b"social", b"ada"]), "$-1\r\n");
14035 assert_eq!(
14036 f.run(&[b"G.DEG", b"social", b"grace", b"F", b"IN"]),
14037 ":0\r\n"
14038 );
14039 assert_eq!(f.run(&[b"G.NDEL", b"social", b"ada"]), ":0\r\n");
14040 assert_eq!(f.run(&[b"EXISTS", b"social"]), ":1\r\n");
14041
14042 assert_eq!(f.run(&[b"G.NDEL", b"social", b"grace"]), ":1\r\n");
14043 assert_eq!(f.run(&[b"EXISTS", b"social"]), ":0\r\n");
14044 assert_eq!(f.run(&[b"DBSIZE"]), ":0\r\n");
14045 assert_eq!(f.run(&[b"G.NDEL", b"nokey", b"ada"]), ":0\r\n");
14046
14047 f.run(&[b"G.NADD", b"social", b"first"]);
14050 f.run(&[b"G.NADD", b"social", b"second"]);
14051 f.run(&[b"G.NDEL", b"social", b"first"]);
14052 f.run(&[b"G.EADD", b"social", b"third", b"second", b"F"]);
14053 assert_eq!(
14054 f.run(&[b"G.OUT", b"social", b"third", b"F"]),
14055 "*2\r\n$1\r\n0\r\n*1\r\n$6\r\nsecond\r\n"
14056 );
14057 }
14058
14059 #[test]
14064 fn a_json_path_answers_a_set_and_a_legacy_path_answers_a_value() {
14065 let mut f = Fixture::new();
14066 let doc = br#"{"a":1,"b":{"c":true}}"#;
14067 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$", doc]), "+OK\r\n");
14068 assert_eq!(
14071 f.run(&[b"JSON.GET", b"doc"]),
14072 bulk(r#"{"a":1,"b":{"c":true}}"#)
14073 );
14074 assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.a"]), bulk("[1]"));
14075 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("1"));
14076 assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$..c"]), bulk("[true]"));
14077 assert_eq!(f.run(&[b"JSON.GET", b"doc", b"$.nope"]), bulk("[]"));
14080 assert_eq!(
14081 f.run(&[b"JSON.GET", b"doc", b".nope"]),
14082 "-ERR Path does not exist\r\n"
14083 );
14084 assert_eq!(f.run(&[b"JSON.GET", b"nokey"]), "$-1\r\n");
14085 assert_eq!(f.run(&[b"TYPE", b"doc"]), "+ReJSON-RL\r\n");
14088 assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":1\r\n");
14089 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"doc"]), bulk("raw"));
14090 assert_eq!(f.run(&[b"DEL", b"doc"]), ":1\r\n");
14091 assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
14092 }
14093
14094 #[test]
14100 fn the_two_json_errors_that_carry_no_prefix() {
14101 let mut f = Fixture::new();
14102 f.run(&[b"SET", b"plain", b"x"]);
14103 let wrong = "-Existing key has wrong Redis type\r\n";
14104 assert_eq!(f.run(&[b"JSON.GET", b"plain"]), wrong);
14105 assert_eq!(f.run(&[b"JSON.SET", b"plain", b"$", b"1"]), wrong);
14106 assert_eq!(f.run(&[b"JSON.DEL", b"plain"]), wrong);
14107 assert_eq!(f.run(&[b"JSON.TYPE", b"plain"]), wrong);
14108 assert_eq!(f.run(&[b"JSON.CLEAR", b"plain"]), wrong);
14109
14110 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"z":1},"b":{"z":2}}"#]);
14111 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.*.z", b"9"]), "+OK\r\n");
14115 assert_eq!(
14116 f.run(&[b"JSON.GET", b"doc"]),
14117 bulk(r#"{"a":{"z":9},"b":{"z":9}}"#)
14118 );
14119 assert_eq!(
14120 f.run(&[b"JSON.SET", b"doc", b"$.*.y", b"9"]),
14121 "-Err wrong static path\r\n"
14122 );
14123 }
14124
14125 #[test]
14127 fn json_set_creates_one_field_and_refuses_to_invent_the_rest() {
14128 let mut f = Fixture::new();
14129 assert_eq!(
14131 f.run(&[b"JSON.SET", b"new", b".a", b"1"]),
14132 "-ERR new objects must be created at the root\r\n"
14133 );
14134 assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
14135 assert_eq!(
14138 f.run(&[b"JSON.SET", b"new", b".a", b"1", b"NX"]),
14139 "-ERR new objects must be created at the root\r\n"
14140 );
14141 assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"XX"]), "$-1\r\n");
14142 assert_eq!(f.run(&[b"JSON.SET", b"new", b"$", b"1", b"NX"]), "+OK\r\n");
14143
14144 f.run(&[
14145 b"JSON.SET",
14146 b"doc",
14147 b"$",
14148 br#"{"o":{},"arr":[1,2],"s":"x"}"#,
14149 ]);
14150 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"1"]), "+OK\r\n");
14152 assert_eq!(
14155 f.run(&[b"JSON.SET", b"doc", b"$.nope.made", b"1"]),
14156 "$-1\r\n"
14157 );
14158 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.s.made", b"1"]), "$-1\r\n");
14159 assert_eq!(
14161 f.run(&[b"JSON.SET", b"doc", b"$.arr[5]", b"9"]),
14162 "-ERR array index out of range\r\n"
14163 );
14164 assert_eq!(
14165 f.run(&[b"JSON.SET", b"doc", b"$.arr[2]", b"9"]),
14166 "-ERR array index out of range\r\n"
14167 );
14168 assert_eq!(f.run(&[b"JSON.SET", b"doc", b"$.arr[1]", b"9"]), "+OK\r\n");
14169 assert_eq!(
14172 f.run(&[b"JSON.SET", b"doc", b"$.o.made", b"2", b"NX"]),
14173 "$-1\r\n"
14174 );
14175 assert_eq!(
14176 f.run(&[b"JSON.SET", b"doc", b"$.gone", b"2", b"XX"]),
14177 "$-1\r\n"
14178 );
14179 assert_eq!(
14180 f.run(&[b"JSON.GET", b"doc"]),
14181 bulk(r#"{"o":{"made":1},"s":"x","arr":[1,9]}"#)
14182 );
14183 assert!(
14187 f.run(&[b"JSON.SET", b"doc", b"$.s", b"nope"])
14188 .starts_with("-this is not the start of a value")
14189 );
14190 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".s"]), bulk("\"x\""));
14191 }
14192
14193 #[test]
14196 fn the_json_commands_that_do_not_answer_text() {
14197 let mut f = Fixture::new();
14198 let doc = br#"{"a":1,"t":true,"o":{"x":1},"arr":[1,2],"f":1.5,"s":"x","n":null}"#;
14199 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14200
14201 assert_eq!(f.run(&[b"JSON.TYPE", b"doc"]), bulk("object"));
14202 assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".a"]), bulk("integer"));
14203 assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".f"]), bulk("number"));
14204 assert_eq!(
14205 f.run(&[b"JSON.TYPE", b"doc", b"$.a"]),
14206 format!("*1\r\n{}", bulk("integer"))
14207 );
14208 assert_eq!(f.run(&[b"JSON.TYPE", b"doc", b".nope"]), "$-1\r\n");
14211 assert_eq!(f.run(&[b"JSON.TYPE", b"nokey"]), "$-1\r\n");
14212
14213 assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.t"]), "*1\r\n:0\r\n");
14216 assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b".t"]), bulk("true"));
14217 assert_eq!(f.run(&[b"JSON.TOGGLE", b"doc", b"$.a"]), "*1\r\n$-1\r\n");
14220 assert_eq!(
14221 f.run(&[b"JSON.TOGGLE", b"doc", b".a"]),
14222 "-ERR Path does not exist or not a bool\r\n"
14223 );
14224 assert_eq!(
14225 f.run(&[b"JSON.TOGGLE", b"doc", b".nope"]),
14226 "-ERR Path does not exist or not a bool\r\n"
14227 );
14228 assert_eq!(
14229 f.run(&[b"JSON.TOGGLE", b"nokey", b"$.a"]),
14230 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14231 );
14232
14233 assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.s"]), ":0\r\n");
14236 assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":4\r\n");
14237 assert_eq!(f.run(&[b"JSON.CLEAR", b"doc", b"$.*"]), ":0\r\n");
14238 assert_eq!(
14239 f.run(&[b"JSON.GET", b"doc"]),
14240 bulk(r#"{"a":0,"f":0,"n":null,"o":{},"s":"x","t":true,"arr":[]}"#)
14241 );
14242
14243 assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.nope"]), ":0\r\n");
14246 assert_eq!(f.run(&[b"JSON.DEL", b"doc", b"$.a"]), ":1\r\n");
14247 assert_eq!(f.run(&[b"JSON.FORGET", b"doc", b"$.*"]), ":6\r\n");
14252 assert_eq!(f.run(&[b"EXISTS", b"doc"]), ":0\r\n");
14253 assert_eq!(f.run(&[b"JSON.GET", b"doc"]), "$-1\r\n");
14254 assert_eq!(f.run(&[b"JSON.DEL", b"doc"]), ":0\r\n");
14255 assert_eq!(f.run(&[b"JSON.SET", b"empty", b"$", b"{}"]), "+OK\r\n");
14256 assert_eq!(f.run(&[b"EXISTS", b"empty"]), ":1\r\n");
14257 assert_eq!(f.run(&[b"JSON.GET", b"empty"]), bulk("{}"));
14258 assert_eq!(f.run(&[b"JSON.DEL", b"nokey"]), ":0\r\n");
14259 }
14260
14261 #[test]
14268 fn json_get_lays_out_the_wrapper_it_builds() {
14269 let mut f = Fixture::new();
14270 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[1,{"c":2}]}"#]);
14271
14272 assert_eq!(
14273 f.run(&[b"JSON.GET", b"doc", b"$.a", b"$.b"]),
14274 bulk(r#"{"$.a":[1],"$.b":[[1,{"c":2}]]}"#)
14275 );
14276 assert_eq!(
14278 f.run(&[b"JSON.GET", b"doc", b".a", b".b"]),
14279 bulk(r#"{".a":1,".b":[1,{"c":2}]}"#)
14280 );
14281 let fmt: &[&[u8]] = &[b"INDENT", b" ", b"NEWLINE", b"\n", b"SPACE", b" "];
14282 let mut one = vec![b"JSON.GET".as_slice(), b"doc"];
14283 one.extend_from_slice(fmt);
14284 one.push(b"$.b");
14285 assert_eq!(
14286 f.run(&one),
14287 bulk("[\n [\n 1,\n {\n \"c\": 2\n }\n ]\n]")
14288 );
14289 let mut two = vec![b"JSON.GET".as_slice(), b"doc"];
14290 two.extend_from_slice(fmt);
14291 two.push(b"$.a");
14292 two.push(b"$.nope");
14293 assert_eq!(
14294 f.run(&two),
14295 bulk("{\n \"$.a\": [\n 1\n ],\n \"$.nope\": []\n}")
14296 );
14297 let mut root = vec![b"JSON.GET".as_slice(), b"doc", b"SPACE", b" "];
14300 root.push(b".a");
14301 assert_eq!(f.run(&root), bulk("1"));
14302 }
14303
14304 #[test]
14307 fn json_mget_answers_once_per_key_whatever_is_under_them() {
14308 let mut f = Fixture::new();
14309 f.run(&[b"JSON.SET", b"one", b"$", br#"{"a":1}"#]);
14310 f.run(&[b"JSON.SET", b"two", b"$", br#"{"a":2}"#]);
14311 f.run(&[b"SET", b"plain", b"x"]);
14312 assert_eq!(
14313 f.run(&[b"JSON.MGET", b"one", b"two", b"$.a"]),
14314 format!("*2\r\n{}{}", bulk("[1]"), bulk("[2]"))
14315 );
14316 assert_eq!(
14319 f.run(&[b"JSON.MGET", b"one", b"nokey", b"plain", b".a"]),
14320 format!("*3\r\n{}$-1\r\n$-1\r\n", bulk("1"))
14321 );
14322 assert_eq!(f.run(&[b"JSON.MGET", b"one", b".nope"]), "*1\r\n$-1\r\n");
14325 }
14326
14327 #[test]
14335 fn the_json_commands_that_answer_a_size_disagree_about_every_failure() {
14336 let mut f = Fixture::new();
14337 let doc = br#"{"a":[1,2,3],"o":{"x":1,"y":2},"s":"hello","n":7}"#;
14338 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14339
14340 assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b".a"]), ":3\r\n");
14341 assert_eq!(f.run(&[b"JSON.ARRLEN", b"doc", b"$.a"]), "*1\r\n:3\r\n");
14342 assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".o"]), ":2\r\n");
14343 assert_eq!(f.run(&[b"JSON.STRLEN", b"doc", b".s"]), ":5\r\n");
14344 assert_eq!(
14345 f.run(&[b"JSON.OBJKEYS", b"doc", b".o"]),
14346 format!("*2\r\n{}{}", bulk("x"), bulk("y"))
14347 );
14348 assert_eq!(
14351 f.run(&[b"JSON.ARRLEN", b"doc", b"$.*"]),
14352 "*4\r\n:3\r\n$-1\r\n$-1\r\n$-1\r\n"
14353 );
14354
14355 assert_eq!(
14358 f.run(&[b"JSON.ARRLEN", b"doc", b".nope"]),
14359 "-ERR Path does not exist\r\n"
14360 );
14361 assert_eq!(
14362 f.run(&[b"JSON.STRLEN", b"doc", b".nope"]),
14363 "-ERR Path does not exist\r\n"
14364 );
14365 assert_eq!(f.run(&[b"JSON.OBJLEN", b"doc", b".nope"]), "$-1\r\n");
14366 assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b".nope"]), "$-1\r\n");
14369 assert_eq!(f.run(&[b"JSON.OBJKEYS", b"doc", b"$.nope"]), "*0\r\n");
14372
14373 assert_eq!(
14377 f.run(&[b"JSON.ARRLEN", b"doc", b".n"]),
14378 "-ERR Path does not exist or not an array\r\n"
14379 );
14380 assert_eq!(
14381 f.run(&[b"JSON.OBJKEYS", b"doc", b".n"]),
14382 "-ERR Path does not exist or not an object\r\n"
14383 );
14384 assert_eq!(
14385 f.run(&[b"JSON.OBJLEN", b"doc", b".n"]),
14386 "-WRONGTYPE wrong type of path value - expected object\r\n"
14387 );
14388 assert_eq!(
14389 f.run(&[b"JSON.STRLEN", b"doc", b".n"]),
14390 "-WRONGTYPE wrong type of path value - expected string\r\n"
14391 );
14392
14393 assert_eq!(f.run(&[b"JSON.ARRLEN", b"nokey", b".a"]), "$-1\r\n");
14396 assert_eq!(f.run(&[b"JSON.OBJLEN", b"nokey", b".a"]), "$-1\r\n");
14397 assert_eq!(f.run(&[b"JSON.STRLEN", b"nokey", b".a"]), "$-1\r\n");
14398 assert_eq!(f.run(&[b"JSON.OBJKEYS", b"nokey", b".a"]), "$-1\r\n");
14399 assert_eq!(
14400 f.run(&[b"JSON.ARRLEN", b"nokey", b"$.a"]),
14401 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14402 );
14403 assert_eq!(
14405 f.run(&[b"JSON.OBJLEN", b"nokey", b"$.a"]),
14406 "-ERR Path does not exist or not an object\r\n"
14407 );
14408 }
14409
14410 #[test]
14416 fn the_json_array_writes_agree_on_the_errors_and_not_on_the_indexes() {
14417 let mut f = Fixture::new();
14418 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3],"n":7}"#]);
14419
14420 assert_eq!(f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"4"]), ":4\r\n");
14421 assert_eq!(
14422 f.run(&[b"JSON.ARRAPPEND", b"doc", b"$.a", b"5", b"6"]),
14423 "*1\r\n:6\r\n"
14424 );
14425 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[1,2,3,4,5,6]"));
14426
14427 assert_eq!(
14430 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-1", b"0"]),
14431 ":7\r\n"
14432 );
14433 assert_eq!(
14434 f.run(&[b"JSON.GET", b"doc", b".a"]),
14435 bulk("[1,2,3,4,5,0,6]")
14436 );
14437 assert_eq!(
14438 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"7", b"9"]),
14439 ":8\r\n"
14440 );
14441 assert_eq!(
14443 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"9", b"9"]),
14444 "-ERR index out of bounds\r\n"
14445 );
14446 assert_eq!(
14447 f.run(&[b"JSON.ARRINSERT", b"doc", b".a", b"-9", b"9"]),
14448 "-ERR index out of bounds\r\n"
14449 );
14450
14451 f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3,4,5]"]);
14454 assert_eq!(
14455 f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"1", b"3"]),
14456 ":3\r\n"
14457 );
14458 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[2,3,4]"));
14459 assert_eq!(
14460 f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"-2", b"99"]),
14461 ":2\r\n"
14462 );
14463 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[3,4]"));
14464 assert_eq!(
14465 f.run(&[b"JSON.ARRTRIM", b"doc", b".a", b"9", b"9"]),
14466 ":0\r\n"
14467 );
14468 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
14469
14470 f.run(&[b"JSON.SET", b"doc", b"$.a", b"[1,2,3]"]);
14473 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), bulk("3"));
14474 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"0"]), bulk("1"));
14475 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a", b"99"]), bulk("2"));
14476 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a"]), "$-1\r\n");
14477
14478 for call in [
14481 &[&b"JSON.ARRAPPEND"[..], b"doc", b"PATH", b"1"][..],
14482 &[&b"JSON.ARRTRIM"[..], b"doc", b"PATH", b"1", b"1"][..],
14483 &[&b"JSON.ARRPOP"[..], b"doc", b"PATH", b"1"][..],
14484 &[&b"JSON.ARRINSERT"[..], b"doc", b"PATH", b"0", b"1"][..],
14485 ] {
14486 for path in [&b".n"[..], &b".nope"[..]] {
14487 let args: Vec<&[u8]> = call
14488 .iter()
14489 .map(|a| if *a == b"PATH" { path } else { *a })
14490 .collect();
14491 assert_eq!(
14492 f.run(&args),
14493 "-ERR Path does not exist or not an array\r\n",
14494 "{} {}",
14495 String::from_utf8_lossy(call[0]),
14496 String::from_utf8_lossy(path)
14497 );
14498 }
14499 }
14500
14501 assert_eq!(
14504 f.run(&[b"JSON.ARRAPPEND", b"nokey", b".a", b"1"]),
14505 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14506 );
14507 assert_eq!(
14508 f.run(&[b"JSON.ARRPOP", b"nokey", b"$.a"]),
14509 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14510 );
14511
14512 assert!(
14517 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a", b"nope"])
14518 .starts_with("-this is not the start of a value")
14519 );
14520 assert_eq!(f.run(&[b"JSON.GET", b"doc", b".a"]), bulk("[]"));
14521 }
14522
14523 #[test]
14531 fn json_arrinsert_is_all_or_nothing_across_the_matches() {
14532 let mut f = Fixture::new();
14533 let doc = br#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#;
14534 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14535 assert_eq!(
14536 f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"-2", b"0"]),
14537 "-ERR index out of bounds\r\n"
14538 );
14539 assert_eq!(
14540 f.run(&[b"JSON.GET", b"doc"]),
14541 bulk(r#"{"a":[1,2,3],"n":{"a":[9,8],"in":{"a":[1]}}}"#)
14542 );
14543 assert_eq!(
14545 f.run(&[b"JSON.ARRINSERT", b"doc", b"$..a", b"0", b"0"]),
14546 "*3\r\n:4\r\n:3\r\n:2\r\n"
14547 );
14548 assert_eq!(
14549 f.run(&[b"JSON.GET", b"doc"]),
14550 bulk(r#"{"a":[0,1,2,3],"n":{"a":[0,9,8],"in":{"a":[0,1]}}}"#)
14551 );
14552 }
14553
14554 #[test]
14562 fn json_arrindex_has_an_exclusive_stop_and_a_start_that_cannot_run_off_the_end() {
14563 let mut f = Fixture::new();
14564 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3,1],"n":7}"#]);
14565
14566 assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"2"]), ":1\r\n");
14567 assert_eq!(f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"9"]), ":-1\r\n");
14568 assert_eq!(
14569 f.run(&[b"JSON.ARRINDEX", b"doc", b"$.a", b"2"]),
14570 "*1\r\n:1\r\n"
14571 );
14572
14573 assert_eq!(
14576 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"0"]),
14577 ":3\r\n"
14578 );
14579 assert_eq!(
14582 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1", b"3"]),
14583 ":-1\r\n"
14584 );
14585
14586 for start in [&b"4"[..], &b"5"[..], &b"-1"[..]] {
14589 assert_eq!(
14590 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", start]),
14591 ":3\r\n",
14592 "{}",
14593 String::from_utf8_lossy(start)
14594 );
14595 }
14596 assert_eq!(
14597 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"-100"]),
14598 ":0\r\n"
14599 );
14600 f.run(&[b"JSON.SET", b"doc", b"$.a", b"[]"]);
14603 assert_eq!(
14604 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"1", b"1"]),
14605 ":-1\r\n"
14606 );
14607
14608 f.run(&[b"JSON.SET", b"doc", b"$.a", br#"[{"k":1},[1,2],"s"]"#]);
14612 assert_eq!(
14613 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", br#"{"k":1}"#]),
14614 ":0\r\n"
14615 );
14616 assert_eq!(
14617 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[1,2]"]),
14618 ":1\r\n"
14619 );
14620 assert_eq!(
14621 f.run(&[b"JSON.ARRINDEX", b"doc", b".a", b"[2,1]"]),
14622 ":-1\r\n"
14623 );
14624
14625 assert_eq!(
14629 f.run(&[b"JSON.ARRINDEX", b"doc", b".nope", b"1"]),
14630 "-ERR Path does not exist\r\n"
14631 );
14632 assert_eq!(
14633 f.run(&[b"JSON.ARRINDEX", b"doc", b".n", b"1"]),
14634 "-WRONGTYPE wrong type of path value - expected array\r\n"
14635 );
14636 assert_eq!(
14637 f.run(&[b"JSON.ARRINDEX", b"nokey", b".a", b"1"]),
14638 "-ERR Path does not exist\r\n"
14639 );
14640 assert_eq!(
14641 f.run(&[b"JSON.ARRINDEX", b"nokey", b"$.a", b"1"]),
14642 "-ERR Path does not exist\r\n"
14643 );
14644 }
14645
14646 #[test]
14649 fn the_json_number_family_answers_json_text_and_keeps_its_integers() {
14650 let mut f = Fixture::new();
14651 let doc = br#"{"i":7,"f":1.5,"neg":-2,"s":"ab"}"#;
14652 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
14653
14654 assert_eq!(
14657 f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2"]),
14658 bulk("9").as_str()
14659 );
14660 assert_eq!(
14662 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.i", b"2"]),
14663 bulk("[11]").as_str()
14664 );
14665 assert_eq!(
14668 f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"2.0"]),
14669 bulk("13.0").as_str()
14670 );
14671 assert_eq!(
14672 f.run(&[b"JSON.TYPE", b"doc", b".i"]),
14673 bulk("number").as_str()
14674 );
14675 assert_eq!(
14676 f.run(&[b"JSON.NUMMULTBY", b"doc", b".f", b"2"]),
14677 bulk("3.0").as_str()
14678 );
14679 assert_eq!(
14680 f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"3"]),
14681 bulk("-8").as_str()
14682 );
14683 f.run(&[b"JSON.SET", b"doc", b"$.f", b"1.5"]);
14686 assert_eq!(
14687 f.run(&[b"JSON.NUMPOWBY", b"doc", b".f", b"0.5"]),
14688 bulk("1.224744871391589").as_str()
14689 );
14690 assert_eq!(
14691 f.run(&[b"JSON.NUMPOWBY", b"doc", b".neg", b"0.5"]),
14692 "-ERR result is not a number\r\n"
14693 );
14694 f.run(&[b"JSON.SET", b"doc", b"$.big", b"9223372036854775807"]);
14698 assert_eq!(
14699 f.run(&[b"JSON.NUMINCRBY", b"doc", b".big", b"1"]),
14700 "-ERR numeric overflow\r\n"
14701 );
14702 f.run(&[b"JSON.SET", b"doc", b"$.p", b"2"]);
14703 assert_eq!(
14704 f.run(&[b"JSON.NUMPOWBY", b"doc", b".p", b"-1"]),
14705 "-ERR numeric overflow\r\n"
14706 );
14707 f.run(&[b"JSON.SET", b"doc", b"$.huge", b"1e308"]);
14709 assert_eq!(
14710 f.run(&[b"JSON.NUMMULTBY", b"doc", b".huge", b"1e10"]),
14711 "-ERR result is not a number\r\n"
14712 );
14713
14714 assert_eq!(
14718 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"1"]),
14719 bulk("[null]").as_str()
14720 );
14721 assert_eq!(
14722 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.nope", b"1"]),
14723 bulk("[]").as_str()
14724 );
14725 assert_eq!(
14726 f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", b"1"]),
14727 "-ERR Path does not exist or does not contains a number\r\n"
14728 );
14729 assert_eq!(
14730 f.run(&[b"JSON.NUMINCRBY", b"doc", b".nope", b"1"]),
14731 "-ERR Path does not exist or does not contains a number\r\n"
14732 );
14733 assert_eq!(
14736 f.run(&[b"JSON.NUMINCRBY", b"doc", b".i", b"true"]),
14737 "-bad input number\r\n"
14738 );
14739 assert_eq!(
14740 f.run(&[b"JSON.NUMINCRBY", b"nokey", b".i", b"1"]),
14741 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14742 );
14743 assert_eq!(
14744 f.run(&[b"JSON.NUMINCRBY", b"nokey", b"$.i", b"1"]),
14745 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14746 );
14747 }
14748
14749 #[test]
14752 fn json_strappend_reads_its_shape_off_the_argument_count() {
14753 let mut f = Fixture::new();
14754 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"s":"ab","n":1}"#]);
14755
14756 assert_eq!(
14757 f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""c""#]),
14758 ":3\r\n"
14759 );
14760 assert_eq!(
14761 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", br#""d""#]),
14762 "*1\r\n:4\r\n"
14763 );
14764 assert_eq!(
14767 f.run(&[b"JSON.STRAPPEND", b"doc", b".s", br#""\u00e9""#]),
14768 ":6\r\n"
14769 );
14770 f.run(&[b"JSON.SET", b"str", b"$", br#""ab""#]);
14773 assert_eq!(f.run(&[b"JSON.STRAPPEND", b"str", br#""c""#]), ":3\r\n");
14774 assert_eq!(f.run(&[b"JSON.GET", b"str"]), bulk("\"abc\"").as_str());
14775
14776 assert_eq!(
14780 f.run(&[b"JSON.STRAPPEND", b"doc", b".s", b"5"]),
14781 "-WRONGTYPE wrong type of path value - expected string\r\n"
14782 );
14783 assert_eq!(
14784 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", br#""c""#]),
14785 "*1\r\n$-1\r\n"
14786 );
14787 assert_eq!(
14788 f.run(&[b"JSON.STRAPPEND", b"doc", b".n", br#""c""#]),
14789 "-ERR Path does not exist or not a string\r\n"
14790 );
14791 assert_eq!(
14792 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.nope", br#""c""#]),
14793 "*0\r\n"
14794 );
14795 assert_eq!(
14796 f.run(&[b"JSON.STRAPPEND", b"nokey", br#""c""#]),
14797 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14798 );
14799 }
14800
14801 #[test]
14804 fn a_legacy_wildcard_write_touches_every_match_and_answers_only_one() {
14805 let mut f = Fixture::new();
14806 let three = br#"{"a":[[7],[7,7],[7,7,7]]}"#;
14809
14810 f.run(&[b"JSON.SET", b"doc", b"$", three]);
14811 assert_eq!(
14812 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
14813 ":4\r\n"
14814 );
14815 f.run(&[b"JSON.SET", b"doc", b"$", three]);
14816 assert_eq!(
14817 f.run(&[b"JSON.ARRINSERT", b"doc", b".a[*]", b"0", b"9"]),
14818 ":2\r\n"
14819 );
14820 f.run(&[b"JSON.SET", b"doc", b"$", three]);
14821 assert_eq!(
14822 f.run(&[b"JSON.ARRTRIM", b"doc", b".a[*]", b"0", b"1"]),
14823 ":1\r\n"
14824 );
14825 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[1,2,3],[4,5,6]]}"#]);
14826 assert_eq!(
14827 f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]", b"0"]),
14828 bulk("1").as_str()
14829 );
14830 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2,3]}"#]);
14831 assert_eq!(
14832 f.run(&[b"JSON.NUMINCRBY", b"doc", b".a[*]", b"10"]),
14833 bulk("13").as_str()
14834 );
14835 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["p","qq","rrr"]}"#]);
14836 assert_eq!(
14837 f.run(&[b"JSON.STRAPPEND", b"doc", b".a[*]", br#""z""#]),
14838 ":4\r\n"
14839 );
14840 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[true,false,true]}"#]);
14841 assert_eq!(
14842 f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
14843 bulk("false").as_str()
14844 );
14845 assert_eq!(
14848 f.run(&[b"JSON.GET", b"doc", b".a"]),
14849 bulk("[false,true,false]").as_str()
14850 );
14851
14852 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x",[1],[1,2]]}"#]);
14855 assert_eq!(
14856 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
14857 ":3\r\n"
14858 );
14859 assert_eq!(
14860 f.run(&[b"JSON.GET", b"doc", b".a"]),
14861 bulk(r#"["x",[1,9],[1,2,9]]"#).as_str()
14862 );
14863 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":["x","y"]}"#]);
14866 assert_eq!(
14867 f.run(&[b"JSON.ARRAPPEND", b"doc", b".a[*]", b"9"]),
14868 "-ERR Path does not exist or not an array\r\n"
14869 );
14870 assert_eq!(
14871 f.run(&[b"JSON.TOGGLE", b"doc", b".a[*]"]),
14872 "-ERR Path does not exist or not a bool\r\n"
14873 );
14874 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[[],[2,3]]}"#]);
14878 assert_eq!(f.run(&[b"JSON.ARRPOP", b"doc", b".a[*]"]), "$-1\r\n");
14879 assert_eq!(
14880 f.run(&[b"JSON.GET", b"doc", b".a"]),
14881 bulk("[[],[2]]").as_str()
14882 );
14883 }
14884
14885 #[test]
14888 fn a_write_reaches_a_match_that_sits_inside_another_match() {
14889 let mut f = Fixture::new();
14890 let nested = br#"{"a":[{"a":[7]},{"a":[7,7]}]}"#;
14891
14892 f.run(&[b"JSON.SET", b"doc", b"$", nested]);
14893 assert_eq!(
14894 f.run(&[b"JSON.ARRAPPEND", b"doc", b"$..a", b"9"]),
14895 "*3\r\n:3\r\n:2\r\n:3\r\n"
14896 );
14897 assert_eq!(
14898 f.run(&[b"JSON.GET", b"doc", b"$"]),
14899 bulk(r#"[{"a":[{"a":[7,9]},{"a":[7,7,9]},9]}]"#).as_str()
14900 );
14901
14902 f.run(&[b"JSON.SET", b"doc", b"$", nested]);
14905 assert_eq!(
14906 f.run(&[b"JSON.ARRTRIM", b"doc", b"$..a", b"0", b"0"]),
14907 "*3\r\n:1\r\n:1\r\n:1\r\n"
14908 );
14909 assert_eq!(
14910 f.run(&[b"JSON.GET", b"doc", b"$"]),
14911 bulk(r#"[{"a":[{"a":[7]}]}]"#).as_str()
14912 );
14913
14914 f.run(&[b"JSON.SET", b"doc", b"$", nested]);
14917 assert_eq!(
14918 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$..a[0]", b"1"]),
14919 bulk("[null,8,8]").as_str()
14920 );
14921 }
14922
14923 #[test]
14926 fn a_bad_operand_is_not_the_answer_when_the_path_found_nothing_to_use_it_on() {
14927 let mut f = Fixture::new();
14928 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"n":7,"s":"t"}"#]);
14929
14930 assert_eq!(
14933 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", br#""x""#]),
14934 bulk("[null]").as_str()
14935 );
14936 assert_eq!(
14937 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.s", b"notjson"]),
14938 bulk("[null]").as_str()
14939 );
14940 assert_eq!(
14941 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.missing", b"notjson"]),
14942 bulk("[]").as_str()
14943 );
14944 assert_eq!(
14945 f.run(&[b"JSON.NUMINCRBY", b"doc", b".s", br#""x""#]),
14946 "-ERR Path does not exist or does not contains a number\r\n"
14947 );
14948 assert_eq!(
14950 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.n", br#""x""#]),
14951 "-bad input number\r\n"
14952 );
14953
14954 assert_eq!(
14956 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.n", b"1"]),
14957 "*1\r\n$-1\r\n"
14958 );
14959 assert_eq!(
14960 f.run(&[b"JSON.STRAPPEND", b"doc", b".n", b"1"]),
14961 "-ERR Path does not exist or not a string\r\n"
14962 );
14963 assert_eq!(
14964 f.run(&[b"JSON.STRAPPEND", b"doc", b"$.s", b"1"]),
14965 "-WRONGTYPE wrong type of path value - expected string\r\n"
14966 );
14967
14968 assert_eq!(
14970 f.run(&[b"JSON.NUMINCRBY", b"nope", b"$.a", br#""x""#]),
14971 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14972 );
14973 assert_eq!(
14974 f.run(&[b"JSON.STRAPPEND", b"nope", b"$.a", b"1"]),
14975 "-ERR could not perform this operation on a key that doesn't exist\r\n"
14976 );
14977 }
14978
14979 #[test]
14982 fn a_merge_patch_adds_replaces_and_deletes_in_one_write() {
14983 let mut f = Fixture::new();
14984
14985 assert_eq!(
14989 f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"x":null,"y":1}"#]),
14990 "+OK\r\n"
14991 );
14992 assert_eq!(
14993 f.run(&[b"JSON.GET", b"doc", b"$"]),
14994 bulk(r#"[{"x":null,"y":1}]"#).as_str()
14995 );
14996
14997 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1,"c":2},"d":3}"#]);
15000 assert_eq!(
15001 f.run(&[b"JSON.MERGE", b"doc", b"$", br#"{"a":{"b":null,"e":4}}"#]),
15002 "+OK\r\n"
15003 );
15004 assert_eq!(
15005 f.run(&[b"JSON.GET", b"doc", b"$"]),
15006 bulk(r#"[{"a":{"c":2,"e":4},"d":3}]"#).as_str()
15007 );
15008
15009 assert_eq!(f.run(&[b"JSON.MERGE", b"doc", b"$.a", b"[1,2]"]), "+OK\r\n");
15011 assert_eq!(
15012 f.run(&[b"JSON.GET", b"doc", b"$"]),
15013 bulk(r#"[{"a":[1,2],"d":3}]"#).as_str()
15014 );
15015
15016 assert_eq!(
15020 f.run(&[b"JSON.MERGE", b"doc", b"$.d", br#"{"p":null,"q":9}"#]),
15021 "+OK\r\n"
15022 );
15023 assert_eq!(
15024 f.run(&[b"JSON.GET", b"doc", b"$"]),
15025 bulk(r#"[{"a":[1,2],"d":{"q":9}}]"#).as_str()
15026 );
15027
15028 assert_eq!(
15032 f.run(&[b"JSON.MERGE", b"doc", b"$.new", br#"{"z":null}"#]),
15033 "+OK\r\n"
15034 );
15035 assert_eq!(
15036 f.run(&[b"JSON.GET", b"doc", b"$.new"]),
15037 bulk(r#"[{"z":null}]"#).as_str()
15038 );
15039 assert_eq!(
15040 f.run(&[b"JSON.MERGE", b"doc", b"$.no.deep", b"1"]),
15041 "$-1\r\n"
15042 );
15043 assert_eq!(
15044 f.run(&[b"JSON.MERGE", b"doc", b"$.no.*", b"1"]),
15045 "-Err wrong static path\r\n"
15046 );
15047
15048 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"n":1},"b":{"n":2}}"#]);
15050 assert_eq!(
15051 f.run(&[b"JSON.MERGE", b"doc", b"$.*", br#"{"m":0}"#]),
15052 "+OK\r\n"
15053 );
15054 assert_eq!(
15055 f.run(&[b"JSON.GET", b"doc", b"$"]),
15056 bulk(r#"[{"a":{"m":0,"n":1},"b":{"m":0,"n":2}}]"#).as_str()
15057 );
15058
15059 assert_eq!(
15061 f.run(&[b"JSON.MERGE", b"doc", b"$", b"{}", b"more"]),
15062 "-ERR syntax error\r\n"
15063 );
15064 assert_eq!(
15065 f.run(&[b"JSON.MERGE", b"gone", b"$.a", b"1"]),
15066 "-ERR new objects must be created at the root\r\n"
15067 );
15068 f.run(&[b"SET", b"str", b"x"]);
15069 assert_eq!(
15070 f.run(&[b"JSON.MERGE", b"str", b"$", b"1"]),
15071 "-Existing key has wrong Redis type\r\n"
15072 );
15073 }
15074
15075 #[test]
15078 fn a_merge_down_a_descent_keeps_what_the_inner_match_did() {
15079 let mut f = Fixture::new();
15080 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
15081 assert_eq!(
15082 f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"m":1}"#]),
15083 "+OK\r\n"
15084 );
15085 assert_eq!(
15089 f.run(&[b"JSON.GET", b"doc", b"$"]),
15090 bulk(r#"[{"a":{"b":{"m":1},"m":1},"c":{"m":1}}]"#).as_str()
15091 );
15092
15093 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":{"b":1},"c":[2]}"#]);
15096 assert_eq!(
15097 f.run(&[b"JSON.MERGE", b"doc", b"$..*", br#"{"a":null}"#]),
15098 "+OK\r\n"
15099 );
15100 assert_eq!(
15101 f.run(&[b"JSON.GET", b"doc", b"$"]),
15102 bulk(r#"[{"a":{"b":{}},"c":{}}]"#).as_str()
15103 );
15104 }
15105
15106 #[test]
15109 fn a_filter_path_reads_and_writes_the_members_it_keeps() {
15110 let mut f = Fixture::new();
15111 let doc = br#"{"book":[{"t":"a","p":8},{"t":"b","p":13},{"t":"c","p":9}],"cap":10}"#;
15112 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
15113
15114 assert_eq!(
15115 f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < 10)].t"]),
15116 bulk(r#"["a","c"]"#).as_str()
15117 );
15118 assert_eq!(
15121 f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p < $.cap)].t"]),
15122 bulk(r#"["a","c"]"#).as_str()
15123 );
15124 assert_eq!(
15126 f.run(&[b"JSON.GET", b"doc", b"book[?(@.p < 10)].t"]),
15127 bulk(r#""a""#).as_str()
15128 );
15129 assert_eq!(
15130 f.run(&[b"JSON.TYPE", b"doc", b"$.book[?(@.p > 10)]"]),
15131 "*1\r\n$6\r\nobject\r\n"
15132 );
15133
15134 assert_eq!(
15138 f.run(&[b"JSON.NUMINCRBY", b"doc", b"$.book[?(@.p < 10)].p", b"1"]),
15139 bulk("[9,10]").as_str()
15140 );
15141 assert_eq!(
15142 f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].t", br#""B""#]),
15143 "+OK\r\n"
15144 );
15145 assert_eq!(
15146 f.run(&[b"JSON.SET", b"doc", b"$.book[?(@.p == 13)].n", b"1"]),
15147 "-Err wrong static path\r\n"
15148 );
15149 assert_eq!(
15150 f.run(&[b"JSON.DEL", b"doc", b"$.book[?(@.p > 9)]"]),
15151 ":2\r\n"
15152 );
15153 assert_eq!(
15154 f.run(&[b"JSON.GET", b"doc", b"$"]),
15155 bulk(r#"[{"cap":10,"book":[{"p":9,"t":"a"}]}]"#).as_str()
15156 );
15157
15158 assert!(
15161 f.run(&[b"JSON.GET", b"doc", b"$.book[?(@.p <)]"])
15162 .starts_with("-ERR")
15163 );
15164 assert!(
15165 f.run(&[b"JSON.GET", b"nokey", b"$.book[?(@.p <)]"])
15166 .starts_with("-ERR")
15167 );
15168 }
15169
15170 #[test]
15173 fn a_filter_takes_the_membership_operators_and_the_methods_too() {
15174 let mut f = Fixture::new();
15175 let doc = br#"{"box":[{"t":"a","n":[1,2],"g":"x"},{"t":"b","n":[9],"g":"y"}]}"#;
15176 f.run(&[b"JSON.SET", b"doc", b"$", doc]);
15177
15178 for (path, want) in [
15179 (&b"$.box[?(@.g in [\"x\"])].t"[..], r#"["a"]"#),
15180 (b"$.box[?(@.g nin [\"x\"])].t", r#"["b"]"#),
15181 (b"$.box[?(@.n anyof [2,3])].t", r#"["a"]"#),
15182 (b"$.box[?(@.n subsetof [1,2,3])].t", r#"["a"]"#),
15183 (b"$.box[?(@.n size 2)].t", r#"["a"]"#),
15184 (b"$.box[?(@.n empty false)].t", r#"["a","b"]"#),
15185 (b"$.box[?(@.n.length() == 1)].t", r#"["b"]"#),
15186 (b"$.box[?(@.n.sum() > 5)].t", r#"["b"]"#),
15187 (b"$.box[?(@.n[0] + 1 == 2)].t", r#"["a"]"#),
15188 (b"$.box[?(@~ size 3)].t", r#"["a","b"]"#),
15189 (b"$.box[?(@.n~)].t", "[]"),
15190 (b"$.box[?(@.n sizeof 2)].t", r#"["a"]"#),
15191 (b"$.box[?(-@.n[0] == -9)].t", r#"["b"]"#),
15192 (b"$.box[?(1 in @.n)].t", r#"["a"]"#),
15193 (b"$.box[?(\"g\" in @~)].t", r#"["a","b"]"#),
15194 ] {
15195 assert_eq!(f.run(&[b"JSON.GET", b"doc", path]), bulk(want).as_str());
15196 }
15197
15198 assert_eq!(
15201 f.run(&[b"JSON.SET", b"doc", b"$.box[?(@.n size 1)].g", br#""z""#]),
15202 "+OK\r\n"
15203 );
15204 assert_eq!(
15205 f.run(&[b"JSON.GET", b"doc", b"$.box[?(@.g == \"z\")].t"]),
15206 bulk(r#"["b"]"#).as_str()
15207 );
15208 }
15209
15210 #[test]
15213 fn a_merge_onto_a_number_inside_an_array_is_a_merge_and_not_an_error() {
15214 let mut f = Fixture::new();
15215 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2]}"#]);
15216 assert_eq!(
15217 f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
15218 "+OK\r\n"
15219 );
15220 assert_eq!(
15221 f.run(&[b"JSON.GET", b"doc", b"$"]),
15222 bulk(r#"[{"a":[{"x":1},2]}]"#).as_str()
15223 );
15224 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,"s"]}"#]);
15227 assert_eq!(
15228 f.run(&[b"JSON.MERGE", b"doc", b"$.a[0]", br#"{"x":1}"#]),
15229 "+OK\r\n"
15230 );
15231 assert_eq!(
15232 f.run(&[b"JSON.GET", b"doc", b"$"]),
15233 bulk(r#"[{"a":[{"x":1},"s"]}]"#).as_str()
15234 );
15235 }
15236
15237 #[test]
15240 fn an_mset_writes_every_triple_it_can_and_checks_the_rest_up_front() {
15241 let mut f = Fixture::new();
15242 assert_eq!(
15243 f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b", b"$", b"2"]),
15244 "+OK\r\n"
15245 );
15246 assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[1]").as_str());
15247 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[2]").as_str());
15248
15249 assert_eq!(
15251 f.run(&[b"JSON.MSET", b"a", b"$", b"3", b"a", b"$", b"4"]),
15252 "+OK\r\n"
15253 );
15254 assert_eq!(f.run(&[b"JSON.GET", b"a", b"$"]), bulk("[4]").as_str());
15255
15256 f.run(&[b"JSON.SET", b"a", b"$", br#"{"n":1}"#]);
15261 assert_eq!(
15262 f.run(&[b"JSON.MSET", b"a", b"$.no.deep", b"9", b"b", b"$", b"5"]),
15263 "$-1\r\n"
15264 );
15265 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[5]").as_str());
15266 assert_eq!(
15267 f.run(&[b"JSON.MSET", b"b", b"$", b"6", b"a", b"$.no.deep", b"9"]),
15268 "$-1\r\n"
15269 );
15270 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
15271
15272 f.run(&[b"SET", b"str", b"x"]);
15277 assert_eq!(
15278 f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"b", b"$", b"notjson"]),
15279 "-this is not the start of a value, at byte 0 of the JSON text\r\n"
15280 );
15281 assert_eq!(
15282 f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"str", b"$", b"1"]),
15283 "-Existing key has wrong Redis type\r\n"
15284 );
15285 assert_eq!(
15286 f.run(&[b"JSON.MSET", b"a", b"$.n", b"7", b"gone", b"$.x", b"1"]),
15287 "-ERR new objects must be created at the root\r\n"
15288 );
15289 assert_eq!(f.run(&[b"JSON.GET", b"a", b"$.n"]), bulk("[1]").as_str());
15290
15291 assert_eq!(
15296 f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$.no.*", b"9"]),
15297 "-Err wrong static path\r\n"
15298 );
15299 assert_eq!(
15300 f.run(&[b"JSON.MSET", b"b", b"$", b"8", b"a", b"$[0]", b"9"]),
15301 "-ERR array index out of range\r\n"
15302 );
15303 assert_eq!(f.run(&[b"JSON.GET", b"b", b"$"]), bulk("[6]").as_str());
15304
15305 f.run(&[b"JSON.SET", b"c", b"$", br#"{"n":1}"#]);
15309 assert_eq!(
15310 f.run(&[b"JSON.MSET", b"c", b"$", br#"{"n":2}"#, b"c", b"$.n", b"3"]),
15311 "+OK\r\n"
15312 );
15313 assert_eq!(
15314 f.run(&[b"JSON.GET", b"c", b"$"]),
15315 bulk(r#"[{"n":3}]"#).as_str()
15316 );
15317
15318 assert_eq!(
15321 f.run(&[b"JSON.MSET", b"a", b"$", b"1", b"b"]),
15322 "-ERR wrong number of arguments for 'json.mset' command\r\n"
15323 );
15324 }
15325
15326 #[test]
15329 fn json_resp_answers_the_document_as_resp_types() {
15330 let mut f = Fixture::new();
15331 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":1,"b":[2,"c"]}"#]);
15332 assert_eq!(
15333 f.run(&[b"JSON.RESP", b"doc"]),
15334 "*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"
15335 );
15336 assert_eq!(
15338 f.run(&[b"JSON.RESP", b"doc", b"$.b"]),
15339 "*1\r\n*3\r\n+[\r\n:2\r\n$1\r\nc\r\n"
15340 );
15341
15342 f.run(&[
15343 b"JSON.SET",
15344 b"doc",
15345 b"$",
15346 br#"{"f":2.5,"t":true,"z":null,"e":[],"o":{}}"#,
15347 ]);
15348 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".e"]), "*1\r\n+[\r\n");
15349 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".o"]), "*1\r\n+{\r\n");
15350 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".f"]), bulk("2.5").as_str());
15353 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".t"]), "+true\r\n");
15354 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b".z"]), "$-1\r\n");
15355
15356 assert_eq!(
15359 f.run(&[b"JSON.RESP", b"doc", b".nope"]),
15360 "-ERR Path does not exist\r\n"
15361 );
15362 assert_eq!(f.run(&[b"JSON.RESP", b"doc", b"$.nope"]), "*0\r\n");
15363 assert_eq!(f.run(&[b"JSON.RESP", b"gone"]), "$-1\r\n");
15364 assert_eq!(f.run(&[b"JSON.RESP", b"gone", b"$"]), "$-1\r\n");
15365 }
15366
15367 #[test]
15371 fn json_debug_answers_a_byte_count_and_its_own_help() {
15372 let mut f = Fixture::new();
15373 f.run(&[b"JSON.SET", b"doc", b"$", br#"{"a":[1,2],"s":"hello"}"#]);
15374 let one = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".s"]);
15375 assert!(one.starts_with(':'), "{one}");
15376 assert_eq!(
15377 f.run(&[b"JSON.DEBUG", b"memory", b"doc", b"$.s"]),
15378 format!("*1\r\n{one}")
15379 );
15380 let whole = f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc"]);
15381 assert!(whole.starts_with(':') && whole.len() > one.len(), "{whole}");
15382
15383 assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone"]), ":0\r\n");
15387 assert_eq!(f.run(&[b"JSON.DEBUG", b"MEMORY", b"gone", b"$"]), "*0\r\n");
15388 assert_eq!(
15389 f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b".nope"]),
15390 "-ERR Path does not exist\r\n"
15391 );
15392 assert_eq!(
15393 f.run(&[b"JSON.DEBUG", b"MEMORY", b"doc", b"$.nope"]),
15394 "*0\r\n"
15395 );
15396
15397 assert_eq!(
15398 f.run(&[b"JSON.DEBUG", b"HELP"]),
15399 "*2\r\n$42\r\nMEMORY <key> [path] - reports memory usage\r\n\
15400 $34\r\nHELP - this message\r\n"
15401 );
15402 assert_eq!(
15403 f.run(&[b"JSON.DEBUG", b"NOPE"]),
15404 "-ERR unknown subcommand - try `JSON.DEBUG HELP`\r\n"
15405 );
15406 assert_eq!(
15407 f.run(&[b"JSON.DEBUG", b"MEMORY"]),
15408 "-ERR wrong number of arguments for 'json.debug' command\r\n"
15409 );
15410 }
15411
15412 #[test]
15417 fn the_first_vadd_decides_how_wide_the_set_is() {
15418 let mut f = Fixture::new();
15419 assert_eq!(
15420 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]),
15421 ":1\r\n"
15422 );
15423 assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
15424 assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
15425 assert_eq!(
15428 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"east"]),
15429 ":0\r\n"
15430 );
15431 assert_eq!(f.run(&[b"VCARD", b"v"]), ":1\r\n");
15432 assert_eq!(
15435 f.run(&[b"VADD", b"v", b"VALUES", b"3", b"1", b"0", b"0", b"up"]),
15436 "-ERR Vector dimension mismatch - got 3 but set has 2\r\n"
15437 );
15438 assert_eq!(
15441 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"0", b"nowhere"]),
15442 ":1\r\n"
15443 );
15444 assert_eq!(
15445 f.run(&[b"VEMB", b"v", b"nowhere"]),
15446 "*2\r\n$1\r\n0\r\n$1\r\n0\r\n"
15447 );
15448 assert_eq!(
15452 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"other", b"BIN"]),
15453 "-ERR asked quantization mismatch with existing vector set\r\n"
15454 );
15455 assert_eq!(f.run(&[b"EXISTS", b"fresh"]), ":0\r\n");
15458 assert_eq!(f.run(&[b"VDIM", b"fresh"]), "-ERR key does not exist\r\n");
15459 assert_eq!(f.run(&[b"VCARD", b"fresh"]), ":0\r\n");
15460 }
15461
15462 #[test]
15465 fn vemb_gives_back_the_vector_and_vsim_gives_back_a_similarity() {
15466 let mut f = Fixture::new();
15467 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"4", b"a"]);
15468 assert_eq!(
15474 f.run(&[b"VEMB", b"v", b"a"]),
15475 "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
15476 );
15477 f.run(&[b"VADD", b"n", b"VALUES", b"2", b"3", b"4", b"a", b"NOQUANT"]);
15479 assert_eq!(
15480 f.run(&[b"VEMB", b"n", b"a"]),
15481 "*2\r\n$1\r\n3\r\n$1\r\n4\r\n"
15482 );
15483 f.run(&[b"VADD", b"b", b"VALUES", b"2", b"3", b"-4", b"a", b"BIN"]);
15486 assert_eq!(
15487 f.run(&[b"VEMB", b"b", b"a"]),
15488 "*2\r\n$1\r\n1\r\n$2\r\n-1\r\n"
15489 );
15490 assert_eq!(f.run(&[b"VEMB", b"v", b"nobody"]), "*-1\r\n");
15491 assert_eq!(f.run(&[b"VEMB", b"nokey", b"a"]), "*-1\r\n");
15492
15493 let mut f = Fixture::new();
15497 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"3", b"0", b"a"]);
15498 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"-1", b"0", b"opposite"]);
15499 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"7", b"across"]);
15500 assert_eq!(
15501 f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"2", b"0", b"WITHSCORES"]),
15502 "*6\r\n$1\r\na\r\n$1\r\n1\r\n$6\r\nacross\r\n$3\r\n0.5\r\n\
15503 $8\r\nopposite\r\n$1\r\n0\r\n"
15504 );
15505 assert_eq!(
15508 f.run(&[b"VSIM", b"v", b"ELE", b"a"]),
15509 "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
15510 );
15511 assert_eq!(f.run(&[b"VSIM", b"v", b"ELE", b"nobody"]), "*0\r\n");
15514 assert_eq!(f.run(&[b"VSIM", b"nokey", b"ELE", b"a"]), "*0\r\n");
15515 assert_eq!(
15518 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1"]),
15519 "*1\r\n$6\r\nacross\r\n"
15520 );
15521 assert_eq!(
15522 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"TRUTH"]),
15523 "*2\r\n$6\r\nacross\r\n$8\r\nopposite\r\n"
15524 );
15525 assert_eq!(
15529 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"COUNT", b"1", b"EF", b"500"]),
15530 "*1\r\n$6\r\nacross\r\n"
15531 );
15532
15533 let mut g = Fixture::new();
15536 g.run(&[b"HELLO", b"3"]);
15537 g.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15538 assert_eq!(
15539 g.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHSCORES"]),
15540 "%1\r\n$4\r\neast\r\n,1\r\n"
15541 );
15542 }
15543
15544 #[test]
15546 fn an_attribute_is_bytes_and_an_empty_one_takes_it_off() {
15547 let mut f = Fixture::new();
15548 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15549 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
15550 assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]), ":1\r\n");
15551 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$7\r\n{\"k\":1}\r\n");
15552 assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b"not json"]), ":1\r\n");
15555 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$8\r\nnot json\r\n");
15556 assert_eq!(f.run(&[b"VSETATTR", b"v", b"east", b""]), ":1\r\n");
15558 assert_eq!(f.run(&[b"VGETATTR", b"v", b"east"]), "$-1\r\n");
15559 assert_eq!(f.run(&[b"VSETATTR", b"v", b"nobody", b"{}"]), ":0\r\n");
15562 assert_eq!(f.run(&[b"VSETATTR", b"nokey", b"east", b"{}"]), ":0\r\n");
15563 assert_eq!(f.run(&[b"EXISTS", b"nokey"]), ":0\r\n");
15564 assert_eq!(f.run(&[b"VGETATTR", b"v", b"nobody"]), "$-1\r\n");
15567 assert_eq!(f.run(&[b"VISMEMBER", b"v", b"east"]), ":1\r\n");
15568 assert_eq!(f.run(&[b"VISMEMBER", b"v", b"nobody"]), ":0\r\n");
15569 assert_eq!(f.run(&[b"VISMEMBER", b"nokey", b"east"]), ":0\r\n");
15570
15571 f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
15573 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
15574 assert_eq!(
15575 f.run(&[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0", b"WITHATTRIBS"]),
15576 "*4\r\n$4\r\neast\r\n$7\r\n{\"k\":1}\r\n$5\r\nnorth\r\n$-1\r\n"
15577 );
15578 }
15579
15580 #[test]
15583 fn vrem_takes_the_attribute_with_it() {
15584 let mut f = Fixture::new();
15585 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15586 f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
15587 assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":1\r\n");
15588 assert_eq!(f.run(&[b"VREM", b"v", b"east"]), ":0\r\n");
15589 assert_eq!(f.run(&[b"VREM", b"nokey", b"east"]), ":0\r\n");
15590 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
15593
15594 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15597 f.run(&[b"VSETATTR", b"v", b"east", b"{\"k\":1}"]);
15598 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
15599 f.run(&[b"VREM", b"v", b"east"]);
15600 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"1", b"between"]);
15601 assert_eq!(f.run(&[b"VGETATTR", b"v", b"between"]), "$-1\r\n");
15602 }
15603
15604 #[test]
15607 fn vinfo_says_partition_first() {
15608 let mut f = Fixture::new();
15609 f.run(&[
15610 b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east", b"M", b"32",
15611 ]);
15612 f.run(&[b"VSETATTR", b"v", b"east", b"{}"]);
15613 let info = f.run(&[b"VINFO", b"v"]);
15614 assert!(info.starts_with("*24\r\n$10\r\nindex-type\r\n$9\r\npartition\r\n"));
15615 assert!(info.contains("$6\r\nhnsw-m\r\n:32\r\n"), "{info}");
15618 assert!(info.contains("$10\r\nvector-dim\r\n:2\r\n"), "{info}");
15619 assert!(info.contains("$16\r\nattributes-count\r\n:1\r\n"), "{info}");
15620 assert!(
15623 info.contains("$10\r\nquant-type\r\n$4\r\nint8\r\n"),
15624 "{info}"
15625 );
15626 let mut f = Fixture::new();
15627 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north", b"BIN"]);
15628 assert!(
15629 f.run(&[b"VINFO", b"v"])
15630 .contains("$10\r\nquant-type\r\n$3\r\nbin\r\n")
15631 );
15632 assert_eq!(f.run(&[b"VINFO", b"nokey"]), "$-1\r\n");
15633 }
15634
15635 fn named() -> Fixture {
15637 let mut f = Fixture::new();
15638 for (i, name) in ["alpha", "beta", "gamma", "delta", "epsilon"]
15639 .iter()
15640 .enumerate()
15641 {
15642 let x = (i + 1).to_string();
15643 f.run(&[
15644 b"VADD",
15645 b"r",
15646 b"VALUES",
15647 b"2",
15648 x.as_bytes(),
15649 b"1",
15650 name.as_bytes(),
15651 ]);
15652 }
15653 f
15654 }
15655
15656 #[test]
15659 fn vrange_walks_the_names_and_not_the_vectors() {
15660 let mut f = named();
15661 assert_eq!(
15662 f.run(&[b"VRANGE", b"r", b"-", b"+"]),
15663 "*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"
15664 );
15665 assert_eq!(
15666 f.run(&[b"VRANGE", b"r", b"[a", b"[d"]),
15667 "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n",
15668 "the high end is a name and not a prefix, so delta is past it"
15669 );
15670 assert_eq!(
15671 f.run(&[b"VRANGE", b"r", b"(alpha", b"(gamma"]),
15672 "*3\r\n$4\r\nbeta\r\n$5\r\ndelta\r\n$7\r\nepsilon\r\n"
15673 );
15674 assert_eq!(
15675 f.run(&[b"VRANGE", b"r", b"[beta", b"[beta"]),
15676 "*1\r\n$4\r\nbeta\r\n"
15677 );
15678 assert_eq!(f.run(&[b"VRANGE", b"r", b"[z", b"+"]), "*0\r\n");
15679 f.run(&[b"VADD", b"r", b"VALUES", b"2", b"1", b"1", b"Beta"]);
15682 assert_eq!(
15683 f.run(&[b"VRANGE", b"r", b"-", b"[beta"]),
15684 "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
15685 );
15686 assert_eq!(f.run(&[b"VRANGE", b"nokey", b"-", b"+"]), "*0\r\n");
15687 }
15688
15689 #[test]
15692 fn a_vrange_count_of_zero_asks_for_nothing() {
15693 let mut f = named();
15694 assert_eq!(
15695 f.run(&[b"VRANGE", b"r", b"-", b"+", b"2"]),
15696 "*2\r\n$5\r\nalpha\r\n$4\r\nbeta\r\n"
15697 );
15698 assert_eq!(f.run(&[b"VRANGE", b"r", b"-", b"+", b"0"]), "*0\r\n");
15699 assert!(
15700 f.run(&[b"VRANGE", b"r", b"-", b"+", b"-1"])
15701 .starts_with("*5\r\n"),
15702 "a negative count is no limit at all"
15703 );
15704 }
15705
15706 #[test]
15709 fn vrange_says_which_end_it_could_not_read() {
15710 let mut f = named();
15711 assert_eq!(
15712 f.run(&[b"VRANGE", b"r", b"x", b"y"]),
15713 "-ERR invalid start range format\r\n"
15714 );
15715 assert_eq!(
15716 f.run(&[b"VRANGE", b"r", b"+", b"x"]),
15717 "-ERR invalid end range format\r\n",
15718 "the high end is spelled wrong, which is worth saying before the \
15719 low end being on the wrong side"
15720 );
15721 assert_eq!(
15722 f.run(&[b"VRANGE", b"r", b"+", b"-"]),
15723 "-ERR '-' can only be used as first argument, '+' only as second\r\n"
15724 );
15725 assert_eq!(
15728 f.run(&[b"VRANGE", b"r", b"[", b"+"]),
15729 "-ERR invalid start range format\r\n"
15730 );
15731 assert_eq!(
15732 f.run(&[b"VRANGE", b"r", b"x", b"+", b"z"]),
15733 "-ERR invalid COUNT value\r\n"
15734 );
15735 assert_eq!(
15736 f.run(&[b"VRANGE", b"r", b"-", b"+", b"2", b"extra"]),
15737 "-ERR wrong number of arguments for 'VRANGE' command\r\n"
15738 );
15739 f.run(&[b"SET", b"s", b"x"]);
15740 assert!(
15741 f.run(&[b"VRANGE", b"s", b"-", b"+"])
15742 .starts_with("-WRONGTYPE")
15743 );
15744 }
15745
15746 #[test]
15749 fn reduce_is_refused_and_not_ignored() {
15750 let mut f = Fixture::new();
15751 let reduce = f.run(&[
15752 b"VADD", b"v", b"REDUCE", b"1", b"VALUES", b"2", b"1", b"0", b"east",
15753 ]);
15754 assert!(
15755 reduce.starts_with("-ERR REDUCE is not supported."),
15756 "{reduce}"
15757 );
15758 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
15759 }
15760
15761 #[test]
15764 fn vsim_filter_reads_the_attributes() {
15765 let mut f = Fixture::new();
15766 for (name, x, y, attr) in [
15767 ("a", "1", "0", r#"{"lang":"en","year":1999}"#),
15768 ("b", "9", "1", r#"{"lang":"fr","year":2005}"#),
15769 ("c", "8", "2", r#"{"lang":"en","year":1970}"#),
15770 ("d", "7", "3", r#"{"lang":"en","year":2020}"#),
15771 ] {
15772 f.run(&[
15773 b"VADD",
15774 b"v",
15775 b"VALUES",
15776 b"2",
15777 x.as_bytes(),
15778 y.as_bytes(),
15779 name.as_bytes(),
15780 b"SETATTR",
15781 attr.as_bytes(),
15782 ]);
15783 }
15784 assert_eq!(
15787 f.run(&[
15788 b"VSIM",
15789 b"v",
15790 b"VALUES",
15791 b"2",
15792 b"9",
15793 b"1",
15794 b"COUNT",
15795 b"2",
15796 b"FILTER",
15797 b".lang == \"en\"",
15798 ]),
15799 "*2\r\n$1\r\na\r\n$1\r\nc\r\n"
15800 );
15801 assert_eq!(
15804 f.run(&[
15805 b"VSIM",
15806 b"v",
15807 b"VALUES",
15808 b"2",
15809 b"9",
15810 b"1",
15811 b"FILTER",
15812 b".lang == 'en' and .year > 1980",
15813 ]),
15814 "*2\r\n$1\r\na\r\n$1\r\nd\r\n"
15815 );
15816 assert_eq!(
15818 f.run(&[
15819 b"VSIM",
15820 b"v",
15821 b"VALUES",
15822 b"2",
15823 b"9",
15824 b"1",
15825 b"FILTER",
15826 b".lang in ['fr', 'de']",
15827 ]),
15828 "*1\r\n$1\r\nb\r\n"
15829 );
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"FILTER",
15839 b".rating > 3"
15840 ]),
15841 "*0\r\n"
15842 );
15843 assert_eq!(
15846 f.run(&[
15847 b"VSIM",
15848 b"v",
15849 b"VALUES",
15850 b"2",
15851 b"9",
15852 b"1",
15853 b"TRUTH",
15854 b"FILTER",
15855 b".year < 1980",
15856 ]),
15857 "*1\r\n$1\r\nc\r\n"
15858 );
15859 f.run(&[b"VSETATTR", b"v", b"b", r#"{"lang":"en"}"#.as_bytes()]);
15862 assert_eq!(
15863 f.run(&[
15864 b"VSIM",
15865 b"v",
15866 b"VALUES",
15867 b"2",
15868 b"9",
15869 b"1",
15870 b"COUNT",
15871 b"1",
15872 b"FILTER",
15873 b".lang == \"en\"",
15874 ]),
15875 "*1\r\n$1\r\nb\r\n"
15876 );
15877 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"9", b"2", b"b"]);
15880 assert_eq!(
15881 f.run(&[
15882 b"VSIM",
15883 b"v",
15884 b"VALUES",
15885 b"2",
15886 b"9",
15887 b"1",
15888 b"COUNT",
15889 b"1",
15890 b"FILTER",
15891 b".lang == \"en\"",
15892 ]),
15893 "*1\r\n$1\r\nb\r\n"
15894 );
15895
15896 let bad = f.run(&[b"VSIM", b"nokey", b"ELE", b"e", b"FILTER", b".k =="]);
15899 assert_eq!(bad, "-ERR invalid FILTER expression\r\n");
15900 assert_eq!(
15901 f.run(&[b"VSIM", b"v", b"ELE", b"a", b"FILTER", b"junk"]),
15902 "-ERR invalid FILTER expression\r\n"
15903 );
15904 assert_eq!(
15907 f.run(&[
15908 b"VSIM",
15909 b"v",
15910 b"VALUES",
15911 b"2",
15912 b"9",
15913 b"1",
15914 b"COUNT",
15915 b"1",
15916 b"FILTER-EF",
15917 b"500",
15918 b"FILTER",
15919 b".lang == 'en'",
15920 ]),
15921 "*1\r\n$1\r\nb\r\n"
15922 );
15923 assert_eq!(
15924 f.run(&[
15925 b"VSIM",
15926 b"v",
15927 b"VALUES",
15928 b"2",
15929 b"9",
15930 b"1",
15931 b"COUNT",
15932 b"1",
15933 b"FILTER-EF",
15934 b"0"
15935 ]),
15936 "*1\r\n$1\r\nb\r\n"
15937 );
15938 assert_eq!(
15939 f.run(&[
15940 b"VSIM",
15941 b"v",
15942 b"VALUES",
15943 b"2",
15944 b"9",
15945 b"1",
15946 b"FILTER-EF",
15947 b"lots"
15948 ]),
15949 "-ERR EF must be a positive integer\r\n"
15950 );
15951 }
15952
15953 #[test]
15956 fn the_keyspace_sees_a_vector_set_key_like_any_other() {
15957 let mut f = Fixture::new();
15958 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15959 assert_eq!(f.run(&[b"TYPE", b"v"]), "+vectorset\r\n");
15960 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":1\r\n");
15961 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"v"]), "$6\r\nrabitq\r\n");
15962 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\nv\r\n");
15963 assert_eq!(f.run(&[b"DBSIZE"]), ":1\r\n");
15964 assert_eq!(f.run(&[b"EXPIRE", b"v", b"100"]), ":1\r\n");
15965 assert_eq!(f.run(&[b"TTL", b"v"]), ":100\r\n");
15966 assert_eq!(f.run(&[b"PERSIST", b"v"]), ":1\r\n");
15967 assert_eq!(f.run(&[b"DEL", b"v"]), ":1\r\n");
15968 assert_eq!(f.run(&[b"EXISTS", b"v"]), ":0\r\n");
15969
15970 f.run(&[b"SET", b"s", b"1"]);
15972 assert_eq!(
15973 f.run(&[b"VADD", b"s", b"VALUES", b"2", b"1", b"0", b"east"]),
15974 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15975 );
15976 assert_eq!(
15977 f.run(&[b"VCARD", b"s"]),
15978 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15979 );
15980 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
15981 assert_eq!(
15982 f.run(&[b"GET", b"v"]),
15983 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15984 );
15985 f.run(&[b"G.NADD", b"social", b"ada"]);
15989 assert_eq!(
15990 f.run(&[b"VCARD", b"social"]),
15991 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15992 );
15993 assert_eq!(
15994 f.run(&[b"G.NGET", b"v", b"ada"]),
15995 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
15996 );
15997 }
15998
15999 #[test]
16002 fn vrandmember_has_the_two_shapes_srandmember_has() {
16003 let mut f = Fixture::new();
16004 for (i, name) in [&b"a"[..], b"b", b"c"].iter().enumerate() {
16005 let x = (i + 1).to_string();
16006 f.run(&[b"VADD", b"v", b"VALUES", b"2", x.as_bytes(), b"1", name]);
16007 }
16008 let one = f.run(&[b"VRANDMEMBER", b"v"]);
16010 assert!(one.starts_with("$1\r\n"), "{one}");
16011 let mut all = f.run(&[b"VRANDMEMBER", b"v", b"9"]);
16013 assert!(all.starts_with("*3\r\n"), "{all}");
16014 for name in ["a", "b", "c"] {
16015 assert!(all.contains(name), "{all} is missing {name}");
16016 }
16017 all = f.run(&[b"VRANDMEMBER", b"v", b"2"]);
16018 assert!(all.starts_with("*2\r\n"), "{all}");
16019 let many = f.run(&[b"VRANDMEMBER", b"v", b"-5"]);
16021 assert!(many.starts_with("*5\r\n"), "{many}");
16022 assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey"]), "$-1\r\n");
16024 assert_eq!(f.run(&[b"VRANDMEMBER", b"nokey", b"3"]), "*0\r\n");
16025 }
16026
16027 #[test]
16030 fn vlinks_reports_one_layer_of_partition_neighbours() {
16031 let mut f = Fixture::new();
16032 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"east"]);
16033 f.run(&[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"north"]);
16034 assert_eq!(
16037 f.run(&[b"VLINKS", b"v", b"east"]),
16038 "*1\r\n*1\r\n$5\r\nnorth\r\n"
16039 );
16040 assert_eq!(
16041 f.run(&[b"VLINKS", b"v", b"east", b"WITHSCORES"]),
16042 "*1\r\n*2\r\n$5\r\nnorth\r\n$3\r\n0.5\r\n"
16043 );
16044 assert_eq!(f.run(&[b"VLINKS", b"v", b"nobody"]), "*-1\r\n");
16045 assert_eq!(f.run(&[b"VLINKS", b"nokey", b"east"]), "*-1\r\n");
16046 }
16047
16048 #[test]
16051 fn fp32_and_values_are_the_same_vector() {
16052 let mut f = Fixture::new();
16053 let mut blob = Vec::new();
16054 for x in [3.0f32, 4.0] {
16055 blob.extend_from_slice(&x.to_le_bytes());
16056 }
16057 assert_eq!(f.run(&[b"VADD", b"v", b"FP32", &blob, b"a"]), ":1\r\n");
16058 assert_eq!(f.run(&[b"VDIM", b"v"]), ":2\r\n");
16059 assert_eq!(
16060 f.run(&[b"VEMB", b"v", b"a"]),
16061 "*2\r\n$17\r\n2.992125988006592\r\n$1\r\n4\r\n"
16062 );
16063 assert_eq!(
16069 f.run(&[b"VEMB", b"v", b"a", b"RAW"]),
16070 "*4\r\n+int8\r\n$2\r\n_\x7f\r\n$1\r\n5\r\n$17\r\n0.800000011920929\r\n"
16071 );
16072 assert_eq!(
16074 f.run(&[b"VADD", b"w", b"FP32", b"abc", b"a"]),
16075 "-ERR invalid vector specification\r\n"
16076 );
16077 assert_eq!(
16079 f.run(&[b"VADD", b"w", b"VALUES", b"4", b"1", b"0", b"a"]),
16080 "-ERR syntax error\r\n"
16081 );
16082 assert_eq!(f.run(&[b"EXISTS", b"w"]), ":0\r\n");
16083 }
16084
16085 #[test]
16090 fn bf_add_makes_the_filter_and_says_whether_it_was_new() {
16091 let mut f = Fixture::new();
16092 assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":1\r\n");
16093 assert_eq!(f.run(&[b"BF.ADD", b"b", b"hello"]), ":0\r\n");
16094 assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"hello"]), ":1\r\n");
16095 assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"never"]), ":0\r\n");
16096 assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":1\r\n");
16097 assert_eq!(
16100 f.run(&[b"BF.INFO", b"b"]),
16101 "*10\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
16102 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
16103 +Expansion rate\r\n:2\r\n"
16104 );
16105 assert_eq!(f.run(&[b"TYPE", b"b"]), "+MBbloom--\r\n");
16106 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"b"]), "$3\r\nraw\r\n");
16107 assert_eq!(f.run(&[b"BF.CARD", b"gone"]), ":0\r\n");
16110 assert_eq!(f.run(&[b"BF.INFO", b"gone"]), "-ERR not found\r\n");
16111 }
16112
16113 #[test]
16119 fn a_wrong_type_is_a_miss_to_the_two_that_only_read_bits() {
16120 let mut f = Fixture::new();
16121 f.run(&[b"SET", b"s", b"text"]);
16122 assert_eq!(f.run(&[b"BF.EXISTS", b"s", b"x"]), ":0\r\n");
16123 assert_eq!(f.run(&[b"BF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
16124 for cmd in [
16125 vec![&b"BF.ADD"[..], b"s", b"x"],
16126 vec![&b"BF.MADD"[..], b"s", b"x"],
16127 vec![&b"BF.CARD"[..], b"s"],
16128 vec![&b"BF.INFO"[..], b"s"],
16129 vec![&b"BF.DEBUG"[..], b"s"],
16130 vec![&b"BF.SCANDUMP"[..], b"s", b"0"],
16131 ] {
16132 let name = String::from_utf8_lossy(cmd[0]).into_owned();
16133 assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
16134 }
16135 assert_eq!(
16138 f.run(&[b"BF.RESERVE", b"s", b"abc", b"10"]),
16139 "-ERR bad error rate\r\n"
16140 );
16141 assert!(
16142 f.run(&[b"BF.RESERVE", b"s", b"0.01", b"10"])
16143 .starts_with("-WRONGTYPE")
16144 );
16145 }
16146
16147 #[test]
16150 fn a_full_filter_grows_a_link_and_a_fixed_one_says_no() {
16151 let mut f = Fixture::new();
16152 assert_eq!(f.run(&[b"BF.RESERVE", b"g", b"0.01", b"10"]), "+OK\r\n");
16153 for i in 0..10u32 {
16154 assert_eq!(
16155 f.run(&[b"BF.ADD", b"g", i.to_string().as_bytes()]),
16156 ":1\r\n"
16157 );
16158 }
16159 assert_eq!(f.run(&[b"BF.INFO", b"g", b"FILTERS"]), "*1\r\n:1\r\n");
16160 assert_eq!(f.run(&[b"BF.ADD", b"g", b"11"]), ":1\r\n");
16161 assert_eq!(f.run(&[b"BF.INFO", b"g", b"filters"]), "*1\r\n:2\r\n");
16162 assert_eq!(f.run(&[b"BF.INFO", b"g", b"CAPACITY"]), "*1\r\n:30\r\n");
16165 assert_eq!(
16166 f.run(&[b"BF.DEBUG", b"g"]),
16167 "*3\r\n$7\r\nsize:11\r\n\
16168 $71\r\nbytes:16 bits:128 hashes:8 hashwidth:64 capacity:10 size:10 ratio:0.005\r\n\
16169 $71\r\nbytes:32 bits:256 hashes:9 hashwidth:64 capacity:20 size:1 ratio:0.0025\r\n"
16170 );
16171
16172 assert_eq!(
16174 f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]),
16175 "+OK\r\n"
16176 );
16177 assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":1\r\n");
16178 assert_eq!(f.run(&[b"BF.ADD", b"n", b"b"]), ":1\r\n");
16179 assert_eq!(
16180 f.run(&[b"BF.ADD", b"n", b"c"]),
16181 "-ERR non scaling filter is full\r\n"
16182 );
16183 assert_eq!(f.run(&[b"BF.ADD", b"n", b"a"]), ":0\r\n");
16186 assert_eq!(f.run(&[b"BF.INFO", b"n", b"EXPANSION"]), "*1\r\n$-1\r\n");
16189 f.run(&[b"BF.RESERVE", b"z", b"0.01", b"2", b"EXPANSION", b"0"]);
16190 assert_eq!(f.run(&[b"BF.INFO", b"z", b"EXPANSION"]), "*1\r\n$-1\r\n");
16191 assert_eq!(
16194 f.run(&[
16195 b"BF.RESERVE",
16196 b"q",
16197 b"0.01",
16198 b"2",
16199 b"NONSCALING",
16200 b"EXPANSION",
16201 b"2"
16202 ]),
16203 "-Nonscaling filters cannot expand\r\n"
16204 );
16205 }
16206
16207 #[test]
16210 fn madd_truncates_its_reply_at_the_item_that_did_not_fit() {
16211 let mut f = Fixture::new();
16212 f.run(&[b"BF.RESERVE", b"n", b"0.01", b"2", b"NONSCALING"]);
16213 assert_eq!(
16214 f.run(&[b"BF.MADD", b"n", b"a", b"b", b"c", b"d"]),
16215 "*3\r\n:1\r\n:1\r\n-ERR non scaling filter is full\r\n"
16216 );
16217 assert_eq!(
16218 f.run(&[b"BF.MEXISTS", b"n", b"a", b"c"]),
16219 "*2\r\n:1\r\n:0\r\n"
16220 );
16221 }
16222
16223 #[test]
16226 fn insert_is_a_reserve_and_a_madd_with_different_errors() {
16227 let mut f = Fixture::new();
16228 assert_eq!(
16229 f.run(&[
16230 b"BF.INSERT",
16231 b"i",
16232 b"CAPACITY",
16233 b"50",
16234 b"ERROR",
16235 b"0.001",
16236 b"ITEMS",
16237 b"a",
16238 b"b"
16239 ]),
16240 "*2\r\n:1\r\n:1\r\n"
16241 );
16242 assert_eq!(f.run(&[b"BF.INFO", b"i", b"CAPACITY"]), "*1\r\n:50\r\n");
16243 assert_eq!(
16245 f.run(&[b"BF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
16246 "-ERR not found\r\n"
16247 );
16248 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
16249 assert_eq!(
16252 f.run(&[b"BF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
16253 "-Bad capacity\r\n"
16254 );
16255 assert_eq!(
16256 f.run(&[b"BF.INSERT", b"i", b"ERROR", b"2", b"ITEMS", b"a"]),
16257 "-Bad error rate\r\n"
16258 );
16259 assert_eq!(
16260 f.run(&[b"BF.INSERT", b"i", b"EXPANSION", b"99999", b"ITEMS", b"a"]),
16261 "-Bad expansion\r\n"
16262 );
16263 assert_eq!(
16268 f.run(&[b"BF.INSERT", b"ns", b"NOSUCHTHING", b"ITEMS", b"a"]),
16269 "*1\r\n:1\r\n"
16270 );
16271 assert_eq!(f.run(&[b"BF.INFO", b"ns", b"EXPANSION"]), "*1\r\n$-1\r\n");
16272 assert_eq!(
16276 f.run(&[b"BF.INSERT", b"e1", b"E", b"4", b"ITEMS", b"a"]),
16277 "*1\r\n:1\r\n"
16278 );
16279 assert_eq!(f.run(&[b"BF.INFO", b"e1", b"EXPANSION"]), "*1\r\n:4\r\n");
16280 assert_eq!(
16281 f.run(&[b"BF.INSERT", b"e2", b"ER", b"0.5", b"ITEMS", b"a"]),
16282 "*1\r\n:1\r\n"
16283 );
16284 assert_eq!(
16285 f.run(&[b"BF.INSERT", b"gone", b"NOC", b"ITEMS", b"a"]),
16286 "-ERR not found\r\n"
16287 );
16288 assert_eq!(
16290 f.run(&[b"BF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
16291 "-Unknown argument received\r\n"
16292 );
16293 assert_eq!(
16295 f.run(&[b"BF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
16296 "*1\r\n:1\r\n"
16297 );
16298 assert!(
16300 f.run(&[b"BF.INSERT", b"i", b"ITEMS"])
16301 .contains("wrong number of arguments")
16302 );
16303 }
16304
16305 #[test]
16308 fn a_dump_replays_into_a_filter_that_answers_the_same() {
16309 let mut f = Fixture::new();
16310 f.run(&[b"BF.RESERVE", b"src", b"0.01", b"10"]);
16311 for i in 0..25u32 {
16312 f.run(&[b"BF.ADD", b"src", i.to_string().as_bytes()]);
16313 }
16314 assert_eq!(f.run(&[b"BF.INFO", b"src", b"FILTERS"]), "*1\r\n:2\r\n");
16315
16316 let mut iter = b"0".to_vec();
16319 let mut chunks = 0;
16320 loop {
16321 let raw = f.raw(&[b"BF.SCANDUMP", b"src", &iter]);
16322 let text = String::from_utf8_lossy(&raw).into_owned();
16323 let next = text
16324 .split("\r\n")
16325 .nth(1)
16326 .and_then(|n| n.strip_prefix(':'))
16327 .expect("a two element reply of an iterator and a chunk")
16328 .to_owned();
16329 let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
16330 let data = &body[body
16331 .windows(2)
16332 .position(|w| w == b"\r\n")
16333 .expect("a length line")
16334 + 2..body.len() - 2];
16335 if next == "0" {
16336 assert!(data.is_empty(), "the last chunk is empty");
16337 break;
16338 }
16339 let put = f.run(&[b"BF.LOADCHUNK", b"dst", next.as_bytes(), data]);
16340 assert_eq!(put, "+OK\r\n", "loading chunk {chunks}");
16341 iter = next.into_bytes();
16342 chunks += 1;
16343 }
16344 assert_eq!(chunks, 3, "a header and one chunk per link");
16345
16346 assert_eq!(f.run(&[b"BF.INFO", b"dst"]), f.run(&[b"BF.INFO", b"src"]));
16347 assert_eq!(f.run(&[b"BF.DEBUG", b"dst"]), f.run(&[b"BF.DEBUG", b"src"]));
16348 for i in 0..25u32 {
16349 assert_eq!(
16350 f.run(&[b"BF.EXISTS", b"dst", i.to_string().as_bytes()]),
16351 ":1\r\n"
16352 );
16353 }
16354
16355 assert_eq!(
16358 f.run(&[b"BF.LOADCHUNK", b"dst", b"1", b"anything"]),
16359 "-ERR received bad data\r\n"
16360 );
16361 assert_eq!(
16362 f.run(&[b"BF.LOADCHUNK", b"fresh", b"1", b"anything"]),
16363 "-ERR received bad data\r\n"
16364 );
16365 assert_eq!(
16367 f.run(&[b"BF.LOADCHUNK", b"dst", b"99999", b"x"]),
16368 "-ERR invalid offset - no link found\r\n"
16369 );
16370 assert_eq!(
16371 f.run(&[b"BF.LOADCHUNK", b"dst", b"nope", b"x"]),
16372 "-ERR Second argument must be numeric\r\n"
16373 );
16374 assert_eq!(
16377 f.run(&[b"BF.SCANDUMP", b"src", b"nope"]),
16378 "-Second argument must be numeric\r\n"
16379 );
16380 }
16381
16382 #[test]
16385 fn reserve_reads_its_numbers_the_way_string2ll_does() {
16386 let mut f = Fixture::new();
16387 for (args, want) in [
16388 (vec![&b"abc"[..], b"10"], "-ERR bad error rate\r\n"),
16389 (vec![&b"nan"[..], b"10"], "-ERR bad error rate\r\n"),
16390 (
16391 vec![&b"0"[..], b"10"],
16392 "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
16393 ),
16394 (
16395 vec![&b"1"[..], b"10"],
16396 "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
16397 ),
16398 (
16399 vec![&b"inf"[..], b"10"],
16400 "-ERR error rate must be in the range (0.000000, 1.000000)\r\n",
16401 ),
16402 (vec![&b"0.01"[..], b"+10"], "-ERR bad capacity\r\n"),
16403 (vec![&b"0.01"[..], b"1e2"], "-ERR bad capacity\r\n"),
16404 (vec![&b"0.01"[..], b"007"], "-ERR bad capacity\r\n"),
16405 (
16406 vec![&b"0.01"[..], b"0"],
16407 "-ERR capacity must be in the range [1, 1073741824]\r\n",
16408 ),
16409 (
16410 vec![&b"0.01"[..], b"1073741825"],
16411 "-ERR capacity must be in the range [1, 1073741824]\r\n",
16412 ),
16413 ] {
16414 let mut cmd = vec![&b"BF.RESERVE"[..], b"k"];
16415 cmd.extend(args.iter().copied());
16416 assert_eq!(f.run(&cmd), want, "{}", String::from_utf8_lossy(args[0]));
16417 }
16418 assert_eq!(
16419 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION"]),
16420 "-ERR no expansion\r\n"
16421 );
16422 assert_eq!(
16423 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"abc"]),
16424 "-ERR bad expansion\r\n"
16425 );
16426 assert_eq!(
16427 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"EXPANSION", b"32769"]),
16428 "-ERR expansion must be in the range [0, 32768]\r\n"
16429 );
16430 assert_eq!(
16432 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10", b"junk"]),
16433 "+OK\r\n"
16434 );
16435 assert_eq!(
16436 f.run(&[b"BF.RESERVE", b"k", b"0.01", b"10"]),
16437 "-ERR item exists\r\n"
16438 );
16439 assert_eq!(
16440 f.run(&[b"BF.INFO", b"k", b"nosuchfield"]),
16441 "-Invalid information value\r\n"
16442 );
16443 assert!(
16444 f.run(&[b"BF.INFO", b"k", b"CAPACITY", b"SIZE"])
16445 .contains("wrong number of arguments")
16446 );
16447 }
16448
16449 #[test]
16451 fn the_bloom_family_answers_in_resp3_spelling_too() {
16452 let mut f = Fixture::new();
16453 f.out.set_proto(Proto::Resp3);
16454 assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#t\r\n");
16455 assert_eq!(f.run(&[b"BF.ADD", b"b", b"a"]), "#f\r\n");
16456 assert_eq!(f.run(&[b"BF.MADD", b"b", b"a", b"c"]), "*2\r\n#f\r\n#t\r\n");
16457 assert_eq!(f.run(&[b"BF.EXISTS", b"b", b"a"]), "#t\r\n");
16458 assert_eq!(
16459 f.run(&[b"BF.MEXISTS", b"b", b"a", b"z"]),
16460 "*2\r\n#t\r\n#f\r\n"
16461 );
16462 assert_eq!(f.run(&[b"BF.CARD", b"b"]), ":2\r\n");
16464 assert_eq!(
16465 f.run(&[b"BF.INFO", b"b"]),
16466 "%5\r\n+Capacity\r\n:100\r\n+Size\r\n:240\r\n\
16467 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:2\r\n\
16468 +Expansion rate\r\n:2\r\n"
16469 );
16470 assert_eq!(
16473 f.run(&[b"BF.INFO", b"b", b"CAPACITY"]),
16474 "%1\r\n+Capacity\r\n:100\r\n"
16475 );
16476 }
16477
16478 fn cf_header(
16487 items: u64,
16488 buckets: u64,
16489 deletes: u64,
16490 filters: u64,
16491 geometry: [u16; 3],
16492 ) -> Vec<u8> {
16493 let mut out = Vec::with_capacity(38);
16494 for n in [items, buckets, deletes, filters] {
16495 out.extend_from_slice(&n.to_le_bytes());
16496 }
16497 for n in geometry {
16498 out.extend_from_slice(&n.to_le_bytes());
16499 }
16500 out
16501 }
16502
16503 #[test]
16507 fn cf_add_makes_the_filter_and_counts_the_copies() {
16508 let mut f = Fixture::new();
16509 assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
16510 assert_eq!(f.run(&[b"CF.ADD", b"d", b"hello"]), ":1\r\n");
16511 assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":2\r\n");
16512 assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"hello"]), ":0\r\n");
16515 assert_eq!(f.run(&[b"CF.ADDNX", b"d", b"other"]), ":1\r\n");
16516 assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"hello"]), ":1\r\n");
16517 assert_eq!(f.run(&[b"CF.EXISTS", b"d", b"no"]), ":0\r\n");
16518 assert_eq!(
16519 f.run(&[b"CF.MEXISTS", b"d", b"hello", b"no"]),
16520 "*2\r\n:1\r\n:0\r\n"
16521 );
16522 assert_eq!(
16525 f.run(&[b"CF.INFO", b"d"]),
16526 "*16\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
16527 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:3\r\n\
16528 +Number of items deleted\r\n:0\r\n+Bucket size\r\n:2\r\n\
16529 +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
16530 );
16531 assert_eq!(
16532 f.run(&[b"CF.DEBUG", b"d"]),
16533 "$79\r\nbktsize:2 buckets:512 items:3 deletes:0 filters:1 \
16534 max_iterations:20 expansion:1\r\n"
16535 );
16536 assert_eq!(f.run(&[b"TYPE", b"d"]), "+MBbloomCF\r\n");
16537 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
16538
16539 assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
16541 assert_eq!(f.run(&[b"CF.COUNT", b"d", b"hello"]), ":1\r\n");
16542 assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":1\r\n");
16543 assert_eq!(f.run(&[b"CF.DEL", b"d", b"hello"]), ":0\r\n");
16544 assert_eq!(f.run(&[b"CF.COMPACT", b"d"]), "+OK\r\n");
16545
16546 assert_eq!(f.run(&[b"CF.INFO", b"gone"]), "-ERR not found\r\n");
16549 assert_eq!(f.run(&[b"CF.DEL", b"gone", b"x"]), "-Not found\r\n");
16550 assert_eq!(
16551 f.run(&[b"CF.COMPACT", b"gone"]),
16552 "-Cuckoo filter was not found\r\n"
16553 );
16554 assert_eq!(f.run(&[b"CF.EXISTS", b"gone", b"x"]), ":0\r\n");
16555 assert!(
16559 f.run(&[b"CF.COMPACT", b"a", b"b"])
16560 .contains("wrong number of arguments")
16561 );
16562 }
16563
16564 #[test]
16567 fn a_wrong_type_is_a_miss_to_the_four_that_only_read_fingerprints() {
16568 let mut f = Fixture::new();
16569 f.run(&[b"SET", b"s", b"text"]);
16570 assert_eq!(f.run(&[b"CF.EXISTS", b"s", b"x"]), ":0\r\n");
16571 assert_eq!(f.run(&[b"CF.MEXISTS", b"s", b"x"]), "*1\r\n:0\r\n");
16572 assert_eq!(f.run(&[b"CF.COUNT", b"s", b"x"]), ":0\r\n");
16573 assert_eq!(f.run(&[b"CF.DEL", b"s", b"x"]), "-Not found\r\n");
16577 assert_eq!(
16578 f.run(&[b"CF.COMPACT", b"s"]),
16579 "-Cuckoo filter was not found\r\n"
16580 );
16581 for cmd in [
16582 vec![&b"CF.ADD"[..], b"s", b"x"],
16583 vec![&b"CF.ADDNX"[..], b"s", b"x"],
16584 vec![&b"CF.INSERT"[..], b"s", b"ITEMS", b"x"],
16585 vec![&b"CF.INSERTNX"[..], b"s", b"ITEMS", b"x"],
16586 vec![&b"CF.INFO"[..], b"s"],
16587 vec![&b"CF.DEBUG"[..], b"s"],
16588 vec![&b"CF.SCANDUMP"[..], b"s", b"0"],
16589 vec![&b"CF.LOADCHUNK"[..], b"s", b"2", b"x"],
16590 vec![&b"CF.RESERVE"[..], b"s", b"64"],
16591 ] {
16592 let name = String::from_utf8_lossy(cmd[0]).into_owned();
16593 assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{name}");
16594 }
16595 }
16596
16597 #[test]
16600 fn reserve_complains_about_its_options_in_the_order_it_looks_for_them() {
16601 let mut f = Fixture::new();
16602 assert_eq!(
16603 f.run(&[
16604 b"CF.RESERVE",
16605 b"r",
16606 b"64",
16607 b"BUCKETSIZE",
16608 b"1",
16609 b"MAXITERATIONS",
16610 b"7",
16611 b"EXPANSION",
16612 b"4"
16613 ]),
16614 "+OK\r\n"
16615 );
16616 assert_eq!(
16617 f.run(&[b"CF.DEBUG", b"r"]),
16618 "$77\r\nbktsize:1 buckets:64 items:0 deletes:0 filters:1 \
16619 max_iterations:7 expansion:4\r\n"
16620 );
16621 assert_eq!(f.run(&[b"CF.RESERVE", b"r", b"64"]), "-ERR item exists\r\n");
16622
16623 assert_eq!(f.run(&[b"CF.RESERVE", b"q", b"abc"]), "-Bad capacity\r\n");
16624 assert_eq!(
16625 f.run(&[b"CF.RESERVE", b"q", b"1"]),
16626 "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
16627 );
16628 assert_eq!(
16631 f.run(&[b"CF.RESERVE", b"q", b"7", b"BUCKETSIZE", b"4"]),
16632 "-Capacity must be in the range [2 * BUCKETSIZE, 1073741824]\r\n"
16633 );
16634 assert_eq!(
16635 f.run(&[b"CF.RESERVE", b"q", b"8", b"BUCKETSIZE", b"4"]),
16636 "+OK\r\n"
16637 );
16638
16639 assert_eq!(
16644 f.run(&[b"CF.RESERVE", b"q2", b"64", b"BUCKETSIZE", b"0"]),
16645 "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
16646 );
16647 assert_eq!(
16648 f.run(&[
16649 b"CF.RESERVE",
16650 b"q2",
16651 b"64",
16652 b"EXPANSION",
16653 b"xx",
16654 b"BUCKETSIZE",
16655 b"0"
16656 ]),
16657 "-BUCKETSIZE: value must be in the range [1, 255]\r\n"
16658 );
16659 assert_eq!(
16660 f.run(&[
16661 b"CF.RESERVE",
16662 b"q2",
16663 b"64",
16664 b"MAXITERATIONS",
16665 b"0",
16666 b"BUCKETSIZE",
16667 b"0"
16668 ]),
16669 "-MAXITERATIONS: value must be in the range [1, 65535]\r\n"
16670 );
16671 assert_eq!(
16675 f.run(&[
16676 b"CF.RESERVE",
16677 b"q3",
16678 b"64",
16679 b"BUCKETSIZE",
16680 b"1",
16681 b"BUCKETSIZE",
16682 b"0"
16683 ]),
16684 "+OK\r\n"
16685 );
16686 assert_eq!(
16689 f.run(&[b"CF.RESERVE", b"q4", b"64", b"NOSUCH", b"9"]),
16690 "+OK\r\n"
16691 );
16692 assert_eq!(
16693 f.run(&[b"CF.DEBUG", b"q4"]),
16694 "$78\r\nbktsize:2 buckets:32 items:0 deletes:0 filters:1 \
16695 max_iterations:20 expansion:1\r\n"
16696 );
16697 assert!(
16700 f.run(&[b"CF.RESERVE", b"q5", b"64", b"BUCKETSIZE"])
16701 .contains("wrong number of arguments")
16702 );
16703 }
16704
16705 #[test]
16708 fn insert_checks_every_occurrence_and_matches_on_the_first_letter() {
16709 let mut f = Fixture::new();
16710 assert_eq!(
16711 f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"64", b"ITEMS", b"a", b"b"]),
16712 "*2\r\n:1\r\n:1\r\n"
16713 );
16714 assert_eq!(
16715 f.run(&[b"CF.DEBUG", b"i"]),
16716 "$78\r\nbktsize:2 buckets:32 items:2 deletes:0 filters:1 \
16717 max_iterations:20 expansion:1\r\n"
16718 );
16719 assert_eq!(
16722 f.run(&[b"CF.INSERTNX", b"i", b"ITEMS", b"a", b"c"]),
16723 "*2\r\n:0\r\n:1\r\n"
16724 );
16725 assert_eq!(
16726 f.run(&[b"CF.INSERT", b"gone", b"NOCREATE", b"ITEMS", b"a"]),
16727 "-ERR not found\r\n"
16728 );
16729 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
16730
16731 assert_eq!(
16732 f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"abc", b"ITEMS", b"a"]),
16733 "-Bad capacity\r\n"
16734 );
16735 assert_eq!(
16738 f.run(&[b"CF.INSERT", b"i", b"CAPACITY", b"2", b"ITEMS", b"a"]),
16739 "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
16740 );
16741 assert_eq!(
16745 f.run(&[
16746 b"CF.INSERT",
16747 b"i",
16748 b"CAPACITY",
16749 b"8",
16750 b"CAPACITY",
16751 b"2",
16752 b"ITEMS",
16753 b"a"
16754 ]),
16755 "-Capacity must be in the range [cf-bucket-size * 2, 1073741824]\r\n"
16756 );
16757 assert_eq!(
16761 f.run(&[b"CF.INSERT", b"i", b"NOSUCH", b"ITEMS", b"a"]),
16762 "*1\r\n:1\r\n"
16763 );
16764 assert_eq!(
16765 f.run(&[b"CF.INSERT", b"i", b"ITEMSXYZ", b"a"]),
16766 "*1\r\n:1\r\n"
16767 );
16768 assert_eq!(
16769 f.run(&[b"CF.INSERT", b"i", b"ZZZ", b"ITEMS", b"a"]),
16770 "-Unknown argument received\r\n"
16771 );
16772 assert_eq!(
16774 f.run(&[b"CF.INSERT", b"i", b"ITEMS", b"NOCREATE"]),
16775 "*1\r\n:1\r\n"
16776 );
16777 assert!(
16779 f.run(&[b"CF.INSERT", b"i", b"ITEMS"])
16780 .contains("wrong number of arguments")
16781 );
16782 assert!(
16783 f.run(&[b"CF.INSERT", b"i", b"CAPACITY"])
16784 .contains("wrong number of arguments")
16785 );
16786 }
16787
16788 #[test]
16791 fn a_full_filter_and_one_that_ran_out_of_filters_answer_differently() {
16792 let mut f = Fixture::new();
16793 f.run(&[
16794 b"CF.RESERVE",
16795 b"s",
16796 b"4",
16797 b"BUCKETSIZE",
16798 b"1",
16799 b"EXPANSION",
16800 b"0",
16801 ]);
16802 for i in 0..4u32 {
16803 assert_eq!(
16804 f.run(&[b"CF.ADD", b"s", i.to_string().as_bytes()]),
16805 ":1\r\n"
16806 );
16807 }
16808 assert_eq!(f.run(&[b"CF.ADD", b"s", b"4"]), "-Filter is full\r\n");
16809 assert_eq!(f.run(&[b"CF.ADDNX", b"s", b"zz"]), "-Filter is full\r\n");
16810 assert_eq!(
16813 f.run(&[b"CF.INSERT", b"s", b"ITEMS", b"p", b"q"]),
16814 "*2\r\n:-1\r\n:-1\r\n"
16815 );
16816 assert_eq!(
16817 f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"0", b"q"]),
16818 "*2\r\n:0\r\n:-1\r\n"
16819 );
16820
16821 let full = cf_header(0, 4, 0, 32, [1, 20, 1]);
16827 assert_eq!(f.run(&[b"CF.LOADCHUNK", b"g", b"1", &full]), "+OK\r\n");
16828 assert_eq!(
16829 f.run(&[b"CF.ADD", b"g", b"q"]),
16830 "-Maximum expansions reached\r\n"
16831 );
16832 assert_eq!(
16833 f.run(&[b"CF.INFO", b"g"]),
16834 "*16\r\n+Size\r\n:680\r\n+Number of buckets\r\n:4\r\n\
16835 +Number of filters\r\n:32\r\n+Number of items inserted\r\n:0\r\n\
16836 +Number of items deleted\r\n:0\r\n+Bucket size\r\n:1\r\n\
16837 +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
16838 );
16839 }
16840
16841 #[test]
16845 fn a_cuckoo_dump_replays_into_a_filter_that_answers_the_same() {
16846 let mut f = Fixture::new();
16847 f.run(&[
16848 b"CF.RESERVE",
16849 b"src",
16850 b"8",
16851 b"BUCKETSIZE",
16852 b"2",
16853 b"EXPANSION",
16854 b"2",
16855 ]);
16856 for i in 0..40u32 {
16857 f.run(&[b"CF.ADD", b"src", i.to_string().as_bytes()]);
16858 }
16859 let mut pos = b"0".to_vec();
16863 let mut chunks = 0;
16864 loop {
16865 let raw = f.raw(&[b"CF.SCANDUMP", b"src", &pos]);
16866 let head = String::from_utf8_lossy(&raw[..raw.len().min(24)]).into_owned();
16867 let next = head
16868 .split("\r\n")
16869 .nth(1)
16870 .and_then(|n| n.strip_prefix(':'))
16871 .expect("a two element reply of a position and a chunk")
16872 .to_owned();
16873 if next == "0" {
16874 assert!(raw.ends_with(b"$-1\r\n"), "the walk ends on a nil");
16875 break;
16876 }
16877 let body = &raw[raw.iter().position(|&b| b == b'$').expect("a bulk chunk")..];
16878 let at = body
16879 .windows(2)
16880 .position(|w| w == b"\r\n")
16881 .expect("a length line")
16882 + 2;
16883 let data = &body[at..body.len() - 2];
16884 assert_eq!(
16885 f.run(&[b"CF.LOADCHUNK", b"dst", next.as_bytes(), data]),
16886 "+OK\r\n",
16887 "loading chunk {chunks}"
16888 );
16889 pos = next.into_bytes();
16890 chunks += 1;
16891 }
16892 assert!(chunks >= 2, "a header and at least one chunk");
16893
16894 assert_eq!(f.run(&[b"CF.INFO", b"dst"]), f.run(&[b"CF.INFO", b"src"]));
16895 assert_eq!(f.run(&[b"CF.DEBUG", b"dst"]), f.run(&[b"CF.DEBUG", b"src"]));
16896 for i in 0..40u32 {
16897 assert_eq!(
16898 f.run(&[b"CF.EXISTS", b"dst", i.to_string().as_bytes()]),
16899 ":1\r\n"
16900 );
16901 }
16902
16903 f.run(&[b"CF.RESERVE", b"empty", b"4", b"BUCKETSIZE", b"1"]);
16906 assert_eq!(
16907 f.run(&[b"CF.SCANDUMP", b"empty", b"0"]),
16908 "*2\r\n:0\r\n$-1\r\n"
16909 );
16910
16911 assert_eq!(
16915 f.run(&[b"CF.SCANDUMP", b"src", b"nope"]),
16916 "-Invalid position\r\n"
16917 );
16918 assert_eq!(
16919 f.run(&[b"CF.SCANDUMP", b"src", b"-1"]),
16920 "-Invalid position\r\n"
16921 );
16922 assert_eq!(
16923 f.run(&[b"CF.LOADCHUNK", b"dst", b"0", b"x"]),
16924 "-Invalid position\r\n"
16925 );
16926 assert_eq!(
16927 f.run(&[b"CF.LOADCHUNK", b"dst", b"99999", b"x"]),
16928 "-Couldn't load chunk!\r\n"
16929 );
16930 let good = cf_header(0, 8, 0, 1, [2, 20, 1]);
16932 assert_eq!(
16933 f.run(&[b"CF.LOADCHUNK", b"dst", b"1", &good]),
16934 "-ERR item exists\r\n"
16935 );
16936 assert_eq!(
16940 f.run(&[b"CF.LOADCHUNK", b"n1", b"1", b"short"]),
16941 "-Invalid header\r\n"
16942 );
16943 for (why, bad) in [
16944 ("no filters at all", cf_header(0, 8, 0, 0, [2, 20, 1])),
16945 ("no buckets", cf_header(0, 0, 0, 1, [2, 20, 1])),
16946 (
16947 "a bucket count that is not a power of two",
16948 cf_header(0, 3, 0, 1, [2, 20, 1]),
16949 ),
16950 ("an empty bucket", cf_header(0, 8, 0, 1, [0, 20, 1])),
16951 ("no kicks", cf_header(0, 8, 0, 1, [2, 0, 1])),
16952 (
16953 "a growth nobody could reach",
16954 cf_header(0, 8, 0, 1, [2, 20, 32769]),
16955 ),
16956 (
16957 "a chain that cannot grow and did",
16958 cf_header(0, 8, 0, 2, [2, 20, 0]),
16959 ),
16960 (
16963 "a filter count that wraps",
16964 cf_header(0, 8, 0, 65_536, [2, 20, 1]),
16965 ),
16966 ] {
16967 assert_eq!(
16968 f.run(&[b"CF.LOADCHUNK", b"bad", b"1", &bad]),
16969 "-Couldn't create filter!\r\n",
16970 "{why}"
16971 );
16972 }
16973 }
16974
16975 #[test]
16978 fn the_cuckoo_family_answers_in_resp3_spelling_too() {
16979 let mut f = Fixture::new();
16980 f.out.set_proto(Proto::Resp3);
16981 assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
16982 assert_eq!(f.run(&[b"CF.ADD", b"c", b"a"]), "#t\r\n");
16983 assert_eq!(f.run(&[b"CF.ADDNX", b"c", b"a"]), "#f\r\n");
16984 assert_eq!(f.run(&[b"CF.EXISTS", b"c", b"a"]), "#t\r\n");
16985 assert_eq!(
16986 f.run(&[b"CF.MEXISTS", b"c", b"a", b"z"]),
16987 "*2\r\n#t\r\n#f\r\n"
16988 );
16989 assert_eq!(f.run(&[b"CF.DEL", b"c", b"a"]), "#t\r\n");
16990 assert_eq!(f.run(&[b"CF.DEL", b"c", b"z"]), "#f\r\n");
16991 assert_eq!(f.run(&[b"CF.COUNT", b"c", b"a"]), ":1\r\n");
16993 assert_eq!(
16994 f.run(&[b"CF.INFO", b"c"]),
16995 "%8\r\n+Size\r\n:1080\r\n+Number of buckets\r\n:512\r\n\
16996 +Number of filters\r\n:1\r\n+Number of items inserted\r\n:1\r\n\
16997 +Number of items deleted\r\n:1\r\n+Bucket size\r\n:2\r\n\
16998 +Expansion rate\r\n:1\r\n+Max iterations\r\n:20\r\n"
16999 );
17000
17001 f.run(&[
17006 b"CF.RESERVE",
17007 b"s",
17008 b"4",
17009 b"BUCKETSIZE",
17010 b"1",
17011 b"EXPANSION",
17012 b"0",
17013 ]);
17014 assert_eq!(
17015 f.run(&[
17016 b"CF.INSERT",
17017 b"s",
17018 b"ITEMS",
17019 b"a",
17020 b"b",
17021 b"c",
17022 b"d",
17023 b"e",
17024 b"f"
17025 ]),
17026 "*6\r\n#t\r\n#t\r\n#t\r\n#f\r\n#f\r\n#f\r\n"
17027 );
17028 assert_eq!(
17029 f.run(&[b"CF.INSERTNX", b"s", b"ITEMS", b"a", b"zz"]),
17030 "*2\r\n:0\r\n:-1\r\n"
17031 );
17032 assert_eq!(f.run(&[b"CF.ADD", b"s", b"zzz"]), "-Filter is full\r\n");
17033 assert_eq!(f.run(&[b"CF.SCANDUMP", b"c", b"9999"]), "*2\r\n:0\r\n_\r\n");
17036 }
17037
17038 #[test]
17043 fn a_sketch_is_made_from_a_size_or_from_an_error_rate() {
17044 let mut f = Fixture::new();
17045 assert_eq!(f.run(&[b"CMS.INITBYDIM", b"d", b"100", b"5"]), "+OK\r\n");
17046 assert_eq!(
17047 f.run(&[b"CMS.INFO", b"d"]),
17048 "*6\r\n+width\r\n:100\r\n+depth\r\n:5\r\n+count\r\n:0\r\n"
17049 );
17050 assert_eq!(f.run(&[b"TYPE", b"d"]), "+CMSk-TYPE\r\n");
17051 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"d"]), "$3\r\nraw\r\n");
17052 assert_eq!(
17055 f.run(&[b"CMS.INITBYPROB", b"p", b"0.01", b"0.03"]),
17056 "+OK\r\n"
17057 );
17058 assert_eq!(
17059 f.run(&[b"CMS.INFO", b"p"]),
17060 "*6\r\n+width\r\n:200\r\n+depth\r\n:6\r\n+count\r\n:0\r\n"
17061 );
17062 assert_eq!(
17065 f.run(&[b"CMS.INITBYDIM", b"d", b"0", b"2"]),
17066 "-CMS: key already exists\r\n"
17067 );
17068 assert_eq!(
17069 f.run(&[b"CMS.INITBYDIM", b"new", b"0", b"2"]),
17070 "-CMS: invalid width\r\n"
17071 );
17072 assert_eq!(
17073 f.run(&[b"CMS.INITBYDIM", b"new", b"2", b"0"]),
17074 "-CMS: invalid depth\r\n"
17075 );
17076 assert_eq!(
17077 f.run(&[b"CMS.INITBYPROB", b"new", b"0", b"0.5"]),
17078 "-CMS: invalid overestimation value\r\n"
17079 );
17080 assert_eq!(
17081 f.run(&[b"CMS.INITBYPROB", b"new", b"0.1", b"1"]),
17082 "-CMS: invalid prob value\r\n"
17083 );
17084 assert_eq!(
17088 f.run(&[b"CMS.INITBYPROB", b"new", b"0.5", b"1e-46"]),
17089 "-CMS: invalid init arguments\r\n"
17090 );
17091 assert_eq!(
17095 f.run(&[b"CMS.INITBYDIM", b"new", b"268435457", b"1"]),
17096 "-CMS: Insufficient memory to create the key\r\n"
17097 );
17098 assert_eq!(f.run(&[b"EXISTS", b"new"]), ":0\r\n");
17099 }
17100
17101 #[test]
17104 fn increments_are_parsed_whole_and_the_counters_saturate() {
17105 let mut f = Fixture::new();
17106 f.run(&[b"CMS.INITBYDIM", b"c", b"100", b"4"]);
17107 assert_eq!(
17108 f.run(&[b"CMS.INCRBY", b"c", b"a", b"3", b"b", b"4"]),
17109 "*2\r\n:3\r\n:4\r\n"
17110 );
17111 assert_eq!(
17114 f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"a", b"1"]),
17115 "*2\r\n:4\r\n:5\r\n"
17116 );
17117 assert_eq!(
17119 f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"x"]),
17120 "-CMS: Cannot parse number\r\n"
17121 );
17122 assert_eq!(
17123 f.run(&[b"CMS.INCRBY", b"c", b"a", b"9", b"b", b"-1"]),
17124 "-CMS: Number cannot be negative\r\n"
17125 );
17126 assert_eq!(
17127 f.run(&[b"CMS.QUERY", b"c", b"a", b"b"]),
17128 "*2\r\n:5\r\n:4\r\n"
17129 );
17130 f.run(&[b"CMS.INCRBY", b"c", b"a", b"4294967295"]);
17133 assert_eq!(
17134 f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b", b"1"]),
17135 "*2\r\n-CMS: INCRBY overflow\r\n:5\r\n"
17136 );
17137 assert_eq!(f.run(&[b"CMS.QUERY", b"c", b"a"]), "*1\r\n:4294967295\r\n");
17138 f.run(&[b"CMS.INITBYDIM", b"w", b"4", b"1"]);
17141 f.run(&[b"CMS.INCRBY", b"w", b"x", b"9223372036854775807"]);
17142 f.run(&[b"CMS.INCRBY", b"w", b"x", b"1"]);
17143 assert_eq!(
17144 f.run(&[b"CMS.INFO", b"w"]),
17145 "*6\r\n+width\r\n:4\r\n+depth\r\n:1\r\n+count\r\n:-9223372036854775808\r\n"
17146 );
17147 assert!(
17150 f.run(&[b"CMS.INCRBY", b"c", b"a", b"1", b"b"])
17151 .contains("wrong number of arguments")
17152 );
17153 assert_eq!(
17154 f.run(&[b"CMS.INCRBY", b"nope", b"a", b"1"]),
17155 "-CMS: key does not exist\r\n"
17156 );
17157 assert_eq!(
17158 f.run(&[b"CMS.QUERY", b"nope", b"a"]),
17159 "-CMS: key does not exist\r\n"
17160 );
17161 }
17162
17163 #[test]
17166 fn a_merge_lands_whole_or_not_at_all() {
17167 let mut f = Fixture::new();
17168 for name in [&b"m1"[..], b"m2", b"dst"] {
17169 f.run(&[b"CMS.INITBYDIM", name, b"64", b"3"]);
17170 }
17171 f.run(&[b"CMS.INCRBY", b"m1", b"a", b"5"]);
17172 f.run(&[b"CMS.INCRBY", b"m2", b"a", b"7"]);
17173 assert_eq!(
17174 f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
17175 "+OK\r\n"
17176 );
17177 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
17178 assert_eq!(
17181 f.run(&[b"CMS.MERGE", b"dst", b"2", b"m1", b"m2"]),
17182 "+OK\r\n"
17183 );
17184 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:12\r\n");
17185 assert_eq!(
17186 f.run(&[
17187 b"CMS.MERGE",
17188 b"dst",
17189 b"2",
17190 b"m1",
17191 b"m2",
17192 b"WEIGHTS",
17193 b"2",
17194 b"3"
17195 ]),
17196 "+OK\r\n"
17197 );
17198 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
17199 assert_eq!(
17202 f.run(&[
17203 b"CMS.MERGE",
17204 b"dst",
17205 b"1",
17206 b"m1",
17207 b"WEIGHTS",
17208 b"4611686018427387904"
17209 ]),
17210 "-CMS: MERGE overflow\r\n"
17211 );
17212 assert_eq!(f.run(&[b"CMS.QUERY", b"dst", b"a"]), "*1\r\n:31\r\n");
17213 f.run(&[b"CMS.INITBYDIM", b"wide", b"128", b"3"]);
17216 assert_eq!(
17217 f.run(&[b"CMS.MERGE", b"gone", b"1", b"m1"]),
17218 "-CMS: key does not exist\r\n"
17219 );
17220 assert_eq!(
17221 f.run(&[b"CMS.MERGE", b"dst", b"0", b"m1"]),
17222 "-CMS: Number of keys must be positive\r\n"
17223 );
17224 assert_eq!(
17225 f.run(&[b"CMS.MERGE", b"dst", b"3", b"m1"]),
17226 "-CMS: wrong number of keys\r\n"
17227 );
17228 assert_eq!(
17229 f.run(&[b"CMS.MERGE", b"dst", b"1", b"m1", b"WEIGHTS", b"1", b"2"]),
17230 "-CMS: wrong number of keys/weights\r\n"
17231 );
17232 assert_eq!(
17233 f.run(&[b"CMS.MERGE", b"dst", b"1", b"wide"]),
17234 "-CMS: width/depth is not equal\r\n"
17235 );
17236 assert_eq!(
17237 f.run(&[b"CMS.MERGE", b"dst", b"1", b"gone"]),
17238 "-CMS: key does not exist\r\n"
17239 );
17240 }
17241
17242 #[test]
17245 fn a_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
17246 let mut f = Fixture::new();
17247 f.run(&[b"SET", b"s", b"text"]);
17248 for cmd in [
17249 vec![&b"CMS.INITBYDIM"[..], b"s", b"8", b"2"],
17250 vec![&b"CMS.INCRBY"[..], b"s", b"a", b"1"],
17251 vec![&b"CMS.QUERY"[..], b"s", b"a"],
17252 vec![&b"CMS.INFO"[..], b"s"],
17253 vec![&b"CMS.MERGE"[..], b"s", b"1", b"s"],
17254 ] {
17255 let name = String::from_utf8_lossy(cmd[0]).into_owned();
17256 let reply = f.run(&cmd);
17257 assert!(
17260 reply.starts_with("-WRONGTYPE") || reply == "-CMS: key already exists\r\n",
17261 "{name}: {reply}"
17262 );
17263 }
17264 f.run(&[b"CMS.INITBYDIM", b"c", b"64", b"2"]);
17265 assert_eq!(
17269 f.run(&[b"COPY", b"c", b"c2"]),
17270 "-ERR not supported for this module key\r\n"
17271 );
17272 assert_eq!(
17273 f.run(&[b"DUMP", b"c"]),
17274 "-ERR DUMP is not supported for this module key\r\n"
17275 );
17276 f.run(&[b"G.NADD", b"g", b"a"]);
17278 assert_eq!(
17279 f.run(&[b"COPY", b"g", b"g2"]),
17280 "-ERR COPY is not supported for a graph\r\n"
17281 );
17282 assert_eq!(
17283 f.run(&[b"DUMP", b"g"]),
17284 "-ERR DUMP is not supported for a graph\r\n"
17285 );
17286 assert_eq!(f.run(&[b"EXPIRE", b"c", b"100"]), ":1\r\n");
17289 assert_eq!(f.run(&[b"PERSIST", b"c"]), ":1\r\n");
17290 assert_eq!(f.run(&[b"RENAME", b"c", b"c3"]), "+OK\r\n");
17291 assert_eq!(f.run(&[b"TYPE", b"c3"]), "+CMSk-TYPE\r\n");
17292 assert_eq!(f.run(&[b"DEL", b"c3"]), ":1\r\n");
17293 }
17294
17295 #[test]
17300 fn a_reserve_takes_three_arguments_or_six() {
17301 let mut f = Fixture::new();
17302 assert_eq!(f.run(&[b"TOPK.RESERVE", b"t", b"5"]), "+OK\r\n");
17303 assert_eq!(
17304 f.run(&[b"TOPK.INFO", b"t"]),
17305 "*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"
17306 );
17307 for cmd in [
17310 vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8"],
17311 vec![&b"TOPK.RESERVE"[..], b"u", b"5", b"8", b"7"],
17312 ] {
17313 assert!(f.run(&cmd).contains("wrong number of arguments"));
17314 }
17315 assert_eq!(
17316 f.run(&[b"TOPK.RESERVE", b"u", b"5", b"8", b"7", b"0.5"]),
17317 "+OK\r\n"
17318 );
17319 assert_eq!(
17322 f.run(&[b"TOPK.RESERVE", b"u", b"0", b"0", b"0", b"9"]),
17323 "-TopK: key already exists\r\n"
17324 );
17325 assert_eq!(
17326 f.run(&[b"TOPK.RESERVE", b"v", b"0"]),
17327 "-TopK: invalid k\r\n"
17328 );
17329 assert_eq!(
17330 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"0", b"7", b"0.9"]),
17331 "-TopK: invalid width\r\n"
17332 );
17333 assert_eq!(
17334 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"x", b"0.9"]),
17335 "-TopK: invalid depth\r\n"
17336 );
17337 assert_eq!(
17339 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"0"]),
17340 "-TopK: invalid decay value. must be '<= 1' & '> 0'\r\n"
17341 );
17342 assert_eq!(
17343 f.run(&[b"TOPK.RESERVE", b"v", b"1", b"8", b"7", b"1"]),
17344 "+OK\r\n"
17345 );
17346 assert_eq!(
17348 f.run(&[
17349 b"TOPK.RESERVE",
17350 b"w",
17351 b"1",
17352 b"4294967295",
17353 b"4294967295",
17354 b"0.9"
17355 ]),
17356 "-ERR Insufficient memory to create topk data structure\r\n"
17357 );
17358 }
17359
17360 #[test]
17362 fn the_kept_set_is_what_query_and_list_answer_from() {
17363 let mut f = Fixture::new();
17364 f.run(&[b"TOPK.RESERVE", b"t", b"2", b"1000", b"5", b"0.9"]);
17365 assert_eq!(
17368 f.run(&[b"TOPK.ADD", b"t", b"a", b"b"]),
17369 "*2\r\n$-1\r\n$-1\r\n"
17370 );
17371 assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"a", b"10"]), "*1\r\n$-1\r\n");
17372 assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"c"]), "*1\r\n$1\r\nb\r\n");
17375 assert_eq!(f.run(&[b"TOPK.INCRBY", b"t", b"c", b"5"]), "*1\r\n$-1\r\n");
17376 assert_eq!(
17377 f.run(&[b"TOPK.QUERY", b"t", b"a", b"b", b"c"]),
17378 "*3\r\n:1\r\n:0\r\n:1\r\n"
17379 );
17380 assert_eq!(
17382 f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
17383 "*3\r\n:11\r\n:1\r\n:6\r\n"
17384 );
17385 assert_eq!(f.run(&[b"TOPK.LIST", b"t"]), "*2\r\n$1\r\na\r\n$1\r\nc\r\n");
17386 assert_eq!(
17387 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"]),
17388 "*4\r\n$1\r\na\r\n:11\r\n$1\r\nc\r\n:6\r\n"
17389 );
17390 assert_eq!(
17393 f.run(&[b"TOPK.LIST", b"t", b"w"]),
17394 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
17395 );
17396 assert_eq!(
17397 f.run(&[b"TOPK.LIST", b"t", b""]),
17398 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNT"])
17399 );
17400 assert_eq!(
17401 f.run(&[b"TOPK.LIST", b"t", b"WITHCOUNTS"]),
17402 "-WITHCOUNT keyword expected\r\n"
17403 );
17404 assert_eq!(
17407 f.run(&[b"TOPK.LIST", b"missing", b"nope"]),
17408 "-WITHCOUNT keyword expected\r\n"
17409 );
17410 assert_eq!(
17411 f.run(&[b"TOPK.LIST", b"missing"]),
17412 "-TopK: key does not exist\r\n"
17413 );
17414 f.run(&[b"TOPK.RESERVE", b"z", b"3"]);
17416 assert_eq!(
17417 f.run(&[b"TOPK.INCRBY", b"z", b"nothing", b"0"]),
17418 "*1\r\n$-1\r\n"
17419 );
17420 assert_eq!(f.run(&[b"TOPK.QUERY", b"z", b"nothing"]), "*1\r\n:1\r\n");
17421 assert_eq!(f.run(&[b"TOPK.LIST", b"z"]), "*0\r\n");
17422 }
17423
17424 #[test]
17427 fn an_increment_is_applied_as_it_goes_and_stops_at_a_bad_one() {
17428 let mut f = Fixture::new();
17429 f.run(&[b"TOPK.RESERVE", b"t", b"5", b"1000", b"5", b"0.9"]);
17430 assert_eq!(
17434 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"3", b"b", b"-1", b"c", b"4"]),
17435 format!(
17436 "*2\r\n$-1\r\n-{}\r\n",
17437 "TopK: increment must be an integer greater or equal to 0 and smaller or equal to 100,000"
17438 )
17439 );
17440 assert_eq!(
17441 f.run(&[b"TOPK.COUNT", b"t", b"a", b"b", b"c"]),
17442 "*3\r\n:3\r\n:0\r\n:0\r\n"
17443 );
17444 assert_eq!(
17446 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100000"]),
17447 "*1\r\n$-1\r\n"
17448 );
17449 assert!(
17450 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"100001"])
17451 .contains("smaller or equal to 100,000")
17452 );
17453 assert!(
17455 f.run(&[b"TOPK.INCRBY", b"t", b"a", b"1", b"b"])
17456 .contains("wrong number of arguments")
17457 );
17458 assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:100003\r\n");
17459 }
17460
17461 #[test]
17463 fn a_query_is_a_bool_and_info_is_a_map_on_resp3() {
17464 let mut f = Fixture::new();
17465 f.run(&[b"HELLO", b"3"]);
17466 f.run(&[b"TOPK.RESERVE", b"t", b"2", b"8", b"7", b"0.5"]);
17467 f.run(&[b"TOPK.ADD", b"t", b"a"]);
17468 assert_eq!(
17469 f.run(&[b"TOPK.QUERY", b"t", b"a", b"b"]),
17470 "*2\r\n#t\r\n#f\r\n"
17471 );
17472 assert_eq!(f.run(&[b"TOPK.COUNT", b"t", b"a"]), "*1\r\n:1\r\n");
17474 assert_eq!(
17475 f.run(&[b"TOPK.INFO", b"t"]),
17476 "%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"
17477 );
17478 assert_eq!(f.run(&[b"TOPK.ADD", b"t", b"a"]), "*1\r\n_\r\n");
17479 }
17480
17481 #[test]
17484 fn a_top_k_sketch_is_a_module_key_to_the_rest_of_the_keyspace() {
17485 let mut f = Fixture::new();
17486 f.run(&[b"SET", b"s", b"text"]);
17487 for cmd in [
17488 vec![&b"TOPK.RESERVE"[..], b"s", b"5"],
17489 vec![&b"TOPK.ADD"[..], b"s", b"a"],
17490 vec![&b"TOPK.INCRBY"[..], b"s", b"a", b"1"],
17491 vec![&b"TOPK.QUERY"[..], b"s", b"a"],
17492 vec![&b"TOPK.COUNT"[..], b"s", b"a"],
17493 vec![&b"TOPK.LIST"[..], b"s"],
17494 vec![&b"TOPK.INFO"[..], b"s"],
17495 ] {
17496 let name = String::from_utf8_lossy(cmd[0]).into_owned();
17497 let reply = f.run(&cmd);
17498 assert!(
17499 reply.starts_with("-WRONGTYPE") || reply == "-TopK: key already exists\r\n",
17500 "{name}: {reply}"
17501 );
17502 }
17503 f.run(&[b"TOPK.RESERVE", b"t", b"5"]);
17504 assert_eq!(
17505 f.run(&[b"COPY", b"t", b"t2"]),
17506 "-ERR not supported for this module key\r\n"
17507 );
17508 assert_eq!(
17509 f.run(&[b"DUMP", b"t"]),
17510 "-ERR DUMP is not supported for this module key\r\n"
17511 );
17512 assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
17513 assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
17514 assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
17515 assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TopK-TYPE\r\n");
17516 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
17517 assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
17518 assert_eq!(
17521 f.run(&[b"TOPK.INFO", b"t3"]),
17522 "-TopK: key does not exist\r\n"
17523 );
17524 }
17525
17526 #[test]
17531 fn a_create_takes_two_arguments_or_four_and_reads_the_last_one() {
17532 let mut f = Fixture::new();
17533 assert_eq!(f.run(&[b"TDIGEST.CREATE", b"t"]), "+OK\r\n");
17534 assert_eq!(
17536 f.run(&[b"TDIGEST.INFO", b"t"]),
17537 "*18\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:0\r\n\
17538 +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:0\r\n+Unmerged weight\r\n:0\r\n\
17539 +Observations\r\n:0\r\n+Total compressions\r\n:0\r\n+Memory usage\r\n:9840\r\n"
17540 );
17541 assert_eq!(
17542 f.run(&[b"TDIGEST.CREATE", b"t"]),
17543 "-ERR T-Digest: key already exists\r\n"
17544 );
17545 assert!(
17547 f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION"])
17548 .contains("wrong number of arguments")
17549 );
17550 assert_eq!(
17551 f.run(&[b"TDIGEST.CREATE", b"u", b"COMPRESSION", b"1000"]),
17552 "+OK\r\n"
17553 );
17554 assert_eq!(
17555 f.run(&[b"TDIGEST.CREATE", b"v", b"compression", b"1"]),
17556 "+OK\r\n"
17557 );
17558 assert_eq!(
17562 f.run(&[b"TDIGEST.CREATE", b"w", b"100", b"COMPRESSION"]),
17563 "-ERR T-Digest: error parsing compression parameter\r\n"
17564 );
17565 assert_eq!(
17566 f.run(&[b"TDIGEST.CREATE", b"w", b"NOPE", b"100"]),
17567 "-ERR T-Digest: wrong keyword\r\n"
17568 );
17569 assert_eq!(
17570 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"1.5"]),
17571 "-ERR T-Digest: error parsing compression parameter\r\n"
17572 );
17573 assert_eq!(
17574 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"0"]),
17575 "-ERR T-Digest: compression parameter needs to be a positive integer\r\n"
17576 );
17577 assert_eq!(
17580 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"357913942"]),
17581 "-ERR T-Digest: allocation failed\r\n"
17582 );
17583 assert_eq!(
17585 f.run(&[b"TDIGEST.CREATE", b"w", b"COMPRESSION", b"100000000"]),
17586 "-ERR T-Digest: allocation failed\r\n"
17587 );
17588 assert_eq!(
17591 f.run(&[b"TDIGEST.CREATE", b"t", b"COMPRESSION", b"0"]),
17592 "-ERR T-Digest: key already exists\r\n"
17593 );
17594 }
17595
17596 #[test]
17599 fn the_quantile_family_answers_what_the_module_answers() {
17600 let mut f = Fixture::new();
17601 f.run(&[b"TDIGEST.CREATE", b"s"]);
17602 assert_eq!(
17603 f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]),
17604 "+OK\r\n"
17605 );
17606 assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), "$1\r\n1\r\n");
17607 assert_eq!(f.run(&[b"TDIGEST.MAX", b"s"]), "$1\r\n4\r\n");
17608 assert_eq!(
17610 f.run(&[b"TDIGEST.CDF", b"s", b"1", b"2", b"3", b"4"]),
17611 "*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"
17612 );
17613 assert_eq!(
17614 f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"0.5", b"1"]),
17615 "*3\r\n$1\r\n1\r\n$1\r\n3\r\n$1\r\n4\r\n"
17616 );
17617 assert_eq!(
17620 f.run(&[b"TDIGEST.QUANTILE", b"s", b"0.5", b"0.1", b"0.9"]),
17621 "*3\r\n$1\r\n3\r\n$1\r\n1\r\n$1\r\n4\r\n"
17622 );
17623 assert_eq!(
17624 f.run(&[b"TDIGEST.RANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
17625 "*5\r\n:-1\r\n:0\r\n:2\r\n:3\r\n:4\r\n"
17626 );
17627 assert_eq!(
17628 f.run(&[b"TDIGEST.REVRANK", b"s", b"0", b"1", b"3", b"4", b"5"]),
17629 "*5\r\n:4\r\n:3\r\n:1\r\n:0\r\n:-1\r\n"
17630 );
17631 assert_eq!(
17632 f.run(&[b"TDIGEST.BYRANK", b"s", b"0", b"1", b"3", b"4"]),
17633 "*4\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n4\r\n$3\r\ninf\r\n"
17634 );
17635 assert_eq!(
17636 f.run(&[b"TDIGEST.BYREVRANK", b"s", b"0", b"1", b"3", b"4"]),
17637 "*4\r\n$1\r\n4\r\n$1\r\n3\r\n$1\r\n1\r\n$4\r\n-inf\r\n"
17638 );
17639 assert_eq!(
17640 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0", b"1"]),
17641 "$3\r\n2.5\r\n"
17642 );
17643 assert_eq!(
17644 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.25", b"0.75"]),
17645 "$3\r\n2.5\r\n"
17646 );
17647 assert_eq!(
17649 f.run(&[b"TDIGEST.QUANTILE", b"s", b"1.1"]),
17650 "-ERR T-Digest: quantile should be in [0,1]\r\n"
17651 );
17652 assert_eq!(
17653 f.run(&[b"TDIGEST.QUANTILE", b"s", b"zzz"]),
17654 "-ERR T-Digest: error parsing quantile\r\n"
17655 );
17656 assert_eq!(
17657 f.run(&[b"TDIGEST.CDF", b"s", b"zzz"]),
17658 "-ERR T-Digest: error parsing cdf\r\n"
17659 );
17660 assert_eq!(
17661 f.run(&[b"TDIGEST.RANK", b"s", b"zzz"]),
17662 "-ERR T-Digest: error parsing value\r\n"
17663 );
17664 assert_eq!(
17665 f.run(&[b"TDIGEST.BYRANK", b"s", b"-1"]),
17666 "-ERR T-Digest: rank needs to be non negative\r\n"
17667 );
17668 assert_eq!(
17669 f.run(&[b"TDIGEST.BYRANK", b"s", b"1.5"]),
17670 "-ERR T-Digest: error parsing rank\r\n"
17671 );
17672 assert_eq!(
17675 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"zzz", b"0.9"]),
17676 "-ERR T-Digest: error parsing low_cut_percentile\r\n"
17677 );
17678 assert_eq!(
17679 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"zzz"]),
17680 "-ERR T-Digest: error parsing high_cut_percentile\r\n"
17681 );
17682 assert_eq!(
17683 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.1", b"1.1"]),
17684 "-ERR T-Digest: low_cut_percentile and high_cut_percentile should be in [0,1]\r\n"
17685 );
17686 assert_eq!(
17687 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"s", b"0.5", b"0.5"]),
17688 "-ERR T-Digest: low_cut_percentile should be lower than high_cut_percentile\r\n"
17689 );
17690 }
17691
17692 #[test]
17695 fn an_empty_digest_has_an_answer_for_everything() {
17696 let mut f = Fixture::new();
17697 f.run(&[b"TDIGEST.CREATE", b"e"]);
17698 assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
17699 assert_eq!(f.run(&[b"TDIGEST.MAX", b"e"]), "$3\r\nnan\r\n");
17700 assert_eq!(
17701 f.run(&[b"TDIGEST.QUANTILE", b"e", b"0", b"1"]),
17702 "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
17703 );
17704 assert_eq!(f.run(&[b"TDIGEST.CDF", b"e", b"0"]), "*1\r\n$3\r\nnan\r\n");
17705 assert_eq!(
17706 f.run(&[b"TDIGEST.TRIMMED_MEAN", b"e", b"0.1", b"0.9"]),
17707 "$3\r\nnan\r\n"
17708 );
17709 assert_eq!(
17712 f.run(&[b"TDIGEST.RANK", b"e", b"0", b"1"]),
17713 "*2\r\n:-2\r\n:-2\r\n"
17714 );
17715 assert_eq!(
17716 f.run(&[b"TDIGEST.REVRANK", b"e", b"0", b"1"]),
17717 "*2\r\n:-2\r\n:-2\r\n"
17718 );
17719 assert_eq!(
17720 f.run(&[b"TDIGEST.BYRANK", b"e", b"0", b"5"]),
17721 "*2\r\n$3\r\nnan\r\n$3\r\nnan\r\n"
17722 );
17723 f.run(&[b"TDIGEST.ADD", b"e", b"1", b"2", b"3"]);
17725 assert_eq!(f.run(&[b"TDIGEST.RESET", b"e"]), "+OK\r\n");
17726 assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), "$3\r\nnan\r\n");
17727 f.run(&[b"TDIGEST.CREATE", b"e2"]);
17730 assert_eq!(
17731 f.run(&[b"TDIGEST.INFO", b"e"]),
17732 f.run(&[b"TDIGEST.INFO", b"e2"])
17733 );
17734 }
17735
17736 #[test]
17739 fn a_sample_is_read_the_way_redis_reads_a_double() {
17740 let mut f = Fixture::new();
17741 f.run(&[b"TDIGEST.CREATE", b"a"]);
17742 for bad in [
17745 &b"nan"[..],
17746 b"1e400",
17747 b"-1e400",
17748 b"1e309",
17749 b"1e-400",
17750 b"",
17751 b" 1",
17752 b"1 ",
17753 b"1e",
17754 b"--1",
17755 ] {
17756 assert_eq!(
17757 f.run(&[b"TDIGEST.ADD", b"a", bad]),
17758 "-ERR T-Digest: error parsing val parameter\r\n",
17759 "{}",
17760 String::from_utf8_lossy(bad)
17761 );
17762 }
17763 for word in [&b"inf"[..], b"-inf", b"+INF", b"Infinity"] {
17766 assert_eq!(
17767 f.run(&[b"TDIGEST.ADD", b"a", word]),
17768 "-ERR T-Digest: val parameter needs to be a finite number\r\n",
17769 "{}",
17770 String::from_utf8_lossy(word)
17771 );
17772 }
17773 for good in [&b"0x10"[..], b".5", b"1.", b"1e-320", b"-0", b"0"] {
17776 assert_eq!(
17777 f.run(&[b"TDIGEST.ADD", b"a", good]),
17778 "+OK\r\n",
17779 "{}",
17780 String::from_utf8_lossy(good)
17781 );
17782 }
17783 assert!(
17785 f.run(&[b"TDIGEST.INFO", b"a"])
17786 .contains("Observations\r\n:6\r\n")
17787 );
17788 assert_eq!(
17791 f.run(&[b"TDIGEST.ADD", b"a", b"1", b"zzz"]),
17792 "-ERR T-Digest: error parsing val parameter\r\n"
17793 );
17794 assert!(
17795 f.run(&[b"TDIGEST.INFO", b"a"])
17796 .contains("Observations\r\n:6\r\n")
17797 );
17798 }
17799
17800 #[test]
17803 fn a_merge_sweeps_the_destination_between_its_inputs() {
17804 let mut f = Fixture::new();
17805 f.run(&[b"TDIGEST.CREATE", b"m1", b"COMPRESSION", b"100"]);
17806 f.run(&[b"TDIGEST.ADD", b"m1", b"1", b"2", b"3"]);
17807 f.run(&[b"TDIGEST.CREATE", b"m2", b"COMPRESSION", b"200"]);
17808 f.run(&[b"TDIGEST.ADD", b"m2", b"4", b"5", b"6"]);
17809 assert_eq!(
17810 f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"m2"]),
17811 "+OK\r\n"
17812 );
17813 let info = f.run(&[b"TDIGEST.INFO", b"d"]);
17818 assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
17819 assert!(info.contains("Merged nodes\r\n:3\r\n"), "{info}");
17820 assert!(info.contains("Unmerged nodes\r\n:3\r\n"), "{info}");
17821 assert!(info.contains("Total compressions\r\n:1\r\n"), "{info}");
17822 assert_eq!(f.run(&[b"TDIGEST.MIN", b"d"]), "$1\r\n1\r\n");
17823 assert_eq!(f.run(&[b"TDIGEST.MAX", b"d"]), "$1\r\n6\r\n");
17824 assert!(
17827 f.run(&[b"TDIGEST.INFO", b"m1"])
17828 .contains("Merged nodes\r\n:3\r\n")
17829 );
17830 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1"]);
17833 let info = f.run(&[b"TDIGEST.INFO", b"d"]);
17834 assert!(info.contains("Observations\r\n:9\r\n"), "{info}");
17835 assert!(info.contains("Compression\r\n:200\r\n"), "{info}");
17836 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"OVERRIDE"]);
17839 let info = f.run(&[b"TDIGEST.INFO", b"d"]);
17840 assert!(info.contains("Observations\r\n:3\r\n"), "{info}");
17841 assert!(info.contains("Compression\r\n:100\r\n"), "{info}");
17842 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION", b"500"]);
17844 assert!(
17845 f.run(&[b"TDIGEST.INFO", b"d"])
17846 .contains("Compression\r\n:500\r\n")
17847 );
17848 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"d"]);
17850 assert!(
17851 f.run(&[b"TDIGEST.INFO", b"d"])
17852 .contains("Observations\r\n:12\r\n")
17853 );
17854 assert_eq!(
17856 f.run(&[b"TDIGEST.MERGE", b"d", b"zzz", b"m1"]),
17857 "-ERR T-Digest: error parsing numkeys\r\n"
17858 );
17859 assert_eq!(
17860 f.run(&[b"TDIGEST.MERGE", b"d", b"0", b"m1"]),
17861 "-ERR T-Digest: numkeys needs to be a positive integer\r\n"
17862 );
17863 assert!(
17864 f.run(&[b"TDIGEST.MERGE", b"d", b"3", b"m1", b"m2"])
17865 .contains("wrong number of arguments")
17866 );
17867 assert!(
17868 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"COMPRESSION"])
17869 .contains("wrong number of arguments")
17870 );
17871 assert_eq!(
17872 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"m1", b"NOPE"]),
17873 "-ERR T-Digest: wrong keyword\r\n"
17874 );
17875 assert_eq!(
17878 f.run(&[b"TDIGEST.MERGE", b"d", b"2", b"m1", b"gone"]),
17879 "-ERR T-Digest: key does not exist\r\n"
17880 );
17881 assert!(
17882 f.run(&[b"TDIGEST.INFO", b"d"])
17883 .contains("Observations\r\n:12\r\n")
17884 );
17885 assert_eq!(
17888 f.run(&[b"TDIGEST.MERGE", b"gone", b"1", b"gone"]),
17889 "-ERR T-Digest: key does not exist\r\n"
17890 );
17891 }
17892
17893 #[test]
17895 fn a_digest_answers_doubles_and_a_map_on_resp3() {
17896 let mut f = Fixture::new();
17897 f.run(&[b"HELLO", b"3"]);
17898 f.run(&[b"TDIGEST.CREATE", b"s"]);
17899 f.run(&[b"TDIGEST.ADD", b"s", b"1", b"2", b"3", b"4"]);
17900 assert_eq!(f.run(&[b"TDIGEST.MIN", b"s"]), ",1\r\n");
17901 assert_eq!(
17902 f.run(&[b"TDIGEST.QUANTILE", b"s", b"0", b"1"]),
17903 "*2\r\n,1\r\n,4\r\n"
17904 );
17905 assert_eq!(f.run(&[b"TDIGEST.CDF", b"s", b"1"]), "*1\r\n,0.125\r\n");
17906 assert_eq!(f.run(&[b"TDIGEST.BYRANK", b"s", b"4"]), "*1\r\n,inf\r\n");
17908 assert_eq!(
17909 f.run(&[b"TDIGEST.BYREVRANK", b"s", b"4"]),
17910 "*1\r\n,-inf\r\n"
17911 );
17912 f.run(&[b"TDIGEST.CREATE", b"e"]);
17913 assert_eq!(f.run(&[b"TDIGEST.MIN", b"e"]), ",nan\r\n");
17914 assert_eq!(f.run(&[b"TDIGEST.RANK", b"s", b"1"]), "*1\r\n:0\r\n");
17916 assert_eq!(
17919 f.run(&[b"TDIGEST.INFO", b"s"]),
17920 "%9\r\n+Compression\r\n:100\r\n+Capacity\r\n:610\r\n+Merged nodes\r\n:4\r\n\
17921 +Unmerged nodes\r\n:0\r\n+Merged weight\r\n:4\r\n+Unmerged weight\r\n:0\r\n\
17922 +Observations\r\n:4\r\n+Total compressions\r\n:1\r\n+Memory usage\r\n:9840\r\n"
17923 );
17924 }
17925
17926 #[test]
17929 fn a_t_digest_is_a_module_key_to_the_rest_of_the_keyspace() {
17930 let mut f = Fixture::new();
17931 f.run(&[b"SET", b"s", b"text"]);
17932 for cmd in [
17933 vec![&b"TDIGEST.CREATE"[..], b"s"],
17934 vec![&b"TDIGEST.RESET"[..], b"s"],
17935 vec![&b"TDIGEST.ADD"[..], b"s", b"1"],
17936 vec![&b"TDIGEST.MIN"[..], b"s"],
17937 vec![&b"TDIGEST.MAX"[..], b"s"],
17938 vec![&b"TDIGEST.QUANTILE"[..], b"s", b"0.5"],
17939 vec![&b"TDIGEST.CDF"[..], b"s", b"1"],
17940 vec![&b"TDIGEST.TRIMMED_MEAN"[..], b"s", b"0.1", b"0.9"],
17941 vec![&b"TDIGEST.RANK"[..], b"s", b"1"],
17942 vec![&b"TDIGEST.REVRANK"[..], b"s", b"1"],
17943 vec![&b"TDIGEST.BYRANK"[..], b"s", b"0"],
17944 vec![&b"TDIGEST.BYREVRANK"[..], b"s", b"0"],
17945 vec![&b"TDIGEST.INFO"[..], b"s"],
17946 ] {
17947 let name = String::from_utf8_lossy(cmd[0]).into_owned();
17948 let reply = f.run(&cmd);
17949 assert!(reply.starts_with("-WRONGTYPE"), "{name}: {reply}");
17950 }
17951 f.run(&[b"TDIGEST.CREATE", b"t"]);
17953 assert!(
17954 f.run(&[b"TDIGEST.MERGE", b"s", b"1", b"t"])
17955 .starts_with("-WRONGTYPE")
17956 );
17957 assert!(
17958 f.run(&[b"TDIGEST.MERGE", b"d", b"1", b"s"])
17959 .starts_with("-WRONGTYPE")
17960 );
17961 assert_eq!(
17962 f.run(&[b"COPY", b"t", b"t2"]),
17963 "-ERR not supported for this module key\r\n"
17964 );
17965 assert_eq!(
17966 f.run(&[b"DUMP", b"t"]),
17967 "-ERR DUMP is not supported for this module key\r\n"
17968 );
17969 assert_eq!(f.run(&[b"EXPIRE", b"t", b"100"]), ":1\r\n");
17970 assert_eq!(f.run(&[b"PERSIST", b"t"]), ":1\r\n");
17971 assert_eq!(f.run(&[b"RENAME", b"t", b"t3"]), "+OK\r\n");
17972 assert_eq!(f.run(&[b"TYPE", b"t3"]), "+TDIS-TYPE\r\n");
17973 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t3"]), "$3\r\nraw\r\n");
17974 assert_eq!(f.run(&[b"DEL", b"t3"]), ":1\r\n");
17975 assert_eq!(
17978 f.run(&[b"TDIGEST.INFO", b"t3"]),
17979 "-ERR T-Digest: key does not exist\r\n"
17980 );
17981 assert_eq!(
17984 f.run(&[b"TDIGEST.QUANTILE", b"t3", b"zzz"]),
17985 "-ERR T-Digest: key does not exist\r\n"
17986 );
17987 }
17988
17989 fn without_memory(reply: &str) -> String {
17999 let head = "+memoryUsage\r\n:";
18000 let at = reply.find(head).expect("every TS.INFO reports memory");
18001 let rest = &reply[at + head.len()..];
18002 let end = rest.find("\r\n").expect("and it is a whole number");
18003 format!("{}{}", &reply[..at + head.len()], &rest[end..])
18004 }
18005
18006 #[test]
18009 fn a_series_is_made_empty_and_reports_on_itself() {
18010 let mut f = Fixture::new();
18011 assert_eq!(f.run(&[b"TS.CREATE", b"t"]), "+OK\r\n");
18012 assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
18013 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"t"]), "$3\r\nraw\r\n");
18014 assert_eq!(
18018 without_memory(&f.run(&[b"TS.INFO", b"t"])),
18019 "*28\r\n\
18020 +totalSamples\r\n:0\r\n\
18021 +memoryUsage\r\n:\r\n\
18022 +firstTimestamp\r\n:0\r\n\
18023 +lastTimestamp\r\n:0\r\n\
18024 +retentionTime\r\n:0\r\n\
18025 +chunkCount\r\n:1\r\n\
18026 +chunkSize\r\n:4096\r\n\
18027 +chunkType\r\n+compressed\r\n\
18028 +duplicatePolicy\r\n+block\r\n\
18029 +labels\r\n*0\r\n\
18030 +sourceKey\r\n$-1\r\n\
18031 +rules\r\n*0\r\n\
18032 +ignoreMaxTimeDiff\r\n:0\r\n\
18033 +ignoreMaxValDiff\r\n$1\r\n0\r\n"
18034 );
18035 assert_eq!(
18038 f.run(&[b"TS.CREATE", b"t"]),
18039 "-ERR TSDB: key already exists\r\n"
18040 );
18041 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
18042 assert_eq!(
18043 f.run(&[b"TS.CREATE", b"str"]),
18044 "-ERR TSDB: key already exists\r\n"
18045 );
18046 assert_eq!(
18049 f.run(&[b"TS.CREATE", b"t", b"RETENTION", b"abc"]),
18050 "-ERR TSDB: Couldn't parse RETENTION\r\n"
18051 );
18052 let wrong = "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
18058 assert_eq!(f.run(&[b"TS.INFO", b"str"]), wrong);
18059 assert_eq!(f.run(&[b"TS.GET", b"str"]), wrong);
18060 assert_eq!(f.run(&[b"TS.ALTER", b"str"]), wrong);
18061 assert_eq!(f.run(&[b"TS.DEL", b"str", b"0", b"1"]), wrong);
18062 assert_eq!(f.run(&[b"TS.INCRBY", b"str", b"1"]), wrong);
18063 assert_eq!(
18064 f.run(&[b"TS.ADD", b"str", b"1", b"1"]),
18065 "-ERR TSDB: the key is not a TSDB key\r\n"
18066 );
18067 assert_eq!(
18069 f.run(&[b"TS.INFO", b"nope"]),
18070 "-ERR TSDB: the key does not exist\r\n"
18071 );
18072 assert_eq!(
18073 f.run(&[b"TS.GET", b"nope"]),
18074 "-ERR TSDB: the key does not exist\r\n"
18075 );
18076 assert_eq!(
18077 f.run(&[b"TS.ALTER", b"nope"]),
18078 "-ERR TSDB: the key does not exist\r\n"
18079 );
18080 assert_eq!(
18081 f.run(&[b"TS.DEL", b"nope", b"1", b"2"]),
18082 "-ERR TSDB: the key does not exist\r\n"
18083 );
18084 }
18085
18086 #[test]
18089 fn the_options_are_a_keyword_scan_and_not_a_grammar() {
18090 let mut f = Fixture::new();
18091 assert_eq!(
18092 f.run(&[
18093 b"TS.CREATE",
18094 b"t",
18095 b"RETENTION",
18096 b"5000",
18097 b"ENCODING",
18098 b"UNCOMPRESSED",
18099 b"CHUNK_SIZE",
18100 b"128",
18101 b"DUPLICATE_POLICY",
18102 b"LAST",
18103 b"IGNORE",
18104 b"10",
18105 b"0.5",
18106 b"LABELS",
18107 b"room",
18108 b"kitchen"
18109 ]),
18110 "+OK\r\n"
18111 );
18112 let info = f.run(&[b"TS.INFO", b"t"]);
18113 assert!(info.contains("+retentionTime\r\n:5000\r\n"), "{info}");
18114 assert!(info.contains("+chunkSize\r\n:128\r\n"), "{info}");
18115 assert!(info.contains("+chunkType\r\n+uncompressed\r\n"), "{info}");
18116 assert!(info.contains("+duplicatePolicy\r\n+last\r\n"), "{info}");
18117 assert!(info.contains("+ignoreMaxTimeDiff\r\n:10\r\n"), "{info}");
18118 assert!(
18121 info.contains("+ignoreMaxValDiff\r\n$3\r\n0.5\r\n"),
18122 "{info}"
18123 );
18124 assert!(
18125 info.contains("+labels\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n"),
18126 "{info}"
18127 );
18128
18129 assert_eq!(f.run(&[b"TS.CREATE", b"junk", b"FOO"]), "+OK\r\n");
18131 assert_eq!(
18135 f.run(&[
18136 b"TS.CREATE",
18137 b"g",
18138 b"LABELS",
18139 b"a",
18140 b"b",
18141 b"RETENTION",
18142 b"5"
18143 ]),
18144 "+OK\r\n"
18145 );
18146 let greedy = f.run(&[b"TS.INFO", b"g"]);
18147 assert!(greedy.contains("+retentionTime\r\n:5\r\n"), "{greedy}");
18148 assert!(
18149 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"),
18150 "{greedy}"
18151 );
18152
18153 assert_eq!(
18155 f.run(&[b"TS.CREATE", b"e", b"LABELS", b"a", b"b(c"]),
18156 "-ERR TSDB: Couldn't parse LABELS\r\n"
18157 );
18158 assert_eq!(
18159 f.run(&[b"TS.CREATE", b"e", b"LABELS", b"", b"b"]),
18160 "-ERR TSDB: Couldn't parse LABELS\r\n"
18161 );
18162 assert_eq!(
18163 f.run(&[b"TS.CREATE", b"e", b"RETENTION"]),
18164 "-ERR TSDB: Couldn't parse RETENTION\r\n"
18165 );
18166 assert_eq!(
18169 f.run(&[b"TS.CREATE", b"e", b"RETENTION", b"-1"]),
18170 "-TSDB: Couldn't parse RETENTION\r\n"
18171 );
18172 assert_eq!(
18173 f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"abc"]),
18174 "-ERR TSDB: Couldn't parse CHUNK_SIZE\r\n"
18175 );
18176 assert_eq!(
18177 f.run(&[b"TS.CREATE", b"e", b"CHUNK_SIZE", b"100"]),
18178 "-ERR TSDB: CHUNK_SIZE value must be a multiple of 8 in the range [48 .. 1048576]\r\n"
18179 );
18180 assert_eq!(
18181 f.run(&[b"TS.CREATE", b"e", b"ENCODING", b"nope"]),
18182 "-ERR TSDB: unknown ENCODING parameter\r\n"
18183 );
18184 assert!(
18187 f.run(&[b"TS.CREATE", b"e", b"ENCODING"])
18188 .contains("wrong number of arguments for 'ts.create' command")
18189 );
18190 assert_eq!(
18191 f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY"]),
18192 "-ERR TSDB: Couldn't parse DUPLICATE_POLICY\r\n"
18193 );
18194 assert_eq!(
18195 f.run(&[b"TS.CREATE", b"e", b"DUPLICATE_POLICY", b"nope"]),
18196 "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
18197 );
18198 assert_eq!(
18199 f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"10"]),
18200 "-ERR TSDB: Couldn't parse IGNORE\r\n"
18201 );
18202 assert_eq!(
18203 f.run(&[b"TS.CREATE", b"e", b"IGNORE", b"-1", b"1"]),
18204 "-ERR TSDB: IGNORE arguments cannot be negative\r\n"
18205 );
18206 assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
18207
18208 assert_eq!(f.run(&[b"TS.ALTER", b"t", b"RETENTION", b"9"]), "+OK\r\n");
18211 let after = f.run(&[b"TS.INFO", b"t"]);
18212 assert!(after.contains("+retentionTime\r\n:9\r\n"), "{after}");
18213 assert!(after.contains("+chunkSize\r\n:128\r\n"), "{after}");
18214 assert!(after.contains("+duplicatePolicy\r\n+last\r\n"), "{after}");
18215 assert_eq!(
18216 f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"nope"]),
18217 "-ERR TSDB: unknown ENCODING parameter\r\n"
18218 );
18219 assert_eq!(
18221 f.run(&[b"TS.ALTER", b"t", b"ENCODING", b"COMPRESSED"]),
18222 "+OK\r\n"
18223 );
18224 assert!(
18225 f.run(&[b"TS.INFO", b"t"])
18226 .contains("+chunkType\r\n+uncompressed\r\n")
18227 );
18228 }
18229
18230 #[test]
18233 fn samples_land_where_they_are_put_and_the_newest_comes_back() {
18234 let mut f = Fixture::new();
18235 assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1.5"]), ":100\r\n");
18236 assert_eq!(f.run(&[b"TYPE", b"t"]), "+TSDB-TYPE\r\n");
18238 assert_eq!(f.run(&[b"TS.ADD", b"t", b"200", b"2"]), ":200\r\n");
18239 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+2\r\n");
18242 assert_eq!(f.run(&[b"TS.ADD", b"t", b"300", b"1e300"]), ":300\r\n");
18243 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+1E300\r\n");
18244 assert_eq!(f.run(&[b"TS.CREATE", b"empty"]), "+OK\r\n");
18247 assert_eq!(f.run(&[b"TS.GET", b"empty"]), "*0\r\n");
18248
18249 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
18252 assert_eq!(
18253 f.run(&[b"TS.ADD", b"str", b"1", b".5"]),
18254 "-ERR TSDB: invalid value\r\n"
18255 );
18256 for bad in [
18260 &b".5"[..],
18261 b"1.",
18262 b"+1",
18263 b" 1",
18264 b"0x10",
18265 b"inf",
18266 b"1e400",
18267 b"--1",
18268 b"1e",
18269 ] {
18270 assert_eq!(
18271 f.run(&[b"TS.ADD", b"v", b"1", bad]),
18272 "-ERR TSDB: invalid value\r\n",
18273 "{}",
18274 String::from_utf8_lossy(bad)
18275 );
18276 }
18277 assert_eq!(f.run(&[b"TS.ADD", b"v", b"1", b"NaN"]), ":1\r\n");
18279
18280 assert_eq!(
18283 f.run(&[b"TS.ADD", b"t", b"abc", b"1"]),
18284 "-ERR TSDB: invalid timestamp\r\n"
18285 );
18286 assert_eq!(
18287 f.run(&[b"TS.ADD", b"t", b"-1", b"1"]),
18288 "-ERR TSDB: invalid timestamp, must be a nonnegative integer\r\n"
18289 );
18290
18291 assert_eq!(
18294 f.run(&[b"TS.ADD", b"t", b"300", b"7"]),
18295 "-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"
18296 );
18297 assert_eq!(
18298 f.run(&[b"TS.ADD", b"t", b"300", b"7", b"ON_DUPLICATE", b"LAST"]),
18299 ":300\r\n"
18300 );
18301 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:300\r\n+7\r\n");
18302 assert_eq!(
18305 f.run(&[b"TS.ADD", b"fresh", b"1", b"1", b"ON_DUPLICATE", b"nope"]),
18306 ":1\r\n"
18307 );
18308 assert_eq!(
18309 f.run(&[b"TS.ADD", b"fresh", b"2", b"1", b"ON_DUPLICATE", b"nope"]),
18310 "-ERR TSDB: Unknown DUPLICATE_POLICY\r\n"
18311 );
18312
18313 assert_eq!(f.run(&[b"TS.CREATE", b"r", b"RETENTION", b"50"]), "+OK\r\n");
18316 assert_eq!(f.run(&[b"TS.ADD", b"r", b"1000", b"1"]), ":1000\r\n");
18317 assert_eq!(f.run(&[b"TS.ADD", b"r", b"960", b"1"]), ":960\r\n");
18318 assert_eq!(
18319 f.run(&[b"TS.ADD", b"r", b"940", b"1"]),
18320 "-ERR TSDB: Timestamp is older than retention\r\n"
18321 );
18322 assert_eq!(f.run(&[b"TS.ADD", b"r", b"1100", b"1"]), ":1100\r\n");
18324 assert!(
18325 f.run(&[b"TS.INFO", b"r"])
18326 .contains("+totalSamples\r\n:1\r\n")
18327 );
18328
18329 assert_eq!(
18332 f.run(&[
18333 b"TS.CREATE",
18334 b"i",
18335 b"DUPLICATE_POLICY",
18336 b"LAST",
18337 b"IGNORE",
18338 b"10",
18339 b"0.5"
18340 ]),
18341 "+OK\r\n"
18342 );
18343 assert_eq!(f.run(&[b"TS.ADD", b"i", b"1000", b"1"]), ":1000\r\n");
18344 assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"1.2"]), ":1000\r\n");
18345 assert_eq!(f.run(&[b"TS.ADD", b"i", b"1005", b"9"]), ":1005\r\n");
18346 }
18347
18348 #[test]
18351 fn a_madd_answers_each_triple_and_creates_nothing() {
18352 let mut f = Fixture::new();
18353 assert_eq!(f.run(&[b"TS.CREATE", b"a"]), "+OK\r\n");
18354 assert_eq!(f.run(&[b"TS.CREATE", b"b"]), "+OK\r\n");
18355 assert_eq!(
18356 f.run(&[
18357 b"TS.MADD", b"a", b"100", b"1", b"b", b"100", b"2", b"a", b"200", b"3"
18358 ]),
18359 "*3\r\n:100\r\n:100\r\n:200\r\n"
18360 );
18361 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
18364 assert_eq!(
18365 f.run(&[
18366 b"TS.MADD", b"gone", b"1", b"1", b"str", b"1", b"1", b"a", b"300", b"4"
18367 ]),
18368 "*3\r\n\
18369 -ERR TSDB: the key is not a TSDB key\r\n\
18370 -ERR TSDB: the key is not a TSDB key\r\n\
18371 :300\r\n"
18372 );
18373 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
18374 assert_eq!(
18376 f.run(&[b"TS.MADD", b"a", b"400", b"zzz", b"a", b"abc", b"1"]),
18377 "*2\r\n-ERR TSDB: invalid value\r\n-ERR TSDB: invalid timestamp\r\n"
18378 );
18379 assert!(
18381 f.run(&[b"TS.MADD", b"a", b"1", b"1", b"a"])
18382 .contains("wrong number of arguments for 'ts.madd' command")
18383 );
18384 }
18385
18386 #[test]
18388 fn an_increment_walks_the_newest_value_up_and_down() {
18389 let mut f = Fixture::new();
18390 assert_eq!(
18391 f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
18392 ":100\r\n"
18393 );
18394 assert_eq!(
18395 f.run(&[b"TS.INCRBY", b"t", b"5", b"TIMESTAMP", b"100"]),
18396 ":100\r\n"
18397 );
18398 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n+10\r\n");
18401 assert_eq!(
18402 f.run(&[b"TS.DECRBY", b"t", b"3", b"TIMESTAMP", b"200"]),
18403 ":200\r\n"
18404 );
18405 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:200\r\n+7\r\n");
18406 assert_eq!(
18409 f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP", b"150"]),
18410 "-TSDB: timestamp must be equal to or higher than the maximum existing timestamp\r\n"
18411 );
18412 assert_eq!(
18416 f.run(&[b"TS.INCRBY", b"p", b"+5", b"TIMESTAMP", b"1"]),
18417 ":1\r\n"
18418 );
18419 assert_eq!(
18420 f.run(&[b"TS.INCRBY", b"q", b".5", b"TIMESTAMP", b"1"]),
18421 ":1\r\n"
18422 );
18423 assert_eq!(
18424 f.run(&[b"TS.INCRBY", b"t", b"nan"]),
18425 "-ERR TSDB: invalid increase/decrease value\r\n"
18426 );
18427 assert_eq!(
18428 f.run(&[b"TS.INCRBY", b"t", b"zzz"]),
18429 "-ERR TSDB: invalid increase/decrease value\r\n"
18430 );
18431 assert_eq!(f.run(&[b"SET", b"str", b"x"]), "+OK\r\n");
18434 assert_eq!(
18435 f.run(&[b"TS.INCRBY", b"str", b"zzz"]),
18436 "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
18437 );
18438 assert_eq!(
18443 f.run(&[b"TS.INCRBY", b"t", b"1", b"TIMESTAMP"]),
18444 "-ERR TSDB: invalid timestamp\r\n"
18445 );
18446 assert_eq!(
18449 f.run(&[b"TS.INCRBY", b"lab", b"1", b"LABELS", b"TIMESTAMP", b"5"]),
18450 format!(":{}\r\n", f.server.now_ms())
18451 );
18452 assert_eq!(f.run(&[b"TS.ADD", b"n", b"1", b"nan"]), ":1\r\n");
18454 assert_eq!(
18455 f.run(&[b"TS.INCRBY", b"n", b"1", b"TIMESTAMP", b"2"]),
18456 "-ERR TSDB: cannot increment/decrement NaN value\r\n"
18457 );
18458 }
18459
18460 #[test]
18462 fn deleting_takes_out_a_span_and_answers_how_many_went() {
18463 let mut f = Fixture::new();
18464 for at in [b"100".as_slice(), b"200", b"300", b"400"] {
18465 f.run(&[b"TS.ADD", b"t", at, b"1"]);
18466 }
18467 assert_eq!(f.run(&[b"TS.DEL", b"t", b"200", b"300"]), ":2\r\n");
18468 assert!(
18469 f.run(&[b"TS.INFO", b"t"])
18470 .contains("+totalSamples\r\n:2\r\n")
18471 );
18472 assert_eq!(f.run(&[b"TS.DEL", b"t", b"400", b"100"]), ":0\r\n");
18474 assert_eq!(f.run(&[b"TS.DEL", b"t", b"-", b"+"]), ":2\r\n");
18476 let empty = f.run(&[b"TS.INFO", b"t"]);
18479 assert!(empty.contains("+totalSamples\r\n:0\r\n"), "{empty}");
18480 assert!(empty.contains("+chunkCount\r\n:1\r\n"), "{empty}");
18481 assert!(empty.contains("+firstTimestamp\r\n:0\r\n"), "{empty}");
18482 assert!(empty.contains("+lastTimestamp\r\n:0\r\n"), "{empty}");
18483 assert_eq!(f.run(&[b"TS.DEL", b"t", b"0", b"1000"]), ":0\r\n");
18484 assert_eq!(
18486 f.run(&[b"TS.DEL", b"t", b"abc", b"5"]),
18487 "-ERR TSDB: wrong fromTimestamp\r\n"
18488 );
18489 assert_eq!(
18490 f.run(&[b"TS.DEL", b"t", b"5", b"abc"]),
18491 "-ERR TSDB: wrong toTimestamp\r\n"
18492 );
18493 assert_eq!(
18494 f.run(&[b"TS.DEL", b"t", b"-5", b"5"]),
18495 "-ERR TSDB: wrong fromTimestamp\r\n"
18496 );
18497 }
18498
18499 #[test]
18502 fn resp3_writes_a_sample_as_a_double_and_the_info_as_a_map() {
18503 let mut f = Fixture::new();
18504 f.out = Out::new(Proto::Resp3);
18505 assert_eq!(
18506 f.run(&[b"TS.CREATE", b"t", b"LABELS", b"room", b"kitchen"]),
18507 "+OK\r\n"
18508 );
18509 assert_eq!(f.run(&[b"TS.ADD", b"t", b"100", b"1e300"]), ":100\r\n");
18510 assert_eq!(f.run(&[b"TS.GET", b"t"]), "*2\r\n:100\r\n,1e+300\r\n");
18512 assert_eq!(
18513 without_memory(&f.run(&[b"TS.INFO", b"t"])),
18514 "%14\r\n\
18515 +totalSamples\r\n:1\r\n\
18516 +memoryUsage\r\n:\r\n\
18517 +firstTimestamp\r\n:100\r\n\
18518 +lastTimestamp\r\n:100\r\n\
18519 +retentionTime\r\n:0\r\n\
18520 +chunkCount\r\n:1\r\n\
18521 +chunkSize\r\n:4096\r\n\
18522 +chunkType\r\n+compressed\r\n\
18523 +duplicatePolicy\r\n+block\r\n\
18524 +labels\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
18525 +sourceKey\r\n_\r\n\
18526 +rules\r\n%0\r\n\
18527 +ignoreMaxTimeDiff\r\n:0\r\n\
18528 +ignoreMaxValDiff\r\n,0\r\n"
18529 );
18530 }
18531
18532 #[test]
18535 fn a_range_walks_a_span_and_a_revrange_walks_it_backwards() {
18536 let mut f = Fixture::new();
18537 for (at, v) in [
18538 (b"100".as_slice(), b"1".as_slice()),
18539 (b"200", b"2"),
18540 (b"300", b"3"),
18541 (b"400", b"4"),
18542 ] {
18543 f.run(&[b"TS.ADD", b"t", at, v]);
18544 }
18545 assert_eq!(
18546 f.run(&[b"TS.RANGE", b"t", b"-", b"+"]),
18547 "*4\r\n*2\r\n:100\r\n+1\r\n*2\r\n:200\r\n+2\r\n\
18548 *2\r\n:300\r\n+3\r\n*2\r\n:400\r\n+4\r\n"
18549 );
18550 assert_eq!(
18552 f.run(&[b"TS.RANGE", b"t", b"150", b"350"]),
18553 "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
18554 );
18555 assert_eq!(
18558 f.run(&[b"TS.REVRANGE", b"t", b"-", b"+", b"COUNT", b"2"]),
18559 "*2\r\n*2\r\n:400\r\n+4\r\n*2\r\n:300\r\n+3\r\n"
18560 );
18561 assert_eq!(f.run(&[b"TS.RANGE", b"t", b"400", b"100"]), "*0\r\n");
18563 assert_eq!(
18565 f.run(&[
18566 b"TS.RANGE",
18567 b"t",
18568 b"-",
18569 b"+",
18570 b"FILTER_BY_VALUE",
18571 b"2",
18572 b"3"
18573 ]),
18574 "*2\r\n*2\r\n:200\r\n+2\r\n*2\r\n:300\r\n+3\r\n"
18575 );
18576 assert_eq!(
18577 f.run(&[
18578 b"TS.RANGE",
18579 b"t",
18580 b"-",
18581 b"+",
18582 b"FILTER_BY_TS",
18583 b"100",
18584 b"400"
18585 ]),
18586 "*2\r\n*2\r\n:100\r\n+1\r\n*2\r\n:400\r\n+4\r\n"
18587 );
18588 assert_eq!(
18590 f.run(&[
18591 b"TS.RANGE",
18592 b"t",
18593 b"-",
18594 b"+",
18595 b"ZZZ",
18596 b"FILTER_BY_TS",
18597 b"400"
18598 ]),
18599 "*1\r\n*2\r\n:400\r\n+4\r\n"
18600 );
18601 assert_eq!(
18603 f.run(&[b"TS.RANGE", b"t", b"-", b"+", b"LATEST", b"COUNT", b"1"]),
18604 "*1\r\n*2\r\n:100\r\n+1\r\n"
18605 );
18606 }
18607
18608 #[test]
18610 fn aggregation_puts_one_column_a_reduction_in_a_flat_row() {
18611 let mut f = Fixture::new();
18612 for (at, v) in [
18613 (b"100".as_slice(), b"1".as_slice()),
18614 (b"200", b"2"),
18615 (b"300", b"3"),
18616 (b"400", b"4"),
18617 ] {
18618 f.run(&[b"TS.ADD", b"t", at, v]);
18619 }
18620 assert_eq!(
18621 f.run(&[
18622 b"TS.RANGE",
18623 b"t",
18624 b"-",
18625 b"+",
18626 b"AGGREGATION",
18627 b"avg",
18628 b"200"
18629 ]),
18630 "*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"
18631 );
18632 assert_eq!(
18635 f.run(&[
18636 b"TS.RANGE",
18637 b"t",
18638 b"-",
18639 b"+",
18640 b"AGGREGATION",
18641 b"min,max,count",
18642 b"200"
18643 ]),
18644 "*3\r\n\
18645 *4\r\n:0\r\n+1\r\n+1\r\n+1\r\n\
18646 *4\r\n:200\r\n+2\r\n+3\r\n+2\r\n\
18647 *4\r\n:400\r\n+4\r\n+4\r\n+1\r\n"
18648 );
18649 assert_eq!(
18651 f.run(&[
18652 b"TS.RANGE",
18653 b"t",
18654 b"-",
18655 b"+",
18656 b"AGGREGATION",
18657 b"avg",
18658 b"200",
18659 b"BUCKETTIMESTAMP",
18660 b"+"
18661 ]),
18662 "*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"
18663 );
18664 assert_eq!(
18666 f.run(&[
18667 b"TS.RANGE",
18668 b"t",
18669 b"100",
18670 b"400",
18671 b"ALIGN",
18672 b"100",
18673 b"AGGREGATION",
18674 b"sum",
18675 b"200"
18676 ]),
18677 "*2\r\n*2\r\n:100\r\n+3\r\n*2\r\n:300\r\n+7\r\n"
18678 );
18679 assert_eq!(
18682 f.run(&[
18683 b"TS.RANGE",
18684 b"t",
18685 b"-",
18686 b"+",
18687 b"AGGREGATION",
18688 b"count",
18689 b"200"
18690 ]),
18691 "*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"
18692 );
18693 assert_eq!(
18694 f.run(&[
18695 b"TS.RANGE",
18696 b"t",
18697 b"-",
18698 b"+",
18699 b"AGGREGATION",
18700 b"count",
18701 b"200",
18702 b"COUNT",
18703 b"1"
18704 ]),
18705 "*1\r\n*2\r\n:0\r\n+1\r\n"
18706 );
18707 }
18708
18709 #[test]
18712 fn empty_fills_a_gap_and_last_carries_the_reading_before_it() {
18713 let mut f = Fixture::new();
18714 for (at, v) in [
18715 (b"0".as_slice(), b"1".as_slice()),
18716 (b"100", b"2"),
18717 (b"500", b"nan"),
18718 (b"600", b"3"),
18719 ] {
18720 f.run(&[b"TS.ADD", b"g", at, v]);
18721 }
18722 assert_eq!(
18725 f.run(&[
18726 b"TS.RANGE",
18727 b"g",
18728 b"-",
18729 b"+",
18730 b"AGGREGATION",
18731 b"avg",
18732 b"100"
18733 ]),
18734 "*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"
18735 );
18736 assert_eq!(
18738 f.run(&[
18739 b"TS.RANGE",
18740 b"g",
18741 b"-",
18742 b"+",
18743 b"AGGREGATION",
18744 b"sum",
18745 b"100",
18746 b"EMPTY"
18747 ]),
18748 "*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\
18749 *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\
18750 *2\r\n:600\r\n+3\r\n"
18751 );
18752 assert_eq!(
18757 f.run(&[
18758 b"TS.RANGE",
18759 b"g",
18760 b"-",
18761 b"+",
18762 b"AGGREGATION",
18763 b"last",
18764 b"100",
18765 b"EMPTY"
18766 ]),
18767 "*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\
18768 *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\
18769 *2\r\n:600\r\n+3\r\n"
18770 );
18771 assert_eq!(
18772 f.run(&[
18773 b"TS.REVRANGE",
18774 b"g",
18775 b"-",
18776 b"+",
18777 b"AGGREGATION",
18778 b"last",
18779 b"100",
18780 b"EMPTY"
18781 ]),
18782 "*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\
18783 *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\
18784 *2\r\n:0\r\n+1\r\n"
18785 );
18786 assert_eq!(
18789 f.run(&[
18790 b"TS.RANGE",
18791 b"g",
18792 b"500",
18793 b"600",
18794 b"AGGREGATION",
18795 b"last",
18796 b"100",
18797 b"EMPTY"
18798 ]),
18799 "*2\r\n*2\r\n:500\r\n+NaN\r\n*2\r\n:600\r\n+3\r\n"
18800 );
18801 }
18802
18803 #[test]
18806 fn a_range_says_what_the_module_says_when_the_options_do_not_add_up() {
18807 let mut f = Fixture::new();
18808 f.run(&[b"TS.ADD", b"t", b"100", b"1"]);
18809 f.run(&[b"SET", b"str", b"x"]);
18810 let cases: &[(&[&[u8]], &str)] = &[
18811 (
18812 &[b"TS.RANGE", b"t"],
18813 "-ERR wrong number of arguments for 'ts.range' command\r\n",
18814 ),
18815 (
18817 &[b"TS.RANGE", b"gone", b"-", b"+", b"COUNT", b"x"],
18818 "-ERR TSDB: the key does not exist\r\n",
18819 ),
18820 (
18821 &[b"TS.RANGE", b"str", b"-", b"+"],
18822 "-ERR WRONGTYPE Operation against a key holding the wrong kind of value\r\n",
18823 ),
18824 (
18825 &[b"TS.RANGE", b"t", b"abc", b"+"],
18826 "-ERR TSDB: wrong fromTimestamp\r\n",
18827 ),
18828 (
18829 &[b"TS.RANGE", b"t", b"-", b"abc"],
18830 "-ERR TSDB: wrong toTimestamp\r\n",
18831 ),
18832 (
18833 &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT"],
18834 "-ERR TSDB: COUNT argument is missing\r\n",
18835 ),
18836 (
18837 &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"x"],
18838 "-ERR TSDB: Couldn't parse COUNT\r\n",
18839 ),
18840 (
18841 &[b"TS.RANGE", b"t", b"-", b"+", b"COUNT", b"0"],
18842 "-ERR TSDB: Invalid COUNT value\r\n",
18843 ),
18844 (
18845 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg"],
18846 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
18847 ),
18848 (
18849 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"x"],
18850 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
18851 ),
18852 (
18853 &[
18854 b"TS.RANGE",
18855 b"t",
18856 b"-",
18857 b"+",
18858 b"AGGREGATION",
18859 b"nope",
18860 b"100",
18861 ],
18862 "-ERR TSDB: Unknown aggregation type\r\n",
18863 ),
18864 (
18865 &[
18866 b"TS.RANGE",
18867 b"t",
18868 b"-",
18869 b"+",
18870 b"AGGREGATION",
18871 b"avg,,min",
18872 b"100",
18873 ],
18874 "-ERR TSDB: Empty aggregation type in list\r\n",
18875 ),
18876 (
18878 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"nope", b"0"],
18879 "-ERR TSDB: Unknown aggregation type\r\n",
18880 ),
18881 (
18882 &[b"TS.RANGE", b"t", b"-", b"+", b"AGGREGATION", b"avg", b"0"],
18883 "-ERR TSDB: bucketDuration must be greater than zero\r\n",
18884 ),
18885 (
18886 &[
18887 b"TS.RANGE",
18888 b"t",
18889 b"-",
18890 b"+",
18891 b"AGGREGATION",
18892 b"avg",
18893 b"100",
18894 b"X",
18895 b"EMPTY",
18896 ],
18897 "-ERR TSDB: EMPTY flag should be the 3rd or 5th flag after AGGREGATION flag\r\n",
18898 ),
18899 (
18900 &[
18901 b"TS.RANGE",
18902 b"t",
18903 b"-",
18904 b"+",
18905 b"AGGREGATION",
18906 b"avg",
18907 b"100",
18908 b"BUCKETTIMESTAMP",
18909 b"z",
18910 ],
18911 "-ERR TSDB: unknown BUCKETTIMESTAMP parameter\r\n",
18912 ),
18913 (
18914 &[
18915 b"TS.RANGE",
18916 b"t",
18917 b"-",
18918 b"+",
18919 b"AGGREGATION",
18920 b"avg",
18921 b"100",
18922 b"X",
18923 b"Y",
18924 b"BUCKETTIMESTAMP",
18925 b"-",
18926 ],
18927 "-ERR TSDB: BUCKETTIMESTAMP flag should be the 3rd or 4th flag after \
18928 AGGREGATION flag\r\n",
18929 ),
18930 (
18931 &[
18932 b"TS.RANGE",
18933 b"t",
18934 b"-",
18935 b"+",
18936 b"ALIGN",
18937 b"z",
18938 b"AGGREGATION",
18939 b"avg",
18940 b"100",
18941 ],
18942 "-ERR TSDB: unknown ALIGN parameter\r\n",
18943 ),
18944 (
18945 &[b"TS.RANGE", b"t", b"-", b"+", b"ALIGN", b"5"],
18946 "-ERR TSDB: ALIGN parameter can only be used with AGGREGATION\r\n",
18947 ),
18948 (
18949 &[
18950 b"TS.RANGE",
18951 b"t",
18952 b"-",
18953 b"+",
18954 b"ALIGN",
18955 b"-",
18956 b"AGGREGATION",
18957 b"avg",
18958 b"100",
18959 ],
18960 "-ERR TSDB: start alignment can only be used with explicit start timestamp\r\n",
18961 ),
18962 (
18963 &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_VALUE", b"1"],
18964 "-ERR TSDB: FILTER_BY_VALUE one or more arguments are missing\r\n",
18965 ),
18966 (
18967 &[
18968 b"TS.RANGE",
18969 b"t",
18970 b"-",
18971 b"+",
18972 b"FILTER_BY_VALUE",
18973 b"x",
18974 b"2",
18975 ],
18976 "-ERR TSDB: Couldn't parse MIN\r\n",
18977 ),
18978 (
18979 &[
18980 b"TS.RANGE",
18981 b"t",
18982 b"-",
18983 b"+",
18984 b"FILTER_BY_VALUE",
18985 b"1",
18986 b"y",
18987 ],
18988 "-ERR TSDB: Couldn't parse MAX\r\n",
18989 ),
18990 (
18991 &[b"TS.RANGE", b"t", b"-", b"+", b"FILTER_BY_TS"],
18992 "-ERR TSDB: FILTER_BY_TS one or more arguments are missing\r\n",
18993 ),
18994 ];
18995 for (argv, want) in cases {
18996 let got = f.run(argv);
18997 assert_eq!(&got, want, "{:?}", argv.last());
18998 }
18999 f.run(&[b"TS.ADD", b"wide", b"0", b"1"]);
19003 f.run(&[b"TS.ADD", b"wide", b"1000000000000", b"2"]);
19004 assert_eq!(
19005 f.run(&[
19006 b"TS.RANGE",
19007 b"wide",
19008 b"-",
19009 b"+",
19010 b"AGGREGATION",
19011 b"avg",
19012 b"1",
19013 b"EMPTY"
19014 ]),
19015 "-ERR TSDB: the requested range holds too many empty buckets\r\n"
19016 );
19017 }
19018
19019 #[test]
19021 fn resp3_writes_a_read_value_as_a_double() {
19022 let mut f = Fixture::new();
19023 f.out = Out::new(Proto::Resp3);
19024 for (at, v) in [
19025 (b"0".as_slice(), b"1".as_slice()),
19026 (b"100", b"2"),
19027 (b"500", b"nan"),
19028 (b"600", b"3"),
19029 ] {
19030 f.run(&[b"TS.ADD", b"g", at, v]);
19031 }
19032 assert_eq!(
19033 f.run(&[
19034 b"TS.RANGE",
19035 b"g",
19036 b"0",
19037 b"100",
19038 b"AGGREGATION",
19039 b"avg,min",
19040 b"200"
19041 ]),
19042 "*1\r\n*3\r\n:0\r\n,1.5\r\n,1\r\n"
19043 );
19044 assert_eq!(
19045 f.run(&[
19046 b"TS.RANGE",
19047 b"g",
19048 b"500",
19049 b"600",
19050 b"AGGREGATION",
19051 b"last",
19052 b"100",
19053 b"EMPTY"
19054 ]),
19055 "*2\r\n*2\r\n:500\r\n,nan\r\n*2\r\n:600\r\n,3\r\n"
19056 );
19057 }
19058
19059 fn joined() -> Fixture {
19062 let mut f = Fixture::new();
19063 f.run(&[b"TS.CREATE", b"z"]);
19064 for (at, v) in [
19065 (b"10".as_slice(), b"1".as_slice()),
19066 (b"20", b"2"),
19067 (b"40", b"4"),
19068 (b"50", b"5"),
19069 ] {
19070 f.run(&[b"TS.ADD", b"x", at, v]);
19071 }
19072 for (at, v) in [
19073 (b"20".as_slice(), b"20".as_slice()),
19074 (b"30", b"30"),
19075 (b"50", b"50"),
19076 (b"60", b"60"),
19077 ] {
19078 f.run(&[b"TS.ADD", b"y", at, v]);
19079 }
19080 f
19081 }
19082
19083 #[test]
19087 fn an_nrange_joins_its_keys_on_the_timestamp() {
19088 let mut f = joined();
19089 assert_eq!(
19091 f.run(&[b"TS.NRANGE", b"1", b"x", b"-", b"+"]),
19092 "*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\
19093 *2\r\n:40\r\n*1\r\n+4\r\n*2\r\n:50\r\n*1\r\n+5\r\n"
19094 );
19095 assert_eq!(
19097 f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+"]),
19098 "*6\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n\
19099 *2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
19100 *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
19101 *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
19102 *2\r\n:50\r\n*2\r\n+5\r\n+50\r\n\
19103 *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
19104 );
19105 assert_eq!(
19108 f.run(&[b"TS.NRANGE", b"2", b"x", b"z", b"20", b"40"]),
19109 "*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"
19110 );
19111 assert_eq!(
19112 f.run(&[b"TS.NRANGE", b"2", b"x", b"x", b"40", b"50"]),
19113 "*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"
19114 );
19115 assert_eq!(
19118 f.run(&[
19119 b"TS.NREVRANGE",
19120 b"2",
19121 b"x",
19122 b"y",
19123 b"-",
19124 b"+",
19125 b"COUNT",
19126 b"1"
19127 ]),
19128 "*1\r\n*2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
19129 );
19130 assert_eq!(
19131 f.run(&[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"+", b"COUNT", b"1"]),
19132 "*1\r\n*2\r\n:10\r\n*2\r\n+1\r\n+NaN\r\n"
19133 );
19134 assert_eq!(
19136 f.run(&[
19137 b"TS.NRANGE",
19138 b"2",
19139 b"x",
19140 b"y",
19141 b"-",
19142 b"+",
19143 b"FILTER_BY_VALUE",
19144 b"2",
19145 b"30"
19146 ]),
19147 "*4\r\n*2\r\n:20\r\n*2\r\n+2\r\n+20\r\n\
19148 *2\r\n:30\r\n*2\r\n+NaN\r\n+30\r\n\
19149 *2\r\n:40\r\n*2\r\n+4\r\n+NaN\r\n\
19150 *2\r\n:50\r\n*2\r\n+5\r\n+NaN\r\n"
19151 );
19152 }
19153
19154 #[test]
19158 fn an_nrange_aggregation_names_one_reduction_a_key() {
19159 let mut f = joined();
19160 assert_eq!(
19161 f.run(&[
19162 b"TS.NRANGE",
19163 b"2",
19164 b"x",
19165 b"y",
19166 b"-",
19167 b"+",
19168 b"AGGREGATION",
19169 b"sum",
19170 b"sum",
19171 b"20"
19172 ]),
19173 "*4\r\n*2\r\n:0\r\n*2\r\n+1\r\n+NaN\r\n\
19174 *2\r\n:20\r\n*2\r\n+2\r\n+50\r\n\
19175 *2\r\n:40\r\n*2\r\n+9\r\n+50\r\n\
19176 *2\r\n:60\r\n*2\r\n+NaN\r\n+60\r\n"
19177 );
19178 assert_eq!(
19180 f.run(&[
19181 b"TS.NRANGE",
19182 b"2",
19183 b"x",
19184 b"y",
19185 b"-",
19186 b"+",
19187 b"AGGREGATION",
19188 b"sum,count",
19189 b"avg",
19190 b"20"
19191 ]),
19192 "*4\r\n*2\r\n:0\r\n*3\r\n+1\r\n+1\r\n+NaN\r\n\
19193 *2\r\n:20\r\n*3\r\n+2\r\n+1\r\n+25\r\n\
19194 *2\r\n:40\r\n*3\r\n+9\r\n+2\r\n+50\r\n\
19195 *2\r\n:60\r\n*3\r\n+NaN\r\n+NaN\r\n+60\r\n"
19196 );
19197 assert_eq!(
19200 f.run(&[
19201 b"TS.NRANGE",
19202 b"2",
19203 b"x",
19204 b"y",
19205 b"-",
19206 b"+",
19207 b"AGGREGATION",
19208 b"avg",
19209 b"sum",
19210 b"100",
19211 b"EMPTY",
19212 b"BUCKETTIMESTAMP",
19213 b"end"
19214 ]),
19215 "*1\r\n*2\r\n:100\r\n*2\r\n+3\r\n+160\r\n"
19216 );
19217 assert_eq!(
19220 f.run(&[
19221 b"TS.NRANGE",
19222 b"2",
19223 b"x",
19224 b"y",
19225 b"-",
19226 b"+",
19227 b"AGGREGATION",
19228 b"avg",
19229 b"COUNT",
19230 b"100"
19231 ]),
19232 "*1\r\n*2\r\n:0\r\n*2\r\n+3\r\n+4\r\n"
19233 );
19234 }
19235
19236 #[test]
19239 fn an_nrange_says_what_the_module_says_when_it_does_not_add_up() {
19240 let mut f = joined();
19241 f.run(&[b"SET", b"str", b"hi"]);
19242 let bad_keys = "-ERR TSDB: numkeys must be a positive integer\r\n";
19243 let numkeys = "-ERR TSDB: the number of AGGREGATION arguments \
19244 must be equal to numkeys\r\n";
19245 let cases: &[(&[&[u8]], &str)] = &[
19246 (&[b"TS.NRANGE", b"0", b"x", b"-", b"+"], bad_keys),
19247 (&[b"TS.NRANGE", b"-1", b"x", b"-", b"+"], bad_keys),
19248 (&[b"TS.NRANGE", b"abc", b"x", b"-", b"+"], bad_keys),
19249 (
19252 &[b"TS.NRANGE", b"2", b"x", b"-", b"+"],
19253 "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
19254 ),
19255 (
19256 &[b"TS.NRANGE", b"99", b"x", b"-", b"+"],
19257 "-ERR wrong number of arguments for 'ts.nrange' command\r\n",
19258 ),
19259 (
19262 &[
19263 b"TS.NRANGE",
19264 b"2",
19265 b"x",
19266 b"y",
19267 b"abc",
19268 b"+",
19269 b"AGGREGATION",
19270 b"nope",
19271 b"sum",
19272 b"100",
19273 ],
19274 "-ERR TSDB: Unknown aggregation type\r\n",
19275 ),
19276 (
19277 &[b"TS.NRANGE", b"2", b"x", b"y", b"abc", b"+"],
19278 "-ERR TSDB: wrong fromTimestamp\r\n",
19279 ),
19280 (
19281 &[b"TS.NRANGE", b"2", b"x", b"y", b"-", b"abc"],
19282 "-ERR TSDB: wrong toTimestamp\r\n",
19283 ),
19284 (
19288 &[
19289 b"TS.NRANGE",
19290 b"2",
19291 b"x",
19292 b"y",
19293 b"-",
19294 b"+",
19295 b"AGGREGATION",
19296 b"avg",
19297 ],
19298 numkeys,
19299 ),
19300 (
19301 &[
19302 b"TS.NRANGE",
19303 b"2",
19304 b"x",
19305 b"y",
19306 b"-",
19307 b"+",
19308 b"AGGREGATION",
19309 b"100",
19310 b"sum",
19311 b"100",
19312 ],
19313 numkeys,
19314 ),
19315 (
19316 &[
19317 b"TS.NRANGE",
19318 b"2",
19319 b"x",
19320 b"y",
19321 b"-",
19322 b"+",
19323 b"AGGREGATION",
19324 b"avg",
19325 b"sum",
19326 b"sum",
19327 b"100",
19328 ],
19329 numkeys,
19330 ),
19331 (
19332 &[
19333 b"TS.NRANGE",
19334 b"2",
19335 b"x",
19336 b"y",
19337 b"-",
19338 b"+",
19339 b"AGGREGATION",
19340 b"avg",
19341 b"sum",
19342 b"abc",
19343 ],
19344 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
19345 ),
19346 (
19347 &[
19348 b"TS.NRANGE",
19349 b"2",
19350 b"x",
19351 b"y",
19352 b"-",
19353 b"+",
19354 b"AGGREGATION",
19355 b"avg",
19356 b"sum",
19357 b"0",
19358 ],
19359 "-ERR TSDB: bucketDuration must be greater than zero\r\n",
19360 ),
19361 (
19364 &[b"TS.NRANGE", b"1", b"x", b"-", b"+", b"AGGREGATION", b"100"],
19365 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
19366 ),
19367 (
19368 &[
19369 b"TS.NRANGE",
19370 b"1",
19371 b"x",
19372 b"-",
19373 b"+",
19374 b"AGGREGATION",
19375 b"100",
19376 b"200",
19377 ],
19378 "-ERR TSDB: Unknown aggregation type\r\n",
19379 ),
19380 (
19382 &[b"TS.NRANGE", b"2", b"x", b"nope", b"-", b"+"],
19383 "-ERR TSDB: the key does not exist\r\n",
19384 ),
19385 (
19386 &[b"TS.NRANGE", b"2", b"str", b"nope", b"-", b"+"],
19387 "-ERR WRONGTYPE Operation against a key \
19388 holding the wrong kind of value\r\n",
19389 ),
19390 ];
19391 for (argv, want) in cases {
19392 let got = f.run(argv);
19393 assert_eq!(&got, want, "{argv:?}");
19394 }
19395 }
19396
19397 #[test]
19399 fn a_read_walks_from_a_timestamp_to_the_end_of_the_series() {
19400 let mut f = joined();
19401 assert_eq!(
19402 f.run(&[b"TS.READ", b"x", b"-"]),
19403 "*4\r\n*2\r\n:10\r\n+1\r\n*2\r\n:20\r\n+2\r\n\
19404 *2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
19405 );
19406 assert_eq!(
19409 f.run(&[b"TS.READ", b"x", b"+"]),
19410 "*1\r\n*2\r\n:50\r\n+5\r\n"
19411 );
19412 assert_eq!(
19413 f.run(&[b"TS.READ", b"x", b"25"]),
19414 "*2\r\n*2\r\n:40\r\n+4\r\n*2\r\n:50\r\n+5\r\n"
19415 );
19416 assert_eq!(f.run(&[b"TS.READ", b"x", b"99"]), "*0\r\n");
19419 assert_eq!(f.run(&[b"TS.READ", b"z", b"-"]), "*0\r\n");
19420 assert_eq!(f.run(&[b"TS.READ", b"z", b"+"]), "*0\r\n");
19421 assert_eq!(f.run(&[b"TS.READ", b"nope", b"-"]), "*0\r\n");
19422 assert_eq!(
19426 f.run(&[b"TS.READ", b"x", b"abc"]),
19427 "-TSDB: invalid timestamp\r\n"
19428 );
19429 assert_eq!(
19430 f.run(&[b"TS.READ", b"x", b"-1"]),
19431 "-TSDB: invalid timestamp\r\n"
19432 );
19433 f.run(&[b"SET", b"str", b"hi"]);
19434 assert_eq!(
19435 f.run(&[b"TS.READ", b"str", b"-"]),
19436 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
19437 );
19438 assert_eq!(
19441 f.run(&[b"TS.READ", b"x"]),
19442 "-ERR wrong number of arguments for 'ts.read' command\r\n"
19443 );
19444 assert_eq!(
19445 f.run(&[b"TS.READ", b"x", b"-", b"COUNT", b"1"]),
19446 "-ERR wrong number of arguments for 'ts.read' command\r\n"
19447 );
19448 }
19449
19450 #[test]
19453 fn getkeys_reads_the_count_of_a_joined_read() {
19454 let mut f = Fixture::new();
19455 assert_eq!(
19456 f.run(&[
19457 b"COMMAND",
19458 b"GETKEYS",
19459 b"TS.NRANGE",
19460 b"2",
19461 b"a",
19462 b"b",
19463 b"-",
19464 b"+"
19465 ]),
19466 "*2\r\n$1\r\na\r\n$1\r\nb\r\n"
19467 );
19468 assert_eq!(
19469 f.run(&[
19470 b"COMMAND",
19471 b"GETKEYS",
19472 b"TS.NREVRANGE",
19473 b"1",
19474 b"a",
19475 b"-",
19476 b"+"
19477 ]),
19478 "*1\r\n$1\r\na\r\n"
19479 );
19480 for n in [b"0".as_slice(), b"9", b"abc"] {
19483 assert_eq!(
19484 f.run(&[b"COMMAND", b"GETKEYS", b"TS.NRANGE", n, b"a", b"-", b"+"]),
19485 "-ERR Invalid arguments specified for command\r\n"
19486 );
19487 }
19488 }
19489
19490 fn labelled() -> Fixture {
19492 let mut f = Fixture::new();
19493 f.run(&[
19494 b"TS.CREATE",
19495 b"a",
19496 b"LABELS",
19497 b"room",
19498 b"kitchen",
19499 b"x",
19500 b"1",
19501 ]);
19502 f.run(&[
19503 b"TS.CREATE",
19504 b"b",
19505 b"LABELS",
19506 b"room",
19507 b"bedroom",
19508 b"x",
19509 b"2",
19510 ]);
19511 f.run(&[b"TS.CREATE", b"c", b"LABELS", b"room", b"kitchen"]);
19512 f.run(&[b"TS.CREATE", b"d"]);
19513 f.run(&[b"TS.CREATE", b"e", b"LABELS", b"r", b"bb", b"r", b"b"]);
19514 f.run(&[b"TS.ADD", b"a", b"100", b"1.5"]);
19515 f.run(&[b"TS.ADD", b"b", b"200", b"2"]);
19516 f
19517 }
19518
19519 #[test]
19522 fn a_filter_is_taken_apart_the_way_the_module_takes_one_apart() {
19523 let mut f = labelled();
19524 let cases: &[(&[&[u8]], &str)] = &[
19525 (
19528 &[b"TS.QUERYINDEX", b"room=kitchen"],
19529 "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
19530 ),
19531 (
19532 &[b"TS.QUERYINDEX", b"room=kitchen", b"x=1"],
19533 "*1\r\n$1\r\na\r\n",
19534 ),
19535 (
19536 &[b"TS.QUERYINDEX", b"room=(kitchen,bedroom)"],
19537 "*3\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n",
19538 ),
19539 (&[b"TS.QUERYINDEX", b"room=()"], "*0\r\n"),
19542 (
19544 &[b"TS.QUERYINDEX", b"x=", b"room=kitchen"],
19545 "*1\r\n$1\r\nc\r\n",
19546 ),
19547 (
19548 &[b"TS.QUERYINDEX", b"room=kitchen", b"x!="],
19549 "*1\r\n$1\r\na\r\n",
19550 ),
19551 (
19552 &[b"TS.QUERYINDEX", b"room!=kitchen", b"x!="],
19553 "-ERR TSDB: please provide at least one matcher\r\n",
19554 ),
19555 (
19558 &[b"TS.QUERYINDEX", b"room==kitchen"],
19559 "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
19560 ),
19561 (
19562 &[b"TS.QUERYINDEX", b"room=kitchen=zz"],
19563 "*2\r\n$1\r\na\r\n$1\r\nc\r\n",
19564 ),
19565 (&[b"TS.QUERYINDEX", b"room!!=kitchen", b"x=1"], "*0\r\n"),
19566 (&[b"TS.QUERYINDEX", b"()=1"], "*0\r\n"),
19569 (
19570 &[b"TS.QUERYINDEX", b"=(1)"],
19571 "-ERR TSDB: failed parsing labels\r\n",
19572 ),
19573 (
19574 &[b"TS.QUERYINDEX", b"room=(kitchen,)"],
19575 "-ERR TSDB: failed parsing labels\r\n",
19576 ),
19577 (
19578 &[b"TS.QUERYINDEX", b"room=(kitchen"],
19579 "-ERR TSDB: failed parsing labels\r\n",
19580 ),
19581 (&[b"TS.QUERYINDEX", b"room=x()"], "*0\r\n"),
19582 (
19583 &[b"TS.QUERYINDEX", b"nonsense"],
19584 "-ERR TSDB: failed parsing labels\r\n",
19585 ),
19586 (
19588 &[b"TS.QUERYINDEX", b"room!=kitchen"],
19589 "-ERR TSDB: please provide at least one matcher\r\n",
19590 ),
19591 (&[b"TS.QUERYINDEX", b"ROOM=kitchen"], "*0\r\n"),
19593 (&[b"TS.QUERYINDEX", b"room=KITCHEN"], "*0\r\n"),
19594 (
19595 &[b"TS.QUERYINDEX"],
19596 "-ERR wrong number of arguments for 'ts.queryindex' command\r\n",
19597 ),
19598 ];
19599 for (argv, want) in cases {
19600 let got = f.run(argv);
19601 assert_eq!(&got, want, "{:?}", argv.last());
19602 }
19603 }
19604
19605 #[test]
19607 fn querylabels_says_which_names_are_worn_and_what_they_are_set_to() {
19608 let mut f = labelled();
19609 let cases: &[(&[&[u8]], &str)] = &[
19610 (
19611 &[b"TS.QUERYLABELS", b"LABELS"],
19612 "*3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
19613 ),
19614 (
19615 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=kitchen"],
19616 "*2\r\n$4\r\nroom\r\n$1\r\nx\r\n",
19617 ),
19618 (
19619 &[b"TS.QUERYLABELS", b"VALUES", b"room"],
19620 "*2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
19621 ),
19622 (&[b"TS.QUERYLABELS", b"VALUES", b"r"], "*1\r\n$1\r\nb\r\n"),
19625 (&[b"TS.QUERYLABELS", b"VALUES", b"nolabel"], "*0\r\n"),
19626 (
19627 &[b"TS.QUERYLABELS", b"VALUES"],
19628 "-ERR wrong number of arguments for 'ts.querylabels' command\r\n",
19629 ),
19630 (
19631 &[b"TS.QUERYLABELS", b"ZZZ"],
19632 "-ERR TSDB: unknown subtype, must be one of LABELS|VALUES\r\n",
19633 ),
19634 (
19635 &[b"TS.QUERYLABELS", b"LABELS", b"ZZZ"],
19636 "-ERR TSDB: unknown argument, expected FILTER\r\n",
19637 ),
19638 (
19639 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER"],
19640 "-ERR TSDB: FILTER given with no filter expressions\r\n",
19641 ),
19642 (
19646 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room!=kitchen"],
19647 "-ERR TSDB: please provide at least one matcher\r\n",
19648 ),
19649 (
19650 &[
19651 b"TS.QUERYLABELS",
19652 b"LABELS",
19653 b"FILTER",
19654 b"room=kitchen",
19655 b"x=",
19656 ],
19657 "*1\r\n$4\r\nroom\r\n",
19658 ),
19659 ];
19660 for (argv, want) in cases {
19661 let got = f.run(argv);
19662 assert_eq!(&got, want, "{:?}", argv.last());
19663 }
19664 }
19665
19666 #[test]
19669 fn mget_writes_the_newest_sample_and_the_labels_that_were_asked_for() {
19670 let mut f = labelled();
19671 let cases: &[(&[&[u8]], &str)] = &[
19672 (
19675 &[b"TS.MGET", b"FILTER", b"room=kitchen"],
19676 "*2\r\n*3\r\n$1\r\na\r\n*0\r\n*2\r\n:100\r\n+1.5\r\n\
19677 *3\r\n$1\r\nc\r\n*0\r\n*0\r\n",
19678 ),
19679 (
19680 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
19681 "*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\
19682 *2\r\n$1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n+1.5\r\n\
19683 *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",
19684 ),
19685 (
19687 &[
19688 b"TS.MGET",
19689 b"SELECTED_LABELS",
19690 b"x",
19691 b"FILTER",
19692 b"room=kitchen",
19693 ],
19694 "*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\
19695 *2\r\n:100\r\n+1.5\r\n\
19696 *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",
19697 ),
19698 (
19701 &[b"TS.MGET", b"SELECTED_LABELS", b"r", b"FILTER", b"r=b"],
19702 "*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",
19703 ),
19704 (
19705 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
19706 "*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\
19707 *2\r\n$1\r\nr\r\n$1\r\nb\r\n*0\r\n",
19708 ),
19709 (
19712 &[b"TS.MGET", b"ZZZ", b"FILTER", b"room=bedroom"],
19713 "*1\r\n*3\r\n$1\r\nb\r\n*0\r\n*2\r\n:200\r\n+2\r\n",
19714 ),
19715 (
19716 &[b"TS.MGET", b"a", b"b", b"c"],
19717 "-ERR wrong number of arguments for 'ts.mget' command\r\n",
19718 ),
19719 (
19720 &[b"TS.MGET", b"FILTER"],
19721 "-ERR wrong number of arguments for 'ts.mget' command\r\n",
19722 ),
19723 (
19726 &[
19727 b"TS.MGET",
19728 b"WITHLABELS",
19729 b"SELECTED_LABELS",
19730 b"x",
19731 b"FILTER",
19732 b"bad",
19733 ],
19734 "-ERR TSDB: cannot accept WITHLABELS and SELECT_LABELS together\r\n",
19735 ),
19736 (
19737 &[b"TS.MGET", b"SELECTED_LABELS", b"FILTER", b"bad"],
19738 "-ERR TSDB: SELECT_LABELS should have at least 1 parameter\r\n",
19739 ),
19740 ];
19741 for (argv, want) in cases {
19742 let got = f.run(argv);
19743 assert_eq!(&got, want, "{:?}", argv.last());
19744 }
19745 }
19746
19747 #[test]
19750 fn resp3_writes_the_label_surface_as_sets_and_maps() {
19751 let mut f = labelled();
19752 f.out = Out::new(Proto::Resp3);
19753 let cases: &[(&[&[u8]], &str)] = &[
19754 (
19755 &[b"TS.QUERYINDEX", b"room=kitchen"],
19756 "~2\r\n$1\r\na\r\n$1\r\nc\r\n",
19757 ),
19758 (
19759 &[b"TS.QUERYLABELS", b"LABELS"],
19760 "~3\r\n$1\r\nr\r\n$4\r\nroom\r\n$1\r\nx\r\n",
19761 ),
19762 (
19763 &[b"TS.QUERYLABELS", b"VALUES", b"room"],
19764 "~2\r\n$7\r\nbedroom\r\n$7\r\nkitchen\r\n",
19765 ),
19766 (
19769 &[b"TS.MGET", b"FILTER", b"room=kitchen"],
19770 "%2\r\n$1\r\na\r\n*2\r\n%0\r\n*2\r\n:100\r\n,1.5\r\n\
19771 $1\r\nc\r\n*2\r\n%0\r\n*0\r\n",
19772 ),
19773 (
19774 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=kitchen"],
19775 "%2\r\n$1\r\na\r\n*2\r\n%2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
19776 $1\r\nx\r\n$1\r\n1\r\n*2\r\n:100\r\n,1.5\r\n\
19777 $1\r\nc\r\n*2\r\n%1\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n*0\r\n",
19778 ),
19779 (
19780 &[
19781 b"TS.MGET",
19782 b"SELECTED_LABELS",
19783 b"x",
19784 b"FILTER",
19785 b"room=kitchen",
19786 ],
19787 "%2\r\n$1\r\na\r\n*2\r\n%1\r\n$1\r\nx\r\n$1\r\n1\r\n\
19788 *2\r\n:100\r\n,1.5\r\n\
19789 $1\r\nc\r\n*2\r\n%1\r\n$1\r\nx\r\n_\r\n*0\r\n",
19790 ),
19791 (
19794 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"r=b"],
19795 "%1\r\n$1\r\ne\r\n*2\r\n%2\r\n$1\r\nr\r\n$2\r\nbb\r\n\
19796 $1\r\nr\r\n$1\r\nb\r\n*0\r\n",
19797 ),
19798 ];
19799 for (argv, want) in cases {
19800 let got = f.run(argv);
19801 assert_eq!(&got, want, "{:?}", argv.last());
19802 }
19803 }
19804
19805 fn spanned() -> Fixture {
19808 let mut f = labelled();
19809 f.run(&[b"TS.ADD", b"a", b"200", b"2.5"]);
19810 f.run(&[b"TS.ADD", b"c", b"100", b"10"]);
19811 f.run(&[b"TS.ADD", b"c", b"300", b"30"]);
19812 f
19813 }
19814
19815 #[test]
19818 fn mrange_reads_every_series_and_folds_the_groups_it_is_asked_for() {
19819 let mut f = spanned();
19820 let cases: &[(&[&[u8]], &str)] = &[
19821 (
19822 &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=kitchen"],
19823 "*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\
19824 *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",
19825 ),
19826 (
19829 &[
19830 b"TS.MREVRANGE",
19831 b"-",
19832 b"+",
19833 b"WITHLABELS",
19834 b"FILTER",
19835 b"room=kitchen",
19836 ],
19837 "*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\
19838 *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\
19839 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$4\r\nroom\r\n$7\r\nkitchen\r\n\
19840 *2\r\n*2\r\n:300\r\n+30\r\n*2\r\n:100\r\n+10\r\n",
19841 ),
19842 (
19845 &[
19846 b"TS.MRANGE",
19847 b"-",
19848 b"+",
19849 b"SELECTED_LABELS",
19850 b"x",
19851 b"FILTER",
19852 b"room=kitchen",
19853 ],
19854 "*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\
19855 *2\r\n*2\r\n:100\r\n+1.5\r\n*2\r\n:200\r\n+2.5\r\n\
19856 *3\r\n$1\r\nc\r\n*1\r\n*2\r\n$1\r\nx\r\n$-1\r\n\
19857 *2\r\n*2\r\n:100\r\n+10\r\n*2\r\n:300\r\n+30\r\n",
19858 ),
19859 (
19862 &[
19863 b"TS.MRANGE",
19864 b"-",
19865 b"+",
19866 b"FILTER",
19867 b"room=kitchen",
19868 b"GROUPBY",
19869 b"room",
19870 b"REDUCE",
19871 b"sum",
19872 ],
19873 "*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\
19874 *2\r\n:200\r\n+2.5\r\n*2\r\n:300\r\n+30\r\n",
19875 ),
19876 (
19879 &[
19880 b"TS.MRANGE",
19881 b"-",
19882 b"+",
19883 b"WITHLABELS",
19884 b"FILTER",
19885 b"room=kitchen",
19886 b"GROUPBY",
19887 b"room",
19888 b"REDUCE",
19889 b"max",
19890 ],
19891 "*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\
19892 *2\r\n$11\r\n__reducer__\r\n$3\r\nmax\r\n\
19893 *2\r\n$10\r\n__source__\r\n$3\r\na,c\r\n\
19894 *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",
19895 ),
19896 (
19898 &[
19899 b"TS.MREVRANGE",
19900 b"-",
19901 b"+",
19902 b"COUNT",
19903 b"1",
19904 b"FILTER",
19905 b"room=kitchen",
19906 b"GROUPBY",
19907 b"room",
19908 b"REDUCE",
19909 b"count",
19910 ],
19911 "*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",
19912 ),
19913 (
19915 &[
19916 b"TS.MRANGE",
19917 b"-",
19918 b"+",
19919 b"FILTER",
19920 b"room=kitchen",
19921 b"GROUPBY",
19922 b"nope",
19923 b"REDUCE",
19924 b"sum",
19925 ],
19926 "*0\r\n",
19927 ),
19928 (
19929 &[
19930 b"TS.MRANGE",
19931 b"-",
19932 b"+",
19933 b"AGGREGATION",
19934 b"sum,avg",
19935 b"100",
19936 b"FILTER",
19937 b"room=bedroom",
19938 ],
19939 "*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",
19940 ),
19941 (
19943 &[b"TS.MRANGE", b"-", b"+", b"room=kitchen"],
19944 "-ERR TSDB: missing FILTER argument\r\n",
19945 ),
19946 (
19947 &[b"TS.MRANGE", b"-", b"+", b"FILTER"],
19948 "-ERR TSDB: missing labels for filter argument\r\n",
19949 ),
19950 (
19951 &[
19952 b"TS.MRANGE",
19953 b"-",
19954 b"+",
19955 b"GROUPBY",
19956 b"room",
19957 b"REDUCE",
19958 b"sum",
19959 b"FILTER",
19960 b"room=kitchen",
19961 ],
19962 "-ERR TSDB: GROUPBY should always come after filter\r\n",
19963 ),
19964 (
19967 &[
19968 b"TS.MRANGE",
19969 b"-",
19970 b"+",
19971 b"FILTER",
19972 b"room=kitchen",
19973 b"GROUPBY",
19974 b"room",
19975 b"REDUCE",
19976 b"sum",
19977 b"x",
19978 ],
19979 "-ERR wrong number of arguments for 'ts.mrange' command\r\n",
19980 ),
19981 (
19983 &[
19984 b"TS.MRANGE",
19985 b"-",
19986 b"+",
19987 b"FILTER",
19988 b"nope",
19989 b"GROUPBY",
19990 b"room",
19991 b"REDUCE",
19992 b"sum",
19993 b"x",
19994 ],
19995 "-ERR TSDB: failed parsing labels\r\n",
19996 ),
19997 (
19998 &[
19999 b"TS.MRANGE",
20000 b"-",
20001 b"+",
20002 b"FILTER",
20003 b"room=kitchen",
20004 b"GROUPBY",
20005 b"room",
20006 b"REDUCE",
20007 b"twa",
20008 ],
20009 "-ERR TSDB: Invalid reducer type\r\n",
20010 ),
20011 (
20012 &[
20013 b"TS.MRANGE",
20014 b"-",
20015 b"+",
20016 b"AGGREGATION",
20017 b"sum,avg",
20018 b"100",
20019 b"FILTER",
20020 b"room=kitchen",
20021 b"GROUPBY",
20022 b"room",
20023 b"REDUCE",
20024 b"sum",
20025 ],
20026 "-ERR TSDB: GROUPBY is not allowed when multiple aggregators are specified\r\n",
20027 ),
20028 (
20031 &[
20032 b"TS.MRANGE",
20033 b"-",
20034 b"+",
20035 b"SELECTED_LABELS",
20036 b"COUNT",
20037 b"FILTER",
20038 b"room=kitchen",
20039 ],
20040 "-ERR TSDB: Couldn't parse COUNT\r\n",
20041 ),
20042 ];
20043 for (argv, want) in cases {
20044 let got = f.run(argv);
20045 assert_eq!(&got, want, "{argv:?}");
20046 }
20047 }
20048
20049 #[test]
20052 fn resp3_writes_a_multi_key_read_as_a_map_of_four() {
20053 let mut f = spanned();
20054 f.out = Out::new(Proto::Resp3);
20055 let cases: &[(&[&[u8]], &str)] = &[
20056 (
20057 &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=bedroom"],
20058 "%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\
20059 *1\r\n*2\r\n:200\r\n,2\r\n",
20060 ),
20061 (
20064 &[
20065 b"TS.MRANGE",
20066 b"-",
20067 b"+",
20068 b"AGGREGATION",
20069 b"sum,avg",
20070 b"100",
20071 b"FILTER",
20072 b"room=bedroom",
20073 ],
20074 "%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\
20075 $3\r\navg\r\n*1\r\n*3\r\n:200\r\n,2\r\n,2\r\n",
20076 ),
20077 (
20078 &[
20079 b"TS.MRANGE",
20080 b"-",
20081 b"+",
20082 b"FILTER",
20083 b"room=kitchen",
20084 b"GROUPBY",
20085 b"room",
20086 b"REDUCE",
20087 b"sum",
20088 ],
20089 "%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\
20090 $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\
20091 *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",
20092 ),
20093 (
20096 &[
20097 b"TS.MRANGE",
20098 b"-",
20099 b"+",
20100 b"WITHLABELS",
20101 b"FILTER",
20102 b"room=kitchen",
20103 b"GROUPBY",
20104 b"room",
20105 b"REDUCE",
20106 b"max",
20107 ],
20108 "%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\
20109 %1\r\n$8\r\nreducers\r\n*1\r\n$3\r\nmax\r\n\
20110 %1\r\n$7\r\nsources\r\n*2\r\n$1\r\na\r\n$1\r\nc\r\n\
20111 *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",
20112 ),
20113 (
20114 &[
20115 b"TS.MRANGE",
20116 b"-",
20117 b"+",
20118 b"FILTER",
20119 b"room=kitchen",
20120 b"GROUPBY",
20121 b"nope",
20122 b"REDUCE",
20123 b"sum",
20124 ],
20125 "%0\r\n",
20126 ),
20127 ];
20128 for (argv, want) in cases {
20129 let got = f.run(argv);
20130 assert_eq!(&got, want, "{argv:?}");
20131 }
20132 }
20133
20134 #[test]
20136 fn createrule_checks_the_two_keys_last_and_the_two_links_after_that() {
20137 let mut f = Fixture::new();
20138 f.run(&[b"TS.CREATE", b"src"]);
20139 f.run(&[b"TS.CREATE", b"dst"]);
20140 f.run(&[b"SET", b"plain", b"v"]);
20141 let cases: &[(&[&[u8]], &str)] = &[
20142 (
20146 &[
20147 b"TS.CREATERULE",
20148 b"src",
20149 b"dst",
20150 b"AGGREGATION",
20151 b"nope",
20152 b"x",
20153 ],
20154 "-ERR TSDB: Couldn't parse AGGREGATION\r\n",
20155 ),
20156 (
20157 &[
20158 b"TS.CREATERULE",
20159 b"src",
20160 b"dst",
20161 b"AGGREGATION",
20162 b"nope",
20163 b"10",
20164 ],
20165 "-ERR TSDB: Unknown aggregation type\r\n",
20166 ),
20167 (
20168 &[
20169 b"TS.CREATERULE",
20170 b"src",
20171 b"dst",
20172 b"AGGREGATION",
20173 b"avg",
20174 b"0",
20175 ],
20176 "-ERR TSDB: bucketDuration must be greater than zero\r\n",
20177 ),
20178 (
20179 &[
20180 b"TS.CREATERULE",
20181 b"src",
20182 b"dst",
20183 b"AGGREGATION",
20184 b"avg",
20185 b"10",
20186 b"x",
20187 ],
20188 "-ERR TSDB: Couldn't parse alignTimestamp\r\n",
20189 ),
20190 (
20191 &[
20192 b"TS.CREATERULE",
20193 b"src",
20194 b"src",
20195 b"AGGREGATION",
20196 b"avg",
20197 b"10",
20198 ],
20199 "-ERR TSDB: the source key and destination key should be different\r\n",
20200 ),
20201 (
20205 &[
20206 b"TS.CREATERULE",
20207 b"nope",
20208 b"plain",
20209 b"AGGREGATION",
20210 b"avg",
20211 b"10",
20212 ],
20213 "-ERR TSDB: the key does not exist\r\n",
20214 ),
20215 (
20216 &[
20217 b"TS.CREATERULE",
20218 b"src",
20219 b"nope",
20220 b"AGGREGATION",
20221 b"avg",
20222 b"10",
20223 ],
20224 "-ERR TSDB: the key does not exist\r\n",
20225 ),
20226 (
20229 &[b"TS.CREATERULE", b"src", b"dst", b"NOPE", b"avg", b"10"],
20230 "-ERR wrong number of arguments for 'ts.createrule' command\r\n",
20231 ),
20232 (
20233 &[
20234 b"TS.CREATERULE",
20235 b"src",
20236 b"dst",
20237 b"AGGREGATION",
20238 b"avg",
20239 b"10",
20240 ],
20241 "+OK\r\n",
20242 ),
20243 (
20246 &[
20247 b"TS.CREATERULE",
20248 b"src",
20249 b"dst",
20250 b"AGGREGATION",
20251 b"avg",
20252 b"10",
20253 ],
20254 "-ERR TSDB: the destination key already has a src rule\r\n",
20255 ),
20256 (
20259 &[
20260 b"TS.CREATERULE",
20261 b"dst",
20262 b"src",
20263 b"AGGREGATION",
20264 b"avg",
20265 b"10",
20266 ],
20267 "-ERR TSDB: the source key already has a source rule\r\n",
20268 ),
20269 (&[b"TS.DELETERULE", b"src", b"dst"], "+OK\r\n"),
20270 (
20271 &[b"TS.DELETERULE", b"src", b"dst"],
20272 "-ERR TSDB: compaction rule does not exist\r\n",
20273 ),
20274 (
20278 &[b"TS.DELETERULE", b"src", b"nope"],
20279 "-ERR TSDB: compaction rule does not exist\r\n",
20280 ),
20281 (
20282 &[b"TS.DELETERULE", b"nope", b"dst"],
20283 "-ERR TSDB: the key does not exist\r\n",
20284 ),
20285 ];
20286 for (argv, want) in cases {
20287 let got = f.run(argv);
20288 assert_eq!(&got, want, "{argv:?}");
20289 }
20290 }
20291
20292 #[test]
20294 fn a_rule_writes_a_bucket_when_a_later_reading_closes_it() {
20295 let mut f = Fixture::new();
20296 f.run(&[b"TS.CREATE", b"src"]);
20297 f.run(&[b"TS.CREATE", b"dst"]);
20298 f.run(&[b"TS.ADD", b"src", b"10", b"1"]);
20301 f.run(&[
20302 b"TS.CREATERULE",
20303 b"src",
20304 b"dst",
20305 b"AGGREGATION",
20306 b"sum",
20307 b"100",
20308 ]);
20309 f.run(&[b"TS.ADD", b"src", b"20", b"2"]);
20310 assert_eq!(f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]), "*0\r\n");
20311 assert_eq!(f.run(&[b"TS.GET", b"dst", b"LATEST"]), "*2\r\n:0\r\n+2\r\n");
20314 f.run(&[b"TS.ADD", b"src", b"110", b"4"]);
20315 assert_eq!(
20316 f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
20317 "*1\r\n*2\r\n:0\r\n+2\r\n"
20318 );
20319 f.run(&[b"TS.ADD", b"src", b"30", b"8"]);
20322 assert_eq!(
20323 f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
20324 "*1\r\n*2\r\n:0\r\n+11\r\n"
20325 );
20326 assert_eq!(f.run(&[b"TS.DEL", b"src", b"0", b"25"]), ":2\r\n");
20329 assert_eq!(
20330 f.run(&[b"TS.RANGE", b"dst", b"-", b"+"]),
20331 "*1\r\n*2\r\n:0\r\n+8\r\n"
20332 );
20333 assert_eq!(
20334 f.run(&[b"TS.GET", b"dst", b"LATEST"]),
20335 "*2\r\n:100\r\n+4\r\n"
20336 );
20337 assert!(f.run(&[b"TS.INFO", b"dst"]).contains("sourceKey"));
20339 f.run(&[b"DEL", b"dst"]);
20340 assert_eq!(
20341 f.run(&[b"TS.DELETERULE", b"src", b"dst"]),
20342 "-ERR TSDB: compaction rule does not exist\r\n"
20343 );
20344 }
20345
20346 #[test]
20349 fn xadd_ids_only_ever_go_up() {
20350 let mut f = Fixture::new();
20351 assert_eq!(f.run(&[b"XADD", b"s", b"5", b"a", b"1"]), "$3\r\n5-0\r\n");
20353 assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"2"]), "$3\r\n5-1\r\n");
20355 assert_eq!(f.run(&[b"XADD", b"s", b"5-*", b"a", b"3"]), "$3\r\n5-2\r\n");
20356 assert_eq!(f.run(&[b"XADD", b"s", b"6-9", b"a", b"4"]), "$3\r\n6-9\r\n");
20357 assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
20358
20359 assert!(
20360 f.run(&[b"XADD", b"s", b"6-9", b"a", b"5"])
20361 .contains("equal or smaller")
20362 );
20363 assert!(
20364 f.run(&[b"XADD", b"s", b"0-0", b"a", b"5"])
20365 .contains("must be greater than 0-0")
20366 );
20367 assert!(
20368 f.run(&[b"XADD", b"s", b"nonsense", b"a", b"5"])
20369 .contains("Invalid stream ID")
20370 );
20371 assert!(
20374 f.run(&[b"XADD", b"s", b"*", b"a"])
20375 .contains("wrong number of arguments")
20376 );
20377
20378 assert_eq!(
20381 f.run(&[b"XADD", b"gone", b"NOMKSTREAM", b"*", b"a", b"1"]),
20382 "$-1\r\n"
20383 );
20384 assert_eq!(f.run(&[b"EXISTS", b"gone"]), ":0\r\n");
20385 assert_eq!(f.run(&[b"TYPE", b"s"]), "+stream\r\n");
20386 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$6\r\nstream\r\n");
20387 }
20388
20389 #[test]
20392 fn trimming_reads_its_options_the_way_redis_does() {
20393 let mut f = Fixture::new();
20394 for i in 1..=10u32 {
20395 f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
20396 }
20397 assert_eq!(f.run(&[b"XTRIM", b"s", b"MAXLEN", b"4"]), ":6\r\n");
20398 assert_eq!(f.run(&[b"XLEN", b"s"]), ":4\r\n");
20399 assert_eq!(f.run(&[b"XTRIM", b"s", b"MINID", b"9"]), ":2\r\n");
20400 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20401
20402 assert!(
20406 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"~"])
20407 .contains("not an integer")
20408 );
20409 assert!(
20410 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"-1"])
20411 .contains("MAXLEN argument must be >= 0")
20412 );
20413 assert!(
20416 f.run(&[b"XTRIM", b"s", b"LIMIT", b"5"])
20417 .contains("without specifying a trimming strategy")
20418 );
20419 assert!(
20420 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"LIMIT", b"5"])
20421 .contains("without the special ~ option")
20422 );
20423 assert!(
20424 f.run(&[b"XTRIM", b"s", b"MAXLEN", b"5", b"MINID", b"5"])
20425 .contains("at the same time are not compatible")
20426 );
20427 assert!(
20429 f.run(&[b"XTRIM", b"s", b"NOMKSTREAM", b"MAXLEN", b"5"])
20430 .contains("syntax error")
20431 );
20432 assert_eq!(f.run(&[b"XTRIM", b"missing", b"MAXLEN", b"5"]), ":0\r\n");
20433 }
20434
20435 #[test]
20437 fn xrange_looks_the_key_up_before_it_reads_the_count() {
20438 let mut f = Fixture::new();
20439 f.run(&[b"XADD", b"s", b"5-1", b"a", b"1"]);
20440 f.run(&[b"XADD", b"s", b"6-1", b"b", b"2"]);
20441
20442 assert_eq!(
20443 f.run(&[b"XRANGE", b"s", b"-", b"+"]),
20444 "*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\
20445 *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
20446 );
20447 assert_eq!(
20448 f.run(&[b"XREVRANGE", b"s", b"+", b"-", b"COUNT", b"1"]),
20449 "*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"
20450 );
20451 assert_eq!(
20455 f.run(&[b"XRANGE", b"s", b"-", b"(6"]),
20456 "*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\
20457 *2\r\n$3\r\n6-1\r\n*2\r\n$1\r\nb\r\n$1\r\n2\r\n"
20458 );
20459 assert_eq!(
20460 f.run(&[b"XRANGE", b"s", b"(5-1", b"+"]),
20461 "*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"
20462 );
20463 assert!(
20464 f.run(&[b"XRANGE", b"s", b"(-", b"+"])
20465 .contains("Invalid stream ID")
20466 );
20467
20468 assert_eq!(
20472 f.run(&[b"XRANGE", b"missing", b"-", b"+", b"COUNT", b"0"]),
20473 "*0\r\n"
20474 );
20475 assert_eq!(
20476 f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"]),
20477 "*-1\r\n"
20478 );
20479 f.run(&[b"SET", b"str", b"v"]);
20480 assert!(
20481 f.run(&[b"XRANGE", b"str", b"-", b"+", b"COUNT", b"0"])
20482 .starts_with("-WRONGTYPE")
20483 );
20484 assert_eq!(
20486 f.run(&[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"2", b"COUNT", b"1"]),
20487 "*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"
20488 );
20489 }
20490
20491 #[test]
20493 fn a_bad_id_late_in_the_list_stops_the_whole_command() {
20494 let mut f = Fixture::new();
20495 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20496 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20497 assert!(
20498 f.run(&[b"XDEL", b"s", b"1-1", b"nonsense"])
20499 .contains("Invalid stream ID")
20500 );
20501 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20502 assert_eq!(f.run(&[b"XDEL", b"s", b"1-1", b"9-9"]), ":1\r\n");
20503 assert_eq!(f.run(&[b"XLEN", b"s"]), ":1\r\n");
20504 assert_eq!(f.run(&[b"XDEL", b"missing", b"1-1"]), ":0\r\n");
20505 assert_eq!(f.run(&[b"XACK", b"missing", b"g", b"1-1"]), ":0\r\n");
20506 }
20507
20508 #[test]
20510 fn xgroup_has_an_arity_per_subcommand() {
20511 let mut f = Fixture::new();
20512 assert!(
20513 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
20514 .contains("requires the key")
20515 );
20516 assert_eq!(
20517 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$", b"MKSTREAM"]),
20518 "+OK\r\n"
20519 );
20520 assert!(
20523 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"])
20524 .starts_with("-BUSYGROUP")
20525 );
20526 assert_eq!(
20527 f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
20528 ":1\r\n"
20529 );
20530 assert_eq!(
20531 f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c"]),
20532 ":0\r\n"
20533 );
20534 assert_eq!(
20535 f.run(&[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c"]),
20536 ":0\r\n"
20537 );
20538
20539 let short = f.run(&[b"XGROUP", b"DESTROY", b"s"]);
20541 assert!(
20542 short.contains("wrong number of arguments for 'xgroup|destroy' command"),
20543 "{short}"
20544 );
20545 let odd = f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0", b"ENTRIESREAD"]);
20547 assert!(
20548 odd.contains("unknown subcommand or wrong number of arguments for 'SETID'"),
20549 "{odd}"
20550 );
20551 assert!(
20552 f.run(&[b"XGROUP", b"NOSUCH", b"s"])
20553 .contains("Try XGROUP HELP")
20554 );
20555
20556 assert_eq!(f.run(&[b"XGROUP", b"SETID", b"s", b"g", b"0"]), "+OK\r\n");
20557 assert!(
20558 f.run(&[b"XGROUP", b"SETID", b"s", b"nogroup", b"0"])
20559 .starts_with("-NOGROUP")
20560 );
20561 assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":1\r\n");
20562 assert_eq!(f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]), ":0\r\n");
20563 assert!(
20564 f.run(&[b"XGROUP", b"DESTROY", b"missing", b"g"])
20565 .contains("requires the key")
20566 );
20567 }
20568
20569 #[test]
20571 fn xreadgroup_hands_out_and_xack_takes_back() {
20572 let mut f = Fixture::new();
20573 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20574 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20575 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20576
20577 let first = f.run(&[
20578 b"XREADGROUP",
20579 b"GROUP",
20580 b"g",
20581 b"c1",
20582 b"COUNT",
20583 b"1",
20584 b"STREAMS",
20585 b"s",
20586 b">",
20587 ]);
20588 assert_eq!(
20589 first,
20590 "*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"
20591 );
20592 assert_eq!(
20595 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b"0"]),
20596 "*1\r\n*2\r\n$1\r\ns\r\n*0\r\n"
20597 );
20598 assert_eq!(
20599 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
20600 "*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"
20601 );
20602
20603 assert_eq!(
20604 f.run(&[b"XPENDING", b"s", b"g"]),
20605 "*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"
20606 );
20607 assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":1\r\n");
20608 assert_eq!(f.run(&[b"XACK", b"s", b"g", b"1-1"]), ":0\r\n");
20609 assert_eq!(
20611 f.run(&[b"XPENDING", b"s", b"g"]),
20612 "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n"
20613 );
20614
20615 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
20618 f.run(&[b"XDEL", b"s", b"2-1"]);
20619 assert_eq!(
20620 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b"0"]),
20621 "*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"
20622 );
20623
20624 assert!(
20627 f.run(&[
20628 b"XREADGROUP",
20629 b"GROUP",
20630 b"nope",
20631 b"c",
20632 b"STREAMS",
20633 b"s",
20634 b"+"
20635 ])
20636 .starts_with("-NOGROUP")
20637 );
20638 assert!(
20639 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"])
20640 .contains("meaningless in the context of XREADGROUP")
20641 );
20642 assert!(
20643 f.run(&[b"XREAD", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"0"])
20644 .contains("only supported by XREADGROUP")
20645 );
20646 assert!(
20647 f.run(&[
20648 b"XREADGROUP",
20649 b"GROUP",
20650 b"g",
20651 b"c",
20652 b"STREAMS",
20653 b"s",
20654 b"a",
20655 b"b"
20656 ])
20657 .contains("Unbalanced 'xreadgroup' list of streams")
20658 );
20659 }
20660
20661 #[test]
20664 fn xread_with_no_block_writes_the_null_itself() {
20665 let mut f = Fixture::new();
20666 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20667 assert_eq!(
20668 f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
20669 "*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"
20670 );
20671 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "*-1\r\n");
20674 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"missing", b"0"]), "*-1\r\n");
20675 f.run(&[b"XADD", b"other", b"1-1", b"b", b"2"]);
20676 assert_eq!(
20677 f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"1-1", b"0"]),
20678 "*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"
20679 );
20680 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"$"]), "*-1\r\n");
20682 assert_eq!(
20684 f.run(&[b"XREAD", b"COUNT", b"5", b"STREAMS", b"s", b"+"]),
20685 "*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"
20686 );
20687 assert_eq!(
20690 f.run(&[b"XREAD", b"COUNT", b"0", b"STREAMS", b"s", b"0"]),
20691 "*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"
20692 );
20693 assert!(
20695 f.run(&[b"XREAD", b"BLOCK", b"0.5", b"STREAMS", b"s", b"$"])
20696 .contains("not an integer")
20697 );
20698 assert!(
20699 f.run(&[b"XREAD", b"BLOCK", b"-1", b"STREAMS", b"s", b"$"])
20700 .contains("timeout is negative")
20701 );
20702 assert!(
20703 f.run(&[b"XREAD", b"STREAMS", b"s", b"other", b"0"])
20704 .contains("Unbalanced 'xread' list of streams")
20705 );
20706 }
20707
20708 #[test]
20710 fn a_blocked_xread_wakes_on_the_next_entry() {
20711 let mut f = Fixture::new();
20712 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20713 let (flow, reply) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
20714 assert_eq!(flow, Flow::Block);
20715 assert!(reply.is_empty());
20716
20717 f.session = Session::new(8);
20722 let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", b"s", b"$"]);
20723 assert_eq!(flow, Flow::Block);
20724 assert_eq!(f.server.parked(), 2);
20725
20726 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20727 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";
20728 for client in [7, 8] {
20729 let mut out = Out::new(Proto::Resp2);
20730 assert!(f.server.serve_waiter(client, 0, &mut out));
20731 assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
20732 }
20733
20734 f.server.forget_waiters(7);
20737 f.server.forget_waiters(8);
20738 let (flow, _) = f.flow(&[b"XREAD", b"BLOCK", b"50", b"STREAMS", b"s", b"$"]);
20739 assert_eq!(flow, Flow::Block);
20740 let mut out = Out::new(Proto::Resp2);
20741 assert!(!f.server.serve_waiter(8, 0, &mut out));
20742 assert!(out.as_slice().is_empty());
20743 assert!(f.server.serve_waiter(8, u64::MAX, &mut out));
20744 assert_eq!(
20745 core::str::from_utf8(out.as_slice()).expect("ascii"),
20746 "*-1\r\n"
20747 );
20748 }
20749
20750 #[test]
20752 fn losing_a_group_while_blocked_is_the_ordinary_sentence() {
20753 let mut f = Fixture::new();
20754 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20755 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"$"]);
20756 let (flow, _) = f.flow(&[
20757 b"XREADGROUP",
20758 b"GROUP",
20759 b"g",
20760 b"c",
20761 b"BLOCK",
20762 b"0",
20763 b"STREAMS",
20764 b"s",
20765 b">",
20766 ]);
20767 assert_eq!(flow, Flow::Block);
20768
20769 f.run(&[b"XGROUP", b"DESTROY", b"s", b"g"]);
20770 let mut out = Out::new(Proto::Resp2);
20771 assert!(f.server.serve_waiter(7, 0, &mut out));
20772 assert_eq!(
20775 core::str::from_utf8(out.as_slice()).expect("ascii"),
20776 "-NOGROUP No such key 's' or consumer group 'g' in XREADGROUP with GROUP option\r\n"
20777 );
20778 }
20779
20780 #[test]
20782 fn xclaim_reads_ids_until_one_will_not_parse() {
20783 let mut f = Fixture::new();
20784 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20785 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20786 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20787 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
20788
20789 assert!(
20792 f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"-"])
20793 .contains("Unrecognized XCLAIM option '-'")
20794 );
20795 assert_eq!(
20796 f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1", b"JUSTID"]),
20797 "*1\r\n$3\r\n1-1\r\n"
20798 );
20799 f.run(&[b"XDEL", b"s", b"2-1"]);
20802 assert_eq!(
20803 f.run(&[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1"]),
20804 "*0\r\n"
20805 );
20806 assert!(
20807 f.run(&[b"XPENDING", b"s", b"g"])
20808 .starts_with("*4\r\n:1\r\n")
20809 );
20810 assert!(
20811 f.run(&[b"XCLAIM", b"s", b"nope", b"c", b"0", b"1-1"])
20812 .starts_with("-NOGROUP")
20813 );
20814 assert!(
20815 f.run(&[b"XCLAIM", b"s", b"g", b"c", b"nan", b"1-1"])
20816 .contains("Invalid min-idle-time argument for XCLAIM")
20817 );
20818 }
20819
20820 #[test]
20822 fn xautoclaim_reports_what_it_dropped() {
20823 let mut f = Fixture::new();
20824 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20825 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20826 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20827 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
20828 f.run(&[b"XDEL", b"s", b"1-1"]);
20829
20830 assert_eq!(
20833 f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"JUSTID"]),
20834 "*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"
20835 );
20836 assert!(
20837 f.run(&[b"XAUTOCLAIM", b"s", b"g", b"c2", b"0", b"-", b"COUNT", b"0"])
20838 .contains("COUNT must be > 0")
20839 );
20840 assert!(
20841 f.run(&[b"XAUTOCLAIM", b"s", b"nope", b"c", b"0", b"-"])
20842 .starts_with("-NOGROUP")
20843 );
20844 }
20845
20846 #[test]
20848 fn xdelex_answers_one_integer_an_id() {
20849 let mut f = Fixture::new();
20850 for i in 1..=4 {
20851 f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
20852 }
20853 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20854 f.run(&[
20855 b"XREADGROUP",
20856 b"GROUP",
20857 b"g",
20858 b"c",
20859 b"COUNT",
20860 b"2",
20861 b"STREAMS",
20862 b"s",
20863 b">",
20864 ]);
20865
20866 assert_eq!(
20868 f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1", b"9-9"]),
20869 "*2\r\n:1\r\n:-1\r\n"
20870 );
20871 assert!(
20874 f.run(&[b"XPENDING", b"s", b"g"])
20875 .starts_with("*4\r\n:2\r\n")
20876 );
20877 assert_eq!(
20879 f.run(&[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"]),
20880 "*1\r\n:1\r\n"
20881 );
20882 assert_eq!(
20884 f.run(&[b"XPENDING", b"s", b"g"]),
20885 "*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"
20886 );
20887
20888 assert_eq!(
20892 f.run(&[b"XDELEX", b"s", b"ACKED", b"IDS", b"2", b"3-1", b"4-1"]),
20893 "*2\r\n:2\r\n:2\r\n"
20894 );
20895
20896 assert_eq!(
20898 f.run(&[b"XDELEX", b"nope", b"IDS", b"2", b"bad", b"worse"]),
20899 "*2\r\n:-1\r\n:-1\r\n"
20900 );
20901 assert!(
20903 f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"3-1", b"bad"])
20904 .starts_with("-ERR Invalid stream ID")
20905 );
20906 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20907
20908 assert!(
20909 f.run(&[b"XDELEX", b"s", b"IDS", b"0", b"1-1"])
20910 .contains("Number of IDs must be a positive integer")
20911 );
20912 assert!(
20913 f.run(&[b"XDELEX", b"s", b"IDS", b"2", b"1-1"])
20914 .contains("The `numids` parameter must match the number of arguments")
20915 );
20916 assert!(
20919 f.run(&[b"XDELEX", b"s", b"KEEPREF", b"DELREF", b"IDS", b"1", b"1-1"])
20920 .starts_with("-ERR syntax error")
20921 );
20922 assert!(
20923 f.run(&[b"XDELEX", b"s", b"IDS", b"1", b"1-1", b"2-1"])
20924 .starts_with("-ERR syntax error")
20925 );
20926 f.run(&[b"SET", b"str", b"v"]);
20928 assert!(
20929 f.run(&[b"XDELEX", b"str", b"BOGUS", b"IDS", b"0", b"1-1"])
20930 .starts_with("-WRONGTYPE")
20931 );
20932 }
20933
20934 #[test]
20936 fn xackdel_reports_what_the_group_was_holding() {
20937 let mut f = Fixture::new();
20938 for i in 1..=3 {
20939 f.run(&[b"XADD", b"s", format!("{i}-1").as_bytes(), b"a", b"1"]);
20940 }
20941 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20942 f.run(&[
20943 b"XREADGROUP",
20944 b"GROUP",
20945 b"g",
20946 b"c",
20947 b"COUNT",
20948 b"1",
20949 b"STREAMS",
20950 b"s",
20951 b">",
20952 ]);
20953
20954 assert_eq!(
20958 f.run(&[b"XACKDEL", b"s", b"g", b"IDS", b"2", b"1-1", b"2-1"]),
20959 "*2\r\n:1\r\n:-1\r\n"
20960 );
20961 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20962
20963 assert_eq!(
20965 f.run(&[b"XACKDEL", b"s", b"nope", b"IDS", b"1", b"2-1"]),
20966 "*1\r\n:-1\r\n"
20967 );
20968 assert_eq!(
20969 f.run(&[b"XACKDEL", b"nope", b"g", b"IDS", b"1", b"2-1"]),
20970 "*1\r\n:-1\r\n"
20971 );
20972
20973 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b">"]);
20976 f.run(&[b"XGROUP", b"CREATE", b"s", b"g2", b"0"]);
20977 assert_eq!(
20978 f.run(&[b"XACKDEL", b"s", b"g", b"ACKED", b"IDS", b"1", b"2-1"]),
20979 "*1\r\n:2\r\n"
20980 );
20981 assert_eq!(
20982 f.run(&[b"XPENDING", b"s", b"g"]),
20983 "*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"
20984 );
20985 assert_eq!(f.run(&[b"XLEN", b"s"]), ":2\r\n");
20986 }
20987
20988 #[test]
20990 fn xnack_releases_an_entry_for_the_next_claim() {
20991 let mut f = Fixture::new();
20992 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
20993 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
20994 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
20995 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
20996 f.run(&[b"XCLAIM", b"s", b"g", b"c1", b"0", b"1-1", b"2-1"]);
20999
21000 assert_eq!(
21001 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1"]),
21002 ":1\r\n"
21003 );
21004 assert_eq!(
21008 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]),
21009 "*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"
21010 );
21011 assert_eq!(
21013 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
21014 "*1\r\n*4\r\n$3\r\n2-1\r\n$2\r\nc1\r\n:0\r\n:2\r\n"
21015 );
21016 assert_eq!(
21018 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c2", b"STREAMS", b"s", b">"]),
21019 "*-1\r\n"
21020 );
21021 assert_eq!(
21023 f.run(&[
21024 b"XAUTOCLAIM",
21025 b"s",
21026 b"g",
21027 b"c2",
21028 b"99999999",
21029 b"-",
21030 b"JUSTID"
21031 ]),
21032 "*3\r\n$3\r\n0-0\r\n*1\r\n$3\r\n1-1\r\n*0\r\n"
21033 );
21034
21035 f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
21039 assert!(
21040 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21041 .contains(":-1\r\n:1\r\n")
21042 );
21043 f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
21045 f.run(&[b"XNACK", b"s", b"g", b"SILENT", b"IDS", b"1", b"1-1"]);
21046 assert!(
21047 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21048 .contains(":-1\r\n:0\r\n")
21049 );
21050 f.run(&[b"XNACK", b"s", b"g", b"FATAL", b"IDS", b"1", b"1-1"]);
21052 assert!(
21053 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21054 .contains(":9223372036854775807\r\n")
21055 );
21056 f.run(&[
21057 b"XNACK",
21058 b"s",
21059 b"g",
21060 b"FATAL",
21061 b"IDS",
21062 b"1",
21063 b"1-1",
21064 b"RETRYCOUNT",
21065 b"3",
21066 ]);
21067 assert!(
21068 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21069 .contains(":-1\r\n:3\r\n")
21070 );
21071
21072 f.run(&[b"XACK", b"s", b"g", b"2-1"]);
21076 assert_eq!(
21077 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"]),
21078 ":0\r\n"
21079 );
21080 assert_eq!(
21081 f.run(&[
21082 b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1", b"FORCE"
21083 ]),
21084 ":1\r\n"
21085 );
21086 assert!(
21087 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"])
21088 .contains(":-1\r\n:0\r\n")
21089 );
21090 assert_eq!(
21092 f.run(&[
21093 b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"9-9", b"FORCE"
21094 ]),
21095 ":0\r\n"
21096 );
21097
21098 assert_eq!(
21101 f.run(&[b"XNACK", b"s", b"nope", b"BOGUS", b"IDS", b"1", b"1-1"]),
21102 "-NOGROUP No such key 's' or consumer group 'nope'\r\n"
21103 );
21104 assert!(
21105 f.run(&[b"XNACK", b"s", b"g", b"BOGUS", b"IDS", b"1", b"1-1"])
21106 .starts_with("-ERR")
21107 );
21108 assert!(
21110 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"0", b"1-1"])
21111 .contains("numids must be a positive integer")
21112 );
21113 assert!(
21114 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"2", b"1-1"])
21115 .contains("number of IDs doesn't match numids")
21116 );
21117 assert!(
21120 f.run(&[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"1-1", b"2-1"])
21121 .contains("Unrecognized XNACK option '2-1'")
21122 );
21123 }
21124
21125 #[test]
21127 fn xinfo_reports_the_stream_the_groups_and_the_consumers() {
21128 let mut f = Fixture::new();
21129 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21130 f.run(&[b"XADD", b"s", b"2-1", b"a", b"2"]);
21131 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
21132 f.run(&[
21133 b"XREADGROUP",
21134 b"GROUP",
21135 b"g",
21136 b"c1",
21137 b"COUNT",
21138 b"1",
21139 b"STREAMS",
21140 b"s",
21141 b">",
21142 ]);
21143
21144 let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
21145 assert!(info.starts_with("*20\r\n"), "{info}");
21148 assert!(info.contains("$6\r\nlength\r\n:2\r\n"), "{info}");
21149 assert!(
21150 info.contains("$17\r\nlast-generated-id\r\n$3\r\n2-1\r\n"),
21151 "{info}"
21152 );
21153 assert!(info.contains("$13\r\nentries-added\r\n:2\r\n"), "{info}");
21154 assert!(info.contains("$6\r\ngroups\r\n:1\r\n"), "{info}");
21155
21156 let groups = f.run(&[b"XINFO", b"GROUPS", b"s"]);
21157 assert!(groups.starts_with("*1\r\n*12\r\n"), "{groups}");
21158 assert!(groups.contains("$9\r\nconsumers\r\n:1\r\n"), "{groups}");
21159 assert!(groups.contains("$7\r\npending\r\n:1\r\n"), "{groups}");
21160 assert!(groups.contains("$3\r\nlag\r\n:1\r\n"), "{groups}");
21161
21162 f.run(&[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"c2"]);
21166 let consumers = f.run(&[b"XINFO", b"CONSUMERS", b"s", b"g"]);
21167 assert!(consumers.starts_with("*2\r\n"), "{consumers}");
21168 assert!(
21169 consumers.contains("$8\r\ninactive\r\n:-1\r\n"),
21170 "{consumers}"
21171 );
21172 let c1 = consumers.find("c1").unwrap();
21174 let c2 = consumers.find("c2").unwrap();
21175 assert!(c1 < c2, "{consumers}");
21176
21177 let full = f.run(&[b"XINFO", b"STREAM", b"s", b"FULL"]);
21178 assert!(full.starts_with("*18\r\n"), "{full}");
21179 assert!(full.contains("$12\r\nnacked-count\r\n:0\r\n"), "{full}");
21180 assert!(full.contains("$11\r\nactive-time\r\n"), "{full}");
21181
21182 assert!(
21183 f.run(&[b"XINFO", b"STREAM", b"missing"])
21184 .contains("no such key")
21185 );
21186 assert!(
21187 f.run(&[b"XINFO", b"GROUPS", b"missing"])
21188 .contains("no such key")
21189 );
21190 assert!(
21191 f.run(&[b"XINFO", b"CONSUMERS", b"s", b"nope"])
21192 .starts_with("-NOGROUP")
21193 );
21194 assert!(
21195 f.run(&[b"XINFO", b"NOSUCH", b"s"])
21196 .contains("Try XINFO HELP")
21197 );
21198 assert!(f.run(&[b"XINFO", b"HELP"]).contains("XINFO <subcommand>"));
21199 assert!(f.run(&[b"XGROUP", b"HELP"]).contains("XGROUP <subcommand>"));
21200 }
21201
21202 #[test]
21204 fn xpending_takes_the_consumer_only_when_the_count_comes_out_right() {
21205 let mut f = Fixture::new();
21206 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21207 f.run(&[b"XGROUP", b"CREATE", b"s", b"g", b"0"]);
21208 f.run(&[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"]);
21209
21210 let list = f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10"]);
21211 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");
21212 assert_eq!(
21213 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"]),
21214 "*1\r\n*4\r\n$3\r\n1-1\r\n$2\r\nc1\r\n:0\r\n:1\r\n"
21215 );
21216 assert_eq!(
21218 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"nope"]),
21219 "*0\r\n"
21220 );
21221 assert_eq!(
21222 f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0", b"-", b"+", b"10"]),
21223 list
21224 );
21225 assert!(
21227 f.run(&[b"XPENDING", b"s", b"g", b"IDLE", b"0"])
21228 .contains("syntax error")
21229 );
21230 assert!(
21231 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+"])
21232 .contains("syntax error")
21233 );
21234 assert_eq!(
21235 f.run(&[b"XPENDING", b"s", b"g", b"-", b"+", b"-1"]),
21236 "*0\r\n"
21237 );
21238 assert!(
21239 f.run(&[b"XPENDING", b"missing", b"g"])
21240 .starts_with("-NOGROUP")
21241 );
21242 }
21243
21244 #[test]
21246 fn xsetid_will_not_go_below_what_is_there() {
21247 let mut f = Fixture::new();
21248 f.run(&[b"XADD", b"s", b"5-5", b"a", b"1"]);
21249 assert_eq!(f.run(&[b"XSETID", b"s", b"9-9"]), "+OK\r\n");
21250 assert_eq!(
21251 f.run(&[
21252 b"XSETID",
21253 b"s",
21254 b"10-1",
21255 b"ENTRIESADDED",
21256 b"7",
21257 b"MAXDELETEDID",
21258 b"9-1"
21259 ]),
21260 "+OK\r\n"
21261 );
21262 let info = f.run(&[b"XINFO", b"STREAM", b"s"]);
21263 assert!(info.contains("$13\r\nentries-added\r\n:7\r\n"), "{info}");
21264 assert!(
21265 info.contains("$20\r\nmax-deleted-entry-id\r\n$3\r\n9-1\r\n"),
21266 "{info}"
21267 );
21268
21269 assert!(
21270 f.run(&[b"XSETID", b"s", b"1-1"])
21271 .contains("smaller than the target stream top item")
21272 );
21273 assert!(
21274 f.run(&[b"XSETID", b"s", b"10-1", b"ENTRIESADDED", b"-1"])
21275 .contains("entries_added must be positive")
21276 );
21277 assert!(
21278 f.run(&[b"XSETID", b"missing", b"1-1"])
21279 .contains("no such key")
21280 );
21281 }
21282
21283 #[test]
21285 fn xread_answers_a_map_on_resp3_and_the_fields_stay_flat() {
21286 let mut f = Fixture::new();
21287 f.run(&[b"HELLO", b"3"]);
21288 f.run(&[b"XADD", b"s", b"1-1", b"a", b"1"]);
21289 assert_eq!(
21292 f.run(&[b"XREAD", b"STREAMS", b"s", b"0"]),
21293 "%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"
21294 );
21295 assert_eq!(
21298 f.run(&[b"XRANGE", b"s", b"-", b"+"]),
21299 "*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"
21300 );
21301 assert_eq!(f.run(&[b"XREAD", b"STREAMS", b"s", b"1-1"]), "_\r\n");
21302 }
21303
21304 struct Mem {
21310 blobs: Vec<Vec<u8>>,
21311 }
21312
21313 impl yo_kv::cold::Blocks for Mem {
21314 fn put(&mut self, bytes: &[u8]) -> yo_common::Result<yo_common::Addr> {
21315 self.blobs.push(bytes.to_vec());
21316 Ok(yo_common::Addr::new(
21317 yo_common::Space::Log,
21318 (self.blobs.len() - 1) as u64,
21319 ))
21320 }
21321
21322 fn get(&self, at: yo_common::Addr) -> yo_common::Result<&[u8]> {
21323 self.blobs
21324 .get(at.offset() as usize)
21325 .map(Vec::as_slice)
21326 .ok_or_else(|| {
21327 yo_common::Error::new(yo_common::Code::Corrupt, "no chunk at that address")
21328 })
21329 }
21330
21331 fn bytes(&self) -> u64 {
21332 self.blobs.iter().map(|b| b.len() as u64).sum()
21333 }
21334 }
21335
21336 fn filled(attach: bool) -> (Fixture, usize) {
21349 let mut f = Fixture::new();
21350 if attach {
21351 f.server
21352 .striped(0)
21353 .hold_stripe(0)
21354 .attach(Box::new(Mem { blobs: Vec::new() }));
21355 }
21356 let val = vec![b'v'; 256];
21357 for i in 0..24000u32 {
21358 let k = format!("key:{i:08}");
21359 f.run(&[b"SET", k.as_bytes(), &val]);
21360 }
21361 let full = f.server.memory_bytes();
21362 assert!(full > 3 * 1024 * 1024, "the arena is several segments");
21363 (f, full)
21364 }
21365
21366 fn press(f: &mut Fixture, limit: usize) {
21372 let val = vec![b'v'; 256];
21373 for i in 0..3000u32 {
21374 let k = format!("new:{i:08}");
21375 assert_eq!(
21376 f.run(&[b"SET", k.as_bytes(), &val]),
21377 "+OK\r\n",
21378 "write {i} was refused"
21379 );
21380 f.server.refresh_memory();
21381 if f.server.memory_bytes() <= limit {
21382 return;
21383 }
21384 }
21385 panic!(
21386 "it never got under: {} against {limit}",
21387 f.server.memory_bytes()
21388 );
21389 }
21390
21391 #[test]
21392 fn the_storage_limit_reads_back_and_minus_one_is_no_limit() {
21393 let mut f = Fixture::new();
21394 assert_eq!(
21395 f.run(&[b"CONFIG", b"GET", b"maxstore"]),
21396 "*2\r\n$8\r\nmaxstore\r\n$2\r\n-1\r\n",
21397 "no limit is the default"
21398 );
21399 for (typed, bytes) in [
21402 (&b"0"[..], "0"),
21403 (b"1024", "1024"),
21404 (b"1k", "1000"),
21405 (b"1gb", "1073741824"),
21406 (b"-1", "-1"),
21407 ] {
21408 assert_eq!(f.run(&[b"CONFIG", b"SET", b"maxstore", typed]), "+OK\r\n");
21409 assert_eq!(
21410 f.run(&[b"CONFIG", b"GET", b"maxstore"]),
21411 format!("*2\r\n$8\r\nmaxstore\r\n${}\r\n{bytes}\r\n", bytes.len()),
21412 "set {}",
21413 String::from_utf8_lossy(typed)
21414 );
21415 }
21416 for bad in [&b"1tb"[..], b"-2", b"", b"lots"] {
21417 assert_eq!(
21418 f.run(&[b"CONFIG", b"SET", b"maxstore", bad]),
21419 "-ERR CONFIG SET failed (possibly related to argument 'maxstore') - argument must be a memory value or -1\r\n",
21420 "refused {}",
21421 String::from_utf8_lossy(bad)
21422 );
21423 }
21424 let info = f.run(&[b"INFO", b"memory"]);
21426 assert!(info.contains("maxstore:-1"), "{info}");
21427 assert!(info.contains("yo_memory_regime:evict"), "{info}");
21428 assert!(info.contains("yo_store_bytes:0"), "{info}");
21429 }
21430
21431 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
21432 #[test]
21433 fn a_memory_limit_moves_values_to_the_file_instead_of_dropping_keys() {
21434 let (mut f, full) = filled(true);
21438 let keys = f.run(&[b"DBSIZE"]);
21439 assert!(
21440 f.run(&[b"INFO", b"memory"])
21441 .contains("yo_memory_regime:migrate"),
21442 "a database with somewhere to put values migrates"
21443 );
21444
21445 let limit = full - 2 * 1024 * 1024;
21446 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
21447 f.run(&[
21448 b"CONFIG",
21449 b"SET",
21450 b"maxmemory",
21451 limit.to_string().as_bytes(),
21452 ]);
21453 press(&mut f, limit);
21454
21455 assert!(
21456 f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
21457 "nothing was thrown away"
21458 );
21459 let after: usize = f.run(&[b"DBSIZE"])[1..]
21460 .trim_end()
21461 .parse()
21462 .expect("a count");
21463 let before: usize = keys[1..].trim_end().parse().expect("a count");
21464 assert!(after > before, "the keys that came in are all still here");
21465 assert!(
21466 f.server.store_bytes() > 0,
21467 "and what came out of memory went to the file"
21468 );
21469 let val = format!("$256\r\n{}\r\n", "v".repeat(256));
21472 assert_eq!(f.run(&[b"GET", b"key:00000000"]), val);
21473 assert_eq!(f.run(&[b"GET", b"key:00023999"]), val);
21474 }
21475
21476 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
21477 #[test]
21478 fn a_storage_limit_of_zero_restores_redis_behaviour_exactly() {
21479 let (mut f, full) = filled(true);
21483 f.run(&[b"CONFIG", b"SET", b"maxstore", b"0"]);
21484 assert!(
21485 f.run(&[b"INFO", b"memory"])
21486 .contains("yo_memory_regime:evict"),
21487 "nothing may go to the file"
21488 );
21489
21490 let limit = full - 2 * 1024 * 1024;
21491 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
21492 f.run(&[
21493 b"CONFIG",
21494 b"SET",
21495 b"maxmemory",
21496 limit.to_string().as_bytes(),
21497 ]);
21498 press(&mut f, limit);
21499
21500 assert!(
21501 !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
21502 "keys were thrown away, which is what was asked for"
21503 );
21504 assert_eq!(f.server.store_bytes(), 0, "and the file was never written");
21505 }
21506
21507 #[cfg_attr(miri, ignore = "several megabytes of arena, see `filled`")]
21508 #[test]
21509 fn a_full_file_goes_back_to_evicting() {
21510 let (mut f, full) = filled(true);
21514 f.run(&[b"CONFIG", b"SET", b"maxstore", b"64kb"]);
21515 let limit = full - 2 * 1024 * 1024;
21516 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]);
21517 f.run(&[
21518 b"CONFIG",
21519 b"SET",
21520 b"maxmemory",
21521 limit.to_string().as_bytes(),
21522 ]);
21523 press(&mut f, limit);
21524
21525 assert!(f.server.store_bytes() >= 64 * 1024, "the file filled up");
21526 assert!(
21527 !f.run(&[b"INFO", b"stats"]).contains("evicted_keys:0"),
21528 "and then it started evicting"
21529 );
21530 assert!(
21531 f.run(&[b"INFO", b"memory"])
21532 .contains("yo_memory_regime:evict"),
21533 "and it says so"
21534 );
21535 }
21536 #[test]
21552 fn the_string_group_answers_the_same_however_many_stripes_there_are() {
21553 let script: &[&[&[u8]]] = &[
21554 &[b"SET", b"k1", b"v1"],
21557 &[b"SET", b"k2", b"v2"],
21558 &[b"GET", b"k1"],
21559 &[b"GET", b"nothing"],
21560 &[b"GETSET", b"k1", b"v1b"],
21561 &[b"SETNX", b"k1", b"no"],
21562 &[b"SETNX", b"k3", b"yes"],
21563 &[b"APPEND", b"k3", b"!"],
21564 &[b"STRLEN", b"k3"],
21565 &[b"SETRANGE", b"k3", b"1", b"XY"],
21566 &[b"GETRANGE", b"k3", b"0", b"-1"],
21567 &[b"INCR", b"n1"],
21568 &[b"INCRBY", b"n1", b"41"],
21569 &[b"DECRBY", b"n1", b"2"],
21570 &[b"INCRBYFLOAT", b"f1", b"1.5"],
21571 &[b"SETEX", b"e1", b"100", b"v"],
21572 &[b"PSETEX", b"e2", b"100000", b"v"],
21573 &[b"GETEX", b"e1", b"PERSIST"],
21574 &[b"GETDEL", b"k2"],
21575 &[b"GET", b"k2"],
21576 &[b"DIGEST", b"k1"],
21577 &[b"DELEX", b"k3"],
21578 &[b"MSET", b"a", b"1", b"b", b"2", b"c", b"3"],
21581 &[b"MGET", b"a", b"b", b"c", b"missing"],
21582 &[b"MSETNX", b"d", b"4", b"e", b"5"],
21583 &[b"MSETNX", b"e", b"6", b"f", b"7"],
21584 &[b"MGET", b"d", b"e", b"f"],
21585 &[b"MSETEX", b"2", b"g", b"7", b"h", b"8", b"NX"],
21586 &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"NX"],
21587 &[b"MSETEX", b"2", b"g", b"9", b"h", b"9", b"XX"],
21588 &[b"MGET", b"g", b"h"],
21589 &[b"SET", b"s1", b"ohmytext"],
21590 &[b"SET", b"s2", b"mynewtext"],
21591 &[b"LCS", b"s1", b"s2"],
21592 &[b"LCS", b"s1", b"s2", b"LEN"],
21593 &[b"LCS", b"s1", b"s2", b"IDX", b"MINMATCHLEN", b"4"],
21594 &[b"LCS", b"s1", b"s2", b"IDX", b"WITHMATCHLEN"],
21595 &[b"LCS", b"s1", b"gone"],
21596 &[b"MSET", b"odd"],
21598 &[b"LCS", b"s1", b"s2", b"LEN", b"IDX"],
21599 &[b"MGET"],
21600 ];
21601
21602 let mut one = Fixture::new();
21603 let mut many = Fixture::striped(8);
21604 for parts in script {
21605 let a = one.run(parts);
21606 let b = many.run(parts);
21607 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
21608 }
21609 }
21610
21611 #[test]
21616 fn a_striped_database_spreads_the_keys_it_is_given() {
21617 let mut f = Fixture::striped(8);
21618 for i in 0..256 {
21619 let key = format!("key:{i}");
21620 f.run(&[b"SET", key.as_bytes(), b"v"]);
21621 }
21622 assert_eq!(f.run(&[b"DBSIZE"]), ":256\r\n");
21623 }
21624
21625 #[test]
21628 fn a_wrong_type_in_the_middle_of_an_mget_is_still_one_nil() {
21629 let mut one = Fixture::new();
21630 let mut many = Fixture::striped(8);
21631 for f in [&mut one, &mut many] {
21632 f.run(&[b"SET", b"str", b"v"]);
21633 f.server
21639 .striped(0)
21640 .hold(b"list")
21641 .push(b"list", yo_kv::End::Right, core::iter::once(&b"v"[..]))
21642 .expect("a new list");
21643 }
21644 assert_eq!(
21645 one.run(&[b"MGET", b"str", b"list", b"gone"]),
21646 many.run(&[b"MGET", b"str", b"list", b"gone"])
21647 );
21648 }
21649
21650 #[test]
21658 fn the_keyspace_group_answers_the_same_however_many_stripes_there_are() {
21659 let script: &[&[&[u8]]] = &[
21660 &[b"SET", b"k1", b"v1"],
21661 &[b"SET", b"k2", b"v2"],
21662 &[b"EXISTS", b"k1", b"k2", b"k1", b"gone"],
21663 &[b"TYPE", b"k1"],
21664 &[b"TYPE", b"gone"],
21665 &[b"TOUCH", b"k1", b"k2", b"k1", b"gone"],
21666 &[b"EXPIRE", b"k1", b"100"],
21667 &[b"TTL", b"k1"],
21668 &[b"EXPIRE", b"k1", b"200", b"NX"],
21669 &[b"PERSIST", b"k1"],
21670 &[b"TTL", b"k1"],
21671 &[b"PEXPIREAT", b"k2", b"1900000000000"],
21672 &[b"EXPIRETIME", b"k2"],
21673 &[b"PEXPIRETIME", b"k2"],
21674 &[b"PERSIST", b"k2"],
21675 &[b"OBJECT", b"ENCODING", b"k1"],
21676 &[b"OBJECT", b"REFCOUNT", b"k1"],
21677 &[b"OBJECT", b"IDLETIME", b"k1"],
21678 &[b"OBJECT", b"FREQ", b"k1"],
21679 &[b"OBJECT", b"ENCODING", b"gone"],
21680 &[b"OBJECT", b"HELP"],
21681 &[b"RENAME", b"k1", b"k9"],
21682 &[b"GET", b"k9"],
21683 &[b"RENAME", b"gone", b"x"],
21684 &[b"RENAMENX", b"k9", b"k2"],
21685 &[b"RENAMENX", b"k9", b"k8"],
21686 &[b"GET", b"k8"],
21687 &[b"COPY", b"k8", b"c1"],
21688 &[b"COPY", b"k8", b"c1"],
21689 &[b"COPY", b"k8", b"c1", b"REPLACE"],
21690 &[b"COPY", b"k8", b"k8"],
21691 &[b"COPY", b"gone", b"c2"],
21692 &[b"COPY", b"k8", b"k8", b"DB", b"1"],
21693 &[b"COPY", b"k8", b"c9", b"DB", b"9"],
21694 &[b"MOVE", b"c1", b"1"],
21695 &[b"MOVE", b"c1", b"1"],
21696 &[b"MOVE", b"k8", b"0"],
21697 &[b"DEL", b"k2", b"gone"],
21698 &[b"UNLINK", b"k8", b"k8"],
21699 &[b"DBSIZE"],
21700 ];
21701
21702 let mut one = Fixture::new();
21703 let mut many = Fixture::striped(8);
21704 for parts in script {
21705 let a = one.run(parts);
21706 let b = many.run(parts);
21707 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
21708 }
21709
21710 for f in [&mut one, &mut many] {
21716 f.run(&[b"SET", b"d1", b"payload"]);
21717 let payload = f
21718 .server
21719 .striped(0)
21720 .hold(b"d1")
21721 .dump(b"d1")
21722 .expect("a key that is there");
21723 assert!(
21724 f.run(&[b"DUMP", b"d1"])
21725 .starts_with(&format!("${}", payload.len())),
21726 "a payload of the length the store gave"
21727 );
21728 assert_eq!(f.run(&[b"DUMP", b"gone"]), "$-1\r\n");
21729 assert_eq!(f.run(&[b"RESTORE", b"d2", b"0", &payload]), "+OK\r\n");
21730 assert_eq!(f.run(&[b"GET", b"d2"]), "$7\r\npayload\r\n");
21731 assert_eq!(
21732 f.run(&[b"RESTORE", b"d2", b"0", &payload]),
21733 "-BUSYKEY Target key name already exists.\r\n"
21734 );
21735 assert_eq!(
21736 f.run(&[b"RESTORE", b"d3", b"0", b"rubbish"]),
21737 "-ERR DUMP payload version or checksum are wrong\r\n"
21738 );
21739 }
21740 }
21741
21742 #[test]
21749 fn a_scan_of_a_striped_database_walks_all_of_it() {
21750 let n = if cfg!(miri) { 80 } else { 500 };
21754 let mut f = Fixture::striped(8);
21755 for i in 0..n {
21756 let key = format!("key:{i}");
21757 f.run(&[b"SET", key.as_bytes(), b"v"]);
21758 }
21759
21760 let mut seen = Vec::new();
21761 let mut cursor = "0".to_owned();
21762 let mut calls = 0;
21763 loop {
21764 let reply = f.run(&[b"SCAN", cursor.as_bytes(), b"COUNT", b"10"]);
21765 let (next, keys) = scan_reply(&reply);
21766 seen.extend(keys);
21767 cursor = next;
21768 calls += 1;
21769 assert!(calls < 5_000, "a scan that will not finish");
21770 if cursor == "0" {
21771 break;
21772 }
21773 }
21774 seen.sort();
21775 assert_eq!(seen.len(), n, "a quiet scan answered a key twice");
21776 assert_eq!(seen, sorted(&f.run(&[b"KEYS", b"*"])));
21777
21778 let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"MATCH", b"key:4?"]);
21782 let (_, keys) = scan_reply(&reply);
21783 assert_eq!(keys.len(), 10, "key:40 through key:49");
21784 let reply = f.run(&[b"SCAN", b"0", b"COUNT", b"1000", b"TYPE", b"list"]);
21785 let (_, keys) = scan_reply(&reply);
21786 assert!(keys.is_empty(), "nothing here is a list");
21787 }
21788
21789 #[test]
21795 fn a_random_key_can_come_from_any_stripe() {
21796 let mut f = Fixture::striped(8);
21797 assert_eq!(f.run(&[b"RANDOMKEY"]), "$-1\r\n");
21798 for i in 0..200 {
21799 let key = format!("key:{i}");
21800 f.run(&[b"SET", key.as_bytes(), b"v"]);
21801 }
21802 let mut homes = std::collections::HashSet::new();
21803 for _ in 0..200 {
21804 let got = f.run(&[b"RANDOMKEY"]);
21805 let key = got.split("\r\n").nth(1).expect("a key").to_owned();
21806 assert_eq!(f.run(&[b"EXISTS", key.as_bytes()]), ":1\r\n");
21807 homes.insert(f.server.striped(0).stripe_of(key.as_bytes()));
21808 }
21809 assert_eq!(homes.len(), 8, "some stripe was never drawn from");
21810 }
21811
21812 fn apart(f: &mut Fixture, src: &str) -> String {
21816 let home = f.server.striped(0).stripe_of(src.as_bytes());
21817 for i in 0..1_000 {
21818 let dst = format!("dst:{i}");
21819 if f.server.striped(0).stripe_of(dst.as_bytes()) != home {
21820 return dst;
21821 }
21822 }
21823 panic!("eight stripes and a thousand keys all landed in one place");
21824 }
21825
21826 #[test]
21829 fn a_rename_across_stripes_takes_everything_with_it() {
21830 let mut f = Fixture::striped(8);
21831 let dst = apart(&mut f, "src");
21832 let (src, dst) = (b"src".as_slice(), dst.as_bytes());
21833
21834 f.run(&[b"SET", src, b"v"]);
21835 f.run(&[b"EXPIRE", src, b"100"]);
21836 assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
21837 assert_eq!(f.run(&[b"EXISTS", src, dst]), ":1\r\n");
21838 assert_eq!(f.run(&[b"GET", dst]), "$1\r\nv\r\n");
21839 assert_eq!(f.run(&[b"TTL", dst]), ":100\r\n", "the deadline came too");
21840
21841 f.server
21846 .striped(0)
21847 .hold(src)
21848 .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
21849 .expect("a new list");
21850 assert_eq!(f.run(&[b"RENAME", src, dst]), "+OK\r\n");
21851 assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n");
21852 assert_eq!(
21853 f.server.striped(0).hold(dst).llen(dst).expect("a list"),
21854 2,
21855 "the members are on the stripe the key moved to"
21856 );
21857
21858 f.run(&[b"SET", src, b"v"]);
21861 assert_eq!(f.run(&[b"RENAMENX", src, dst]), ":0\r\n");
21862 assert_eq!(f.run(&[b"TYPE", dst]), "+list\r\n", "and left it alone");
21863 assert_eq!(f.run(&[b"GET", src]), "$1\r\nv\r\n", "and left the source");
21864 }
21865
21866 #[test]
21868 fn a_copy_across_stripes_leaves_the_source_where_it_was() {
21869 let mut f = Fixture::striped(8);
21870 let dst = apart(&mut f, "src");
21871 let (src, dst) = (b"src".as_slice(), dst.as_bytes());
21872
21873 f.run(&[b"SET", src, b"v"]);
21874 assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
21875 assert_eq!(f.run(&[b"EXISTS", src, dst]), ":2\r\n");
21876 assert_eq!(
21877 f.run(&[b"COPY", src, dst]),
21878 ":0\r\n",
21879 "the destination is taken"
21880 );
21881 f.run(&[b"SET", src, b"w"]);
21882 assert_eq!(f.run(&[b"COPY", src, dst, b"REPLACE"]), ":1\r\n");
21883 assert_eq!(f.run(&[b"GET", dst]), "$1\r\nw\r\n");
21884
21885 f.run(&[b"DEL", src, dst]);
21889 f.server
21890 .striped(0)
21891 .hold(src)
21892 .push(src, yo_kv::End::Right, [&b"a"[..], &b"b"[..]].into_iter())
21893 .expect("a new list");
21894 assert_eq!(f.run(&[b"COPY", src, dst]), ":1\r\n");
21895 f.server
21896 .striped(0)
21897 .hold(src)
21898 .push(src, yo_kv::End::Right, core::iter::once(&b"c"[..]))
21899 .expect("a list that is there");
21900 assert_eq!(f.server.striped(0).hold(src).llen(src).expect("a list"), 3);
21901 assert_eq!(f.server.striped(0).hold(dst).llen(dst).expect("a list"), 2);
21902 }
21903
21904 #[test]
21912 fn the_bitmap_group_answers_the_same_however_many_stripes_there_are() {
21913 let script: &[&[&[u8]]] = &[
21914 &[b"SET", b"k1", b"foobar"],
21915 &[b"SETBIT", b"b1", b"7", b"1"],
21916 &[b"SETBIT", b"b1", b"7", b"0"],
21917 &[b"GETBIT", b"k1", b"6"],
21918 &[b"GETBIT", b"k1", b"100"],
21919 &[b"BITCOUNT", b"k1"],
21920 &[b"BITCOUNT", b"k1", b"0", b"0"],
21921 &[b"BITCOUNT", b"k1", b"5", b"30", b"BIT"],
21922 &[b"BITPOS", b"k1", b"1"],
21923 &[b"BITPOS", b"k1", b"0", b"2"],
21924 &[b"BITPOS", b"k1", b"1", b"2", b"-1", b"BIT"],
21925 &[
21926 b"BITFIELD",
21927 b"bf",
21928 b"SET",
21929 b"u8",
21930 b"0",
21931 b"255",
21932 b"GET",
21933 b"u8",
21934 b"0",
21935 ],
21936 &[
21937 b"BITFIELD",
21938 b"bf",
21939 b"OVERFLOW",
21940 b"SAT",
21941 b"INCRBY",
21942 b"u8",
21943 b"0",
21944 b"10",
21945 ],
21946 &[b"BITFIELD_RO", b"bf", b"GET", b"u8", b"0"],
21947 &[b"SET", b"s1", b"abc"],
21950 &[b"SET", b"s2", b"abd"],
21951 &[b"SET", b"s3", b"a"],
21952 &[b"BITOP", b"AND", b"d1", b"s1", b"s2"],
21953 &[b"GET", b"d1"],
21954 &[b"BITOP", b"OR", b"d2", b"s1", b"s2", b"s3"],
21955 &[b"GET", b"d2"],
21956 &[b"BITOP", b"XOR", b"d3", b"s1", b"s2"],
21957 &[b"STRLEN", b"d3"],
21958 &[b"BITOP", b"NOT", b"d4", b"s1"],
21959 &[b"STRLEN", b"d4"],
21960 &[b"BITOP", b"DIFF", b"d5", b"s1", b"s2"],
21961 &[b"BITOP", b"DIFF1", b"d6", b"s1", b"s2"],
21962 &[b"BITOP", b"ANDOR", b"d7", b"s1", b"s2"],
21963 &[b"BITOP", b"ONE", b"d8", b"s1", b"s2"],
21964 &[b"BITOP", b"AND", b"d1", b"gone", b"also-gone"],
21967 &[b"EXISTS", b"d1"],
21968 &[b"BITOP", b"OR", b"d9", b"s1", b"gone"],
21969 &[b"GET", b"d9"],
21970 &[b"BITOP", b"AND", b"d1", b"s1", b"list"],
21974 &[b"BITOP", b"AND", b"list", b"s1", b"s2"],
21975 &[b"BITOP", b"NOT", b"d1", b"s1", b"s2"],
21976 &[b"BITOP", b"DIFF", b"d1", b"s1"],
21977 &[b"BITOP", b"NOPE", b"d1", b"s1"],
21978 &[b"BITCOUNT", b"list"],
21979 &[b"BITFIELD_RO", b"bf", b"SET", b"u8", b"0", b"1"],
21980 ];
21981
21982 let mut one = Fixture::new();
21983 let mut many = Fixture::striped(8);
21984 for f in [&mut one, &mut many] {
21985 plant_list(f, b"list");
21986 }
21987 for parts in script {
21988 let a = one.run(parts);
21989 let b = many.run(parts);
21990 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
21991 }
21992 }
21993
21994 fn plant_list(f: &mut Fixture, key: &[u8]) {
22000 f.server
22001 .striped(0)
22002 .hold(key)
22003 .push(key, yo_kv::End::Right, core::iter::once(&b"x"[..]))
22004 .expect("a new list");
22005 }
22006
22007 #[test]
22014 fn a_bitop_across_stripes_reads_every_source() {
22015 let mut f = Fixture::striped(8);
22016 let other = apart(&mut f, "src");
22017 let (src, far) = (b"src".as_slice(), other.as_bytes());
22018 assert_ne!(
22019 f.server.striped(0).stripe_of(src),
22020 f.server.striped(0).stripe_of(far),
22021 "the two keys are the point of the test"
22022 );
22023
22024 f.run(&[b"SET", src, b"abc"]);
22025 f.run(&[b"SET", far, b"abd"]);
22026 assert_eq!(f.run(&[b"BITOP", b"AND", far, src, far]), ":3\r\n");
22027 assert_eq!(
22028 f.run(&[b"GET", far]),
22029 "$3\r\nab`\r\n",
22030 "a destination that is also a source"
22031 );
22032 f.run(&[b"SET", far, b"abd"]);
22033 assert_eq!(f.run(&[b"BITOP", b"XOR", src, src, far]), ":3\r\n");
22034 assert_eq!(
22035 f.run(&[b"GET", src]),
22036 "$3\r\n\0\0\x07\r\n",
22037 "and the other way round"
22038 );
22039
22040 f.run(&[b"SET", src, b"abc"]);
22044 f.run(&[b"DEL", far]);
22045 assert_eq!(f.run(&[b"BITOP", b"AND", src, far, b"gone"]), ":0\r\n");
22046 assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n");
22047 f.run(&[b"SET", src, b"abc"]);
22048 f.run(&[b"DEL", far]);
22049 plant_list(&mut f, far);
22050 assert_eq!(
22051 f.run(&[b"BITOP", b"OR", b"out", src, far]),
22052 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22053 );
22054 assert_eq!(f.run(&[b"EXISTS", b"out"]), ":0\r\n");
22055 }
22056
22057 #[cfg_attr(miri, ignore = "sixteen thousand registers a command")]
22063 #[test]
22064 fn the_hyperloglog_group_answers_the_same_however_many_stripes_there_are() {
22065 let script: &[&[&[u8]]] = &[
22066 &[b"PFADD", b"h1", b"a", b"b", b"c"],
22067 &[b"PFADD", b"h1", b"a"],
22068 &[b"PFADD", b"h2"],
22069 &[b"PFADD", b"h2", b"c", b"d", b"e"],
22070 &[b"PFCOUNT", b"h1"],
22071 &[b"PFCOUNT", b"h2"],
22072 &[b"PFCOUNT", b"missing"],
22073 &[b"PFCOUNT", b"h1", b"h2"],
22075 &[b"PFCOUNT", b"h1", b"missing"],
22076 &[b"PFMERGE", b"m", b"h1", b"h2"],
22077 &[b"PFCOUNT", b"m"],
22078 &[b"STRLEN", b"m"],
22079 &[b"PFMERGE", b"m"],
22080 &[b"PFCOUNT", b"m"],
22081 &[b"PFMERGE", b"m2", b"missing"],
22082 &[b"PFCOUNT", b"m2"],
22083 &[b"PFDEBUG", b"ENCODING", b"h1"],
22086 &[b"PFDEBUG", b"DECODE", b"h1"],
22087 &[b"PFDEBUG", b"TODENSE", b"h1"],
22088 &[b"PFDEBUG", b"ENCODING", b"h1"],
22089 &[b"PFDEBUG", b"TODENSE", b"h1"],
22090 &[b"PFCOUNT", b"h1", b"h2"],
22091 &[b"PFSELFTEST"],
22092 &[b"SET", b"plain", b"not a sketch at all"],
22094 &[b"PFADD", b"plain", b"a"],
22095 &[b"PFCOUNT", b"plain"],
22096 &[b"PFCOUNT", b"h1", b"plain"],
22097 &[b"PFMERGE", b"plain", b"h1"],
22098 &[b"PFMERGE", b"m", b"plain"],
22099 &[b"PFDEBUG", b"ENCODING", b"gone"],
22100 &[b"PFDEBUG", b"NOPE", b"h1"],
22101 ];
22102
22103 let mut one = Fixture::new();
22104 let mut many = Fixture::striped(8);
22105 for parts in script {
22106 let a = one.run(parts);
22107 let b = many.run(parts);
22108 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22109 }
22110 }
22111
22112 #[test]
22121 fn the_set_group_answers_the_same_however_many_stripes_there_are() {
22122 const UNORDERED: [&str; 4] = ["SMEMBERS", "SINTER", "SUNION", "SDIFF"];
22123 let script: &[&[&[u8]]] = &[
22124 &[b"SADD", b"s1", b"a", b"b", b"c"],
22125 &[b"SADD", b"s1", b"a"],
22126 &[b"SADD", b"s2", b"b", b"c", b"d"],
22127 &[b"SADD", b"ints", b"1", b"2", b"3"],
22128 &[b"SCARD", b"s1"],
22129 &[b"SISMEMBER", b"s1", b"a"],
22130 &[b"SISMEMBER", b"s1", b"z"],
22131 &[b"SMISMEMBER", b"s1", b"a", b"z", b"c"],
22132 &[b"SMEMBERS", b"s1"],
22133 &[b"SREM", b"s1", b"c"],
22134 &[b"SADD", b"s1", b"c"],
22135 &[b"SSCAN", b"s1", b"0"],
22136 &[b"SSCAN", b"s1", b"0", b"COUNT", b"100", b"MATCH", b"a*"],
22137 &[b"SADD", b"one", b"m"],
22140 &[b"SRANDMEMBER", b"one"],
22141 &[b"SRANDMEMBER", b"one", b"-3"],
22142 &[b"SRANDMEMBER", b"gone"],
22143 &[b"SPOP", b"one"],
22144 &[b"SPOP", b"one"],
22145 &[b"SPOP", b"gone", b"2"],
22146 &[b"SMOVE", b"s1", b"s2", b"a"],
22148 &[b"SMOVE", b"s1", b"s2", b"zzz"],
22149 &[b"SMOVE", b"gone", b"s2", b"a"],
22150 &[b"SMEMBERS", b"s1"],
22151 &[b"SMEMBERS", b"s2"],
22152 &[b"SINTER", b"s1", b"s2"],
22154 &[b"SUNION", b"s1", b"s2"],
22155 &[b"SDIFF", b"s2", b"s1"],
22156 &[b"SINTER", b"s1", b"gone"],
22157 &[b"SUNION", b"s1", b"gone"],
22158 &[b"SDIFF", b"gone", b"s1"],
22159 &[b"SINTER", b"ints", b"s1"],
22160 &[b"SINTERCARD", b"2", b"s1", b"s2"],
22161 &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"1"],
22162 &[b"SUNIONCARD", b"2", b"s1", b"s2"],
22163 &[b"SDIFFCARD", b"2", b"s2", b"s1"],
22164 &[b"SINTERSTORE", b"d1", b"s1", b"s2"],
22165 &[b"SMEMBERS", b"d1"],
22166 &[b"SUNIONSTORE", b"d2", b"s1", b"s2"],
22167 &[b"SCARD", b"d2"],
22168 &[b"SDIFFSTORE", b"d3", b"s2", b"s1"],
22169 &[b"SCARD", b"d3"],
22170 &[b"SINTERSTORE", b"d4", b"s1", b"gone"],
22173 &[b"EXISTS", b"d4"],
22174 &[b"SUNIONSTORE", b"s2", b"s1", b"s2"],
22176 &[b"SCARD", b"s2"],
22177 &[b"SET", b"str", b"v"],
22179 &[b"SADD", b"str", b"a"],
22180 &[b"SINTER", b"s1", b"str"],
22181 &[b"SINTERSTORE", b"d5", b"s1", b"str"],
22182 &[b"EXISTS", b"d5"],
22183 &[b"SMOVE", b"str", b"s2", b"a"],
22184 &[b"SMOVE", b"s1", b"str", b"b"],
22185 &[b"SMOVE", b"gone", b"str", b"b"],
22186 &[b"SINTERCARD", b"0", b"s1"],
22187 &[b"SINTERCARD", b"3", b"s1", b"s2"],
22188 &[b"SINTERCARD", b"2", b"s1", b"s2", b"LIMIT", b"-1"],
22189 &[b"SPOP", b"s1", b"-1"],
22190 ];
22191
22192 let mut one = Fixture::new();
22193 let mut many = Fixture::striped(8);
22194 for parts in script {
22195 let a = one.run(parts);
22196 let b = many.run(parts);
22197 let name = String::from_utf8_lossy(parts[0]).to_uppercase();
22198 if UNORDERED.contains(&name.as_str()) && a.starts_with(['*', '~']) {
22199 assert_eq!(sorted(&a), sorted(&b), "{name}");
22200 } else {
22201 assert_eq!(a, b, "{name}");
22202 }
22203 }
22204 }
22205
22206 #[test]
22208 fn a_set_operation_across_stripes_reads_every_set() {
22209 let mut f = Fixture::striped(8);
22210 let second = apart(&mut f, "s1");
22211 let third = apart(&mut f, &second);
22212 let (s1, s2, s3) = (b"s1".as_slice(), second.as_bytes(), third.as_bytes());
22213
22214 f.run(&[b"SADD", s1, b"a", b"b", b"c"]);
22215 f.run(&[b"SADD", s2, b"b", b"c", b"d"]);
22216 assert_eq!(sorted(&f.run(&[b"SINTER", s1, s2])), ["b", "c"]);
22217 assert_eq!(
22218 sorted(&f.run(&[b"SUNION", s1, s2])),
22219 ["a", "b", "c", "d"],
22220 "a union of two stripes is both of them"
22221 );
22222 assert_eq!(sorted(&f.run(&[b"SDIFF", s1, s2])), ["a"]);
22223 assert_eq!(f.run(&[b"SINTERCARD", b"2", s1, s2]), ":2\r\n");
22224 assert_eq!(f.run(&[b"SUNIONCARD", b"2", s1, s2]), ":4\r\n");
22225 assert_eq!(f.run(&[b"SDIFFCARD", b"2", s1, s2]), ":1\r\n");
22226
22227 assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, s2]), ":2\r\n");
22229 assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["b", "c"]);
22230 assert_eq!(f.run(&[b"SUNIONSTORE", s2, s1, s2]), ":4\r\n");
22231 assert_eq!(sorted(&f.run(&[b"SMEMBERS", s2])), ["a", "b", "c", "d"]);
22232 assert_eq!(f.run(&[b"SDIFFSTORE", s3, s2, s1]), ":1\r\n");
22233 assert_eq!(sorted(&f.run(&[b"SMEMBERS", s3])), ["d"]);
22234
22235 assert_eq!(f.run(&[b"SINTERSTORE", s3, s1, b"gone"]), ":0\r\n");
22238 assert_eq!(f.run(&[b"EXISTS", s3]), ":0\r\n");
22239 f.run(&[b"SET", s3, b"v"]);
22240 assert_eq!(
22241 f.run(&[b"SINTER", s1, s3]),
22242 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22243 );
22244 assert_eq!(f.run(&[b"GET", s3]), "$1\r\nv\r\n", "and left it alone");
22245 }
22246
22247 #[test]
22249 fn a_move_across_stripes_takes_the_member_with_it() {
22250 let mut f = Fixture::striped(8);
22251 let other = apart(&mut f, "src");
22252 let (src, dst) = (b"src".as_slice(), other.as_bytes());
22253
22254 f.run(&[b"SADD", src, b"a", b"b"]);
22255 f.run(&[b"SADD", dst, b"c"]);
22256 assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":1\r\n");
22257 assert_eq!(sorted(&f.run(&[b"SMEMBERS", src])), ["b"]);
22258 assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["a", "c"]);
22259 assert_eq!(f.run(&[b"SMOVE", src, dst, b"a"]), ":0\r\n", "it has gone");
22260
22261 f.run(&[b"DEL", dst]);
22264 assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":1\r\n");
22265 assert_eq!(f.run(&[b"EXISTS", src]), ":0\r\n", "the source is empty");
22266 assert_eq!(sorted(&f.run(&[b"SMEMBERS", dst])), ["b"]);
22267
22268 f.run(&[b"SET", dst, b"v"]);
22272 assert_eq!(f.run(&[b"SMOVE", src, dst, b"b"]), ":0\r\n");
22273 f.run(&[b"SADD", src, b"b"]);
22274 assert_eq!(
22275 f.run(&[b"SMOVE", src, dst, b"b"]),
22276 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22277 );
22278 }
22279
22280 #[test]
22282 fn a_pfcount_and_a_pfmerge_reach_across_stripes() {
22283 let mut f = Fixture::striped(8);
22284 let other = apart(&mut f, "src");
22285 let (src, far) = (b"src".as_slice(), other.as_bytes());
22286
22287 for i in 0..150 {
22288 let ele = format!("e:{i}");
22289 f.run(&[b"PFADD", src, ele.as_bytes()]);
22290 }
22291 for i in 150..200 {
22292 let ele = format!("e:{i}");
22293 f.run(&[b"PFADD", far, ele.as_bytes()]);
22294 }
22295 assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n");
22298 assert_eq!(f.run(&[b"PFCOUNT", far]), ":49\r\n");
22299 assert_eq!(f.run(&[b"PFCOUNT", src, far]), ":199\r\n");
22300
22301 let dest = apart(&mut f, &other);
22304 assert_eq!(f.run(&[b"PFMERGE", dest.as_bytes(), src, far]), "+OK\r\n");
22305 assert_eq!(f.run(&[b"PFCOUNT", dest.as_bytes()]), ":199\r\n");
22306 assert_eq!(f.run(&[b"PFMERGE", far, src]), "+OK\r\n");
22307 assert_eq!(f.run(&[b"PFCOUNT", far]), ":199\r\n", "and kept its own");
22308 assert_eq!(f.run(&[b"PFCOUNT", src]), ":151\r\n", "and left the source");
22309 }
22310
22311 #[test]
22318 fn the_sorted_set_group_answers_the_same_however_many_stripes_there_are() {
22319 let script: &[&[&[u8]]] = &[
22320 &[b"ZADD", b"z1", b"1", b"a", b"2", b"b", b"3", b"c"],
22321 &[b"ZADD", b"z1", b"NX", b"9", b"a"],
22322 &[b"ZADD", b"z1", b"XX", b"CH", b"5", b"a"],
22323 &[b"ZADD", b"z1", b"GT", b"CH", b"1", b"a"],
22324 &[b"ZADD", b"z1", b"INCR", b"2", b"a"],
22325 &[b"ZINCRBY", b"z1", b"1.5", b"b"],
22326 &[b"ZADD", b"z2", b"1", b"b", b"2", b"c", b"3", b"d"],
22327 &[b"ZADD", b"lex", b"0", b"a", b"0", b"b", b"0", b"c"],
22328 &[b"ZADD", b"one", b"1", b"m"],
22329 &[b"ZCARD", b"z1"],
22330 &[b"ZCARD", b"gone"],
22331 &[b"ZSCORE", b"z1", b"a"],
22332 &[b"ZSCORE", b"z1", b"zz"],
22333 &[b"ZMSCORE", b"z1", b"a", b"zz", b"c"],
22334 &[b"ZRANK", b"z1", b"c"],
22335 &[b"ZRANK", b"z1", b"c", b"WITHSCORE"],
22336 &[b"ZREVRANK", b"z1", b"c"],
22337 &[b"ZRANK", b"z1", b"gone"],
22338 &[b"ZCOUNT", b"z1", b"-inf", b"+inf"],
22339 &[b"ZCOUNT", b"z1", b"(1", b"3"],
22340 &[b"ZLEXCOUNT", b"lex", b"-", b"+"],
22341 &[b"ZRANGE", b"z1", b"0", b"-1"],
22343 &[b"ZRANGE", b"z1", b"0", b"-1", b"WITHSCORES"],
22344 &[b"ZRANGE", b"z1", b"1", b"9", b"BYSCORE"],
22345 &[b"ZRANGE", b"z1", b"9", b"1", b"BYSCORE", b"REV"],
22346 &[b"ZRANGE", b"lex", b"[a", b"(c", b"BYLEX"],
22347 &[b"ZREVRANGE", b"z1", b"0", b"-1"],
22348 &[
22349 b"ZRANGEBYSCORE",
22350 b"z1",
22351 b"-inf",
22352 b"+inf",
22353 b"LIMIT",
22354 b"1",
22355 b"1",
22356 ],
22357 &[b"ZREVRANGEBYLEX", b"lex", b"+", b"-"],
22358 &[b"ZSCAN", b"z1", b"0"],
22359 &[b"ZSCAN", b"z1", b"0", b"MATCH", b"a*", b"COUNT", b"100"],
22360 &[b"ZRANDMEMBER", b"one"],
22363 &[b"ZRANDMEMBER", b"one", b"-3", b"WITHSCORES"],
22364 &[b"ZRANDMEMBER", b"gone"],
22365 &[b"ZRANGESTORE", b"d0", b"z1", b"0", b"1"],
22367 &[b"ZRANGE", b"d0", b"0", b"-1", b"WITHSCORES"],
22368 &[b"ZRANGESTORE", b"d0", b"z1", b"5", b"1"],
22369 &[b"EXISTS", b"d0"],
22370 &[b"ZUNION", b"2", b"z1", b"z2"],
22372 &[b"ZUNION", b"2", b"z1", b"z2", b"WITHSCORES"],
22373 &[
22374 b"ZUNION",
22375 b"2",
22376 b"z1",
22377 b"z2",
22378 b"WEIGHTS",
22379 b"2",
22380 b"3",
22381 b"AGGREGATE",
22382 b"MAX",
22383 b"WITHSCORES",
22384 ],
22385 &[b"ZINTER", b"2", b"z1", b"z2", b"WITHSCORES"],
22386 &[b"ZDIFF", b"2", b"z1", b"z2", b"WITHSCORES"],
22387 &[b"ZDIFF", b"2", b"gone", b"z1"],
22388 &[b"ZINTERCARD", b"2", b"z1", b"z2"],
22389 &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"1"],
22390 &[b"ZUNIONSTORE", b"d1", b"2", b"z1", b"z2"],
22391 &[b"ZRANGE", b"d1", b"0", b"-1", b"WITHSCORES"],
22392 &[
22393 b"ZINTERSTORE",
22394 b"d2",
22395 b"2",
22396 b"z1",
22397 b"z2",
22398 b"AGGREGATE",
22399 b"MIN",
22400 ],
22401 &[b"ZRANGE", b"d2", b"0", b"-1", b"WITHSCORES"],
22402 &[b"ZDIFFSTORE", b"d3", b"2", b"z1", b"z2"],
22403 &[b"ZCARD", b"d3"],
22404 &[b"ZINTERSTORE", b"d4", b"2", b"z1", b"gone"],
22407 &[b"EXISTS", b"d4"],
22408 &[b"SADD", b"plain", b"a", b"x"],
22411 &[b"ZUNIONSTORE", b"d5", b"2", b"z1", b"plain"],
22412 &[b"ZRANGE", b"d5", b"0", b"-1", b"WITHSCORES"],
22413 &[b"ZUNIONSTORE", b"z2", b"2", b"z1", b"z2"],
22415 &[b"ZRANGE", b"z2", b"0", b"-1", b"WITHSCORES"],
22416 &[b"ZREM", b"d5", b"x", b"nothere"],
22418 &[b"ZREMRANGEBYRANK", b"d5", b"0", b"0"],
22419 &[b"ZREMRANGEBYSCORE", b"d1", b"-inf", b"1"],
22420 &[b"ZREMRANGEBYLEX", b"lex", b"[a", b"[a"],
22421 &[b"ZPOPMIN", b"z1"],
22422 &[b"ZPOPMAX", b"z1", b"2"],
22423 &[b"ZPOPMIN", b"gone"],
22424 &[b"ZMPOP", b"2", b"gone", b"z2", b"MIN"],
22425 &[b"ZMPOP", b"2", b"gone", b"nothere", b"MAX", b"COUNT", b"2"],
22426 &[b"SET", b"str", b"v"],
22428 &[b"ZADD", b"str", b"1", b"a"],
22429 &[b"ZSCORE", b"str", b"a"],
22430 &[b"ZADD", b"z1", b"nan", b"a"],
22431 &[b"ZUNION", b"2", b"z1", b"str"],
22432 &[b"ZUNIONSTORE", b"d6", b"2", b"z1", b"str"],
22433 &[b"EXISTS", b"d6"],
22434 &[b"ZINTERCARD", b"0", b"z1"],
22435 &[b"ZINTERCARD", b"2", b"z1", b"z2", b"LIMIT", b"-1"],
22436 &[b"ZRANGESTORE", b"d7", b"str", b"0", b"-1"],
22437 &[b"ZMPOP", b"1", b"str", b"MIN"],
22438 &[b"ZPOPMIN", b"z1", b"-1"],
22439 ];
22440
22441 let mut one = Fixture::new();
22442 let mut many = Fixture::striped(8);
22443 for parts in script {
22444 let a = one.run(parts);
22445 let b = many.run(parts);
22446 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22447 }
22448 }
22449
22450 #[test]
22452 fn a_sorted_set_operation_across_stripes_reads_every_input() {
22453 let mut f = Fixture::striped(8);
22454 let second = apart(&mut f, "z1");
22455 let third = apart(&mut f, &second);
22456 let (z1, z2, z3) = (b"z1".as_slice(), second.as_bytes(), third.as_bytes());
22457
22458 f.run(&[b"ZADD", z1, b"1", b"a", b"2", b"b"]);
22459 f.run(&[b"ZADD", z2, b"3", b"b", b"4", b"c"]);
22460 assert_eq!(
22463 f.run(&[b"ZUNION", b"2", z1, z2]),
22464 "*3\r\n$1\r\na\r\n$1\r\nc\r\n$1\r\nb\r\n"
22465 );
22466 assert_eq!(f.run(&[b"ZINTER", b"2", z1, z2]), "*1\r\n$1\r\nb\r\n");
22467 assert_eq!(f.run(&[b"ZDIFF", b"2", z1, z2]), "*1\r\n$1\r\na\r\n");
22468 assert_eq!(f.run(&[b"ZINTERCARD", b"2", z1, z2]), ":1\r\n");
22469 assert_eq!(
22470 f.run(&[b"ZINTERCARD", b"2", z1, z2, b"LIMIT", b"1"]),
22471 ":1\r\n"
22472 );
22473
22474 assert_eq!(f.run(&[b"ZUNIONSTORE", z3, b"2", z1, z2]), ":3\r\n");
22477 assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n5\r\n");
22478 assert_eq!(
22479 f.run(&[
22480 b"ZUNIONSTORE",
22481 z3,
22482 b"2",
22483 z1,
22484 z2,
22485 b"WEIGHTS",
22486 b"2",
22487 b"3",
22488 b"AGGREGATE",
22489 b"MAX"
22490 ]),
22491 ":3\r\n"
22492 );
22493 assert_eq!(f.run(&[b"ZSCORE", z3, b"b"]), "$1\r\n9\r\n");
22494 assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, z2]), ":1\r\n");
22495 assert_eq!(f.run(&[b"ZCARD", z3]), ":1\r\n");
22496 assert_eq!(f.run(&[b"ZDIFFSTORE", z3, b"2", z2, z1]), ":1\r\n");
22497 assert_eq!(f.run(&[b"ZSCORE", z3, b"c"]), "$1\r\n4\r\n");
22498
22499 let popped = format!(
22502 "*2\r\n${}\r\n{second}\r\n*1\r\n*2\r\n$1\r\nb\r\n$1\r\n3\r\n",
22503 second.len()
22504 );
22505 assert_eq!(f.run(&[b"ZMPOP", b"3", b"gone", z2, z1, b"MIN"]), popped);
22506 f.run(&[b"ZADD", z2, b"3", b"b"]);
22507
22508 assert_eq!(f.run(&[b"ZINTERSTORE", z3, b"2", z1, b"gone"]), ":0\r\n");
22511 assert_eq!(f.run(&[b"EXISTS", z3]), ":0\r\n");
22512 f.run(&[b"SET", z3, b"v"]);
22513 assert_eq!(
22514 f.run(&[b"ZUNION", b"2", z1, z3]),
22515 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22516 );
22517 assert_eq!(f.run(&[b"GET", z3]), "$1\r\nv\r\n", "and left it alone");
22518
22519 assert_eq!(f.run(&[b"ZUNIONSTORE", z2, b"2", z1, z2]), ":3\r\n");
22523 assert_eq!(f.run(&[b"ZSCORE", z2, b"b"]), "$1\r\n5\r\n");
22524 assert_eq!(f.run(&[b"ZCARD", z2]), ":3\r\n");
22525 }
22526
22527 #[test]
22529 fn a_range_store_across_stripes_copies_the_window() {
22530 let mut f = Fixture::striped(8);
22531 let other = apart(&mut f, "src");
22532 let third = apart(&mut f, &other);
22533 let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
22534
22535 f.run(&[b"ZADD", src, b"1", b"a", b"2", b"b", b"3", b"c"]);
22536 assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"0", b"1"]), ":2\r\n");
22537 assert_eq!(
22538 f.run(&[b"ZRANGE", dst, b"0", b"-1", b"WITHSCORES"]),
22539 "*4\r\n$1\r\na\r\n$1\r\n1\r\n$1\r\nb\r\n$1\r\n2\r\n"
22540 );
22541 assert_eq!(f.run(&[b"ZCARD", src]), ":3\r\n", "the source kept its own");
22542
22543 assert_eq!(
22546 f.run(&[
22547 b"ZRANGESTORE",
22548 dst,
22549 src,
22550 b"+inf",
22551 b"-inf",
22552 b"BYSCORE",
22553 b"REV",
22554 b"LIMIT",
22555 b"0",
22556 b"2"
22557 ]),
22558 ":2\r\n"
22559 );
22560 assert_eq!(
22561 f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
22562 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
22563 );
22564
22565 assert_eq!(f.run(&[b"ZRANGESTORE", dst, src, b"5", b"1"]), ":0\r\n");
22568 assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
22569 f.run(&[b"ZRANGESTORE", dst, src, b"0", b"-1"]);
22570 f.run(&[b"SET", plain, b"v"]);
22571 assert_eq!(
22572 f.run(&[b"ZRANGESTORE", dst, plain, b"0", b"-1"]),
22573 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22574 );
22575 assert_eq!(
22576 f.run(&[b"ZCARD", dst]),
22577 ":3\r\n",
22578 "and left the destination"
22579 );
22580 }
22581
22582 #[test]
22589 fn the_list_group_answers_the_same_however_many_stripes_there_are() {
22590 let script: &[&[&[u8]]] = &[
22591 &[b"RPUSH", b"l1", b"a", b"b", b"c"],
22592 &[b"LPUSH", b"l1", b"z"],
22593 &[b"RPUSHX", b"l1", b"d"],
22594 &[b"LPUSHX", b"gone", b"x"],
22595 &[b"RPUSHX", b"gone", b"x"],
22596 &[b"LLEN", b"l1"],
22597 &[b"LLEN", b"gone"],
22598 &[b"LRANGE", b"l1", b"0", b"-1"],
22599 &[b"LRANGE", b"l1", b"1", b"2"],
22600 &[b"LRANGE", b"l1", b"5", b"9"],
22601 &[b"LINDEX", b"l1", b"0"],
22602 &[b"LINDEX", b"l1", b"-1"],
22603 &[b"LINDEX", b"l1", b"99"],
22604 &[b"LSET", b"l1", b"0", b"y"],
22605 &[b"LINSERT", b"l1", b"BEFORE", b"b", b"aa"],
22606 &[b"LINSERT", b"l1", b"AFTER", b"nothere", b"x"],
22607 &[b"LPOS", b"l1", b"b"],
22608 &[b"LPOS", b"l1", b"b", b"COUNT", b"0"],
22609 &[b"LPOS", b"l1", b"nothere"],
22610 &[b"LPOS", b"l1", b"b", b"RANK", b"-1", b"MAXLEN", b"2"],
22611 &[b"LREM", b"l1", b"1", b"aa"],
22612 &[b"LTRIM", b"l1", b"0", b"3"],
22613 &[b"LRANGE", b"l1", b"0", b"-1"],
22614 &[b"LPOP", b"l1"],
22615 &[b"RPOP", b"l1"],
22616 &[b"LPOP", b"l1", b"2"],
22617 &[b"LPOP", b"gone"],
22618 &[b"LPOP", b"gone", b"2"],
22619 &[b"EXISTS", b"l1"],
22620 &[b"RPUSH", b"src", b"a", b"b", b"c", b"d"],
22623 &[b"LMOVE", b"src", b"dst", b"LEFT", b"RIGHT"],
22624 &[b"RPOPLPUSH", b"src", b"dst"],
22625 &[b"LRANGE", b"dst", b"0", b"-1"],
22626 &[b"LMOVE", b"gone", b"dst", b"LEFT", b"RIGHT"],
22627 &[b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT"],
22628 &[
22629 b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK",
22630 ],
22631 &[
22632 b"LMOVEM", b"dst", b"dst", b"LEFT", b"RIGHT", b"COUNT", b"2", b"OBO",
22633 ],
22634 &[b"LRANGE", b"dst", b"0", b"-1"],
22635 &[
22636 b"LMOVEM", b"src", b"dst", b"LEFT", b"RIGHT", b"EXACTLY", b"9", b"BULK",
22637 ],
22638 &[b"LMPOP", b"2", b"gone", b"dst", b"LEFT"],
22639 &[b"LMPOP", b"2", b"gone", b"dst", b"RIGHT", b"COUNT", b"2"],
22640 &[b"LMPOP", b"1", b"gone", b"LEFT"],
22641 &[b"RPUSH", b"q", b"a", b"b", b"c"],
22644 &[b"BLPOP", b"gone", b"q", b"0"],
22645 &[b"BRPOP", b"q", b"0"],
22646 &[b"BLMPOP", b"0", b"2", b"gone", b"q", b"LEFT"],
22647 &[b"RPUSH", b"q", b"x", b"y", b"z"],
22648 &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
22649 &[b"BRPOPLPUSH", b"q", b"dst", b"0"],
22650 &[b"BLMOVEM", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
22651 &[b"BLPOP", b"q", b"0"],
22652 &[b"BLMOVE", b"q", b"dst", b"LEFT", b"RIGHT", b"0"],
22653 &[b"SET", b"plain", b"v"],
22655 &[b"LPUSH", b"plain", b"a"],
22656 &[b"LLEN", b"plain"],
22657 &[b"LMOVE", b"dst", b"plain", b"LEFT", b"RIGHT"],
22658 &[b"LRANGE", b"dst", b"0", b"-1"],
22659 &[b"LMOVEM", b"dst", b"plain", b"LEFT", b"RIGHT"],
22660 &[b"LSET", b"gone", b"0", b"v"],
22661 &[b"LSET", b"dst", b"99", b"v"],
22662 &[b"LPOP", b"dst", b"-1"],
22663 &[b"LMPOP", b"0", b"dst", b"LEFT"],
22664 &[b"LPOS", b"dst", b"a", b"RANK", b"0"],
22665 ];
22666
22667 let mut one = Fixture::new();
22668 let mut many = Fixture::striped(8);
22669 for parts in script {
22670 let a = one.run(parts);
22671 let b = many.run(parts);
22672 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22673 }
22674 }
22675
22676 #[test]
22678 fn a_list_move_across_stripes_takes_the_elements_with_it() {
22679 let mut f = Fixture::striped(8);
22680 let other = apart(&mut f, "src");
22681 let third = apart(&mut f, &other);
22682 let (src, dst, plain) = (b"src".as_slice(), other.as_bytes(), third.as_bytes());
22683
22684 f.run(&[b"RPUSH", src, b"a", b"b", b"c", b"d"]);
22685 assert_eq!(
22686 f.run(&[b"LMOVE", src, dst, b"LEFT", b"RIGHT"]),
22687 "$1\r\na\r\n"
22688 );
22689 assert_eq!(f.run(&[b"RPOPLPUSH", src, dst]), "$1\r\nd\r\n");
22690 assert_eq!(
22691 f.run(&[b"LRANGE", dst, b"0", b"-1"]),
22692 "*2\r\n$1\r\nd\r\n$1\r\na\r\n",
22693 "one went on each end of the destination"
22694 );
22695 assert_eq!(
22696 f.run(&[b"LRANGE", src, b"0", b"-1"]),
22697 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
22698 );
22699
22700 assert_eq!(
22702 f.run(&[
22703 b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"COUNT", b"2", b"BULK"
22704 ]),
22705 "*2\r\n$1\r\nb\r\n$1\r\nc\r\n"
22706 );
22707 assert_eq!(
22708 f.run(&[b"LRANGE", dst, b"0", b"-1"]),
22709 "*4\r\n$1\r\nd\r\n$1\r\na\r\n$1\r\nb\r\n$1\r\nc\r\n"
22710 );
22711 assert_eq!(
22712 f.run(&[b"EXISTS", src]),
22713 ":0\r\n",
22714 "and the source is gone with its last element"
22715 );
22716
22717 f.run(&[b"RPUSH", src, b"e", b"f"]);
22720 assert_eq!(
22721 f.run(&[
22722 b"LMOVEM", src, dst, b"LEFT", b"RIGHT", b"EXACTLY", b"3", b"BULK"
22723 ]),
22724 "*-1\r\n"
22725 );
22726 assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n", "and took none of them");
22727 assert_eq!(
22728 f.run(&[b"LMOVE", b"gone", dst, b"LEFT", b"RIGHT"]),
22729 "$-1\r\n"
22730 );
22731 assert_eq!(
22732 f.run(&[b"LMOVEM", b"gone", dst, b"LEFT", b"RIGHT"]),
22733 "*-1\r\n"
22734 );
22735
22736 f.run(&[b"SET", plain, b"v"]);
22740 assert_eq!(
22741 f.run(&[b"LMOVE", src, plain, b"LEFT", b"RIGHT"]),
22742 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22743 );
22744 assert_eq!(
22745 f.run(&[b"LLEN", src]),
22746 ":2\r\n",
22747 "and left the source alone"
22748 );
22749 assert_eq!(
22750 f.run(&[b"LMOVEM", src, plain, b"LEFT", b"RIGHT"]),
22751 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
22752 );
22753 assert_eq!(f.run(&[b"LLEN", src]), ":2\r\n");
22754 }
22755
22756 #[test]
22762 fn a_parked_client_is_served_from_the_stripe_its_key_is_on() {
22763 let mut f = Fixture::striped(8);
22764 let other = apart(&mut f, "q");
22765 let (q, far) = (b"q".as_slice(), other.as_bytes());
22766
22767 assert_eq!(f.flow(&[b"BLPOP", q, far, b"0"]).0, Flow::Block);
22768 assert_eq!(f.server.parked(), 1);
22769 f.run(&[b"RPUSH", far, b"v"]);
22770 let mut out = Out::new(Proto::Resp2);
22771 assert!(f.server.serve_waiter(7, 0, &mut out));
22772 let want = format!("*2\r\n${}\r\n{other}\r\n$1\r\nv\r\n", other.len());
22773 assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
22774 assert_eq!(
22775 f.run(&[b"EXISTS", far]),
22776 ":0\r\n",
22777 "and it took the element with it"
22778 );
22779
22780 f.server.forget_waiters(7);
22783 assert_eq!(
22784 f.flow(&[b"BLMOVE", q, far, b"LEFT", b"RIGHT", b"0"]).0,
22785 Flow::Block
22786 );
22787 f.run(&[b"RPUSH", q, b"w"]);
22788 let mut out = Out::new(Proto::Resp2);
22789 assert!(f.server.serve_waiter(7, 0, &mut out));
22790 assert_eq!(
22791 core::str::from_utf8(out.as_slice()).expect("ascii"),
22792 "$1\r\nw\r\n"
22793 );
22794 assert_eq!(f.run(&[b"LRANGE", far, b"0", b"-1"]), "*1\r\n$1\r\nw\r\n");
22795 }
22796
22797 #[test]
22803 fn the_stream_group_answers_the_same_however_many_stripes_there_are() {
22804 let script: &[&[&[u8]]] = &[
22805 &[b"XADD", b"s", b"1-1", b"a", b"1"],
22806 &[b"XADD", b"s", b"2-1", b"b", b"2", b"c", b"3"],
22807 &[b"XADD", b"s", b"3-1", b"d", b"4"],
22808 &[b"XADD", b"s", b"1-1", b"e", b"5"],
22809 &[b"XADD", b"nomk", b"NOMKSTREAM", b"1-1", b"a", b"1"],
22810 &[b"XLEN", b"s"],
22811 &[b"XLEN", b"gone"],
22812 &[b"XRANGE", b"s", b"-", b"+"],
22813 &[b"XRANGE", b"s", b"2", b"+", b"COUNT", b"1"],
22814 &[b"XRANGE", b"gone", b"-", b"+", b"COUNT", b"0"],
22815 &[b"XRANGE", b"s", b"-", b"+", b"COUNT", b"0"],
22816 &[b"XREVRANGE", b"s", b"+", b"-"],
22817 &[b"XREAD", b"COUNT", b"2", b"STREAMS", b"s", b"0"],
22818 &[b"XREAD", b"STREAMS", b"s", b"gone", b"0", b"0"],
22819 &[b"XREAD", b"STREAMS", b"s", b"$"],
22820 &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
22822 &[b"XGROUP", b"CREATE", b"s", b"g", b"0"],
22823 &[b"XGROUP", b"CREATE", b"gone", b"g", b"0"],
22824 &[b"XGROUP", b"CREATE", b"made", b"g", b"$", b"MKSTREAM"],
22825 &[b"XGROUP", b"CREATECONSUMER", b"s", b"g", b"idle"],
22826 &[b"XREADGROUP", b"GROUP", b"g", b"c1", b"STREAMS", b"s", b">"],
22827 &[
22828 b"XREADGROUP",
22829 b"GROUP",
22830 b"g",
22831 b"c1",
22832 b"COUNT",
22833 b"1",
22834 b"STREAMS",
22835 b"s",
22836 b"0",
22837 ],
22838 &[
22839 b"XREADGROUP",
22840 b"GROUP",
22841 b"nope",
22842 b"c1",
22843 b"STREAMS",
22844 b"s",
22845 b">",
22846 ],
22847 &[b"XPENDING", b"s", b"g"],
22848 &[b"XPENDING", b"s", b"g", b"-", b"+", b"10"],
22849 &[b"XPENDING", b"s", b"g", b"-", b"+", b"10", b"c1"],
22850 &[b"XPENDING", b"s", b"nope"],
22851 &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"1-1"],
22852 &[b"XCLAIM", b"s", b"g", b"c2", b"0", b"2-1", b"JUSTID"],
22853 &[b"XAUTOCLAIM", b"s", b"g", b"c3", b"0", b"0"],
22854 &[b"XACK", b"s", b"g", b"1-1"],
22855 &[b"XACK", b"s", b"g", b"1-1"],
22856 &[b"XNACK", b"s", b"g", b"FAIL", b"IDS", b"1", b"2-1"],
22857 &[b"XPENDING", b"s", b"g"],
22858 &[b"XINFO", b"STREAM", b"s"],
22859 &[b"XINFO", b"GROUPS", b"s"],
22860 &[b"XINFO", b"CONSUMERS", b"s", b"g"],
22861 &[b"XINFO", b"STREAM", b"gone"],
22862 &[b"XDEL", b"s", b"3-1"],
22864 &[b"XDELEX", b"s", b"DELREF", b"IDS", b"1", b"2-1"],
22865 &[b"XACKDEL", b"s", b"g", b"KEEPREF", b"IDS", b"1", b"1-1"],
22866 &[b"XADD", b"s", b"9-1", b"z", b"9"],
22867 &[b"XTRIM", b"s", b"MAXLEN", b"1"],
22868 &[b"XTRIM", b"s", b"MINID", b"9"],
22869 &[b"XSETID", b"s", b"99-1"],
22870 &[b"XSETID", b"s", b"1-1"],
22871 &[b"XLEN", b"s"],
22872 &[b"XGROUP", b"SETID", b"s", b"g", b"0"],
22873 &[b"XGROUP", b"DELCONSUMER", b"s", b"g", b"c1"],
22874 &[b"XGROUP", b"DESTROY", b"s", b"g"],
22875 &[b"XGROUP", b"DESTROY", b"s", b"g"],
22876 &[b"SET", b"plain", b"v"],
22878 &[b"XADD", b"plain", b"1-1", b"a", b"1"],
22879 &[b"XLEN", b"plain"],
22880 &[b"XREAD", b"STREAMS", b"plain", b"0"],
22881 &[b"XRANGE", b"s", b"bogus", b"+"],
22882 &[b"XADD", b"s", b"1-1", b"a"],
22883 &[b"XREAD", b"STREAMS", b"s", b"gone", b"0"],
22884 &[b"XREADGROUP", b"GROUP", b"g", b"c", b"STREAMS", b"s", b"$"],
22885 ];
22886
22887 let mut one = Fixture::new();
22888 let mut many = Fixture::striped(8);
22889 for parts in script {
22890 let a = one.run(parts);
22891 let b = many.run(parts);
22892 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
22893 }
22894 }
22895
22896 #[test]
22902 fn a_stream_read_across_stripes_reads_every_key() {
22903 let mut f = Fixture::striped(8);
22904 let other = apart(&mut f, "s1");
22905 let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
22906
22907 f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
22908 f.run(&[b"XADD", s2, b"2-1", b"b", b"2"]);
22909 let got = f.run(&[b"XREAD", b"STREAMS", s1, s2, b"0", b"0"]);
22910 assert!(got.starts_with("*2\r\n"), "both streams answered: {got}");
22911 assert!(got.contains("1-1"), "the first one is in there: {got}");
22912 assert!(got.contains("2-1"), "and so is the second: {got}");
22913
22914 f.run(&[b"XGROUP", b"CREATE", s1, b"g", b"0"]);
22917 let got = f.run(&[
22918 b"XREADGROUP",
22919 b"GROUP",
22920 b"g",
22921 b"c",
22922 b"STREAMS",
22923 s1,
22924 s2,
22925 b">",
22926 b">",
22927 ]);
22928 assert!(got.starts_with("-NOGROUP"), "{got}");
22929 assert_eq!(
22930 f.run(&[b"XPENDING", s1, b"g"]),
22931 "*4\r\n:0\r\n$-1\r\n$-1\r\n*-1\r\n",
22932 "and read nothing from the key that did have the group"
22933 );
22934
22935 f.run(&[b"XGROUP", b"CREATE", s2, b"g", b"0"]);
22936 let got = f.run(&[
22937 b"XREADGROUP",
22938 b"GROUP",
22939 b"g",
22940 b"c",
22941 b"STREAMS",
22942 s1,
22943 s2,
22944 b">",
22945 b">",
22946 ]);
22947 assert!(got.starts_with("*2\r\n"), "now both are read: {got}");
22948 }
22949
22950 #[test]
22952 fn a_parked_stream_reader_is_served_from_the_stripe_its_key_is_on() {
22953 let mut f = Fixture::striped(8);
22954 let other = apart(&mut f, "s1");
22955 let (s1, far) = (b"s1".as_slice(), other.as_bytes());
22956 f.run(&[b"XADD", s1, b"1-1", b"a", b"1"]);
22957 f.run(&[b"XADD", far, b"1-1", b"a", b"1"]);
22958
22959 assert_eq!(
22960 f.flow(&[b"XREAD", b"BLOCK", b"0", b"STREAMS", s1, far, b"$", b"$"])
22961 .0,
22962 Flow::Block
22963 );
22964 f.run(&[b"XADD", far, b"2-1", b"b", b"2"]);
22965 let mut out = Out::new(Proto::Resp2);
22966 assert!(f.server.serve_waiter(7, 0, &mut out));
22967 let want = format!(
22968 "*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",
22969 other.len()
22970 );
22971 assert_eq!(core::str::from_utf8(out.as_slice()).expect("ascii"), want);
22972 }
22973
22974 #[test]
22976 fn the_json_group_answers_the_same_however_many_stripes_there_are() {
22977 let script: &[&[&[u8]]] = &[
22978 &[
22979 b"JSON.SET",
22980 b"d",
22981 b"$",
22982 br#"{"a":1,"b":[1,2,3],"s":"hi","t":true}"#,
22983 ],
22984 &[b"JSON.SET", b"d", b"$.a", b"2"],
22985 &[b"JSON.SET", b"d", b"$.new", b"9", b"NX"],
22986 &[b"JSON.SET", b"d", b"$.new", b"8", b"NX"],
22987 &[b"JSON.SET", b"d", b"$.nope", b"7", b"XX"],
22988 &[b"JSON.GET", b"d"],
22989 &[b"JSON.GET", b"d", b"$.b"],
22990 &[b"JSON.GET", b"gone", b"$"],
22991 &[b"JSON.TYPE", b"d", b"$.b"],
22992 &[b"JSON.TYPE", b"d", b"$.s"],
22993 &[b"JSON.TOGGLE", b"d", b"$.t"],
22994 &[b"JSON.ARRLEN", b"d", b"$.b"],
22995 &[b"JSON.OBJLEN", b"d", b"$"],
22996 &[b"JSON.OBJKEYS", b"d", b"$"],
22997 &[b"JSON.STRLEN", b"d", b"$.s"],
22998 &[b"JSON.STRAPPEND", b"d", b"$.s", br#""there""#],
22999 &[b"JSON.ARRAPPEND", b"d", b"$.b", b"4"],
23000 &[b"JSON.ARRINSERT", b"d", b"$.b", b"0", b"0"],
23001 &[b"JSON.ARRINDEX", b"d", b"$.b", b"3"],
23002 &[b"JSON.ARRTRIM", b"d", b"$.b", b"1", b"3"],
23003 &[b"JSON.ARRPOP", b"d", b"$.b"],
23004 &[b"JSON.NUMINCRBY", b"d", b"$.a", b"5"],
23005 &[b"JSON.NUMMULTBY", b"d", b"$.a", b"2"],
23006 &[b"JSON.NUMPOWBY", b"d", b"$.a", b"2"],
23007 &[b"JSON.MERGE", b"d", b"$", br#"{"a":null,"m":1}"#],
23008 &[b"JSON.RESP", b"d", b"$.b"],
23009 &[b"JSON.DEBUG", b"MEMORY", b"d"],
23010 &[b"JSON.CLEAR", b"d", b"$.b"],
23011 &[b"JSON.DEL", b"d", b"$.m"],
23012 &[b"JSON.FORGET", b"d", b"$.nothere"],
23013 &[
23015 b"JSON.MSET",
23016 b"m1",
23017 b"$",
23018 b"1",
23019 b"m2",
23020 b"$",
23021 b"2",
23022 b"m3",
23023 b"$",
23024 b"3",
23025 ],
23026 &[b"JSON.MGET", b"m1", b"m2", b"m3", b"gone", b"$"],
23027 &[b"JSON.MSET", b"m1", b"$", b"9", b"m2", b"$.deep", b"9"],
23028 &[b"JSON.GET", b"m1", b"$"],
23029 &[b"JSON.MSET", b"m1", b"$", b"nonsense", b"m2", b"$", b"5"],
23030 &[b"JSON.GET", b"m2", b"$"],
23031 &[b"SET", b"plain", b"v"],
23033 &[b"JSON.GET", b"plain", b"$"],
23034 &[b"JSON.SET", b"plain", b"$", b"1"],
23035 &[b"JSON.MGET", b"m1", b"plain", b"$"],
23036 &[b"JSON.SET", b"d", b"$.b", b"["],
23037 &[b"JSON.DEL", b"plain"],
23038 ];
23039
23040 let mut one = Fixture::new();
23041 let mut many = Fixture::striped(8);
23042 for parts in script {
23043 let a = one.run(parts);
23044 let b = many.run(parts);
23045 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23046 }
23047 }
23048
23049 #[test]
23056 fn a_json_multi_write_across_stripes_reaches_every_key() {
23057 let mut f = Fixture::striped(8);
23058 let second = apart(&mut f, "m1");
23059 let third = apart(&mut f, &second);
23060 let (m1, m2, m3) = (b"m1".as_slice(), second.as_bytes(), third.as_bytes());
23061
23062 assert_eq!(
23063 f.run(&[b"JSON.MSET", m1, b"$", b"1", m2, b"$", b"2", m3, b"$", b"3"]),
23064 "+OK\r\n"
23065 );
23066 assert_eq!(
23067 f.run(&[b"JSON.MGET", m1, m2, m3, b"gone", b"$"]),
23068 "*4\r\n$3\r\n[1]\r\n$3\r\n[2]\r\n$3\r\n[3]\r\n$-1\r\n"
23069 );
23070
23071 assert_eq!(
23074 f.run(&[b"JSON.MSET", m1, b"$", b"9", m2, b"$", b"nonsense"]),
23075 "-this is not the start of a value, at byte 0 of the JSON text\r\n"
23076 );
23077 assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[1]\r\n");
23078
23079 assert_eq!(
23083 f.run(&[
23084 b"JSON.MSET",
23085 m1,
23086 b"$",
23087 b"9",
23088 m2,
23089 b"$.deep",
23090 b"9",
23091 m3,
23092 b"$",
23093 b"7"
23094 ]),
23095 "$-1\r\n"
23096 );
23097 assert_eq!(f.run(&[b"JSON.GET", m1, b"$"]), "$3\r\n[9]\r\n");
23098 assert_eq!(f.run(&[b"JSON.GET", m2, b"$"]), "$3\r\n[2]\r\n");
23099 assert_eq!(f.run(&[b"JSON.GET", m3, b"$"]), "$3\r\n[7]\r\n");
23100 }
23101
23102 #[test]
23104 fn the_geo_group_answers_the_same_however_many_stripes_there_are() {
23105 let script: &[&[&[u8]]] = &[
23106 &[
23107 b"GEOADD",
23108 b"g",
23109 b"13.361389",
23110 b"38.115556",
23111 b"palermo",
23112 b"15.087269",
23113 b"37.502669",
23114 b"catania",
23115 ],
23116 &[
23117 b"GEOADD",
23118 b"g",
23119 b"NX",
23120 b"13.361389",
23121 b"38.115556",
23122 b"palermo",
23123 ],
23124 &[b"GEOADD", b"g", b"XX", b"CH", b"13.4", b"38.1", b"palermo"],
23125 &[b"GEOPOS", b"g", b"palermo", b"nothere"],
23126 &[b"GEOHASH", b"g", b"palermo", b"catania"],
23127 &[b"GEODIST", b"g", b"palermo", b"catania"],
23128 &[b"GEODIST", b"g", b"palermo", b"catania", b"KM"],
23129 &[b"GEODIST", b"g", b"palermo", b"nothere"],
23130 &[
23131 b"GEOSEARCH",
23132 b"g",
23133 b"FROMLONLAT",
23134 b"15",
23135 b"37",
23136 b"BYRADIUS",
23137 b"200",
23138 b"KM",
23139 b"ASC",
23140 b"WITHCOORD",
23141 b"WITHDIST",
23142 b"WITHHASH",
23143 ],
23144 &[
23145 b"GEOSEARCH",
23146 b"g",
23147 b"FROMMEMBER",
23148 b"palermo",
23149 b"BYBOX",
23150 b"400",
23151 b"400",
23152 b"KM",
23153 b"DESC",
23154 ],
23155 &[
23156 b"GEORADIUS",
23157 b"g",
23158 b"15",
23159 b"37",
23160 b"200",
23161 b"KM",
23162 b"COUNT",
23163 b"1",
23164 ],
23165 &[b"GEORADIUSBYMEMBER", b"g", b"palermo", b"200", b"KM"],
23166 &[b"GEORADIUSBYMEMBER_RO", b"g", b"nothere", b"200", b"KM"],
23167 &[
23168 b"GEOSEARCHSTORE",
23169 b"dst",
23170 b"g",
23171 b"FROMLONLAT",
23172 b"15",
23173 b"37",
23174 b"BYRADIUS",
23175 b"200",
23176 b"KM",
23177 ],
23178 &[b"ZRANGE", b"dst", b"0", b"-1"],
23179 &[
23180 b"GEOSEARCHSTORE",
23181 b"dst",
23182 b"g",
23183 b"FROMLONLAT",
23184 b"15",
23185 b"37",
23186 b"BYRADIUS",
23187 b"1",
23188 b"M",
23189 b"STOREDIST",
23190 ],
23191 &[b"EXISTS", b"dst"],
23192 &[
23193 b"GEORADIUS",
23194 b"g",
23195 b"15",
23196 b"37",
23197 b"200",
23198 b"KM",
23199 b"STORE",
23200 b"dst",
23201 ],
23202 &[b"ZCARD", b"dst"],
23203 &[b"GEOADD", b"g", b"181", b"38", b"nowhere"],
23205 &[b"SET", b"plain", b"v"],
23206 &[b"GEOPOS", b"plain", b"a"],
23207 &[b"GEOSEARCH", b"g", b"FROMLONLAT", b"15", b"37"],
23208 &[
23209 b"GEOSEARCHSTORE",
23210 b"dst",
23211 b"g",
23212 b"FROMLONLAT",
23213 b"15",
23214 b"37",
23215 b"BYRADIUS",
23216 b"200",
23217 b"KM",
23218 b"WITHCOORD",
23219 ],
23220 ];
23221
23222 let mut one = Fixture::new();
23223 let mut many = Fixture::striped(8);
23224 for parts in script {
23225 let a = one.run(parts);
23226 let b = many.run(parts);
23227 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23228 }
23229 }
23230
23231 #[test]
23233 fn a_geo_search_store_across_stripes_writes_what_it_found() {
23234 let mut f = Fixture::striped(8);
23235 let other = apart(&mut f, "g");
23236 let third = apart(&mut f, &other);
23237 let (g, dst, plain) = (b"g".as_slice(), other.as_bytes(), third.as_bytes());
23238
23239 f.run(&[
23240 b"GEOADD",
23241 g,
23242 b"13.361389",
23243 b"38.115556",
23244 b"palermo",
23245 b"15.087269",
23246 b"37.502669",
23247 b"catania",
23248 ]);
23249 assert_eq!(
23250 f.run(&[
23251 b"GEOSEARCHSTORE",
23252 dst,
23253 g,
23254 b"FROMLONLAT",
23255 b"15",
23256 b"37",
23257 b"BYRADIUS",
23258 b"200",
23259 b"KM",
23260 b"ASC",
23261 ]),
23262 ":2\r\n"
23263 );
23264 assert_eq!(
23265 f.run(&[b"ZRANGE", dst, b"0", b"-1"]),
23266 "*2\r\n$7\r\npalermo\r\n$7\r\ncatania\r\n",
23267 "the geohash is the score, so the order is not the search order"
23268 );
23269 assert_eq!(f.run(&[b"ZCARD", g]), ":2\r\n", "the source is untouched");
23270
23271 assert_eq!(
23274 f.run(&[
23275 b"GEOSEARCHSTORE",
23276 dst,
23277 g,
23278 b"FROMMEMBER",
23279 b"palermo",
23280 b"BYRADIUS",
23281 b"200",
23282 b"KM",
23283 b"STOREDIST",
23284 ]),
23285 ":2\r\n"
23286 );
23287 assert_eq!(
23288 f.run(&[b"ZSCORE", dst, b"palermo"]),
23289 "$1\r\n0\r\n",
23290 "the centre is nought away from itself"
23291 );
23292
23293 assert_eq!(
23297 f.run(&[
23298 b"GEOSEARCHSTORE",
23299 dst,
23300 g,
23301 b"FROMLONLAT",
23302 b"0",
23303 b"0",
23304 b"BYRADIUS",
23305 b"1",
23306 b"M",
23307 ]),
23308 ":0\r\n"
23309 );
23310 assert_eq!(f.run(&[b"EXISTS", dst]), ":0\r\n");
23311 f.run(&[
23312 b"GEOSEARCHSTORE",
23313 dst,
23314 g,
23315 b"FROMLONLAT",
23316 b"15",
23317 b"37",
23318 b"BYRADIUS",
23319 b"200",
23320 b"KM",
23321 ]);
23322 f.run(&[b"SET", plain, b"v"]);
23323 assert_eq!(
23324 f.run(&[
23325 b"GEOSEARCHSTORE",
23326 dst,
23327 plain,
23328 b"FROMLONLAT",
23329 b"15",
23330 b"37",
23331 b"BYRADIUS",
23332 b"200",
23333 b"KM",
23334 ]),
23335 "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"
23336 );
23337 assert_eq!(
23338 f.run(&[b"ZCARD", dst]),
23339 ":2\r\n",
23340 "and left the destination"
23341 );
23342 }
23343
23344 #[test]
23350 fn the_time_series_group_answers_the_same_however_many_stripes_there_are() {
23351 let script: &[&[&[u8]]] = &[
23352 &[
23353 b"TS.CREATE",
23354 b"ts:a",
23355 b"LABELS",
23356 b"sensor",
23357 b"a",
23358 b"room",
23359 b"1",
23360 ],
23361 &[b"TS.CREATE", b"ts:a"],
23362 &[b"TS.ALTER", b"ts:a", b"RETENTION", b"0"],
23363 &[b"TS.ADD", b"ts:a", b"1000", b"1.5"],
23364 &[
23365 b"TS.ADD", b"ts:b", b"1000", b"2", b"LABELS", b"sensor", b"b", b"room", b"1",
23366 ],
23367 &[
23368 b"TS.MADD", b"ts:a", b"2000", b"2.5", b"ts:b", b"2000", b"3", b"gone", b"1", b"1",
23369 ],
23370 &[b"TS.INCRBY", b"ts:a", b"1", b"TIMESTAMP", b"3000"],
23371 &[b"TS.DECRBY", b"ts:a", b"0.5", b"TIMESTAMP", b"4000"],
23372 &[b"TS.GET", b"ts:a"],
23373 &[b"TS.GET", b"gone"],
23374 &[b"TS.RANGE", b"ts:a", b"-", b"+"],
23375 &[b"TS.RANGE", b"ts:a", b"1000", b"3000", b"COUNT", b"2"],
23376 &[
23377 b"TS.RANGE",
23378 b"ts:a",
23379 b"-",
23380 b"+",
23381 b"AGGREGATION",
23382 b"avg",
23383 b"2000",
23384 ],
23385 &[b"TS.REVRANGE", b"ts:a", b"-", b"+"],
23386 &[b"TS.NRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
23387 &[b"TS.NREVRANGE", b"2", b"ts:a", b"ts:b", b"-", b"+"],
23388 &[b"TS.NRANGE", b"2", b"ts:a", b"gone", b"-", b"+"],
23389 &[b"TS.READ", b"ts:a", b"0"],
23390 &[b"TS.READ", b"ts:a", b"+"],
23391 &[b"TS.QUERYINDEX", b"sensor=a"],
23393 &[b"TS.QUERYINDEX", b"room=1"],
23394 &[b"TS.QUERYINDEX", b"room=9"],
23395 &[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"],
23396 &[
23397 b"TS.QUERYLABELS",
23398 b"VALUES",
23399 b"sensor",
23400 b"FILTER",
23401 b"room=1",
23402 ],
23403 &[b"TS.MGET", b"WITHLABELS", b"FILTER", b"room=1"],
23404 &[
23405 b"TS.MGET",
23406 b"SELECTED_LABELS",
23407 b"sensor",
23408 b"FILTER",
23409 b"sensor=a",
23410 ],
23411 &[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"],
23412 &[
23413 b"TS.MREVRANGE",
23414 b"-",
23415 b"+",
23416 b"WITHLABELS",
23417 b"FILTER",
23418 b"sensor=a",
23419 ],
23420 &[
23421 b"TS.MRANGE",
23422 b"-",
23423 b"+",
23424 b"FILTER",
23425 b"room=1",
23426 b"GROUPBY",
23427 b"room",
23428 b"REDUCE",
23429 b"max",
23430 ],
23431 &[b"TS.INFO", b"ts:a"],
23432 &[
23434 b"TS.CREATERULE",
23435 b"ts:a",
23436 b"ts:down",
23437 b"AGGREGATION",
23438 b"avg",
23439 b"1000",
23440 ],
23441 &[b"TS.CREATE", b"ts:down"],
23442 &[
23443 b"TS.CREATERULE",
23444 b"ts:a",
23445 b"ts:down",
23446 b"AGGREGATION",
23447 b"avg",
23448 b"1000",
23449 ],
23450 &[b"TS.ADD", b"ts:a", b"5000", b"4"],
23451 &[b"TS.ADD", b"ts:a", b"6000", b"5"],
23452 &[b"TS.RANGE", b"ts:down", b"-", b"+"],
23453 &[b"TS.GET", b"ts:down", b"LATEST"],
23454 &[b"TS.INFO", b"ts:down"],
23455 &[b"TS.DEL", b"ts:a", b"5000", b"6000"],
23456 &[b"TS.RANGE", b"ts:down", b"-", b"+"],
23457 &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
23458 &[b"TS.DELETERULE", b"ts:a", b"ts:down"],
23459 &[b"TS.DEL", b"ts:a", b"0", b"1000"],
23460 &[b"SET", b"plain", b"v"],
23462 &[b"TS.ADD", b"plain", b"1", b"1"],
23463 &[b"TS.GET", b"plain"],
23464 &[b"TS.READ", b"plain", b"0"],
23465 &[b"TS.ALTER", b"gone", b"RETENTION", b"0"],
23466 &[b"TS.RANGE", b"gone", b"-", b"+"],
23467 &[b"TS.INFO", b"gone"],
23468 ];
23469
23470 let mut one = Fixture::new();
23471 let mut many = Fixture::striped(8);
23472 for parts in script {
23473 let a = one.run(parts);
23474 let b = many.run(parts);
23475 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23476 }
23477 }
23478
23479 #[test]
23489 fn a_compaction_rule_across_stripes_reaches_both_ends() {
23490 let mut many = Fixture::striped(8);
23491 let other = apart(&mut many, "src");
23492 let (src, dst) = (b"src".as_slice(), other.as_bytes());
23493 let mut one = Fixture::new();
23494 let mut both = |parts: &[&[u8]]| {
23495 let a = one.run(parts);
23496 let b = many.run(parts);
23497 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23498 a
23499 };
23500
23501 both(&[b"TS.CREATE", src]);
23502 both(&[b"TS.CREATE", dst]);
23503 assert_eq!(
23504 both(&[b"TS.CREATERULE", src, dst, b"AGGREGATION", b"avg", b"1000"]),
23505 "+OK\r\n"
23506 );
23507 both(&[b"TS.ADD", src, b"1000", b"1"]);
23508 both(&[b"TS.ADD", src, b"1500", b"3"]);
23509 assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
23512 let open = both(&[b"TS.GET", dst, b"LATEST"]);
23513 assert!(open.contains(":1000"), "the open bucket is folded: {open}");
23514
23515 both(&[b"TS.ADD", src, b"2000", b"5"]);
23518 let got = both(&[b"TS.RANGE", dst, b"-", b"+"]);
23519 assert!(got.starts_with("*1\r\n"), "the bucket was written: {got}");
23520 assert!(got.contains(":1000"), "{got}");
23521
23522 both(&[b"TS.DEL", src, b"1000", b"1999"]);
23524 assert_eq!(both(&[b"TS.RANGE", dst, b"-", b"+"]), "*0\r\n");
23525
23526 assert!(
23529 both(&[b"TS.INFO", dst]).contains("src"),
23530 "the source is named"
23531 );
23532 assert_eq!(both(&[b"TS.DELETERULE", src, dst]), "+OK\r\n");
23533 assert_eq!(
23534 both(&[b"TS.DELETERULE", src, dst]),
23535 "-ERR TSDB: compaction rule does not exist\r\n"
23536 );
23537 }
23538
23539 #[test]
23541 fn a_label_query_across_stripes_finds_every_series() {
23542 let names: [&[u8]; 6] = [b"q:1", b"q:2", b"q:3", b"q:4", b"q:5", b"q:6"];
23543 let mut many = Fixture::striped(8);
23544 let mut homes: Vec<usize> = names
23545 .iter()
23546 .map(|name| many.server.striped(0).stripe_of(name))
23547 .collect();
23548 homes.sort_unstable();
23549 homes.dedup();
23550 assert!(homes.len() > 1, "the six keys are not all on one stripe");
23551
23552 let mut one = Fixture::new();
23553 let mut both = |parts: &[&[u8]]| {
23554 let a = one.run(parts);
23555 let b = many.run(parts);
23556 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23557 a
23558 };
23559 for name in &names {
23560 both(&[b"TS.CREATE", name, b"LABELS", b"room", b"1"]);
23561 both(&[b"TS.ADD", name, b"1000", b"1"]);
23562 }
23563
23564 let got = both(&[b"TS.QUERYINDEX", b"room=1"]);
23565 assert!(got.starts_with("*6\r\n"), "every series answered: {got}");
23566 assert!(both(&[b"TS.MGET", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
23567 assert!(both(&[b"TS.MRANGE", b"-", b"+", b"FILTER", b"room=1"]).starts_with("*6\r\n"));
23568 assert_eq!(
23569 both(&[b"TS.QUERYLABELS", b"LABELS", b"FILTER", b"room=1"]),
23570 "*1\r\n$4\r\nroom\r\n"
23571 );
23572 }
23573
23574 #[test]
23581 fn the_hash_group_answers_the_same_however_many_stripes_there_are() {
23582 let script: &[&[&[u8]]] = &[
23583 &[b"HSET", b"h", b"a", b"1", b"b", b"2"],
23584 &[b"HMSET", b"h", b"c", b"3"],
23585 &[b"HSETNX", b"h", b"a", b"9"],
23586 &[b"HSETNX", b"h", b"d", b"4"],
23587 &[b"HGET", b"h", b"a"],
23588 &[b"HGET", b"h", b"nope"],
23589 &[b"HMGET", b"h", b"a", b"nope"],
23590 &[b"HLEN", b"h"],
23591 &[b"HEXISTS", b"h", b"a"],
23592 &[b"HSTRLEN", b"h", b"a"],
23593 &[b"HGETALL", b"h"],
23594 &[b"HKEYS", b"h"],
23595 &[b"HVALS", b"h"],
23596 &[b"HINCRBY", b"h", b"a", b"5"],
23597 &[b"HINCRBYFLOAT", b"h", b"a", b"1.5"],
23598 &[b"HSCAN", b"h", b"0"],
23599 &[b"HSCAN", b"h", b"0", b"MATCH", b"a", b"COUNT", b"10"],
23600 &[b"HSCAN", b"h", b"0", b"NOVALUES"],
23601 &[b"HDEL", b"h", b"d"],
23602 &[b"HSET", b"one", b"f", b"v"],
23603 &[b"HRANDFIELD", b"one"],
23604 &[b"HRANDFIELD", b"one", b"1", b"WITHVALUES"],
23605 &[b"HEXPIRE", b"h", b"100", b"FIELDS", b"1", b"a"],
23607 &[b"HTTL", b"h", b"FIELDS", b"1", b"a"],
23608 &[b"HPTTL", b"h", b"FIELDS", b"1", b"a"],
23609 &[b"HEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
23610 &[b"HPEXPIRETIME", b"h", b"FIELDS", b"1", b"a"],
23611 &[b"HPERSIST", b"h", b"FIELDS", b"1", b"a"],
23612 &[b"HPEXPIREAT", b"h", b"1", b"FIELDS", b"1", b"b"],
23613 &[b"HGET", b"h", b"b"],
23614 &[b"HSETEX", b"h", b"EX", b"100", b"FIELDS", b"1", b"e", b"5"],
23616 &[b"HGETEX", b"h", b"PERSIST", b"FIELDS", b"1", b"e"],
23617 &[b"HGETDEL", b"h", b"FIELDS", b"1", b"e"],
23618 &[b"HGET", b"h", b"e"],
23619 &[b"HIMPORT", b"PREPARE", b"fs", b"x", b"y"],
23621 &[b"HIMPORT", b"SET", b"imp", b"fs", b"1", b"2"],
23622 &[b"HGETALL", b"imp"],
23623 &[b"HIMPORT", b"SET", b"imp", b"nofs", b"1", b"2"],
23624 &[b"HIMPORT", b"DISCARD", b"fs"],
23625 &[b"SET", b"plain", b"v"],
23627 &[b"HSET", b"plain", b"a", b"1"],
23628 &[b"HGETALL", b"plain"],
23629 &[b"HGET", b"gone", b"a"],
23630 &[b"HINCRBY", b"h", b"a", b"nan"],
23631 ];
23632
23633 let mut one = Fixture::new();
23634 let mut many = Fixture::striped(8);
23635 one.server.set_clock_ms(1_700_000_000_000);
23639 many.server.set_clock_ms(1_700_000_000_000);
23640 for parts in script {
23641 let a = one.run(parts);
23642 let b = many.run(parts);
23643 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23644 }
23645 }
23646
23647 #[test]
23649 fn the_array_group_answers_the_same_however_many_stripes_there_are() {
23650 let script: &[&[&[u8]]] = &[
23651 &[b"ARSET", b"a", b"0", b"x", b"y", b"z"],
23652 &[b"ARMSET", b"a", b"5", b"p", b"7", b"q"],
23653 &[b"ARGET", b"a", b"1"],
23654 &[b"ARGET", b"a", b"99"],
23655 &[b"ARMGET", b"a", b"0", b"5", b"99"],
23656 &[b"ARGETRANGE", b"a", b"0", b"7"],
23657 &[b"ARLEN", b"a"],
23658 &[b"ARCOUNT", b"a"],
23659 &[b"ARINSERT", b"a", b"m", b"n"],
23660 &[b"ARSCAN", b"a", b"0", b"20"],
23661 &[b"ARSCAN", b"a", b"0", b"20", b"LIMIT", b"2"],
23662 &[b"ARGREP", b"a", b"0", b"20", b"EXACT", b"x"],
23663 &[b"ARGREP", b"a", b"0", b"20", b"GLOB", b"*", b"WITHVALUES"],
23664 &[b"ARLASTITEMS", b"a", b"2"],
23665 &[b"ARLASTITEMS", b"a", b"2", b"REV"],
23666 &[b"ARNEXT", b"a"],
23667 &[b"ARSEEK", b"a", b"3"],
23668 &[b"AROP", b"a", b"0", b"20", b"USED"],
23669 &[b"AROP", b"a", b"0", b"20", b"MATCH", b"x"],
23670 &[b"ARINFO", b"a"],
23671 &[b"ARINFO", b"a", b"FULL"],
23672 &[b"ARDEL", b"a", b"0"],
23673 &[b"ARDELRANGE", b"a", b"1", b"2"],
23674 &[b"ARCOUNT", b"a"],
23675 &[b"ARRING", b"r", b"3", b"1", b"2", b"3", b"4"],
23676 &[b"ARGETRANGE", b"r", b"0", b"9"],
23677 &[b"SET", b"plain", b"v"],
23679 &[b"ARGET", b"plain", b"0"],
23680 &[b"ARSET", b"plain", b"0", b"v"],
23681 &[b"ARGET", b"gone", b"0"],
23682 &[b"ARSET", b"a", b"bad", b"v"],
23683 ];
23684
23685 let mut one = Fixture::new();
23686 let mut many = Fixture::striped(8);
23687 for parts in script {
23688 let a = one.run(parts);
23689 let b = many.run(parts);
23690 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23691 }
23692 }
23693
23694 #[test]
23700 fn the_graph_and_vector_groups_answer_the_same_however_many_stripes_there_are() {
23701 let script: &[&[&[u8]]] = &[
23702 &[b"G.NADD", b"g", b"n1", b"name", b"one"],
23703 &[b"G.NADD", b"g", b"n2", b"name", b"two"],
23704 &[b"G.NADD", b"g", b"n3"],
23705 &[b"G.NGET", b"g", b"n1"],
23706 &[b"G.NGET", b"g", b"gone"],
23707 &[b"G.EADD", b"g", b"n1", b"n2", b"knows"],
23708 &[b"G.EADD", b"g", b"n2", b"n3", b"knows"],
23709 &[b"G.OUT", b"g", b"n1", b"knows"],
23710 &[b"G.IN", b"g", b"n2", b"knows"],
23711 &[b"G.DEG", b"g", b"n1", b"knows"],
23712 &[b"G.DEG", b"g", b"n2", b"knows", b"BOTH"],
23713 &[b"G.NEIGH", b"g", b"n1", b"knows", b"DEPTH", b"2"],
23714 &[b"G.PATH", b"g", b"n1", b"n3"],
23715 &[b"G.EDEL", b"g", b"n1", b"n2", b"knows"],
23716 &[b"G.NDEL", b"g", b"n3"],
23717 &[b"G.NGET", b"g", b"n3"],
23718 &[b"VADD", b"v", b"VALUES", b"2", b"1", b"0", b"e1"],
23720 &[b"VADD", b"v", b"VALUES", b"2", b"0", b"1", b"e2"],
23721 &[b"VCARD", b"v"],
23722 &[b"VDIM", b"v"],
23723 &[b"VEMB", b"v", b"e1"],
23724 &[b"VSIM", b"v", b"VALUES", b"2", b"1", b"0"],
23725 &[b"VSIM", b"v", b"ELE", b"e1"],
23726 &[b"VISMEMBER", b"v", b"e1"],
23727 &[b"VISMEMBER", b"v", b"gone"],
23728 &[b"VSETATTR", b"v", b"e1", b"{\"k\":1}"],
23729 &[b"VGETATTR", b"v", b"e1"],
23730 &[b"VRANGE", b"v", b"-", b"+"],
23731 &[b"VLINKS", b"v", b"e1"],
23732 &[b"VINFO", b"v"],
23733 &[b"VREM", b"v", b"e2"],
23734 &[b"VCARD", b"v"],
23735 &[b"SET", b"plain", b"v"],
23737 &[b"G.NGET", b"plain", b"n1"],
23738 &[b"VCARD", b"plain"],
23739 &[b"G.NADD", b"gone2", b"n"],
23740 &[b"VEMB", b"gone3", b"e"],
23741 ];
23742
23743 let mut one = Fixture::new();
23744 let mut many = Fixture::striped(8);
23745 for parts in script {
23746 let a = one.run(parts);
23747 let b = many.run(parts);
23748 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23749 }
23750 }
23751
23752 #[test]
23755 fn the_probabilistic_groups_answer_the_same_however_many_stripes_there_are() {
23756 let script: &[&[&[u8]]] = &[
23757 &[b"BF.RESERVE", b"bf", b"0.01", b"100"],
23759 &[b"BF.ADD", b"bf", b"a"],
23760 &[b"BF.ADD", b"bf", b"a"],
23761 &[b"BF.MADD", b"bf", b"b", b"c"],
23762 &[b"BF.EXISTS", b"bf", b"a"],
23763 &[b"BF.MEXISTS", b"bf", b"a", b"zz"],
23764 &[b"BF.CARD", b"bf"],
23765 &[b"BF.INFO", b"bf"],
23766 &[b"BF.INFO", b"bf", b"CAPACITY"],
23767 &[b"BF.DEBUG", b"bf"],
23768 &[b"BF.INSERT", b"made", b"CAPACITY", b"50", b"ITEMS", b"x"],
23769 &[b"BF.EXISTS", b"made", b"x"],
23770 &[b"BF.SCANDUMP", b"bf", b"0"],
23771 &[b"CF.RESERVE", b"cf", b"100"],
23773 &[b"CF.ADD", b"cf", b"a"],
23774 &[b"CF.ADDNX", b"cf", b"a"],
23775 &[b"CF.COUNT", b"cf", b"a"],
23776 &[b"CF.EXISTS", b"cf", b"a"],
23777 &[b"CF.MEXISTS", b"cf", b"a", b"zz"],
23778 &[b"CF.INSERT", b"cf", b"ITEMS", b"b", b"c"],
23779 &[b"CF.DEL", b"cf", b"a"],
23780 &[b"CF.COMPACT", b"cf"],
23781 &[b"CF.INFO", b"cf"],
23782 &[b"CF.DEBUG", b"cf"],
23783 &[b"CF.SCANDUMP", b"cf", b"0"],
23784 &[b"CMS.INITBYDIM", b"cms", b"100", b"5"],
23786 &[b"CMS.INITBYPROB", b"cms2", b"0.01", b"0.01"],
23787 &[b"CMS.INCRBY", b"cms", b"a", b"5", b"b", b"3"],
23788 &[b"CMS.QUERY", b"cms", b"a", b"b", b"gone"],
23789 &[b"CMS.INFO", b"cms"],
23790 &[b"TOPK.RESERVE", b"tk", b"3"],
23792 &[b"TOPK.ADD", b"tk", b"a", b"b", b"a"],
23793 &[b"TOPK.INCRBY", b"tk", b"c", b"4"],
23794 &[b"TOPK.QUERY", b"tk", b"a", b"zz"],
23795 &[b"TOPK.COUNT", b"tk", b"a", b"c"],
23796 &[b"TOPK.LIST", b"tk"],
23797 &[b"TOPK.LIST", b"tk", b"WITHCOUNT"],
23798 &[b"TOPK.INFO", b"tk"],
23799 &[b"TDIGEST.CREATE", b"td"],
23801 &[b"TDIGEST.ADD", b"td", b"1", b"2", b"3", b"4", b"5"],
23802 &[b"TDIGEST.MIN", b"td"],
23803 &[b"TDIGEST.MAX", b"td"],
23804 &[b"TDIGEST.QUANTILE", b"td", b"0.5"],
23805 &[b"TDIGEST.CDF", b"td", b"3"],
23806 &[b"TDIGEST.RANK", b"td", b"3"],
23807 &[b"TDIGEST.REVRANK", b"td", b"3"],
23808 &[b"TDIGEST.BYRANK", b"td", b"0"],
23809 &[b"TDIGEST.BYREVRANK", b"td", b"0"],
23810 &[b"TDIGEST.TRIMMED_MEAN", b"td", b"0.1", b"0.9"],
23811 &[b"TDIGEST.INFO", b"td"],
23812 &[b"TDIGEST.RESET", b"td"],
23813 &[b"TDIGEST.MIN", b"td"],
23814 &[b"SET", b"plain", b"v"],
23816 &[b"BF.ADD", b"plain", b"a"],
23817 &[b"CF.ADD", b"plain", b"a"],
23818 &[b"CMS.QUERY", b"plain", b"a"],
23819 &[b"TOPK.ADD", b"plain", b"a"],
23820 &[b"TDIGEST.ADD", b"plain", b"1"],
23821 &[b"CMS.INFO", b"gone"],
23822 &[b"TOPK.INFO", b"gone"],
23823 &[b"TDIGEST.INFO", b"gone"],
23824 ];
23825
23826 let mut one = Fixture::new();
23827 let mut many = Fixture::striped(8);
23828 for parts in script {
23829 let a = one.run(parts);
23830 let b = many.run(parts);
23831 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23832 }
23833 }
23834
23835 #[test]
23842 fn a_sketch_merge_across_stripes_reads_every_source() {
23843 let mut many = Fixture::striped(8);
23844 let other = apart(&mut many, "s1");
23845 let (s1, s2) = (b"s1".as_slice(), other.as_bytes());
23846 let mut one = Fixture::new();
23847 let mut both = |parts: &[&[u8]]| {
23848 let a = one.run(parts);
23849 let b = many.run(parts);
23850 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23851 a
23852 };
23853
23854 for key in [b"cd".as_slice(), s1, s2] {
23858 both(&[b"CMS.INITBYDIM", key, b"100", b"5"]);
23859 }
23860 both(&[b"CMS.INCRBY", s1, b"x", b"5"]);
23861 both(&[b"CMS.INCRBY", s2, b"x", b"3"]);
23862 assert_eq!(
23863 both(&[b"CMS.MERGE", b"cd", b"2", s1, s2]),
23864 "+OK\r\n",
23865 "the merge took both sources"
23866 );
23867 assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:8\r\n");
23868 both(&[b"CMS.MERGE", b"cd", b"2", s1, s2, b"WEIGHTS", b"2", b"1"]);
23870 assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
23871 both(&[b"SET", b"plain", b"v"]);
23873 assert!(both(&[b"CMS.MERGE", b"cd", b"2", s1, b"plain"]).starts_with('-'));
23874 assert_eq!(both(&[b"CMS.QUERY", b"cd", b"x"]), "*1\r\n:13\r\n");
23875
23876 both(&[b"FLUSHALL"]);
23879 both(&[b"TDIGEST.CREATE", b"td"]);
23880 both(&[b"TDIGEST.CREATE", s1]);
23881 both(&[b"TDIGEST.CREATE", s2]);
23882 both(&[b"TDIGEST.ADD", s1, b"1", b"2"]);
23883 both(&[b"TDIGEST.ADD", s2, b"9", b"10"]);
23884 assert_eq!(both(&[b"TDIGEST.MERGE", b"td", b"2", s1, s2]), "+OK\r\n");
23885 assert_eq!(both(&[b"TDIGEST.MIN", b"td"]), "$1\r\n1\r\n");
23886 assert_eq!(both(&[b"TDIGEST.MAX", b"td"]), "$2\r\n10\r\n");
23887 }
23888
23889 #[test]
23896 fn the_sort_command_answers_the_same_however_many_stripes_there_are() {
23897 let script: &[&[&[u8]]] = &[
23898 &[b"RPUSH", b"l", b"3", b"1", b"2", b"10"],
23899 &[b"SORT", b"l"],
23900 &[b"SORT", b"l", b"DESC"],
23901 &[b"SORT", b"l", b"ALPHA"],
23902 &[b"SORT", b"l", b"LIMIT", b"1", b"2"],
23903 &[b"SORT_RO", b"l"],
23904 &[
23907 b"MSET", b"w_1", b"4", b"w_2", b"3", b"w_3", b"2", b"w_10", b"1",
23908 ],
23909 &[b"SORT", b"l", b"BY", b"w_*"],
23910 &[b"SORT", b"l", b"BY", b"w_*", b"DESC"],
23911 &[b"DEL", b"w_2"],
23912 &[b"SORT", b"l", b"BY", b"w_*"],
23913 &[b"MSET", b"d_1", b"one", b"d_3", b"three"],
23916 &[b"SORT", b"l", b"BY", b"w_*", b"GET", b"#", b"GET", b"d_*"],
23917 &[b"HSET", b"h_1", b"f", b"9"],
23919 &[b"HSET", b"h_2", b"f", b"8"],
23920 &[b"HSET", b"h_3", b"f", b"7"],
23921 &[b"HSET", b"h_10", b"f", b"6"],
23922 &[b"SORT", b"l", b"BY", b"h_*->f"],
23923 &[b"SORT", b"l", b"BY", b"nosort", b"GET", b"h_*->f"],
23924 &[b"SORT", b"l", b"BY", b"w_*", b"STORE", b"out"],
23926 &[b"LRANGE", b"out", b"0", b"-1"],
23927 &[b"SORT", b"l", b"STORE", b"l"],
23928 &[b"LRANGE", b"l", b"0", b"-1"],
23929 &[b"SORT", b"missing", b"STORE", b"out"],
23932 &[b"EXISTS", b"out"],
23933 &[b"SADD", b"s", b"c", b"a", b"b"],
23936 &[b"SORT", b"s", b"ALPHA"],
23937 &[b"SORT", b"s", b"BY", b"nosort", b"STORE", b"out"],
23938 &[b"LRANGE", b"out", b"0", b"-1"],
23939 &[b"ZADD", b"z", b"3", b"c", b"1", b"a", b"2", b"b"],
23940 &[b"SORT", b"z", b"BY", b"nosort"],
23941 &[b"SORT", b"z", b"ALPHA", b"DESC"],
23942 &[b"SET", b"str", b"v"],
23945 &[b"SORT", b"str"],
23946 &[b"RPUSH", b"words", b"one", b"two"],
23947 &[b"SORT", b"words"],
23948 &[b"SORT_RO", b"l", b"STORE", b"out"],
23949 ];
23950
23951 let mut one = Fixture::new();
23952 let mut many = Fixture::striped(8);
23953 for parts in script {
23954 let a = one.run(parts);
23955 let b = many.run(parts);
23956 assert_eq!(a, b, "{}", String::from_utf8_lossy(parts[0]));
23957 }
23958 }
23959
23960 #[test]
23967 fn a_sort_across_stripes_reads_every_pattern_key() {
23968 let mut f = Fixture::striped(8);
23969 let out = apart(&mut f, "l");
23970 let (list, dest) = (b"l".as_slice(), out.as_bytes());
23971
23972 f.run(&[b"RPUSH", list, b"a", b"b", b"c", b"d"]);
23973 f.run(&[
23974 b"MSET", b"w_a", b"4", b"w_b", b"3", b"w_c", b"2", b"w_d", b"1",
23975 ]);
23976 f.run(&[
23977 b"MSET", b"d_a", b"A", b"d_b", b"B", b"d_c", b"C", b"d_d", b"D",
23978 ]);
23979
23980 let db = f.server.striped(0);
23983 let weights: Vec<usize> = [b"w_a", b"w_b", b"w_c", b"w_d"]
23984 .iter()
23985 .map(|k| db.stripe_of(k.as_slice()))
23986 .collect();
23987 assert!(
23988 weights.iter().any(|s| *s != weights[0]),
23989 "the four weight keys all landed on one stripe, so this proves nothing"
23990 );
23991
23992 assert_eq!(
23993 f.run(&[b"SORT", list, b"BY", b"w_*", b"GET", b"d_*"]),
23994 "*4\r\n$1\r\nD\r\n$1\r\nC\r\n$1\r\nB\r\n$1\r\nA\r\n",
23995 "the order came off the weights and the answer off the data keys"
23996 );
23997 assert_eq!(
23998 f.run(&[b"SORT", list, b"BY", b"w_*", b"STORE", dest]),
23999 ":4\r\n"
24000 );
24001 assert_eq!(
24002 f.run(&[b"LRANGE", dest, b"0", b"-1"]),
24003 "*4\r\n$1\r\nd\r\n$1\r\nc\r\n$1\r\nb\r\n$1\r\na\r\n",
24004 "the destination is on a stripe of its own and got the whole answer"
24005 );
24006 }
24007
24008 #[test]
24016 fn a_setting_reaches_every_stripe_and_reads_back_from_any_of_them() {
24017 let mut f = Fixture::striped(8);
24018 let other = apart(&mut f, "h");
24019 let (first, second) = (b"h".as_slice(), other.as_bytes());
24020
24021 assert_eq!(
24022 f.run(&[b"CONFIG", b"SET", b"hash-max-listpack-entries", b"2"]),
24023 "+OK\r\n"
24024 );
24025 assert_eq!(
24026 f.run(&[b"CONFIG", b"GET", b"hash-max-listpack-entries"]),
24027 "*2\r\n$25\r\nhash-max-listpack-entries\r\n$1\r\n2\r\n",
24028 "the read comes off one stripe and has to answer for all of them"
24029 );
24030 for key in [first, second] {
24031 f.run(&[b"HSET", key, b"a", b"1", b"b", b"2"]);
24032 assert_eq!(
24033 f.run(&[b"OBJECT", b"ENCODING", key]),
24034 "$8\r\nlistpack\r\n",
24035 "two fields is still under the ladder"
24036 );
24037 f.run(&[b"HSET", key, b"c", b"3"]);
24038 assert_eq!(
24039 f.run(&[b"OBJECT", b"ENCODING", key]),
24040 "$9\r\nhashtable\r\n",
24041 "three fields is over it, on whichever stripe the key is on"
24042 );
24043 }
24044
24045 assert_eq!(
24048 f.run(&[b"CONFIG", b"SET", b"maxmemory-policy", b"allkeys-lru"]),
24049 "+OK\r\n"
24050 );
24051 let db = f.server.striped(0);
24052 assert!(
24053 (0..db.width()).all(|i| db.hold_stripe(i).policy().name() == "allkeys-lru"),
24054 "a stripe kept the old policy"
24055 );
24056 }
24057
24058 fn held(f: &Fixture, name: &[u8]) -> (usize, u32) {
24064 let search = f.server.search.lock();
24065 let index = search.named(name).expect("the index is there");
24066 (index.held.docs.len(), index.held.docs.last())
24067 }
24068
24069 #[test]
24072 fn a_hash_that_is_written_reaches_the_index_that_follows_it() {
24073 let mut f = Fixture::new();
24074 f.run(&[
24075 b"FT.CREATE",
24076 b"ix",
24077 b"PREFIX",
24078 b"1",
24079 b"p:",
24080 b"SCHEMA",
24081 b"t",
24082 b"TEXT",
24083 ]);
24084 f.run(&[b"HSET", b"p:1", b"t", b"running dogs"]);
24085 assert_eq!(held(&f, b"ix"), (1, 1));
24086 f.run(&[b"HSET", b"other:1", b"t", b"running dogs"]);
24087 assert_eq!(held(&f, b"ix"), (1, 1));
24088
24089 f.run(&[b"HSET", b"p:1", b"u", b"beta"]);
24092 f.run(&[b"HDEL", b"p:1", b"u"]);
24093 assert_eq!(held(&f, b"ix"), (1, 3));
24094 let search = f.server.search.lock();
24095 let index = search.named(b"ix").expect("there");
24096 assert_eq!(index.held.docs.id(b"p:1"), Some(3));
24097 }
24098
24099 #[test]
24102 fn a_fresh_index_reads_the_keys_that_were_already_there() {
24103 let mut f = Fixture::new();
24104 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24105 f.run(&[b"SET", b"p:str", b"not a hash"]);
24106 f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
24107 f.run(&[
24108 b"FT.CREATE",
24109 b"ix",
24110 b"PREFIX",
24111 b"1",
24112 b"p:",
24113 b"SCHEMA",
24114 b"t",
24115 b"TEXT",
24116 ]);
24117
24118 assert_eq!(held(&f, b"ix"), (1, 1));
24119 let search = f.server.search.lock();
24120 let index = search.named(b"ix").expect("there");
24121 assert_eq!(index.trouble.whole().failures(), 0);
24122 }
24123
24124 #[test]
24127 fn an_index_that_skipped_the_scan_fills_up_on_the_next_write() {
24128 let mut f = Fixture::new();
24129 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24130 f.run(&[
24131 b"FT.CREATE",
24132 b"ix",
24133 b"PREFIX",
24134 b"1",
24135 b"p:",
24136 b"SKIPINITIALSCAN",
24137 b"SCHEMA",
24138 b"t",
24139 b"TEXT",
24140 ]);
24141 assert_eq!(held(&f, b"ix"), (0, 0));
24142 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24143 assert_eq!(held(&f, b"ix"), (1, 1));
24144 }
24145
24146 #[test]
24154 fn only_a_real_change_gives_the_document_a_new_number() {
24155 let mut f = Fixture::new();
24156 f.run(&[
24157 b"FT.CREATE",
24158 b"ix",
24159 b"PREFIX",
24160 b"1",
24161 b"p:",
24162 b"SCHEMA",
24163 b"t",
24164 b"TEXT",
24165 ]);
24166 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24167 assert_eq!(held(&f, b"ix"), (1, 1));
24168
24169 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24170 assert_eq!(held(&f, b"ix"), (1, 2), "the same value still rewrites");
24171
24172 for quiet in [
24173 vec![b"HSETNX".as_slice(), b"p:1", b"t", b"other"],
24174 vec![b"HDEL".as_slice(), b"p:1", b"nosuch"],
24175 vec![b"HGET".as_slice(), b"p:1", b"t"],
24176 vec![b"HGETALL".as_slice(), b"p:1"],
24177 vec![b"HEXPIRE".as_slice(), b"p:1", b"100", b"FIELDS", b"1", b"t"],
24178 vec![b"HPERSIST".as_slice(), b"p:1", b"FIELDS", b"1", b"t"],
24179 vec![
24180 b"HGETEX".as_slice(),
24181 b"p:1",
24182 b"EX",
24183 b"100",
24184 b"FIELDS",
24185 b"1",
24186 b"t",
24187 ],
24188 vec![b"HGETDEL".as_slice(), b"p:1", b"FIELDS", b"1", b"nosuch"],
24189 ] {
24190 f.run(&quiet);
24191 assert_eq!(held(&f, b"ix"), (1, 2), "{:?} moved the document", quiet[0]);
24192 }
24193
24194 f.run(&[b"HSET", b"p:2", b"n", b"1"]);
24196 f.run(&[b"HINCRBY", b"p:2", b"n", b"1"]);
24197 assert_eq!(held(&f, b"ix"), (2, 4));
24198 f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"n"]);
24204 assert_eq!(held(&f, b"ix"), (1, 5));
24205 }
24206
24207 #[test]
24213 fn a_key_emptied_by_a_deadline_spends_a_number_and_one_emptied_by_hdel_does_not() {
24214 fn refused(f: &Fixture, name: &[u8]) -> u64 {
24216 let search = f.server.search.lock();
24217 let index = search.named(name).expect("the index is there");
24218 index.trouble.whole().failures()
24219 }
24220
24221 let mut f = Fixture::new();
24222 f.run(&[
24223 b"FT.CREATE",
24224 b"ix",
24225 b"PREFIX",
24226 b"1",
24227 b"p:",
24228 b"SCHEMA",
24229 b"t",
24230 b"TEXT",
24231 ]);
24232 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24233 assert_eq!(held(&f, b"ix"), (1, 1));
24234 f.run(&[b"HDEL", b"p:1", b"t"]);
24235 assert_eq!(
24236 held(&f, b"ix"),
24237 (0, 1),
24238 "HDEL of the last field spends none"
24239 );
24240 assert_eq!(refused(&f, b"ix"), 1, "and is counted as a refusal");
24241
24242 f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
24243 assert_eq!(held(&f, b"ix"), (1, 2));
24244 f.run(&[b"HEXPIRE", b"p:2", b"0", b"FIELDS", b"1", b"t"]);
24245 assert_eq!(held(&f, b"ix"), (0, 3), "a deadline spends one");
24246 assert_eq!(refused(&f, b"ix"), 1, "and is counted as nothing");
24247
24248 f.run(&[b"HSET", b"p:3", b"t", b"alpha"]);
24249 assert_eq!(held(&f, b"ix"), (1, 4));
24250 f.run(&[b"HGETDEL", b"p:3", b"FIELDS", b"1", b"t"]);
24251 assert_eq!(held(&f, b"ix"), (0, 5), "and so does HGETDEL");
24252
24253 f.run(&[b"HSET", b"p:4", b"t", b"alpha", b"u", b"beta"]);
24256 assert_eq!(held(&f, b"ix"), (1, 6));
24257 f.run(&[b"HEXPIRE", b"p:4", b"0", b"FIELDS", b"2", b"t", b"u"]);
24258 assert_eq!(held(&f, b"ix"), (0, 7));
24259 assert_eq!(refused(&f, b"ix"), 1);
24260 }
24261
24262 #[test]
24266 fn a_field_written_already_past_its_deadline_moves_the_number_twice() {
24267 let mut f = Fixture::new();
24268 f.run(&[
24269 b"FT.CREATE",
24270 b"ix",
24271 b"PREFIX",
24272 b"1",
24273 b"p:",
24274 b"SCHEMA",
24275 b"t",
24276 b"TEXT",
24277 b"u",
24278 b"TEXT",
24279 ]);
24280 f.run(&[b"HSET", b"p:1", b"u", b"keepme"]);
24281 assert_eq!(held(&f, b"ix"), (1, 1));
24282 f.run(&[
24283 b"HSETEX", b"p:1", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
24284 ]);
24285 assert_eq!(
24286 held(&f, b"ix"),
24287 (1, 3),
24288 "the key lived and the field did not"
24289 );
24290
24291 f.run(&[b"HSET", b"p:2", b"t", b"alpha"]);
24293 assert_eq!(held(&f, b"ix"), (2, 4));
24294 f.run(&[
24295 b"HSETEX", b"p:2", b"EXAT", b"1", b"FIELDS", b"1", b"t", b"zqx",
24296 ]);
24297 assert_eq!(held(&f, b"ix"), (1, 6));
24298 }
24299
24300 fn number(f: &Fixture, name: &[u8], key: &[u8]) -> Option<u32> {
24303 let search = f.server.search.lock();
24304 let index = search.named(name).expect("the index is there");
24305 index.held.docs.id(key)
24306 }
24307
24308 fn indexed() -> Fixture {
24311 let mut f = Fixture::new();
24312 f.run(&[
24313 b"FT.CREATE",
24314 b"ix",
24315 b"PREFIX",
24316 b"1",
24317 b"p:",
24318 b"SCHEMA",
24319 b"t",
24320 b"TEXT",
24321 ]);
24322 f.run(&[b"HSET", b"p:1", b"t", b"alpha"]);
24323 f
24324 }
24325
24326 #[test]
24329 fn a_key_a_keyspace_command_takes_away_loses_its_document() {
24330 for take in [
24331 vec![b"DEL".as_slice(), b"p:1"],
24332 vec![b"UNLINK".as_slice(), b"p:1"],
24333 vec![b"PEXPIREAT".as_slice(), b"p:1", b"1"],
24334 vec![b"EXPIRE".as_slice(), b"p:1", b"-1"],
24335 ] {
24336 let mut f = indexed();
24337 assert_eq!(held(&f, b"ix"), (1, 1));
24338 f.run(&take);
24339 assert_eq!(held(&f, b"ix"), (0, 1), "{:?} left something", take[0]);
24340 let search = f.server.search.lock();
24341 let index = search.named(b"ix").expect("the index is there");
24342 assert_eq!(index.trouble.whole().failures(), 0, "{:?}", take[0]);
24343 }
24344
24345 let mut f = indexed();
24347 f.run(&[b"EXPIRE", b"p:1", b"1000"]);
24348 assert_eq!(held(&f, b"ix"), (1, 1));
24349 f.run(&[b"PERSIST", b"p:1"]);
24350 assert_eq!(held(&f, b"ix"), (1, 1));
24351 }
24352
24353 #[test]
24357 fn a_rename_inside_the_prefix_keeps_the_number_the_document_had() {
24358 let mut f = indexed();
24359 f.run(&[b"RENAME", b"p:1", b"p:2"]);
24360 assert_eq!(held(&f, b"ix"), (1, 1), "nothing was read again");
24361 assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
24362 assert_eq!(number(&f, b"ix", b"p:1"), None);
24363
24364 f.run(&[b"RENAME", b"p:2", b"q:1"]);
24365 assert_eq!(held(&f, b"ix"), (0, 1), "out of the prefix is an erase");
24366
24367 f.run(&[b"RENAME", b"q:1", b"p:3"]);
24368 assert_eq!(held(&f, b"ix"), (1, 2), "and into it is a reading");
24369 assert_eq!(number(&f, b"ix", b"p:3"), Some(2));
24370
24371 f.run(&[b"HSET", b"p:4", b"t", b"beta"]);
24374 assert_eq!(f.run(&[b"RENAMENX", b"p:3", b"p:4"]), ":0\r\n");
24375 assert_eq!(held(&f, b"ix"), (2, 3));
24376 f.run(&[b"RENAMENX", b"p:3", b"p:5"]);
24377 assert_eq!(number(&f, b"ix", b"p:5"), Some(2));
24378 }
24379
24380 #[test]
24383 fn a_rename_over_a_document_leaves_one_of_them() {
24384 let mut f = indexed();
24385 f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
24386 assert_eq!(held(&f, b"ix"), (2, 2));
24387 f.run(&[b"RENAME", b"p:1", b"p:2"]);
24388 assert_eq!(held(&f, b"ix"), (1, 2));
24389 assert_eq!(number(&f, b"ix", b"p:2"), Some(1));
24390 }
24391
24392 #[test]
24396 fn a_key_that_arrives_under_the_prefix_is_read_and_one_overwritten_is_erased() {
24397 let mut f = indexed();
24398 f.run(&[b"HSET", b"q:1", b"t", b"beta"]);
24399 f.run(&[b"COPY", b"q:1", b"p:2"]);
24400 assert_eq!(held(&f, b"ix"), (2, 2));
24401 assert_eq!(number(&f, b"ix", b"p:2"), Some(2));
24402
24403 f.run(&[b"COPY", b"p:1", b"q:2"]);
24405 assert_eq!(held(&f, b"ix"), (2, 2));
24406
24407 f.run(&[b"COPY", b"q:1", b"p:1", b"REPLACE"]);
24409 assert_eq!(held(&f, b"ix"), (2, 3));
24410 assert_eq!(number(&f, b"ix", b"p:1"), Some(3));
24411
24412 f.run(&[b"SET", b"s:1", b"plain"]);
24415 f.run(&[b"COPY", b"s:1", b"p:1", b"REPLACE"]);
24416 assert_eq!(held(&f, b"ix"), (1, 3));
24417 let dump = f.run(&[b"DUMP", b"q:1"]);
24418 assert!(dump.starts_with('$'), "{dump}");
24419 }
24420
24421 #[test]
24426 fn the_keyspace_group_reads_database_zero_whatever_database_it_ran_on() {
24427 let mut f = indexed();
24428 f.run(&[b"HSET", b"p:2", b"t", b"beta"]);
24429 assert_eq!(held(&f, b"ix"), (2, 2));
24430 f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
24433 assert_eq!(held(&f, b"ix"), (2, 3));
24434 f.run(&[b"DEL", b"p:2"]);
24437 assert_eq!(held(&f, b"ix"), (1, 3));
24438 f.run(&[b"COPY", b"p:1", b"p:2", b"DB", b"1", b"REPLACE"]);
24439 assert_eq!(held(&f, b"ix"), (1, 3), "the copy landed out of sight");
24440
24441 let dump = f.run(&[b"DUMP", b"p:1"]);
24443 assert!(dump.starts_with('$'), "{dump}");
24444 f.run(&[b"SELECT", b"1"]);
24445 f.run(&[b"HSET", b"q:1", b"t", b"gamma"]);
24446 f.run(&[b"RENAME", b"q:1", b"p:3"]);
24447 assert_eq!(held(&f, b"ix"), (1, 3), "and so is a rename");
24448 }
24449
24450 #[test]
24453 fn a_move_leaves_the_document_where_it_is() {
24454 let mut f = indexed();
24455 f.run(&[b"MOVE", b"p:1", b"1"]);
24456 assert_eq!(held(&f, b"ix"), (1, 1), "the key moved and nothing else");
24457 assert_eq!(number(&f, b"ix", b"p:1"), Some(1));
24458
24459 f.run(&[b"SELECT", b"1"]);
24460 f.run(&[b"HSET", b"p:1", b"t", b"beta"]);
24461 assert_eq!(held(&f, b"ix"), (1, 2), "and a write there still lands");
24462 f.run(&[b"DEL", b"p:1"]);
24463 assert_eq!(held(&f, b"ix"), (0, 2));
24464 }
24465
24466 #[test]
24468 fn a_flush_drops_the_indexes() {
24469 for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
24470 let mut f = indexed();
24471 f.run(&[flush]);
24472 assert!(f.server.search.lock().is_empty(), "{flush:?} kept an index");
24473 assert_eq!(f.run(&[b"FT._LIST"]), "*0\r\n");
24474 }
24475
24476 let mut f = indexed();
24479 f.run(&[b"SELECT", b"9"]);
24480 f.run(&[b"FLUSHDB"]);
24481 assert!(f.server.search.lock().is_empty());
24482 }
24483
24484 fn tagged() -> Fixture {
24487 let mut f = Fixture::new();
24488 f.run(&[
24489 b"FT.CREATE",
24490 b"tv",
24491 b"PREFIX",
24492 b"1",
24493 b"tv:",
24494 b"SCHEMA",
24495 b"g",
24496 b"AS",
24497 b"gg",
24498 b"TAG",
24499 b"h",
24500 b"TAG",
24501 b"SEPARATOR",
24502 b"|",
24503 b"CASESENSITIVE",
24504 b"n",
24505 b"NUMERIC",
24506 ]);
24507 f.run(&[
24508 b"HSET",
24509 b"tv:1",
24510 b"g",
24511 b"Red, BLUE ",
24512 b"h",
24513 b"Aa|bB",
24514 b"n",
24515 b"1",
24516 ]);
24517 f.run(&[b"HSET", b"tv:2", b"g", b"red", b"h", b"aa", b"n", b"2"]);
24518 f
24519 }
24520
24521 #[test]
24525 fn tag_values_come_back_as_they_are_stored_and_sorted_by_their_bytes() {
24526 let mut f = tagged();
24527 assert_eq!(
24528 f.run(&[b"FT.TAGVALS", b"tv", b"gg"]),
24529 "*2\r\n$4\r\nblue\r\n$3\r\nred\r\n"
24530 );
24531 assert_eq!(
24532 f.run(&[b"FT.TAGVALS", b"tv", b"h"]),
24533 "*3\r\n$2\r\nAa\r\n$2\r\naa\r\n$2\r\nbB\r\n"
24534 );
24535 }
24536
24537 #[test]
24540 fn tag_values_are_asked_for_by_the_attribute_and_not_the_identifier() {
24541 let mut f = tagged();
24542 for (name, want) in [
24543 (b"g".as_slice(), "-SEARCH_ATTR_BAD No such field\r\n"),
24544 (b"zz", "-SEARCH_ATTR_BAD No such field\r\n"),
24545 (b"n", "-SEARCH_ATTR_BAD Not a tag field\r\n"),
24546 ] {
24547 assert_eq!(f.run(&[b"FT.TAGVALS", b"tv", name]), want);
24548 }
24549 assert_eq!(
24550 f.run(&[b"FT.TAGVALS", b"nope", b"g"]),
24551 "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
24552 );
24553 }
24554
24555 #[test]
24558 fn asking_for_tag_values_counts_a_use_of_the_index() {
24559 let mut f = tagged();
24560 let uses = |f: &mut Fixture| {
24561 let reply = f.run(&[b"FT.INFO", b"tv"]);
24562 let at = reply.find("number_of_uses").expect("the field is reported");
24563 let value = reply[at..].split("\r\n").nth(1).unwrap();
24564 value.trim_start_matches(':').parse::<i64>().unwrap()
24565 };
24566 let before = uses(&mut f);
24567 f.run(&[b"FT.TAGVALS", b"tv", b"gg"]);
24568 f.run(&[b"FT.TAGVALS", b"tv", b"zz"]);
24569 assert_eq!(uses(&mut f), before + 3);
24571 }
24572
24573 #[test]
24576 fn a_tag_field_with_nothing_in_it_answers_empty() {
24577 let mut f = Fixture::new();
24578 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"g", b"TAG"]);
24579 assert_eq!(f.run(&[b"FT.TAGVALS", b"e", b"g"]), "*0\r\n");
24580 }
24581
24582 #[test]
24585 fn a_dictionary_is_not_a_key() {
24586 let mut f = Fixture::new();
24587 assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"a", b"b"]), ":2\r\n");
24588 assert_eq!(f.run(&[b"TYPE", b"d"]), "+none\r\n");
24589 assert_eq!(f.run(&[b"EXISTS", b"d"]), ":0\r\n");
24590 assert_eq!(f.run(&[b"KEYS", b"d"]), "*0\r\n");
24591 }
24592
24593 #[test]
24596 fn a_dictionary_counts_the_terms_it_had_not_seen() {
24597 let mut f = Fixture::new();
24598 assert_eq!(
24599 f.run(&[b"FT.DICTADD", b"d", b"zeta", b"alpha", b"Beta", b"alpha"]),
24600 ":3\r\n"
24601 );
24602 assert_eq!(f.run(&[b"FT.DICTADD", b"d", b"alpha"]), ":0\r\n");
24603 assert_eq!(f.run(&[b"FT.DICTADD", b"d", b""]), ":0\r\n");
24604 assert_eq!(
24605 f.run(&[b"FT.DICTDUMP", b"d"]),
24606 "*3\r\n$4\r\nBeta\r\n$5\r\nalpha\r\n$4\r\nzeta\r\n"
24607 );
24608 assert_eq!(f.run(&[b"FT.DICTDEL", b"d", b"alpha", b"nope"]), ":1\r\n");
24609 }
24610
24611 #[test]
24615 fn a_dictionary_nobody_made_dumps_empty_rather_than_failing() {
24616 let mut f = Fixture::new();
24617 assert_eq!(f.run(&[b"FT.DICTDUMP", b"nope"]), "*0\r\n");
24618 assert_eq!(f.run(&[b"FT.DICTDEL", b"nope", b"a"]), ":0\r\n");
24619 }
24620
24621 #[test]
24623 fn a_flush_drops_the_dictionaries() {
24624 for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
24625 let mut f = Fixture::new();
24626 f.run(&[b"FT.DICTADD", b"d", b"a"]);
24627 f.run(&[flush]);
24628 assert_eq!(f.run(&[b"FT.DICTDUMP", b"d"]), "*0\r\n", "{flush:?}");
24629 }
24630 }
24631
24632 fn profiling() -> Fixture {
24637 let mut f = Fixture::new();
24638 f.run(&[
24639 b"FT.CREATE",
24640 b"ix",
24641 b"PREFIX",
24642 b"1",
24643 b"p:",
24644 b"SCHEMA",
24645 b"t",
24646 b"TEXT",
24647 b"n",
24648 b"NUMERIC",
24649 ]);
24650 f.run(&[b"HSET", b"p:1", b"t", b"alpha", b"n", b"1"]);
24651 f.run(&[b"HSET", b"p:2", b"t", b"alpha beta", b"n", b"2"]);
24652 f.run(&[b"HSET", b"p:3", b"t", b"beta", b"n", b"3"]);
24653 f
24654 }
24655
24656 fn timeless(reply: &str) -> String {
24659 const KEYS: &[&str] = &[
24660 "+Total profile time",
24661 "+Parsing time",
24662 "+Workers queue time",
24663 "+Pipeline creation time",
24664 "+Time",
24665 ];
24666 let mut out = String::new();
24667 let mut parts = reply.split("\r\n").peekable();
24668 while let Some(part) = parts.next() {
24669 out.push_str(part);
24670 out.push_str("\r\n");
24671 if !KEYS.contains(&part) {
24672 continue;
24673 }
24674 match parts.next() {
24677 Some(head) if head.starts_with('$') => {
24678 parts.next();
24679 }
24680 _ => {}
24681 }
24682 out.push_str("<t>\r\n");
24683 }
24684 out.truncate(out.len() - 2);
24686 out
24687 }
24688
24689 #[test]
24692 fn a_profile_wraps_the_reply_it_would_have_answered_anyway() {
24693 let mut f = profiling();
24694 assert_eq!(
24695 timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"])),
24696 "*2\r\n\
24697 *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\
24698 $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\
24699 *4\r\n+Shards\r\n*1\r\n*14\r\n\
24700 +Total profile time\r\n<t>\r\n+Parsing time\r\n<t>\r\n\
24701 +Workers queue time\r\n<t>\r\n+Pipeline creation time\r\n<t>\r\n\
24702 +Warning\r\n*1\r\n+None\r\n\
24703 +Iterators profile\r\n*10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
24704 +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
24705 +Estimated number of matches\r\n:2\r\n\
24706 +Result processors profile\r\n*4\r\n\
24707 *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
24708 *6\r\n+Type\r\n+Scorer\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
24709 *6\r\n+Type\r\n+Sorter\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
24710 *6\r\n+Type\r\n+Loader\r\n+Time\r\n<t>\r\n+Results processed\r\n:2\r\n\
24711 +Coordinator\r\n*0\r\n"
24712 );
24713 let mut g = profiling();
24714 g.run(&[b"HELLO", b"3"]);
24715 let three = timeless(&g.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"]));
24716 assert!(three.starts_with("%2\r\n+Results\r\n"), "{three}");
24717 assert!(
24718 three.contains("+Profile\r\n%2\r\n+Shards\r\n*1\r\n%7\r\n"),
24719 "{three}"
24720 );
24721 assert!(three.ends_with("+Coordinator\r\n%0\r\n"), "{three}");
24722 assert!(
24723 three.contains(
24724 "+Iterators profile\r\n%5\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n\
24725 +Time\r\n<t>\r\n+Number of reading operations\r\n:2\r\n\
24726 +Estimated number of matches\r\n:2\r\n"
24727 ),
24728 "{three}"
24729 );
24730 }
24731
24732 #[test]
24735 fn each_kind_of_step_writes_the_keys_that_belong_to_it() {
24736 let mut f = profiling();
24737 let tree = |f: &mut Fixture, query: &[u8]| {
24738 let reply = timeless(&f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", query]));
24739 let at = reply.find("+Iterators profile").expect("a tree");
24740 let end = reply.find("+Result processors").expect("a list of steps");
24741 reply[at..end].to_string()
24742 };
24743 assert_eq!(
24744 tree(&mut f, b"alpha beta"),
24745 "+Iterators profile\r\n*8\r\n+Type\r\n+INTERSECT\r\n+Time\r\n<t>\r\n\
24746 +Number of reading operations\r\n:1\r\n+Child iterators\r\n*2\r\n\
24747 *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n+Time\r\n<t>\r\n\
24748 +Number of reading operations\r\n:2\r\n+Estimated number of matches\r\n:2\r\n\
24749 *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$4\r\nbeta\r\n+Time\r\n<t>\r\n\
24750 +Number of reading operations\r\n:1\r\n+Estimated number of matches\r\n:2\r\n"
24751 );
24752 assert!(tree(&mut f, b"alpha|beta").starts_with(
24753 "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n+Query type\r\n+UNION\r\n\
24754 +Time\r\n<t>\r\n+Number of reading operations\r\n:3\r\n+Child iterators\r\n*2\r\n"
24755 ));
24756 assert!(tree(&mut f, b"-alpha").starts_with(
24759 "+Iterators profile\r\n*8\r\n+Type\r\n+NOT\r\n+Time\r\n<t>\r\n\
24760 +Number of reading operations\r\n:1\r\n+Child iterator\r\n*10\r\n"
24761 ));
24762 assert!(tree(&mut f, b"~alpha").starts_with(
24763 "+Iterators profile\r\n*8\r\n+Type\r\n+OPTIONAL\r\n+Time\r\n<t>\r\n\
24764 +Number of reading operations\r\n:3\r\n+Child iterator\r\n*10\r\n"
24765 ));
24766 assert_eq!(
24768 tree(&mut f, b"*"),
24769 "+Iterators profile\r\n*6\r\n+Type\r\n+WILDCARD\r\n+Time\r\n<t>\r\n\
24770 +Number of reading operations\r\n:3\r\n"
24771 );
24772 assert!(tree(&mut f, b"@n:[1 2]").starts_with(
24773 "+Iterators profile\r\n*10\r\n+Type\r\n+NUMERIC\r\n+Term\r\n\
24774 $19\r\n1.000000 - 2.000000\r\n"
24775 ));
24776 }
24777
24778 #[test]
24781 fn limited_folds_the_branches_an_expansion_made_and_leaves_a_bar_alone() {
24782 let mut f = profiling();
24783 f.run(&[b"HSET", b"p:4", b"t", b"alps"]);
24784 let tree = |f: &mut Fixture, words: &[&[u8]]| {
24785 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH"];
24786 argv.extend_from_slice(words);
24787 let reply = timeless(&f.run(&argv));
24788 let at = reply.find("+Iterators profile").expect("a tree");
24789 let end = reply.find("+Result processors").expect("a list of steps");
24790 reply[at..end].to_string()
24791 };
24792 assert_eq!(
24793 tree(&mut f, &[b"LIMITED", b"QUERY", b"al*"]),
24794 "+Iterators profile\r\n*10\r\n+Type\r\n+UNION\r\n\
24795 +Query type\r\n$11\r\nPREFIX - al\r\n+Time\r\n<t>\r\n\
24796 +Number of reading operations\r\n:3\r\n+Child iterators\r\n\
24797 +The number of iterators in the union is 2\r\n"
24798 );
24799 assert!(tree(&mut f, &[b"QUERY", b"al*"]).contains("+Child iterators\r\n*2\r\n"));
24800 assert!(
24801 tree(&mut f, &[b"LIMITED", b"QUERY", b"alpha|beta"])
24802 .contains("+Child iterators\r\n*2\r\n")
24803 );
24804 assert!(tree(&mut f, &[b"QUERY", b"alpha|beta"]).contains("+Query type\r\n+UNION\r\n"));
24808 assert!(
24809 tree(&mut f, &[b"QUERY", b"al*"]).contains("+Query type\r\n$11\r\nPREFIX - al\r\n")
24810 );
24811 }
24812
24813 #[test]
24816 fn the_steps_a_search_runs_depend_on_what_was_asked_for() {
24817 let mut f = profiling();
24818 let steps = |f: &mut Fixture, words: &[&[u8]]| {
24819 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY", b"alpha"];
24820 argv.extend_from_slice(words);
24821 let reply = timeless(&f.run(&argv));
24822 let at = reply.find("+Result processors").expect("a list of steps");
24823 let end = reply.find("+Coordinator").expect("an end");
24824 let mut out = Vec::new();
24825 let mut parts = reply[at..end].split("\r\n").peekable();
24826 while let Some(part) = parts.next() {
24827 if part == "+Type" {
24828 out.push(parts.next().unwrap_or_default().to_string());
24829 }
24830 }
24831 out
24832 };
24833 assert_eq!(
24834 steps(&mut f, &[]),
24835 ["+Index", "+Scorer", "+Sorter", "+Loader"]
24836 );
24837 assert_eq!(
24838 steps(&mut f, &[b"NOCONTENT"]),
24839 ["+Index", "+Scorer", "+Sorter"]
24840 );
24841 assert_eq!(
24844 steps(&mut f, &[b"LIMIT", b"0", b"0"]),
24845 ["+Index", "+Counter"]
24846 );
24847 assert_eq!(
24850 steps(&mut f, &[b"SORTBY", b"n"]),
24851 ["+Index", "+Sorter", "+Loader"]
24852 );
24853 assert_eq!(
24854 steps(&mut f, &[b"SORTBY", b"n", b"WITHSCORES"]),
24855 ["+Index", "+Scorer", "+Sorter", "+Loader"]
24856 );
24857 assert_eq!(
24858 steps(&mut f, &[b"HIGHLIGHT"]),
24859 ["+Index", "+Scorer", "+Sorter", "+Loader", "+Highlighter"]
24860 );
24861 assert_eq!(
24862 steps(&mut f, &[b"SUMMARIZE", b"NOCONTENT"]),
24863 ["+Index", "+Scorer", "+Sorter"]
24864 );
24865 }
24866
24867 #[test]
24870 fn a_pipeline_names_every_step_after_what_it_runs() {
24871 let mut f = profiling();
24872 let steps = |f: &mut Fixture, words: &[&[u8]]| {
24873 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"AGGREGATE", b"QUERY", b"*"];
24874 argv.extend_from_slice(words);
24875 let reply = timeless(&f.run(&argv));
24876 let at = reply.find("+Result processors").expect("a list of steps");
24877 let end = reply.find("+Coordinator").expect("an end");
24878 let mut out = Vec::new();
24879 let mut parts = reply[at..end].split("\r\n").peekable();
24880 while let Some(part) = parts.next() {
24881 if part == "+Type" {
24882 out.push(parts.next().unwrap_or_default().to_string());
24883 }
24884 }
24885 out
24886 };
24887 assert_eq!(steps(&mut f, &[]), ["+Index"]);
24888 assert_eq!(
24889 steps(&mut f, &[b"APPLY", b"1", b"AS", b"one"]),
24890 ["+Index", "+Projector - Literal 1"]
24891 );
24892 assert_eq!(
24893 steps(
24894 &mut f,
24895 &[b"LOAD", b"1", b"@n", b"APPLY", b"@n * 2", b"AS", b"d"]
24896 ),
24897 ["+Index", "+Loader", "+Projector - Operator *"]
24898 );
24899 assert_eq!(
24900 steps(&mut f, &[b"LOAD", b"1", b"@n", b"FILTER", b"@n > 1"]),
24901 ["+Index", "+Loader", "+Filter - Predicate >"]
24902 );
24903 assert_eq!(
24904 steps(
24905 &mut f,
24906 &[b"GROUPBY", b"1", b"@n", b"REDUCE", b"COUNT", b"0"]
24907 ),
24908 ["+Index", "+Loader", "+Grouper"]
24909 );
24910 assert_eq!(
24911 steps(&mut f, &[b"SORTBY", b"1", b"@n"]),
24912 ["+Index", "+Loader", "+Sorter"]
24913 );
24914 assert_eq!(
24915 steps(&mut f, &[b"LIMIT", b"0", b"2"]),
24916 ["+Index", "+Pager/Limiter"]
24917 );
24918 assert_eq!(
24921 steps(
24922 &mut f,
24923 &[
24924 b"ADDSCORES",
24925 b"LOAD",
24926 b"1",
24927 b"@n",
24928 b"APPLY",
24929 b"@__score",
24930 b"AS",
24931 b"s"
24932 ]
24933 ),
24934 [
24935 "+Index",
24936 "+Scorer",
24937 "+Loader",
24938 "+Projector - Property __score"
24939 ]
24940 );
24941 }
24942
24943 #[test]
24951 fn a_sortable_field_is_read_without_the_key_being_opened() {
24952 let mut f = Fixture::new();
24953 f.run(&[
24954 b"FT.CREATE",
24955 b"sx",
24956 b"PREFIX",
24957 b"1",
24958 b"s:",
24959 b"SCHEMA",
24960 b"n",
24961 b"NUMERIC",
24962 b"SORTABLE",
24963 b"g",
24964 b"TAG",
24965 ]);
24966 f.run(&[b"HSET", b"s:1", b"n", b"1", b"g", b"one"]);
24967 f.run(&[b"HSET", b"s:2", b"n", b"2", b"g", b"two"]);
24968 let loads = |f: &mut Fixture, words: &[&[u8]]| {
24969 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"sx", b"AGGREGATE", b"QUERY", b"*"];
24970 argv.extend_from_slice(words);
24971 f.run(&argv).contains("+Loader")
24972 };
24973 assert!(!loads(&mut f, &[b"LOAD", b"1", b"@n"]));
24974 assert!(!loads(&mut f, &[b"SORTBY", b"1", b"@n"]));
24975 assert!(!loads(&mut f, &[b"APPLY", b"@n * 2", b"AS", b"d"]));
24976 assert!(loads(&mut f, &[b"LOAD", b"1", b"@g"]));
24977 assert!(loads(&mut f, &[b"LOAD", b"2", b"@n", b"@g"]));
24978 assert!(loads(
24979 &mut f,
24980 &[b"GROUPBY", b"1", b"@g", b"REDUCE", b"COUNT", b"0"]
24981 ));
24982 assert!(loads(&mut f, &[b"LOAD", b"*"]));
24983 }
24984
24985 #[test]
24988 fn a_profile_checks_its_own_words_before_it_runs_anything() {
24989 let mut f = profiling();
24990 assert_eq!(
24991 f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"QUERY"]),
24992 "-ERR wrong number of arguments for 'FT.PROFILE' command\r\n"
24993 );
24994 assert_eq!(
24995 f.run(&[b"FT.PROFILE", b"ix", b"BOGUS", b"QUERY", b"alpha"]),
24996 "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
24997 );
24998 assert_eq!(
25001 f.run(&[
25002 b"FT.PROFILE",
25003 b"ix",
25004 b"LIMITED",
25005 b"SEARCH",
25006 b"QUERY",
25007 b"alpha"
25008 ]),
25009 "-No `SEARCH`, `AGGREGATE`, or `HYBRID` provided\r\n"
25010 );
25011 assert_eq!(
25012 f.run(&[b"FT.PROFILE", b"ix", b"SEARCH", b"BOGUS", b"alpha"]),
25013 "-The QUERY keyword is expected\r\n"
25014 );
25015 assert_eq!(
25016 f.run(&[
25017 b"FT.PROFILE",
25018 b"ix",
25019 b"AGGREGATE",
25020 b"QUERY",
25021 b"alpha",
25022 b"WITHCURSOR"
25023 ]),
25024 "-FT.PROFILE does not support cursor\r\n"
25025 );
25026 assert_eq!(
25029 f.run(&[b"FT.PROFILE", b"nope", b"SEARCH", b"QUERY", b"alpha"]),
25030 "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
25031 );
25032 assert_eq!(
25033 f.run(&[
25034 b"FT.PROFILE",
25035 b"ix",
25036 b"SEARCH",
25037 b"QUERY",
25038 b"alpha",
25039 b"extra"
25040 ]),
25041 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `extra` at position 1 for <main>\r\n"
25042 );
25043 }
25044
25045 #[test]
25047 fn the_words_of_a_profile_are_read_the_way_every_other_word_is() {
25048 let mut f = profiling();
25049 let one = f.run(&[
25050 b"FT.PROFILE",
25051 b"ix",
25052 b"search",
25053 b"limited",
25054 b"query",
25055 b"alpha",
25056 ]);
25057 let two = f.run(&[
25058 b"FT.PROFILE",
25059 b"ix",
25060 b"SEARCH",
25061 b"LIMITED",
25062 b"QUERY",
25063 b"alpha",
25064 ]);
25065 assert_eq!(timeless(&one), timeless(&two));
25066 }
25067
25068 #[test]
25073 fn the_two_ways_of_dropping_an_index_disagree_about_the_documents() {
25074 let mut f = profiling();
25075 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix"]), "+OK\r\n");
25076 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25077
25078 let mut f = profiling();
25079 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
25080 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
25081
25082 let mut f = profiling();
25083 assert_eq!(f.run(&[b"FT.DROP", b"ix"]), "+OK\r\n");
25084 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
25085
25086 let mut f = profiling();
25087 assert_eq!(f.run(&[b"FT.DROP", b"ix", b"KEEPDOCS"]), "+OK\r\n");
25088 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25089 }
25090
25091 #[test]
25094 fn neither_way_of_dropping_an_index_takes_the_other_ones_word() {
25095 let mut f = profiling();
25096 let line = "-SEARCH_ARG_UNRECOGNIZED Unknown argument\r\n";
25097 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"KEEPDOCS"]), line);
25098 assert_eq!(f.run(&[b"FT.DROP", b"ix", b"DD"]), line);
25099 assert_eq!(f.run(&[b"FT._LIST"]), "*1\r\n+ix\r\n");
25101 }
25102
25103 #[test]
25106 fn dropping_the_documents_leaves_a_key_the_index_never_read() {
25107 let mut f = profiling();
25108 f.run(&[b"SET", b"p:4", b"alpha"]);
25109 f.run(&[b"HSET", b"q:1", b"t", b"alpha"]);
25110 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
25111 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":0\r\n");
25112 assert_eq!(f.run(&[b"EXISTS", b"p:4", b"q:1"]), ":2\r\n");
25113 }
25114
25115 #[test]
25118 fn another_index_over_the_same_keys_loses_the_documents_too() {
25119 let mut f = profiling();
25120 f.run(&[
25121 b"FT.CREATE",
25122 b"other",
25123 b"PREFIX",
25124 b"1",
25125 b"p:",
25126 b"SCHEMA",
25127 b"t",
25128 b"TEXT",
25129 ]);
25130 assert_eq!(f.run(&[b"FT.DROPINDEX", b"ix", b"DD"]), "+OK\r\n");
25131 assert_eq!(
25132 f.run(&[b"FT.SEARCH", b"other", b"alpha", b"NOCONTENT"]),
25133 "*1\r\n:0\r\n"
25134 );
25135 }
25136
25137 #[test]
25140 fn a_drop_of_an_index_that_is_not_there_touches_no_keys() {
25141 let mut f = profiling();
25142 assert_eq!(f.run(&[b"FT._DROPINDEXIFX", b"nope", b"DD"]), "+OK\r\n");
25143 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25144 assert_eq!(f.run(&[b"FT._DROPIFX", b"nope"]), "+OK\r\n");
25145 assert_eq!(f.run(&[b"EXISTS", b"p:1", b"p:2", b"p:3"]), ":3\r\n");
25146 }
25147
25148 #[test]
25153 fn a_setting_reads_back_as_a_pair_on_one_protocol_and_a_map_on_the_other() {
25154 let mut f = Fixture::new();
25155 assert_eq!(
25156 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25157 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25158 );
25159 assert_eq!(
25160 f.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
25161 "*1\r\n*2\r\n+EXTLOAD\r\n$-1\r\n"
25162 );
25163 let mut g = Fixture::new();
25164 g.run(&[b"HELLO", b"3"]);
25165 assert_eq!(
25166 g.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25167 "%1\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25168 );
25169 assert_eq!(
25170 g.run(&[b"FT.CONFIG", b"GET", b"EXTLOAD"]),
25171 "%1\r\n+EXTLOAD\r\n_\r\n"
25172 );
25173 }
25174
25175 #[test]
25178 fn a_help_row_carries_the_description_and_the_value_together() {
25179 let mut f = Fixture::new();
25180 assert_eq!(
25181 f.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
25182 "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
25183 +Value\r\n$3\r\n500\r\n"
25184 );
25185 let mut g = Fixture::new();
25186 g.run(&[b"HELLO", b"3"]);
25187 assert_eq!(
25188 g.run(&[b"FT.CONFIG", b"HELP", b"TIMEOUT"]),
25189 "%1\r\n+TIMEOUT\r\n%2\r\n+Description\r\n+Query (search) timeout\r\n\
25190 +Value\r\n$3\r\n500\r\n"
25191 );
25192 }
25193
25194 #[test]
25197 fn only_a_bare_star_asks_for_every_setting_and_nothing_else_globs() {
25198 let mut f = Fixture::new();
25199 assert_eq!(
25200 f.run(&[b"FT.CONFIG", b"GET", b"timeout"]),
25201 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25202 );
25203 for name in [
25204 b"TIMEOUT*".as_slice(),
25205 b"?IMEOUT",
25206 b"*TIMEOUT*",
25207 b"TIME",
25208 b"NOSUCH",
25209 b"",
25210 ] {
25211 assert_eq!(f.run(&[b"FT.CONFIG", b"GET", name]), "*0\r\n", "{name:?}");
25212 }
25213 assert!(f.run(&[b"FT.CONFIG", b"GET", b"*"]).starts_with("*69\r\n"));
25214 assert!(f.run(&[b"FT.CONFIG", b"HELP", b"*"]).starts_with("*69\r\n"));
25215 }
25216
25217 #[test]
25220 fn a_read_ignores_whatever_follows_the_name() {
25221 let mut f = Fixture::new();
25222 assert_eq!(
25223 f.run(&[b"FT.CONFIG", b"GET", b"timeout", b"extra", b"more"]),
25224 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n500\r\n"
25225 );
25226 assert_eq!(
25227 f.run(&[b"FT.CONFIG", b"HELP", b"timeout", b"extra"]),
25228 "*1\r\n*5\r\n+TIMEOUT\r\n+Description\r\n+Query (search) timeout\r\n\
25229 +Value\r\n$3\r\n500\r\n"
25230 );
25231 }
25232
25233 #[test]
25236 fn a_missing_subcommand_and_a_missing_name_are_told_apart() {
25237 let mut f = Fixture::new();
25238 assert_eq!(
25239 f.run(&[b"FT.CONFIG"]),
25240 "-ERR wrong number of arguments for 'FT.CONFIG' command\r\n"
25241 );
25242 for sub in [b"GET".as_slice(), b"SET", b"HELP"] {
25243 let want = format!(
25244 "-ERR wrong number of arguments for 'FT.CONFIG|{}' command\r\n",
25245 String::from_utf8_lossy(sub)
25246 );
25247 assert_eq!(f.run(&[b"FT.CONFIG", sub]), want);
25248 }
25249 assert_eq!(
25250 f.run(&[b"ft.config", b"get"]),
25251 "-ERR wrong number of arguments for 'FT.CONFIG|GET' command\r\n"
25252 );
25253 assert_eq!(
25254 f.run(&[b"FT.CONFIG", b"bogus"]),
25255 "-ERR unknown subcommand 'bogus'. Try FT.CONFIG HELP.\r\n"
25256 );
25257 }
25258
25259 #[test]
25262 fn a_write_checks_the_name_then_the_setting_then_the_value() {
25263 let mut f = Fixture::new();
25264 for tail in [vec![b"1".as_slice()], vec![], vec![b"1", b"2", b"3"]] {
25265 let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"NOSUCH"];
25266 cmd.extend(tail);
25267 assert_eq!(f.run(&cmd), "-SEARCH_OPTION_INVALID Invalid option\r\n");
25268 }
25269 for tail in [vec![b"1000".as_slice()], vec![], vec![b"x", b"y"]] {
25270 let mut cmd: Vec<&[u8]> = vec![b"FT.CONFIG", b"SET", b"MAXDOCTABLESIZE"];
25271 cmd.extend(tail);
25272 assert_eq!(
25273 f.run(&cmd),
25274 "-SEARCH_OPTION_BAD Not modifiable at runtime\r\n"
25275 );
25276 }
25277 assert_eq!(
25278 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"x", b"y", b"z"]),
25279 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n"
25280 );
25281 }
25282
25283 #[test]
25286 fn an_excess_of_words_is_noticed_after_the_value_is_kept() {
25287 let mut f = Fixture::new();
25288 assert_eq!(
25289 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"500"]),
25290 "+OK\r\n"
25291 );
25292 assert_eq!(
25293 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"600", b"junk"]),
25294 "+EXCESSARGS\r\n"
25295 );
25296 assert_eq!(
25297 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25298 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n600\r\n"
25299 );
25300 }
25301
25302 #[test]
25305 fn a_number_is_read_the_strict_way_and_then_the_loose_one() {
25306 let mut f = Fixture::new();
25307 for (given, want) in [
25308 (b"0x10".as_slice(), "16"),
25309 (b"0X1f", "31"),
25310 (b"+0x10", "16"),
25311 (b"+5", "5"),
25312 (b"010", "10"),
25313 (b"08", "8"),
25314 (b"0777", "777"),
25315 (b"1e3", "1000"),
25316 (b"0.0", "0"),
25317 (b"-0.0", "0"),
25318 ] {
25319 assert_eq!(
25320 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25321 "+OK\r\n",
25322 "{given:?}"
25323 );
25324 let want = format!("*1\r\n*2\r\n+TIMEOUT\r\n${}\r\n{want}\r\n", want.len());
25325 assert_eq!(
25326 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25327 want,
25328 "{given:?}"
25329 );
25330 }
25331 for given in [
25332 b" 5".as_slice(),
25333 b"5 ",
25334 b"1.5",
25335 b"1e-3",
25336 b"x",
25337 b"",
25338 b"0b11",
25339 b"0xg",
25340 b"nan",
25341 b"inf",
25342 b"1e100",
25343 b"99999999999999999999",
25344 ] {
25345 assert_eq!(
25346 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25347 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
25348 "{given:?}"
25349 );
25350 }
25351 }
25352
25353 #[test]
25356 fn a_negative_is_answered_by_whichever_reader_found_it() {
25357 let mut f = Fixture::new();
25358 for given in [b"-1".as_slice(), b"-16"] {
25359 assert_eq!(
25360 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25361 "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n",
25362 "{given:?}"
25363 );
25364 }
25365 for given in [b"-0x10".as_slice(), b"-1e3", b"-010", b"-2.0"] {
25366 assert_eq!(
25367 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", given]),
25368 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
25369 "{given:?}"
25370 );
25371 }
25372 let unlimited = "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n$9\r\nunlimited\r\n";
25373 for given in [b"-1".as_slice(), b"-0x10", b"-1e3", b"-010"] {
25374 assert_eq!(
25375 f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
25376 "+OK\r\n",
25377 "{given:?}"
25378 );
25379 assert_eq!(
25380 f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
25381 unlimited,
25382 "{given:?}"
25383 );
25384 }
25385 }
25386
25387 #[test]
25390 fn a_wide_setting_wraps_into_its_slot_before_it_is_read_back() {
25391 let mut f = Fixture::new();
25392 for (given, want) in [
25393 (b"2147483647".as_slice(), "2147483647"),
25394 (b"2147483648", "unlimited"),
25395 (b"4294967295", "unlimited"),
25396 (b"9223372036854775806", "unlimited"),
25397 (b"0", "0"),
25398 ] {
25399 assert_eq!(
25400 f.run(&[b"FT.CONFIG", b"SET", b"MAXSEARCHRESULTS", given]),
25401 "+OK\r\n",
25402 "{given:?}"
25403 );
25404 let want = format!(
25405 "*1\r\n*2\r\n+MAXSEARCHRESULTS\r\n${}\r\n{want}\r\n",
25406 want.len()
25407 );
25408 assert_eq!(
25409 f.run(&[b"FT.CONFIG", b"GET", b"MAXSEARCHRESULTS"]),
25410 want,
25411 "{given:?}"
25412 );
25413 }
25414 }
25415
25416 #[test]
25419 fn a_number_out_of_range_names_the_limit_it_crossed() {
25420 let mut f = Fixture::new();
25421 let bounds = "-SEARCH_PARSE_ARGS Value is outside acceptable bounds\r\n";
25422 for (name, given) in [
25423 (b"MINPREFIX".as_slice(), b"0".as_slice()),
25424 (b"MAX_AGGREGATE_GROUPS", b"0"),
25425 (b"BM25STD_TANH_FACTOR", b"0"),
25426 (b"DEFAULT_DIALECT", b"0"),
25427 (b"MINSTEMLEN", b"4294967296"),
25428 (b"_BG_INDEX_OOM_PAUSE_TIME", b"4294967296"),
25429 (b"INDEXER_YIELD_EVERY_OPS", b"4294967296"),
25430 (b"CONNECT_TIMEOUT", b"2147483648"),
25431 ] {
25432 assert_eq!(
25433 f.run(&[b"FT.CONFIG", b"SET", name, given]),
25434 bounds,
25435 "{name:?}"
25436 );
25437 }
25438 for (name, given, want) in [
25439 (
25440 b"MINSTEMLEN".as_slice(),
25441 b"1".as_slice(),
25442 "-SEARCH_SYNTAX Minimum stem length cannot be lower than 2\r\n",
25443 ),
25444 (
25445 b"MAX_AGGREGATE_GROUPS",
25446 b"67108865",
25447 "-SEARCH_LIMIT_OVER Value exceeds maximum possible aggregate groups\r\n",
25448 ),
25449 (
25450 b"WORKERS",
25451 b"17",
25452 "-SEARCH_LIMIT_OVER Number of worker threads cannot exceed 16\r\n",
25453 ),
25454 (
25455 b"_NUMERIC_RANGES_PARENTS",
25456 b"3",
25457 "-SEARCH_PARSE_ARGS Max depth for range cannot be higher than max \
25458 depth for balance\r\n",
25459 ),
25460 (
25461 b"DEFAULT_DIALECT",
25462 b"5",
25463 "-SEARCH_VALUE_BAD Default dialect version cannot be higher than 4\r\n",
25464 ),
25465 (
25466 b"_BG_INDEX_MEM_PCT_THR",
25467 b"101",
25468 "-SEARCH_LIMIT_OVER Memory limit for indexing cannot be greater then \
25469 100%\r\n",
25470 ),
25471 (
25472 b"BM25STD_TANH_FACTOR",
25473 b"10001",
25474 "-SEARCH_LIMIT_OVER BM25STD_TANH_FACTOR must be between 1 and 10000 \
25475 inclusive\r\n",
25476 ),
25477 (
25478 b"BG_INDEX_SLEEP_DURATION_US",
25479 b"1000000",
25480 "-SEARCH_LIMIT_OVER BG_INDEX_SLEEP_DURATION_US must be between 1 and \
25481 999999 (usleep POSIX limit)\r\n",
25482 ),
25483 ] {
25484 assert_eq!(
25485 f.run(&[b"FT.CONFIG", b"SET", name, given]),
25486 want,
25487 "{name:?}"
25488 );
25489 }
25490 }
25491
25492 #[test]
25495 fn the_trimming_delays_are_checked_against_one_another() {
25496 let mut f = Fixture::new();
25497 assert_eq!(
25498 f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"5000"]),
25499 "-SEARCH_PARSE_ARGS _MIN_TRIM_DELAY_MS (5000) must be less than \
25500 _MAX_TRIM_DELAY_MS (5000)\r\n"
25501 );
25502 assert_eq!(
25503 f.run(&[b"FT.CONFIG", b"SET", b"_MAX_TRIM_DELAY_MS", b"1999"]),
25504 "-SEARCH_PARSE_ARGS _MAX_TRIM_DELAY_MS (1999) must be greater than \
25505 _MIN_TRIM_DELAY_MS (2000)\r\n"
25506 );
25507 assert_eq!(
25508 f.run(&[b"FT.CONFIG", b"SET", b"_MIN_TRIM_DELAY_MS", b"4999"]),
25509 "+OK\r\n"
25510 );
25511 }
25512
25513 #[test]
25516 fn a_word_setting_folds_where_a_real_server_folds_and_not_otherwise() {
25517 let mut f = Fixture::new();
25518 assert_eq!(
25519 f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"RETURN"]),
25520 "+OK\r\n"
25521 );
25522 assert_eq!(
25523 f.run(&[b"FT.CONFIG", b"GET", b"ON_TIMEOUT"]),
25524 "*1\r\n*2\r\n+ON_TIMEOUT\r\n$6\r\nreturn\r\n"
25525 );
25526 assert_eq!(
25527 f.run(&[b"FT.CONFIG", b"SET", b"ON_TIMEOUT", b"nope"]),
25528 "-SEARCH_VALUE_BAD Invalid ON_TIMEOUT value\r\n"
25529 );
25530 assert_eq!(
25531 f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"IGNORE"]),
25532 "+OK\r\n"
25533 );
25534 assert_eq!(
25535 f.run(&[b"FT.CONFIG", b"GET", b"ON_OOM"]),
25536 "*1\r\n*2\r\n+ON_OOM\r\n$6\r\nignore\r\n"
25537 );
25538 assert_eq!(
25539 f.run(&[b"FT.CONFIG", b"SET", b"ON_OOM", b"nope"]),
25540 "-SEARCH_VALUE_BAD Invalid ON_OOM value\r\n"
25541 );
25542 let bad = "-SEARCH_VALUE_BAD Invalid default scorer value\r\n";
25543 for given in [b"bm25std".as_slice(), b"Bm25", b"TFIDF.docnorm", b""] {
25544 assert_eq!(
25545 f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", given]),
25546 bad,
25547 "{given:?}"
25548 );
25549 }
25550 assert_eq!(
25551 f.run(&[b"FT.CONFIG", b"SET", b"DEFAULT_SCORER", b"TFIDF.DOCNORM"]),
25552 "+OK\r\n"
25553 );
25554 }
25555
25556 #[test]
25559 fn a_yes_or_no_setting_takes_those_two_words_only() {
25560 let mut f = Fixture::new();
25561 assert_eq!(
25562 f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", b"TRUE"]),
25563 "+OK\r\n"
25564 );
25565 assert_eq!(
25566 f.run(&[b"FT.CONFIG", b"GET", b"_NUMERIC_COMPRESS"]),
25567 "*1\r\n*2\r\n+_NUMERIC_COMPRESS\r\n$4\r\ntrue\r\n"
25568 );
25569 for given in [b"yes".as_slice(), b"no", b"1", b"0", b"enabled", b""] {
25570 assert_eq!(
25571 f.run(&[b"FT.CONFIG", b"SET", b"_NUMERIC_COMPRESS", given]),
25572 "-SEARCH_PARSE_ARGS Could not convert argument to expected type\r\n",
25573 "{given:?}"
25574 );
25575 }
25576 }
25577
25578 #[test]
25581 fn two_names_for_one_setting_move_together() {
25582 let mut f = Fixture::new();
25583 f.run(&[b"FT.CONFIG", b"SET", b"MAXEXPANSIONS", b"300"]);
25584 assert_eq!(
25585 f.run(&[b"FT.CONFIG", b"GET", b"MAXPREFIXEXPANSIONS"]),
25586 "*1\r\n*2\r\n+MAXPREFIXEXPANSIONS\r\n$3\r\n300\r\n"
25587 );
25588 f.run(&[b"FT.CONFIG", b"SET", b"MAXPREFIXEXPANSIONS", b"200"]);
25589 assert_eq!(
25590 f.run(&[b"FT.CONFIG", b"GET", b"MAXEXPANSIONS"]),
25591 "*1\r\n*2\r\n+MAXEXPANSIONS\r\n$3\r\n200\r\n"
25592 );
25593 let long = b"_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
25594 let short = b"FORK_GC_CLEAN_NUMERIC_EMPTY_NODES".as_slice();
25595 f.run(&[b"FT.CONFIG", b"SET", long, b"false"]);
25596 assert_eq!(
25597 f.run(&[b"FT.CONFIG", b"GET", short]),
25598 "*1\r\n*2\r\n+FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$5\r\nfalse\r\n"
25599 );
25600 assert_eq!(f.run(&[b"FT.CONFIG", b"SET", short]), "+OK\r\n");
25601 assert_eq!(
25602 f.run(&[b"FT.CONFIG", b"GET", long]),
25603 "*1\r\n*2\r\n+_FORK_GC_CLEAN_NUMERIC_EMPTY_NODES\r\n$4\r\ntrue\r\n"
25604 );
25605 }
25606
25607 #[test]
25609 fn a_password_reads_back_as_stars_whatever_was_written() {
25610 let mut f = Fixture::new();
25611 assert_eq!(
25612 f.run(&[b"FT.CONFIG", b"SET", b"OSS_GLOBAL_PASSWORD", b"hunter2"]),
25613 "+OK\r\n"
25614 );
25615 assert_eq!(
25616 f.run(&[b"FT.CONFIG", b"GET", b"OSS_GLOBAL_PASSWORD"]),
25617 "*1\r\n*2\r\n+OSS_GLOBAL_PASSWORD\r\n$17\r\nPassword: *******\r\n"
25618 );
25619 }
25620
25621 #[test]
25624 fn a_flush_leaves_the_settings_alone() {
25625 for flush in [b"FLUSHALL".as_slice(), b"FLUSHDB"] {
25626 let mut f = Fixture::new();
25627 f.run(&[b"FT.CONFIG", b"SET", b"TIMEOUT", b"777"]);
25628 f.run(&[flush]);
25629 assert_eq!(
25630 f.run(&[b"FT.CONFIG", b"GET", b"TIMEOUT"]),
25631 "*1\r\n*2\r\n+TIMEOUT\r\n$3\r\n777\r\n",
25632 "{flush:?}"
25633 );
25634 }
25635 }
25636
25637 fn debugging() -> Fixture {
25642 let mut f = Fixture::new();
25643 f.run(&[
25644 b"FT.CREATE",
25645 b"dx",
25646 b"PREFIX",
25647 b"1",
25648 b"d:",
25649 b"SCHEMA",
25650 b"t",
25651 b"TEXT",
25652 b"g",
25653 b"TAG",
25654 b"n",
25655 b"NUMERIC",
25656 b"s",
25657 b"TEXT",
25658 b"SORTABLE",
25659 ]);
25660 f.run(&[
25661 b"HSET",
25662 b"d:1",
25663 b"t",
25664 b"running dogs",
25665 b"g",
25666 b"red,blue",
25667 b"n",
25668 b"1",
25669 b"s",
25670 b"Alpha",
25671 ]);
25672 f.run(&[
25673 b"HSET", b"d:2", b"t", b"running", b"g", b"red", b"n", b"2", b"s", b"beta",
25674 ]);
25675 f.run(&[
25676 b"HSET",
25677 b"d:3",
25678 b"t",
25679 b"dogs alpha",
25680 b"g",
25681 b"green",
25682 b"n",
25683 b"3",
25684 ]);
25685 f
25686 }
25687
25688 #[test]
25691 fn a_term_dump_lists_the_stems_beside_the_words() {
25692 let mut f = debugging();
25693 assert_eq!(
25694 f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"dx"]),
25695 "*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\
25696 $4\r\ndogs\r\n$7\r\nrunning\r\n"
25697 );
25698 }
25699
25700 #[test]
25703 fn a_posting_list_is_read_by_the_bytes_and_not_by_the_word() {
25704 let mut f = debugging();
25705 assert_eq!(
25706 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
25707 "*2\r\n:1\r\n:2\r\n"
25708 );
25709 assert_eq!(
25710 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"+run"]),
25711 "*2\r\n:1\r\n:2\r\n"
25712 );
25713 for term in [b"RUNNING".as_slice(), b"nosuchterm", b""] {
25714 assert_eq!(
25715 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", term]),
25716 "-Can not find the inverted index\r\n",
25717 "{term:?}"
25718 );
25719 }
25720 }
25721
25722 #[test]
25725 fn a_tag_dump_pairs_every_value_with_its_documents() {
25726 let mut f = debugging();
25727 assert_eq!(
25728 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"dx", b"g"]),
25729 "*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\
25730 *2\r\n$3\r\nred\r\n*2\r\n:1\r\n:2\r\n"
25731 );
25732 }
25733
25734 #[test]
25737 fn a_number_dump_answers_a_single_range() {
25738 let mut f = debugging();
25739 assert_eq!(
25740 f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"dx", b"n"]),
25741 "*1\r\n*3\r\n:1\r\n:2\r\n:3\r\n"
25742 );
25743 }
25744
25745 #[test]
25748 fn a_geo_field_is_dumped_as_a_numeric_one() {
25749 let mut f = Fixture::new();
25750 f.run(&[
25751 b"FT.CREATE",
25752 b"gx",
25753 b"PREFIX",
25754 b"1",
25755 b"q:",
25756 b"SCHEMA",
25757 b"loc",
25758 b"GEO",
25759 b"gg",
25760 b"AS",
25761 b"tag",
25762 b"TAG",
25763 ]);
25764 f.run(&[b"HSET", b"q:1", b"loc", b"1,2", b"gg", b"red"]);
25765 f.run(&[b"HSET", b"q:2", b"loc", b"3,4", b"gg", b"BLUE"]);
25766 assert_eq!(
25767 f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"gx", b"loc"]),
25768 "*1\r\n*2\r\n:1\r\n:2\r\n"
25769 );
25770 assert_eq!(
25771 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"gx", b"loc"]),
25772 "-Could not find given field in index spec\r\n"
25773 );
25774 }
25775
25776 #[test]
25779 fn a_dump_takes_the_attribute_and_not_the_identifier() {
25780 let mut f = Fixture::new();
25781 f.run(&[
25782 b"FT.CREATE",
25783 b"zx",
25784 b"PREFIX",
25785 b"1",
25786 b"z:",
25787 b"SCHEMA",
25788 b"gg",
25789 b"AS",
25790 b"tag",
25791 b"TAG",
25792 ]);
25793 f.run(&[b"HSET", b"z:1", b"gg", b"red"]);
25794 assert_eq!(
25795 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"zx", b"tag"]),
25796 "*1\r\n*2\r\n$3\r\nred\r\n*1\r\n:1\r\n"
25797 );
25798 assert_eq!(
25799 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"zx", b"gg"]),
25800 "-Could not find given field in index spec\r\n"
25801 );
25802 }
25803
25804 #[test]
25807 fn a_document_row_is_flat_on_one_protocol_and_a_map_on_the_other() {
25808 let mut f = debugging();
25809 assert_eq!(
25810 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]),
25811 "*14\r\n+internal_id\r\n:1\r\n$5\r\nflags\r\n\
25812 $36\r\n(0xc):HasSortVector,HasOffsetVector,\r\n+score\r\n$1\r\n1\r\n\
25813 +num_tokens\r\n:3\r\n+max_freq\r\n:1\r\n+refcount\r\n:1\r\n\
25814 +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\
25815 $5\r\nvalue\r\n$5\r\nalpha\r\n"
25816 );
25817 let mut g = debugging();
25818 g.run(&[b"HELLO", b"3"]);
25819 assert_eq!(
25820 g.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]),
25821 "%7\r\n+internal_id\r\n:1\r\n$5\r\nflags\r\n\
25822 $36\r\n(0xc):HasSortVector,HasOffsetVector,\r\n+score\r\n,1\r\n\
25823 +num_tokens\r\n:3\r\n+max_freq\r\n:1\r\n+refcount\r\n:1\r\n\
25824 +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\
25825 $5\r\nvalue\r\n$5\r\nalpha\r\n"
25826 );
25827 }
25828
25829 #[test]
25832 fn a_document_with_no_sortable_value_drops_the_key() {
25833 let mut f = debugging();
25834 assert_eq!(
25835 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:3", b"REVEAL"]),
25836 "*12\r\n+internal_id\r\n:3\r\n$5\r\nflags\r\n$22\r\n(0x8):HasOffsetVector,\r\n\
25837 +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"
25838 );
25839 }
25840
25841 #[test]
25844 fn the_flag_word_spells_out_the_bits_it_carries() {
25845 let mut f = Fixture::new();
25846 f.run(&[
25847 b"FT.CREATE",
25848 b"nx",
25849 b"NOOFFSETS",
25850 b"PREFIX",
25851 b"1",
25852 b"o:",
25853 b"SCHEMA",
25854 b"t",
25855 b"TEXT",
25856 ]);
25857 f.run(&[b"HSET", b"o:1", b"t", b"alpha"]);
25858 assert!(
25859 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"nx", b"o:1", b"REVEAL"])
25860 .contains("$6\r\n(0x0):\r\n")
25861 );
25862 }
25863
25864 #[test]
25867 fn obfuscation_numbers_a_field_by_its_place_in_the_schema() {
25868 let mut f = debugging();
25869 assert!(
25870 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"OBFUSCATE"])
25871 .contains("$22\r\nFieldPath@3 AS Field@3\r\n")
25872 );
25873 }
25874
25875 #[test]
25878 fn a_document_row_reads_its_keyword_at_a_fixed_place() {
25879 let mut f = debugging();
25880 let want = f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL"]);
25881 assert_eq!(
25882 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"REVEAL", b"more"]),
25883 want
25884 );
25885 assert_eq!(
25886 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"more", b"REVEAL"]),
25887 "-Invalid argument. Expected REVEAL or OBFUSCATE as the last argument\r\n"
25888 );
25889 assert_eq!(
25890 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1"]),
25891 "-ERR wrong number of arguments for '_FT.DEBUG|DOCINFO' command\r\n"
25892 );
25893 }
25894
25895 #[test]
25898 fn a_document_row_looks_the_key_up_before_it_reads_the_keyword() {
25899 let mut f = debugging();
25900 assert_eq!(
25901 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"nope", b"zz"]),
25902 "-Document not found in index\r\n"
25903 );
25904 assert_eq!(
25905 f.run(&[b"_FT.DEBUG", b"DOCINFO", b"dx", b"d:1", b"zz"]),
25906 "-Invalid argument. Expected REVEAL or OBFUSCATE as the last argument\r\n"
25907 );
25908 }
25909
25910 #[test]
25913 fn a_document_number_goes_both_ways() {
25914 let mut f = debugging();
25915 assert_eq!(
25916 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"2"]),
25917 "$3\r\nd:2\r\n"
25918 );
25919 assert_eq!(
25920 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:2"]),
25921 ":2\r\n"
25922 );
25923 assert_eq!(
25924 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"nope"]),
25925 ":0\r\n"
25926 );
25927 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"dx"]), ":3\r\n");
25928 for id in [b"9".as_slice(), b"0", b"-1", b"9223372036854775807"] {
25929 assert_eq!(
25930 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", id]),
25931 "-document was removed\r\n",
25932 "{id:?}"
25933 );
25934 }
25935 }
25936
25937 #[test]
25940 fn a_document_number_is_read_the_strict_way() {
25941 let mut f = debugging();
25942 for id in [
25943 b"x".as_slice(),
25944 b"1.5",
25945 b" 1",
25946 b"+1",
25947 b"01",
25948 b"0x1",
25949 b"",
25950 b"9223372036854775808",
25951 b"18446744073709551615",
25952 ] {
25953 assert_eq!(
25954 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", id]),
25955 "-bad id given\r\n",
25956 "{id:?}"
25957 );
25958 }
25959 }
25960
25961 #[test]
25964 fn a_dump_keeps_a_number_the_table_has_given_up() {
25965 let mut f = debugging();
25966 f.run(&[b"DEL", b"d:2"]);
25967 assert_eq!(
25968 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
25969 "*2\r\n:1\r\n:2\r\n"
25970 );
25971 assert_eq!(
25972 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"2"]),
25973 "-document was removed\r\n"
25974 );
25975 assert_eq!(
25976 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:2"]),
25977 ":0\r\n"
25978 );
25979 }
25980
25981 #[test]
25984 fn a_rewrite_takes_a_number_of_its_own() {
25985 let mut f = debugging();
25986 f.run(&[b"HSET", b"d:1", b"t", b"cats"]);
25987 assert_eq!(
25988 f.run(&[b"_FT.DEBUG", b"DOCIDTOID", b"dx", b"d:1"]),
25989 ":4\r\n"
25990 );
25991 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"dx"]), ":4\r\n");
25992 assert_eq!(
25993 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"dx", b"1"]),
25994 "-document was removed\r\n"
25995 );
25996 assert_eq!(
25997 f.run(&[b"_FT.DEBUG", b"DUMP_INVIDX", b"dx", b"running"]),
25998 "*2\r\n:1\r\n:2\r\n"
25999 );
26000 }
26001
26002 #[test]
26004 fn a_dump_follows_an_alias() {
26005 let mut f = debugging();
26006 f.run(&[b"FT.ALIASADD", b"da", b"dx"]);
26007 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"da"]), ":3\r\n");
26008 assert_eq!(
26009 f.run(&[b"_FT.DEBUG", b"IDTODOCID", b"da", b"1"]),
26010 "$3\r\nd:1\r\n"
26011 );
26012 }
26013
26014 #[test]
26017 fn an_index_name_is_case_sensitive_and_a_subcommand_name_is_not() {
26018 let mut f = debugging();
26019 assert_eq!(f.run(&[b"_FT.DEBUG", b"get_max_doc_id", b"dx"]), ":3\r\n");
26020 assert_eq!(
26021 f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"DX"]),
26022 "-Can not create a search ctx\r\n"
26023 );
26024 assert_eq!(
26025 f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"nope"]),
26026 "-Can not create a search ctx\r\n"
26027 );
26028 }
26029
26030 #[test]
26033 fn an_empty_field_dumps_as_nothing_at_all() {
26034 let mut f = Fixture::new();
26035 f.run(&[
26036 b"FT.CREATE",
26037 b"ex",
26038 b"PREFIX",
26039 b"1",
26040 b"e:",
26041 b"SCHEMA",
26042 b"t",
26043 b"TEXT",
26044 b"g",
26045 b"TAG",
26046 b"n",
26047 b"NUMERIC",
26048 ]);
26049 assert_eq!(f.run(&[b"_FT.DEBUG", b"DUMP_TERMS", b"ex"]), "*0\r\n");
26050 assert_eq!(
26051 f.run(&[b"_FT.DEBUG", b"DUMP_TAGIDX", b"ex", b"g"]),
26052 "*0\r\n"
26053 );
26054 assert_eq!(
26055 f.run(&[b"_FT.DEBUG", b"DUMP_NUMIDX", b"ex", b"n"]),
26056 "*0\r\n"
26057 );
26058 assert_eq!(f.run(&[b"_FT.DEBUG", b"GET_MAX_DOC_ID", b"ex"]), ":0\r\n");
26059 }
26060
26061 #[test]
26064 fn the_two_lines_with_a_code_word_are_the_arity_and_the_unknown_one() {
26065 let mut f = debugging();
26066 for (sub, extra) in [
26067 (b"DUMP_TERMS".as_slice(), 1),
26068 (b"GET_MAX_DOC_ID", 1),
26069 (b"DUMP_INVIDX", 2),
26070 (b"DUMP_TAGIDX", 2),
26071 (b"DUMP_NUMIDX", 2),
26072 (b"IDTODOCID", 2),
26073 (b"DOCIDTOID", 2),
26074 ] {
26075 let want = format!(
26076 "-ERR wrong number of arguments for '_FT.DEBUG|{}' command\r\n",
26077 str::from_utf8(sub).unwrap()
26078 );
26079 for given in [extra - 1, extra + 1] {
26080 let mut cmd: Vec<&[u8]> = vec![b"_FT.DEBUG", sub];
26081 cmd.extend(std::iter::repeat_n(b"dx".as_slice(), given));
26082 assert_eq!(f.run(&cmd), want, "{sub:?} {given}");
26083 }
26084 let mut right: Vec<&[u8]> = vec![b"_FT.DEBUG", sub, b"dx"];
26085 right.extend(std::iter::repeat_n(b"g".as_slice(), extra - 1));
26086 assert_ne!(f.run(&right), want, "{sub:?}");
26087 }
26088 assert_eq!(
26089 f.run(&[b"_FT.DEBUG", b"bogus", b"dx"]),
26090 "-ERR unknown subcommand 'bogus'. Try _FT.DEBUG HELP.\r\n"
26091 );
26092 }
26093
26094 #[test]
26097 fn the_help_names_the_subcommands_that_answer() {
26098 let mut f = Fixture::new();
26099 let want = "*8\r\n$11\r\nDUMP_INVIDX\r\n$11\r\nDUMP_NUMIDX\r\n$11\r\nDUMP_TAGIDX\r\n\
26100 $9\r\nIDTODOCID\r\n$9\r\nDOCIDTOID\r\n$7\r\nDOCINFO\r\n$10\r\nDUMP_TERMS\r\n\
26101 $14\r\nGET_MAX_DOC_ID\r\n";
26102 assert_eq!(f.run(&[b"_FT.DEBUG", b"HELP"]), want);
26103 assert_eq!(f.run(&[b"_FT.DEBUG", b"HELP", b"extra"]), want);
26104 }
26105
26106 #[test]
26111 fn a_synonym_dump_folds_the_terms_and_keeps_the_ids_as_given() {
26112 let mut f = Fixture::new();
26113 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26114 assert_eq!(
26115 f.run(&[b"FT.SYNUPDATE", b"e", b"G1", b"BOY", b"kid"]),
26116 "+OK\r\n"
26117 );
26118 assert_eq!(f.run(&[b"FT.SYNUPDATE", b"e", b"g2", b"boy"]), "+OK\r\n");
26119 assert_eq!(
26120 f.run(&[b"FT.SYNDUMP", b"e"]),
26121 "*4\r\n$3\r\nboy\r\n*2\r\n$2\r\nG1\r\n$2\r\ng2\r\n\
26122 $3\r\nkid\r\n*1\r\n$2\r\nG1\r\n"
26123 );
26124 }
26125
26126 #[test]
26130 fn a_word_in_a_group_reads_as_a_union_with_the_group_term() {
26131 let mut f = Fixture::new();
26132 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26133 f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"jogging"]);
26134 assert_eq!(
26135 f.run(&[b"FT.EXPLAIN", b"e", b"jogging"]),
26136 "$69\r\nUNION {\n jogging\n ~gr(expanded)\n +jog(expanded)\n jog(expanded)\n}\n\r\n"
26137 );
26138 }
26139
26140 #[test]
26149 fn a_group_matches_the_word_it_holds_and_not_a_stem_of_it() {
26150 let mut f = Fixture::new();
26151 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26152 f.run(&[b"HSET", b"d1", b"t", b"boy"]);
26153 f.run(&[b"HSET", b"d2", b"t", b"runs"]);
26154 f.run(&[b"FT.SYNUPDATE", b"e", b"gr", b"boy", b"child", b"running"]);
26155 assert_eq!(
26156 f.run(&[b"FT.SEARCH", b"e", b"child", b"NOCONTENT"]),
26157 "*2\r\n:1\r\n$2\r\nd1\r\n"
26158 );
26159 assert_eq!(
26160 f.run(&[b"FT.SEARCH", b"e", b"running", b"NOCONTENT"]),
26161 "*3\r\n:2\r\n$2\r\nd1\r\n$2\r\nd2\r\n"
26162 );
26163 }
26164
26165 #[test]
26168 fn a_synonym_command_on_a_name_that_is_not_there_fails() {
26169 let mut f = Fixture::new();
26170 let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
26171 assert_eq!(f.run(&[b"FT.SYNDUMP", b"nope"]), missing);
26172 assert_eq!(f.run(&[b"FT.SYNUPDATE", b"nope", b"g", b"a"]), missing);
26173 }
26174
26175 #[test]
26179 fn params_counts_the_words_before_it_pairs_them_up() {
26180 let mut f = Fixture::new();
26181 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT"]);
26182 let none = "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: \
26183 Expected an argument, but none provided\r\n";
26184 let odd = "-SEARCH_ADD_ARGS Parameters must be specified in PARAM VALUE pairs\r\n";
26185 assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1"]), none);
26186 assert_eq!(
26187 f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"3", b"a", b"b"]),
26188 none
26189 );
26190 assert_eq!(
26191 f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"1", b"a"]),
26192 odd
26193 );
26194 assert_eq!(f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"0"]), odd);
26195 assert_eq!(
26196 f.run(&[b"FT.SEARCH", b"e", b"x", b"PARAMS", b"-1"]),
26197 "-SEARCH_PARSE_ARGS Bad arguments for PARAMS: Value is outside acceptable bounds\r\n"
26198 );
26199 }
26200
26201 fn vectored(f: &mut Fixture) {
26211 f.run(&[
26212 b"FT.CREATE",
26213 b"h",
26214 b"SCHEMA",
26215 b"t",
26216 b"TEXT",
26217 b"v",
26218 b"VECTOR",
26219 b"FLAT",
26220 b"6",
26221 b"TYPE",
26222 b"FLOAT32",
26223 b"DIM",
26224 b"2",
26225 b"DISTANCE_METRIC",
26226 b"L2",
26227 ]);
26228 let at: [&[u8]; 5] = [
26229 b"\x00\x00\x80\x40\x00\x00\x00\x00",
26230 b"\x00\x00\x40\x40\x00\x00\x00\x00",
26231 b"\x00\x00\x00\x40\x00\x00\x00\x00",
26232 b"\x00\x00\x80\x3f\x00\x00\x00\x00",
26233 b"\x00\x00\x00\x00\x00\x00\x00\x00",
26234 ];
26235 for (n, point) in at.iter().enumerate() {
26236 let key = format!("d{}", n + 1);
26237 let word: &[u8] = match n {
26238 0 | 1 | 3 => b"alpha",
26239 _ => b"beta",
26240 };
26241 f.run(&[b"HSET", key.as_bytes(), b"t", word, b"v", point]);
26242 }
26243 }
26244
26245 const ORIGIN: &[u8] = b"\x00\x00\x00\x00\x00\x00\x00\x00";
26247
26248 #[test]
26252 fn a_knn_picks_the_nearest_and_answers_them_in_document_order() {
26253 let mut f = Fixture::new();
26254 vectored(&mut f);
26255 assert_eq!(
26256 f.run(&[
26257 b"FT.SEARCH",
26258 b"h",
26259 b"*=>[KNN 5 @v $vec]",
26260 b"PARAMS",
26261 b"2",
26262 b"vec",
26263 ORIGIN,
26264 b"DIALECT",
26265 b"2",
26266 b"NOCONTENT",
26267 ]),
26268 "*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"
26269 );
26270 assert_eq!(
26271 f.run(&[
26272 b"FT.SEARCH",
26273 b"h",
26274 b"*=>[KNN 3 @v $vec]",
26275 b"PARAMS",
26276 b"2",
26277 b"vec",
26278 ORIGIN,
26279 b"DIALECT",
26280 b"2",
26281 b"NOCONTENT",
26282 ]),
26283 "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
26284 );
26285 }
26286
26287 #[test]
26290 fn a_range_takes_what_is_inside_it_and_the_distance_is_squared() {
26291 let mut f = Fixture::new();
26292 vectored(&mut f);
26293 for (radius, want) in [
26294 ("0", "*2\r\n:1\r\n$2\r\nd5\r\n"),
26295 ("2", "*3\r\n:2\r\n$2\r\nd4\r\n$2\r\nd5\r\n"),
26296 (
26297 "9",
26298 "*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",
26299 ),
26300 ] {
26301 let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
26302 assert_eq!(
26303 f.run(&[
26304 b"FT.SEARCH",
26305 b"h",
26306 query.as_bytes(),
26307 b"PARAMS",
26308 b"2",
26309 b"vec",
26310 ORIGIN,
26311 b"DIALECT",
26312 b"2",
26313 b"NOCONTENT",
26314 ]),
26315 want,
26316 "radius {radius}"
26317 );
26318 }
26319 }
26320
26321 #[test]
26325 fn a_knn_measures_what_the_query_in_front_of_it_matched() {
26326 let mut f = Fixture::new();
26327 vectored(&mut f);
26328 assert_eq!(
26329 f.run(&[
26330 b"FT.SEARCH",
26331 b"h",
26332 b"alpha=>[KNN 2 @v $vec]",
26333 b"PARAMS",
26334 b"2",
26335 b"vec",
26336 ORIGIN,
26337 b"DIALECT",
26338 b"2",
26339 b"NOCONTENT",
26340 ]),
26341 "*3\r\n:2\r\n$2\r\nd2\r\n$2\r\nd4\r\n"
26342 );
26343 }
26344
26345 #[test]
26354 fn a_count_and_a_radius_are_refused_in_their_own_words() {
26355 let mut f = Fixture::new();
26356 vectored(&mut f);
26357 let ask = |f: &mut Fixture, query: &str| {
26358 f.run(&[
26359 b"FT.SEARCH",
26360 b"h",
26361 query.as_bytes(),
26362 b"PARAMS",
26363 b"2",
26364 b"vec",
26365 ORIGIN,
26366 b"DIALECT",
26367 b"2",
26368 b"NOCONTENT",
26369 ])
26370 };
26371 for (query, at, near) in [
26372 ("*=>[KNN -1 @v $vec]", 8, "-1"),
26373 ("*=>[KNN 1.5 @v $vec]", 8, "1.5"),
26374 ("*=>[KNN +3 @v $vec]", 8, "+3"),
26375 ("*=>[KNN 0x10 @v $vec]", 8, "0x10"),
26376 ("*=>[KNN abc @v $vec]", 8, "abc"),
26377 ("*=>[KNN 3 $vec]", 10, "vec"),
26378 ("*=>[KNN 3 @v vec]", 13, "vec"),
26379 ("@v:[VECTOR_RANGE 2 -1]", 19, "-1"),
26380 ] {
26381 assert_eq!(
26382 ask(&mut f, query),
26383 format!("-SEARCH_SYNTAX Syntax error at offset {at} near {near}\r\n"),
26384 "{query}"
26385 );
26386 }
26387
26388 let large = "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26391 query KNN K parameter is too large, must not exceed 288230376151711744\r\n";
26392 assert_eq!(
26393 ask(&mut f, "*=>[KNN 288230376151711776 @v $vec]"),
26394 "*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"
26395 );
26396 assert_eq!(ask(&mut f, "*=>[KNN 288230376151711777 @v $vec]"), large);
26397 assert_eq!(ask(&mut f, "*=>[KNN 99999999999999999999 @v $vec]"), large);
26398
26399 for (radius, printed) in [("-1", "-1"), ("-0.5", "-0.5"), ("-1e2", "-100")] {
26400 let query = format!("@v:[VECTOR_RANGE {radius} $vec]");
26401 assert_eq!(
26402 ask(&mut f, &query),
26403 format!(
26404 "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26405 negative radius ({printed}) given in a range query\r\n"
26406 ),
26407 "{query}"
26408 );
26409 }
26410 assert_eq!(
26412 ask(&mut f, "@v:[VECTOR_RANGE -0 $vec]"),
26413 "*2\r\n:1\r\n$2\r\nd5\r\n"
26414 );
26415 }
26416
26417 #[test]
26420 fn a_count_that_came_from_params_is_read_by_its_own_rules() {
26421 let mut f = Fixture::new();
26422 vectored(&mut f);
26423 let ask = |f: &mut Fixture, count: &[u8]| {
26424 f.run(&[
26425 b"FT.SEARCH",
26426 b"h",
26427 b"*=>[KNN $k @v $vec]",
26428 b"PARAMS",
26429 b"4",
26430 b"vec",
26431 ORIGIN,
26432 b"k",
26433 count,
26434 b"DIALECT",
26435 b"2",
26436 b"NOCONTENT",
26437 ])
26438 };
26439 let three = "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n";
26440 assert_eq!(ask(&mut f, b"3"), three);
26441 assert_eq!(ask(&mut f, b" 3"), three);
26442 assert_eq!(ask(&mut f, b"+3"), three);
26443 for bad in [
26444 &b"3.0"[..],
26445 b"0x3",
26446 b"-1",
26447 b"abc",
26448 b"",
26449 b"99999999999999999999",
26450 ] {
26451 let value = String::from_utf8_lossy(bad).into_owned();
26452 assert_eq!(
26453 ask(&mut f, bad),
26454 format!(
26455 "-SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value ({value}) \
26456 for parameter `k`\r\n"
26457 ),
26458 "{value}"
26459 );
26460 }
26461 assert_eq!(
26462 ask(&mut f, b"288230376151711777"),
26463 "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26464 query KNN K parameter is too large, must not exceed 288230376151711744\r\n"
26465 );
26466 }
26467
26468 #[test]
26471 fn a_vector_the_wrong_size_is_refused_by_the_field_it_reached() {
26472 let mut f = Fixture::new();
26473 vectored(&mut f);
26474 assert_eq!(
26475 f.run(&[
26476 b"FT.SEARCH",
26477 b"h",
26478 b"*=>[KNN 5 @v $vec]",
26479 b"PARAMS",
26480 b"2",
26481 b"vec",
26482 b"abc",
26483 b"DIALECT",
26484 b"2",
26485 b"NOCONTENT",
26486 ]),
26487 "-SEARCH_QUERY_BAD Error parsing vector similarity query: \
26488 query vector blob size (3) does not match index's expected size (8).\r\n"
26489 );
26490 }
26491
26492 #[test]
26497 fn a_vector_clause_yields_its_distance_under_the_name_it_was_given() {
26498 let mut f = Fixture::new();
26499 vectored(&mut f);
26500 let ask = |f: &mut Fixture, query: &str| {
26501 f.run(&[
26502 b"FT.SEARCH",
26503 b"h",
26504 query.as_bytes(),
26505 b"PARAMS",
26506 b"2",
26507 b"vec",
26508 ORIGIN,
26509 b"DIALECT",
26510 b"2",
26511 b"LIMIT",
26512 b"0",
26513 b"1",
26514 ])
26515 };
26516 assert_eq!(
26517 ask(&mut f, "*=>[KNN 3 @v $vec]"),
26518 "*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"
26519 );
26520 assert_eq!(
26521 ask(&mut f, "*=>[KNN 3 @v $vec AS d]"),
26522 "*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"
26523 );
26524 assert_eq!(
26525 ask(&mut f, "@v:[VECTOR_RANGE 4 $vec]"),
26526 "*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"
26527 );
26528 assert_eq!(
26529 ask(&mut f, "@v:[VECTOR_RANGE 4 $vec]=>{$YIELD_DISTANCE_AS: d}"),
26530 "*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"
26531 );
26532 }
26533
26534 #[test]
26541 fn a_return_answers_the_distance_by_the_name_the_row_carries_it_under() {
26542 let mut f = Fixture::new();
26543 vectored(&mut f);
26544 let ask = |f: &mut Fixture, ret: &[&[u8]]| {
26545 let mut args: Vec<&[u8]> = vec![b"FT.SEARCH", b"h", b"*=>[KNN 1 @v $vec]"];
26546 args.extend_from_slice(ret);
26547 args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
26548 f.run(&args)
26549 };
26550 assert_eq!(
26551 ask(&mut f, &[b"RETURN", b"1", b"__v_score"]),
26552 "*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"
26553 );
26554 assert_eq!(
26555 ask(&mut f, &[b"RETURN", b"3", b"__v_score", b"AS", b"x"]),
26556 "*3\r\n:1\r\n$2\r\nd5\r\n*0\r\n"
26557 );
26558 assert_eq!(
26559 ask(&mut f, &[b"RETURN", b"3", b"t", b"AS", b"__v_score"]),
26560 "*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"
26561 );
26562 assert_eq!(
26563 ask(&mut f, &[b"RETURN", b"1", b"t"]),
26564 "*3\r\n:1\r\n$2\r\nd5\r\n*2\r\n$1\r\nt\r\n$4\r\nbeta\r\n"
26565 );
26566 assert_eq!(
26569 ask(&mut f, &[b"RETURN", b"2", b"t", b"__v_score"]),
26570 "*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"
26571 );
26572 assert_eq!(ask(&mut f, &[b"NOCONTENT"]), "*2\r\n:1\r\n$2\r\nd5\r\n");
26573 }
26574
26575 #[test]
26579 fn a_sortby_can_name_a_distance_the_query_yielded() {
26580 let mut f = Fixture::new();
26581 vectored(&mut f);
26582 let ask = |f: &mut Fixture, query: &str, by: &[u8], desc: bool| {
26583 let mut args: Vec<&[u8]> = vec![b"FT.SEARCH", b"h", query.as_bytes(), b"SORTBY", by];
26584 if desc {
26585 args.push(b"DESC");
26586 }
26587 args.extend_from_slice(&[
26588 b"PARAMS",
26589 b"2",
26590 b"vec",
26591 ORIGIN,
26592 b"DIALECT",
26593 b"2",
26594 b"NOCONTENT",
26595 ]);
26596 f.run(&args)
26597 };
26598 assert_eq!(
26599 ask(&mut f, "*=>[KNN 3 @v $vec]", b"__v_score", false),
26600 "*4\r\n:3\r\n$2\r\nd5\r\n$2\r\nd4\r\n$2\r\nd3\r\n"
26601 );
26602 assert_eq!(
26603 ask(&mut f, "*=>[KNN 3 @v $vec]", b"__v_score", true),
26604 "*4\r\n:3\r\n$2\r\nd3\r\n$2\r\nd4\r\n$2\r\nd5\r\n"
26605 );
26606 assert_eq!(
26607 ask(&mut f, "*=>[KNN 3 @v $vec AS d]", b"d", false),
26608 "*4\r\n:3\r\n$2\r\nd5\r\n$2\r\nd4\r\n$2\r\nd3\r\n"
26609 );
26610 let missing = "-SEARCH_PROP_NOT_FOUND Property `__v_score` \
26613 not loaded nor in schema\r\n";
26614 assert_eq!(
26615 ask(&mut f, "*=>[KNN 3 @v $vec AS d]", b"__v_score", false),
26616 missing
26617 );
26618 assert_eq!(ask(&mut f, "alpha", b"__v_score", false), missing);
26619 assert_eq!(
26622 ask(&mut f, "foo(", b"zz", false),
26623 "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
26624 );
26625 }
26626
26627 #[test]
26629 fn two_vector_clauses_answer_two_distances() {
26630 let mut f = Fixture::new();
26631 vectored(&mut f);
26632 assert_eq!(
26633 f.run(&[
26634 b"FT.SEARCH",
26635 b"h",
26636 b"@v:[VECTOR_RANGE 9 $vec]=>{$YIELD_DISTANCE_AS: rr}=>[KNN 2 @v $vec]",
26637 b"RETURN",
26638 b"2",
26639 b"rr",
26640 b"__v_score",
26641 b"PARAMS",
26642 b"2",
26643 b"vec",
26644 ORIGIN,
26645 b"DIALECT",
26646 b"2",
26647 ]),
26648 "*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"
26649 );
26650 }
26651
26652 #[test]
26656 fn an_aggregation_answers_a_distance_nothing_asked_for() {
26657 let mut f = Fixture::new();
26658 vectored(&mut f);
26659 let ask = |f: &mut Fixture, query: &str, rest: &[&[u8]]| {
26660 let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h", query.as_bytes()];
26661 args.extend_from_slice(rest);
26662 args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
26663 f.run(&args)
26664 };
26665 assert_eq!(
26666 ask(&mut f, "*=>[KNN 2 @v $vec]", &[]),
26667 "*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"
26668 );
26669 assert_eq!(
26670 ask(&mut f, "*=>[KNN 2 @v $vec]", &[b"LOAD", b"1", b"@t"]),
26671 "*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"
26672 );
26673 assert_eq!(
26674 ask(&mut f, "*=>[KNN 2 @v $vec AS d]", &[]),
26675 "*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"
26676 );
26677 assert_eq!(
26679 ask(&mut f, "@v:[VECTOR_RANGE 1 $vec]", &[]),
26680 "*3\r\n:1\r\n*0\r\n*0\r\n"
26681 );
26682 assert_eq!(
26683 ask(
26684 &mut f,
26685 "@v:[VECTOR_RANGE 1 $vec]=>{$YIELD_DISTANCE_AS: rr}",
26686 &[]
26687 ),
26688 "*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"
26689 );
26690 }
26691
26692 #[test]
26696 fn an_aggregation_keeps_the_order_a_nearest_neighbour_clause_made() {
26697 let mut f = Fixture::new();
26698 vectored(&mut f);
26699 const MIDDLE: &[u8] = b"\x00\x00\x00\x40\x00\x00\x00\x00";
26701 let ask = |f: &mut Fixture, query: &str, vec: &[u8]| {
26702 f.run(&[
26703 b"FT.AGGREGATE",
26704 b"h",
26705 query.as_bytes(),
26706 b"LOAD",
26707 b"1",
26708 b"@t",
26709 b"PARAMS",
26710 b"2",
26711 b"vec",
26712 vec,
26713 b"DIALECT",
26714 b"2",
26715 ])
26716 };
26717 assert_eq!(
26718 ask(&mut f, "*=>[KNN 3 @v $vec]", MIDDLE),
26719 "*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"
26720 );
26721 assert_eq!(
26723 ask(
26724 &mut f,
26725 "@v:[VECTOR_RANGE 1 $vec]=>{$YIELD_DISTANCE_AS: rr}",
26726 MIDDLE
26727 ),
26728 "*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"
26729 );
26730 }
26731
26732 #[test]
26736 fn a_pipeline_step_can_name_a_distance_the_query_yielded() {
26737 let mut f = Fixture::new();
26738 vectored(&mut f);
26739 let ask = |f: &mut Fixture, query: &str, rest: &[&[u8]]| {
26740 let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h", query.as_bytes()];
26741 args.extend_from_slice(rest);
26742 args.extend_from_slice(&[b"PARAMS", b"2", b"vec", ORIGIN, b"DIALECT", b"2"]);
26743 f.run(&args)
26744 };
26745 let knn = "*=>[KNN 2 @v $vec]";
26746 assert_eq!(
26747 ask(&mut f, knn, &[b"APPLY", b"@__v_score * 2", b"AS", b"x"]),
26748 "*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"
26749 );
26750 assert_eq!(
26751 ask(&mut f, knn, &[b"FILTER", b"@__v_score > 0"]),
26752 "*2\r\n:1\r\n*2\r\n$9\r\n__v_score\r\n$1\r\n1\r\n"
26753 );
26754 assert_eq!(
26755 ask(&mut f, knn, &[b"SORTBY", b"2", b"@__v_score", b"DESC"]),
26756 "*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"
26757 );
26758 assert_eq!(
26759 ask(
26760 &mut f,
26761 knn,
26762 &[
26763 b"GROUPBY",
26764 b"1",
26765 b"@t",
26766 b"REDUCE",
26767 b"MAX",
26768 b"1",
26769 b"@__v_score",
26770 b"AS",
26771 b"m"
26772 ]
26773 ),
26774 "*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"
26775 );
26776 assert_eq!(
26777 ask(&mut f, "*", &[b"APPLY", b"@__v_score", b"AS", b"x"]),
26778 "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: \
26779 `__v_score`\r\n"
26780 );
26781 assert_eq!(
26782 ask(&mut f, "*", &[b"GROUPBY", b"1", b"@__v_score"]),
26783 "-SEARCH_PROP_NOT_FOUND No such property `__v_score`\r\n"
26784 );
26785 assert_eq!(
26786 ask(&mut f, "*", &[b"SORTBY", b"2", b"@__v_score", b"ASC"]),
26787 "-SEARCH_PROP_NOT_FOUND Property `__v_score` not loaded nor in \
26788 schema\r\n"
26789 );
26790 }
26791
26792 #[test]
26800 fn the_words_come_before_the_query_and_the_query_before_the_pipeline() {
26801 let mut f = Fixture::new();
26802 vectored(&mut f);
26803 let ask = |f: &mut Fixture, rest: &[&[u8]]| {
26804 let mut args: Vec<&[u8]> = vec![b"FT.AGGREGATE", b"h"];
26805 args.extend_from_slice(rest);
26806 f.run(&args)
26807 };
26808 assert_eq!(
26809 ask(
26810 &mut f,
26811 &[b"foo(", b"APPLY", b"@zz", b"AS", b"x", b"LIMIT", b"x", b"1"]
26812 ),
26813 "-SEARCH_PARSE_ARGS LIMIT needs two numeric arguments\r\n"
26814 );
26815 assert_eq!(
26816 ask(&mut f, &[b"foo(", b"APPLY", b"@zz", b"AS", b"x"]),
26817 "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
26818 );
26819 assert_eq!(
26820 ask(&mut f, &[b"*", b"APPLY", b"@zz", b"AS", b"x"]),
26821 "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: `zz`\r\n"
26822 );
26823 assert_eq!(
26826 ask(&mut f, &[b"foo(", b"APPLY", b"@@@", b"AS", b"x"]),
26827 "-SEARCH_SYNTAX Syntax error at offset 3 near foo\r\n"
26828 );
26829 assert_eq!(
26830 ask(
26831 &mut f,
26832 &[
26833 b"*", b"APPLY", b"@zz", b"AS", b"x", b"APPLY", b"@@@", b"AS", b"y"
26834 ]
26835 ),
26836 "-SEARCH_PROP_NOT_FOUND Property not loaded nor in pipeline: `zz`\r\n"
26837 );
26838 assert_eq!(
26839 ask(&mut f, &[b"*", b"APPLY", b"@@@", b"AS", b"x"]),
26840 "-SEARCH_EXPR Syntax error at offset 0 near ''\r\n"
26841 );
26842 }
26843
26844 #[test]
26847 fn a_vector_step_says_which_way_it_was_answered() {
26848 let mut f = Fixture::new();
26849 vectored(&mut f);
26850 let tree = |f: &mut Fixture, query: &[u8]| {
26851 let reply = timeless(&f.run(&[
26852 b"FT.PROFILE",
26853 b"h",
26854 b"AGGREGATE",
26855 b"QUERY",
26856 query,
26857 b"PARAMS",
26858 b"2",
26859 b"vec",
26860 ORIGIN,
26861 b"DIALECT",
26862 b"2",
26863 ]));
26864 let at = reply.find("+Iterators profile").expect("a tree");
26865 let end = reply.find("+Result processors").expect("a list of steps");
26866 reply[at..end].to_string()
26867 };
26868 assert_eq!(
26869 tree(&mut f, b"*=>[KNN 3 @v $vec]"),
26870 "+Iterators profile\r\n*8\r\n+Type\r\n+VECTOR\r\n+Time\r\n<t>\r\n\
26871 +Number of reading operations\r\n:3\r\n\
26872 +Vector search mode\r\n+STANDARD_KNN\r\n"
26873 );
26874 assert_eq!(
26876 tree(&mut f, b"*=>[KNN 3 @v $vec AS d]"),
26877 tree(&mut f, b"*=>[KNN 3 @v $vec]")
26878 );
26879 assert_eq!(
26882 tree(&mut f, b"@v:[VECTOR_RANGE 9 $vec]"),
26883 "+Iterators profile\r\n*6\r\n+Type\r\n+ID-LIST-SORTED\r\n+Time\r\n<t>\r\n\
26884 +Number of reading operations\r\n:4\r\n"
26885 );
26886 assert_eq!(
26887 tree(
26888 &mut f,
26889 b"@v:[VECTOR_RANGE 9 $vec]=>{$YIELD_DISTANCE_AS: rr}"
26890 ),
26891 "+Iterators profile\r\n*8\r\n\
26892 +Type\r\n+METRIC SORTED BY ID - VECTOR DISTANCE\r\n+Time\r\n<t>\r\n\
26893 +Number of reading operations\r\n:4\r\n\
26894 +Vector search mode\r\n+RANGE_QUERY\r\n"
26895 );
26896 }
26897
26898 #[test]
26902 fn a_clause_in_front_of_a_vector_hangs_under_it_as_one_child() {
26903 let mut f = Fixture::new();
26904 vectored(&mut f);
26905 let ask = |f: &mut Fixture, query: &[u8]| {
26906 timeless(&f.run(&[
26907 b"FT.PROFILE",
26908 b"h",
26909 b"AGGREGATE",
26910 b"QUERY",
26911 query,
26912 b"PARAMS",
26913 b"2",
26914 b"vec",
26915 ORIGIN,
26916 b"DIALECT",
26917 b"2",
26918 ]))
26919 };
26920 let cut = |reply: &str| {
26921 let at = reply.find("+Iterators profile").expect("a tree");
26922 reply[at..].to_string()
26923 };
26924 assert_eq!(
26925 cut(&ask(&mut f, b"@t:alpha=>[KNN 3 @v $vec]")),
26926 "+Iterators profile\r\n*10\r\n+Type\r\n+VECTOR\r\n+Time\r\n<t>\r\n\
26927 +Number of reading operations\r\n:3\r\n\
26928 +Vector search mode\r\n+HYBRID_ADHOC_BF\r\n+Child iterator\r\n\
26929 *10\r\n+Type\r\n+TEXT\r\n+Term\r\n$5\r\nalpha\r\n+Time\r\n<t>\r\n\
26930 +Number of reading operations\r\n:3\r\n\
26931 +Estimated number of matches\r\n:3\r\n\
26932 +Result processors profile\r\n*2\r\n\
26933 *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:3\r\n\
26934 *6\r\n+Type\r\n+Metrics Applier\r\n+Time\r\n<t>\r\n\
26935 +Results processed\r\n:3\r\n+Coordinator\r\n*0\r\n"
26936 );
26937 assert!(ask(&mut f, b"@v:[VECTOR_RANGE 9 $vec]").ends_with(
26940 "+Result processors profile\r\n*1\r\n*6\r\n+Type\r\n+Index\r\n\
26941 +Time\r\n<t>\r\n+Results processed\r\n:4\r\n+Coordinator\r\n*0\r\n"
26942 ));
26943 assert!(ask(&mut f, b"*=>[KNN 3 @v $vec]").contains("+Type\r\n+Metrics Applier\r\n"));
26946 }
26947
26948 #[test]
26952 fn a_window_of_nothing_on_an_aggregation_counts_rather_than_pages() {
26953 let mut f = profiling();
26954 let steps = |f: &mut Fixture, words: &[&[u8]]| {
26955 let mut argv: Vec<&[u8]> = vec![b"FT.PROFILE", b"ix", b"AGGREGATE", b"QUERY", b"*"];
26956 argv.extend_from_slice(words);
26957 let reply = timeless(&f.run(&argv));
26958 let at = reply.find("+Result processors").expect("a list of steps");
26959 reply[at..].to_string()
26960 };
26961 assert_eq!(
26962 steps(&mut f, &[b"LIMIT", b"0", b"0"]),
26963 "+Result processors profile\r\n*2\r\n\
26964 *6\r\n+Type\r\n+Index\r\n+Time\r\n<t>\r\n+Results processed\r\n:3\r\n\
26965 *6\r\n+Type\r\n+Counter\r\n+Time\r\n<t>\r\n+Results processed\r\n:1\r\n\
26966 +Coordinator\r\n*0\r\n"
26967 );
26968 assert!(
26969 steps(
26970 &mut f,
26971 &[b"SORTBY", b"2", b"@n", b"ASC", b"LIMIT", b"0", b"0"]
26972 )
26973 .contains("+Type\r\n+Counter\r\n")
26974 );
26975 assert!(steps(&mut f, &[b"LIMIT", b"0", b"2"]).contains(
26977 "+Type\r\n+Pager/Limiter\r\n+Time\r\n<t>\r\n\
26978 +Results processed\r\n:2\r\n"
26979 ));
26980 }
26981
26982 #[test]
26988 fn a_spellcheck_scores_a_suggestion_by_how_common_it_is() {
26989 let mut f = Fixture::new();
26990 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
26991 f.run(&[b"HSET", b"d1", b"t", b"hello"]);
26992 f.run(&[b"HSET", b"d2", b"t", b"hallo hello"]);
26993 assert_eq!(
26994 f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"2"]),
26995 "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
26996 *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"
26997 );
26998 }
26999
27000 #[test]
27004 fn a_spellcheck_answers_a_map_of_maps_on_resp3() {
27005 let mut f = Fixture::new();
27006 f.run(&[b"HELLO", b"3"]);
27007 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
27008 f.run(&[b"HSET", b"d1", b"t", b"hello"]);
27009 assert_eq!(
27010 f.run(&[b"FT.SPELLCHECK", b"e", b"hellp"]),
27011 "%1\r\n$7\r\nresults\r\n%1\r\n$5\r\nhellp\r\n\
27012 *1\r\n%1\r\n$5\r\nhello\r\n,1\r\n"
27013 );
27014 }
27015
27016 #[test]
27020 fn a_word_the_index_holds_is_never_asked_about_whatever_field_it_names() {
27021 let mut f = Fixture::new();
27022 f.run(&[
27023 b"FT.CREATE",
27024 b"e",
27025 b"SCHEMA",
27026 b"a",
27027 b"TEXT",
27028 b"NOSTEM",
27029 b"b",
27030 b"TEXT",
27031 b"NOSTEM",
27032 ]);
27033 f.run(&[b"HSET", b"d1", b"b", b"world"]);
27034 assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"@a:world"]), "*0\r\n");
27035 assert_eq!(
27036 f.run(&[b"FT.SPELLCHECK", b"e", b"@a:worlt"]),
27037 "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nworlt\r\n*0\r\n"
27038 );
27039 }
27040
27041 #[test]
27045 fn a_spellcheck_reads_the_dictionaries_it_is_pointed_at() {
27046 let mut f = Fixture::new();
27047 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
27048 f.run(&[b"FT.DICTADD", b"d", b"Hellp", b"hellq"]);
27049 assert_eq!(
27050 f.run(&[b"FT.SPELLCHECK", b"e", b"hellz", b"TERMS", b"INCLUDE", b"d"]),
27051 "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellz\r\n\
27052 *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"
27053 );
27054 assert_eq!(
27055 f.run(&[b"FT.SPELLCHECK", b"e", b"hellq", b"TERMS", b"EXCLUDE", b"d"]),
27056 "*0\r\n"
27057 );
27058 assert_eq!(
27059 f.run(&[b"FT.SPELLCHECK", b"e", b"x", b"TERMS", b"INCLUDE", b"nope"]),
27060 "-Dict does not exist: nope\r\n"
27061 );
27062 }
27063
27064 #[test]
27068 fn a_spellcheck_reads_its_arguments_leniently() {
27069 let mut f = Fixture::new();
27070 f.run(&[b"FT.CREATE", b"e", b"SCHEMA", b"t", b"TEXT", b"NOSTEM"]);
27071 f.run(&[b"HSET", b"d1", b"t", b"hello"]);
27072 let one = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhellp\r\n\
27073 *1\r\n*2\r\n$1\r\n1\r\n$5\r\nhello\r\n";
27074 assert_eq!(f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"BOGUS"]), one);
27075 let none = "*1\r\n*3\r\n$4\r\nTERM\r\n$5\r\nhelqp\r\n*0\r\n";
27076 let args: &[&[u8]] = &[
27077 b"FT.SPELLCHECK",
27078 b"e",
27079 b"helqp",
27080 b"DISTANCE",
27081 b"1",
27082 b"DISTANCE",
27083 b"4",
27084 ];
27085 assert_eq!(f.run(args), none);
27086 assert_eq!(
27087 f.run(&[b"FT.SPELLCHECK", b"e", b"hellp", b"DISTANCE", b"5"]),
27088 "-bad distance given, distance must be a natural number between 1 to 4\r\n"
27089 );
27090 assert_eq!(
27091 f.run(&[b"FT.SPELLCHECK", b"nope", b"hellp"]),
27092 "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n"
27093 );
27094 }
27095
27096 #[test]
27101 fn an_add_answers_how_many_suggestions_are_in_there_now() {
27102 let mut f = Fixture::new();
27103 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]), ":1\r\n");
27104 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"one", b"9"]), ":1\r\n");
27105 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]), ":2\r\n");
27106 assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":2\r\n");
27107 assert_eq!(f.run(&[b"FT.SUGLEN", b"nokey"]), ":0\r\n");
27108 }
27109
27110 #[test]
27113 fn a_suggestion_dictionary_is_a_key_with_a_type_of_its_own() {
27114 let mut f = Fixture::new();
27115 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27116 assert_eq!(f.run(&[b"TYPE", b"s"]), "+trietype0\r\n");
27117 assert_eq!(f.run(&[b"OBJECT", b"ENCODING", b"s"]), "$3\r\nraw\r\n");
27118 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":1\r\n");
27119 assert_eq!(f.run(&[b"KEYS", b"*"]), "*1\r\n$1\r\ns\r\n");
27120 assert_eq!(f.run(&[b"EXPIRE", b"s", b"100"]), ":1\r\n");
27121 assert_eq!(f.run(&[b"TTL", b"s"]), ":100\r\n");
27122 assert_eq!(f.run(&[b"DEL", b"s"]), ":1\r\n");
27123 assert_eq!(f.run(&[b"FT.SUGLEN", b"s"]), ":0\r\n");
27124 }
27125
27126 #[test]
27129 fn deleting_the_last_suggestion_deletes_the_key() {
27130 let mut f = Fixture::new();
27131 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27132 assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"nope"]), ":0\r\n");
27133 assert_eq!(f.run(&[b"FT.SUGDEL", b"s", b"one"]), ":1\r\n");
27134 assert_eq!(f.run(&[b"EXISTS", b"s"]), ":0\r\n");
27135 assert_eq!(f.run(&[b"FT.SUGDEL", b"nokey", b"a"]), ":0\r\n");
27136 }
27137
27138 #[test]
27141 fn a_suggestion_command_on_another_kind_of_key_is_wrongtype() {
27142 let mut f = Fixture::new();
27143 f.run(&[b"SET", b"s", b"x"]);
27144 for cmd in [
27145 vec![&b"FT.SUGADD"[..], b"s", b"t", b"1"],
27146 vec![&b"FT.SUGGET"[..], b"s", b"t"],
27147 vec![&b"FT.SUGDEL"[..], b"s", b"t"],
27148 vec![&b"FT.SUGLEN"[..], b"s"],
27149 ] {
27150 assert!(f.run(&cmd).starts_with("-WRONGTYPE"), "{cmd:?}");
27151 }
27152 assert_eq!(f.run(&[b"GET", b"s"]), "$1\r\nx\r\n");
27153 }
27154
27155 #[test]
27158 fn a_lookup_answers_a_score_it_works_out_rather_than_the_one_stored() {
27159 let mut f = Fixture::new();
27160 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27161 f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
27162 f.run(&[b"FT.SUGADD", b"s", b"ontario", b"3"]);
27163 assert_eq!(
27164 f.run(&[b"FT.SUGGET", b"s", b"on", b"WITHSCORES"]),
27165 "*6\r\n$7\r\nontario\r\n$18\r\n1.2247449159622192\r\n\
27166 $4\r\nonly\r\n$17\r\n1.154700517654419\r\n\
27167 $3\r\none\r\n$18\r\n0.7071067690849304\r\n"
27168 );
27169 assert_eq!(
27170 f.run(&[b"FT.SUGGET", b"s", b"one", b"WITHSCORES"]),
27171 "*2\r\n$3\r\none\r\n$10\r\n2147483648\r\n"
27172 );
27173 assert_eq!(f.run(&[b"FT.SUGGET", b"nokey", b"a"]), "*0\r\n");
27174 }
27175
27176 #[test]
27178 fn fuzzy_allows_one_edit_and_nothing_allows_two() {
27179 let mut f = Fixture::new();
27180 f.run(&[b"FT.SUGADD", b"s", b"only", b"2"]);
27181 assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"one"]), "*0\r\n");
27182 assert_eq!(
27183 f.run(&[b"FT.SUGGET", b"s", b"one", b"FUZZY", b"WITHSCORES"]),
27184 "*2\r\n$4\r\nonly\r\n$19\r\n0.19139298796653748\r\n"
27185 );
27186 assert_eq!(f.run(&[b"FT.SUGGET", b"s", b"xyz", b"FUZZY"]), "*0\r\n");
27187 }
27188
27189 #[test]
27191 fn a_lookup_answers_five_unless_it_is_told_otherwise() {
27192 let mut f = Fixture::new();
27193 for (term, score) in [
27194 (&b"a1"[..], &b"1"[..]),
27195 (b"a2", b"2"),
27196 (b"a3", b"3"),
27197 (b"a4", b"4"),
27198 (b"a5", b"5"),
27199 (b"a6", b"6"),
27200 ] {
27201 f.run(&[b"FT.SUGADD", b"s", term, score]);
27202 }
27203 assert_eq!(
27204 f.run(&[b"FT.SUGGET", b"s", b"a"]),
27205 "*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"
27206 );
27207 assert_eq!(
27208 f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"2"]),
27209 "*2\r\n$2\r\na6\r\n$2\r\na5\r\n"
27210 );
27211 assert!(
27213 f.run(&[b"FT.SUGGET", b"s", b"a", b"MAX", b"100"])
27214 .starts_with("*6\r\n")
27215 );
27216 }
27217
27218 #[test]
27221 fn a_payload_comes_back_beside_the_term_or_a_null_does() {
27222 let mut f = Fixture::new();
27223 f.run(&[b"FT.SUGADD", b"s", b"one", b"1", b"PAYLOAD", b"p"]);
27224 assert_eq!(
27225 f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
27226 "*2\r\n$3\r\none\r\n$1\r\np\r\n"
27227 );
27228 f.run(&[b"FT.SUGADD", b"s", b"one", b"2"]);
27229 assert_eq!(
27230 f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
27231 "*2\r\n$3\r\none\r\n$1\r\np\r\n"
27232 );
27233 f.run(&[b"FT.SUGADD", b"s", b"one", b"2", b"PAYLOAD", b""]);
27236 assert_eq!(
27237 f.run(&[b"FT.SUGGET", b"s", b"o", b"WITHPAYLOADS"]),
27238 "*2\r\n$3\r\none\r\n$1\r\np\r\n"
27239 );
27240 f.run(&[b"FT.SUGADD", b"s", b"other", b"1", b"PAYLOAD", b""]);
27242 assert_eq!(
27243 f.run(&[b"FT.SUGGET", b"s", b"ot", b"WITHPAYLOADS"]),
27244 "*2\r\n$5\r\nother\r\n$-1\r\n"
27245 );
27246 }
27247
27248 #[test]
27252 fn incr_adds_to_the_score_that_is_already_there() {
27253 let mut f = Fixture::new();
27254 for _ in 0..3 {
27255 f.run(&[b"FT.SUGADD", b"s", b"xxx", b"0.1", b"INCR"]);
27256 }
27257 assert_eq!(
27258 f.run(&[b"FT.SUGGET", b"s", b"xx", b"WITHSCORES"]),
27259 "*2\r\n$3\r\nxxx\r\n$18\r\n0.2121320366859436\r\n"
27260 );
27261 }
27262
27263 #[test]
27265 fn the_suggestion_errors_are_the_lines_the_module_sends() {
27266 let mut f = Fixture::new();
27267 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27268 assert_eq!(
27269 f.run(&[b"FT.SUGADD", b"s", b"t", b"abc"]),
27270 "-ERR invalid score\r\n"
27271 );
27272 assert_eq!(
27274 f.run(&[b"FT.SUGADD", b"s", b"t", b"abc", b"NOPE"]),
27275 "-Unknown argument `NOPE`\r\n"
27276 );
27277 assert_eq!(
27278 f.run(&[b"FT.SUGADD", b"s", b"t", b"1", b"PAYLOAD"]),
27279 "-Invalid payload: Expected an argument, but none provided\r\n"
27280 );
27281 assert!(
27283 f.run(&[
27284 b"FT.SUGADD",
27285 b"s",
27286 b"t",
27287 b"1",
27288 b"PAYLOAD",
27289 b"a",
27290 b"PAYLOAD",
27291 b"b"
27292 ])
27293 .contains("wrong number of arguments")
27294 );
27295 assert_eq!(
27296 f.run(&[b"FT.SUGGET", b"s", b"o", b"NOPE"]),
27297 "-SEARCH_PARSE_ARGS Unrecognized argument: NOPE\r\n"
27298 );
27299 for max in [&b"0"[..], b"-1", b"4294967296", b"1e10", b"inf"] {
27303 assert_eq!(
27304 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
27305 "-SEARCH_PARSE_ARGS MAX: Value is outside acceptable bounds\r\n",
27306 "{}",
27307 String::from_utf8_lossy(max)
27308 );
27309 }
27310 for max in [
27311 &b"abc"[..],
27312 b"0.0",
27313 b"00",
27314 b"-0",
27315 b"+0",
27316 b"0.5",
27317 b"-1.5",
27318 b"1e400",
27319 ] {
27320 assert_eq!(
27321 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
27322 "-SEARCH_PARSE_ARGS MAX: Could not convert argument to expected type\r\n",
27323 "{}",
27324 String::from_utf8_lossy(max)
27325 );
27326 }
27327 for max in [&b"01"[..], b"+1", b"1.5", b"0x10", b"1e2"] {
27328 assert_eq!(
27329 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX", max]),
27330 "*1\r\n$3\r\none\r\n",
27331 "{}",
27332 String::from_utf8_lossy(max)
27333 );
27334 }
27335 assert_eq!(
27336 f.run(&[b"FT.SUGGET", b"s", b"o", b"MAX"]),
27337 "-SEARCH_PARSE_ARGS MAX: Expected an argument, but none provided\r\n"
27338 );
27339 assert_eq!(
27342 f.run(&[b"FT.SUGADD", b"s", b"t", b"1e400"]),
27343 "-ERR invalid score\r\n"
27344 );
27345 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"t", b"inf"]), ":2\r\n");
27346 }
27347
27348 #[test]
27352 fn an_empty_suggestion_is_taken_and_dropped_but_still_makes_the_key() {
27353 let mut f = Fixture::new();
27354 f.run(&[b"FT.SUGADD", b"s", b"one", b"1"]);
27355 assert_eq!(f.run(&[b"FT.SUGADD", b"s", b"", b"1"]), ":1\r\n");
27356 assert_eq!(f.run(&[b"FT.SUGGET", b"s", b""]), "*1\r\n$3\r\none\r\n");
27357 assert_eq!(f.run(&[b"FT.SUGADD", b"e", b"", b"1"]), ":0\r\n");
27358 assert_eq!(f.run(&[b"EXISTS", b"e"]), ":1\r\n");
27359 assert_eq!(f.run(&[b"TYPE", b"e"]), "+trietype0\r\n");
27360 assert_eq!(f.run(&[b"FT.SUGDEL", b"e", b"nothing"]), ":0\r\n");
27361 assert_eq!(f.run(&[b"EXISTS", b"e"]), ":0\r\n");
27362 }
27363
27364 #[test]
27367 fn a_hash_that_will_not_read_is_counted_where_ft_info_reports_it() {
27368 let mut f = Fixture::new();
27369 f.run(&[
27370 b"FT.CREATE",
27371 b"ix",
27372 b"PREFIX",
27373 b"1",
27374 b"p:",
27375 b"SCHEMA",
27376 b"n",
27377 b"NUMERIC",
27378 ]);
27379 f.run(&[b"HSET", b"p:1", b"n", b"notanumber"]);
27380 assert_eq!(held(&f, b"ix"), (0, 0));
27381
27382 let reply = f.run(&[b"FT.INFO", b"ix"]);
27383 assert!(
27384 reply.contains("SEARCH_NUMERIC_VALUE_INVALID Invalid numeric value: 'notanumber'"),
27385 "{reply}"
27386 );
27387 assert!(reply.contains("hash_indexing_failures"), "{reply}");
27388 }
27389
27390 #[test]
27393 fn an_index_can_only_be_made_on_database_zero() {
27394 let mut f = Fixture::new();
27395 f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]);
27396 f.run(&[b"SELECT", b"1"]);
27397 let refused = "-Cannot create index on db != 0\r\n";
27398 assert_eq!(
27399 f.run(&[b"FT.CREATE", b"jx", b"SCHEMA", b"t", b"TEXT"]),
27400 refused
27401 );
27402 assert_eq!(
27404 f.run(&[b"FT.CREATE", b"ix", b"SCHEMA", b"t", b"TEXT"]),
27405 refused
27406 );
27407 assert_eq!(
27409 f.run(&[b"FT.CREATE", b"zz", b"BOGUS", b"SCHEMA", b"t", b"TEXT"]),
27410 refused
27411 );
27412 assert_eq!(
27414 f.run(&[b"FT._CREATEIFNX", b"ix", b"SCHEMA", b"t", b"TEXT"]),
27415 "+OK\r\n"
27416 );
27417 assert_eq!(f.server.search.lock().len(), 1);
27418 }
27419
27420 #[test]
27426 fn the_scan_is_one_database_and_the_following_is_all_of_them() {
27427 let mut f = Fixture::new();
27428 f.run(&[b"SELECT", b"1"]);
27429 f.run(&[b"HSET", b"p:9", b"t", b"on one"]);
27430 f.run(&[b"SELECT", b"0"]);
27431 f.run(&[b"HSET", b"p:0", b"t", b"on zero"]);
27432 f.run(&[
27433 b"FT.CREATE",
27434 b"ix",
27435 b"PREFIX",
27436 b"1",
27437 b"p:",
27438 b"SCHEMA",
27439 b"t",
27440 b"TEXT",
27441 ]);
27442 assert_eq!(held(&f, b"ix"), (1, 1), "the scan read database zero only");
27443
27444 f.run(&[b"SELECT", b"1"]);
27445 f.run(&[b"HSET", b"p:8", b"t", b"later"]);
27446 assert_eq!(
27447 held(&f, b"ix"),
27448 (2, 2),
27449 "and then it follows every database"
27450 );
27451 }
27452
27453 fn corpus(f: &mut Fixture) {
27456 f.run(&[
27457 b"FT.CREATE",
27458 b"sx",
27459 b"PREFIX",
27460 b"1",
27461 b"d:",
27462 b"SCHEMA",
27463 b"t",
27464 b"TEXT",
27465 b"g",
27466 b"TAG",
27467 b"n",
27468 b"NUMERIC",
27469 ]);
27470 for (key, text, tag, number) in [
27471 (b"d:1".as_slice(), "alpha beta", "aa,bb", "1"),
27472 (b"d:2", "alpha gamma", "bb", "2"),
27473 (b"d:3", "delta", "cc", "3"),
27474 (b"d:4", "alpha beta gamma", "aa,cc", "4"),
27475 ] {
27476 f.run(&[
27477 b"HSET",
27478 key,
27479 b"t",
27480 text.as_bytes(),
27481 b"g",
27482 tag.as_bytes(),
27483 b"n",
27484 number.as_bytes(),
27485 ]);
27486 }
27487 }
27488
27489 fn sortable(f: &mut Fixture) {
27493 f.run(&[
27494 b"FT.CREATE",
27495 b"sy",
27496 b"PREFIX",
27497 b"1",
27498 b"s:",
27499 b"SCHEMA",
27500 b"t",
27501 b"TEXT",
27502 b"SORTABLE",
27503 b"n",
27504 b"NUMERIC",
27505 b"SORTABLE",
27506 b"body",
27507 b"AS",
27508 b"b",
27509 b"TEXT",
27510 b"SORTABLE",
27511 b"p",
27512 b"TEXT",
27513 ]);
27514 for (key, text, number) in [
27515 (b"s:1".as_slice(), "Banana Split", "2"),
27516 (b"s:2", "apple", "10"),
27517 ] {
27518 f.run(&[
27519 b"HSET",
27520 key,
27521 b"t",
27522 text.as_bytes(),
27523 b"n",
27524 number.as_bytes(),
27525 b"body",
27526 text.as_bytes(),
27527 b"p",
27528 b"alpha",
27529 ]);
27530 }
27531 f.run(&[b"HSET", b"s:3", b"p", b"alpha"]);
27534 }
27535
27536 #[test]
27539 fn a_search_sorts_by_a_field_the_index_keeps_a_copy_of() {
27540 let mut f = Fixture::new();
27541 sortable(&mut f);
27542 assert_eq!(
27543 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"NOCONTENT"]),
27544 "*4\r\n:3\r\n$3\r\ns:1\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
27545 );
27546 assert_eq!(
27547 f.run(&[
27548 b"FT.SEARCH",
27549 b"sy",
27550 b"alpha",
27551 b"SORTBY",
27552 b"n",
27553 b"DESC",
27554 b"NOCONTENT"
27555 ]),
27556 "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
27557 );
27558 assert_eq!(
27561 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"t", b"NOCONTENT"]),
27562 "*4\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:1\r\n$3\r\ns:3\r\n"
27563 );
27564 }
27565
27566 #[test]
27569 fn a_search_sorts_by_a_field_it_has_to_read_the_key_for() {
27570 let mut f = Fixture::new();
27571 sortable(&mut f);
27572 f.run(&[b"HSET", b"s:1", b"p", b"alpha zulu"]);
27573 assert_eq!(
27574 f.run(&[
27575 b"FT.SEARCH",
27576 b"sy",
27577 b"alpha",
27578 b"SORTBY",
27579 b"p",
27580 b"NOCONTENT",
27581 b"LIMIT",
27582 b"0",
27583 b"2"
27584 ]),
27585 "*3\r\n:3\r\n$3\r\ns:2\r\n$3\r\ns:3\r\n"
27586 );
27587 assert_eq!(
27590 f.run(&[
27591 b"FT.SEARCH",
27592 b"sy",
27593 b"alpha",
27594 b"SORTBY",
27595 b"p",
27596 b"WITHSORTKEYS",
27597 b"NOCONTENT",
27598 b"LIMIT",
27599 b"2",
27600 b"1"
27601 ]),
27602 "*3\r\n:3\r\n$3\r\ns:1\r\n$11\r\n$alpha zulu\r\n"
27603 );
27604 }
27605
27606 #[test]
27609 fn a_search_can_send_the_value_it_sorted_by_back() {
27610 let mut f = Fixture::new();
27611 sortable(&mut f);
27612 assert_eq!(
27613 f.run(&[
27614 b"FT.SEARCH",
27615 b"sy",
27616 b"alpha",
27617 b"SORTBY",
27618 b"n",
27619 b"WITHSORTKEYS",
27620 b"NOCONTENT"
27621 ]),
27622 concat!(
27623 "*7\r\n:3\r\n",
27624 "$3\r\ns:1\r\n$2\r\n#2\r\n",
27625 "$3\r\ns:2\r\n$3\r\n#10\r\n",
27626 "$3\r\ns:3\r\n$-1\r\n"
27627 )
27628 );
27629 assert_eq!(
27630 f.run(&[
27631 b"FT.SEARCH",
27632 b"sy",
27633 b"alpha",
27634 b"SORTBY",
27635 b"t",
27636 b"WITHSORTKEYS",
27637 b"NOCONTENT"
27638 ]),
27639 concat!(
27640 "*7\r\n:3\r\n",
27641 "$3\r\ns:2\r\n$6\r\n$apple\r\n",
27642 "$3\r\ns:1\r\n$13\r\n$banana split\r\n",
27643 "$3\r\ns:3\r\n$-1\r\n"
27644 )
27645 );
27646 assert_eq!(
27649 f.run(&[
27650 b"FT.SEARCH",
27651 b"sy",
27652 b"banana",
27653 b"WITHSORTKEYS",
27654 b"NOCONTENT"
27655 ]),
27656 "*3\r\n:1\r\n$3\r\ns:1\r\n$-1\r\n"
27657 );
27658 }
27659
27660 #[test]
27663 fn a_sort_puts_the_field_it_sorted_by_in_front_of_the_row() {
27664 let mut f = Fixture::new();
27665 sortable(&mut f);
27666 assert_eq!(
27670 f.run(&[
27671 b"FT.SEARCH",
27672 b"sy",
27673 b"alpha",
27674 b"SORTBY",
27675 b"b",
27676 b"LIMIT",
27677 b"0",
27678 b"1"
27679 ]),
27680 concat!(
27681 "*3\r\n:3\r\n$3\r\ns:2\r\n*10\r\n",
27682 "$1\r\nb\r\n$5\r\napple\r\n",
27683 "$1\r\nt\r\n$5\r\napple\r\n",
27684 "$1\r\nn\r\n$2\r\n10\r\n",
27685 "$4\r\nbody\r\n$5\r\napple\r\n",
27686 "$1\r\np\r\n$5\r\nalpha\r\n"
27687 )
27688 );
27689 assert_eq!(
27692 f.run(&[
27693 b"FT.SEARCH",
27694 b"sy",
27695 b"alpha",
27696 b"SORTBY",
27697 b"b",
27698 b"RETURN",
27699 b"2",
27700 b"p",
27701 b"b",
27702 b"LIMIT",
27703 b"0",
27704 b"1"
27705 ]),
27706 concat!(
27707 "*3\r\n:3\r\n$3\r\ns:2\r\n*4\r\n",
27708 "$1\r\nb\r\n$5\r\napple\r\n",
27709 "$1\r\np\r\n$5\r\nalpha\r\n"
27710 )
27711 );
27712 }
27713
27714 #[test]
27716 fn a_search_refuses_the_sorts_it_cannot_run() {
27717 let mut f = Fixture::new();
27718 sortable(&mut f);
27719 assert_eq!(
27720 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY"]),
27721 "-SEARCH_PARSE_ARGS Bad SORTBY arguments\r\n"
27722 );
27723 assert_eq!(
27724 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"SORTBY"]),
27725 "-SEARCH_PARSE_ARGS Multiple SORTBY steps are not allowed\r\n"
27726 );
27727 assert_eq!(
27728 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"n", b"MAX", b"2"]),
27729 "-SEARCH_PARSE_ARGS SORTBY MAX is not supported by FT.SEARCH\r\n"
27730 );
27731 assert_eq!(
27732 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz"]),
27733 "-SEARCH_PROP_NOT_FOUND Property `zz` not loaded nor in schema\r\n"
27734 );
27735 assert_eq!(
27738 f.run(&[b"FT.SEARCH", b"sy", b"alpha", b"SORTBY", b"zz", b"NOPE"]),
27739 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `NOPE` at position 3 for <main>\r\n"
27740 );
27741 }
27742
27743 fn marking(f: &mut Fixture) {
27747 f.run(&[
27748 b"FT.CREATE",
27749 b"mk",
27750 b"ON",
27751 b"HASH",
27752 b"PREFIX",
27753 b"1",
27754 b"m:",
27755 b"SCHEMA",
27756 b"a",
27757 b"TEXT",
27758 b"b",
27759 b"TEXT",
27760 b"n",
27761 b"NUMERIC",
27762 b"g",
27763 b"TAG",
27764 ]);
27765 f.run(&[
27766 b"HSET",
27767 b"m:1",
27768 b"a",
27769 b"c1 c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2 e3",
27770 b"b",
27771 b"t1 t2 t3 t4 t5 t6 t7 t8",
27772 b"n",
27773 b"1",
27774 b"g",
27775 b"red",
27776 ]);
27777 }
27778
27779 #[test]
27782 fn a_summarize_cuts_a_field_down_to_what_matched() {
27783 let mut f = Fixture::new();
27784 marking(&mut f);
27785 let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"SUMMARIZE", b"LEN", b"2"]);
27786 assert!(got.contains("c3 fox d1 d2... d9 fox e1 e2... "), "{got}");
27787 assert!(got.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{got}");
27789 assert!(got.contains("$1\r\nr\r\n"), "{got}");
27791 }
27792
27793 #[test]
27797 fn a_dropped_fragment_stops_bounding_the_one_that_was_kept() {
27798 let mut f = Fixture::new();
27799 marking(&mut f);
27800 let got = f.run(&[
27801 b"FT.SEARCH",
27802 b"mk",
27803 b"fox",
27804 b"SUMMARIZE",
27805 b"FRAGS",
27806 b"1",
27807 b"LEN",
27808 b"20",
27809 ]);
27810 assert!(
27811 got.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9 fox e1 e2... "),
27812 "{got}"
27813 );
27814 let two = f.run(&[
27817 b"FT.SEARCH",
27818 b"mk",
27819 b"fox",
27820 b"SUMMARIZE",
27821 b"FRAGS",
27822 b"2",
27823 b"LEN",
27824 b"20",
27825 ]);
27826 assert!(
27827 two.contains("c2 c3 fox d1 d2 d3 d4 d5 d6 d7 d8 d9... d1"),
27828 "{two}"
27829 );
27830 }
27831
27832 #[test]
27835 fn a_highlight_marks_the_matches_and_leaves_the_rest_of_the_field_alone() {
27836 let mut f = Fixture::new();
27837 marking(&mut f);
27838 let got = f.run(&[b"FT.SEARCH", b"mk", b"fox", b"HIGHLIGHT"]);
27839 assert!(got.contains("<b>fox</b> d1 d2"), "{got}");
27840 let both = f.run(&[
27841 b"FT.SEARCH",
27842 b"mk",
27843 b"fox",
27844 b"SUMMARIZE",
27845 b"LEN",
27846 b"2",
27847 b"HIGHLIGHT",
27848 ]);
27849 assert!(both.contains("c3 <b>fox</b> d1 d2... "), "{both}");
27850 assert!(both.contains("t1 t2 t3 t4 t5 t6 t7 t8\r\n"), "{both}");
27852 assert!(both.contains("$3\r\nred\r\n"), "{both}");
27853 let split = f.run(&[
27855 b"FT.SEARCH",
27856 b"mk",
27857 b"fox",
27858 b"SUMMARIZE",
27859 b"FIELDS",
27860 b"1",
27861 b"b",
27862 b"LEN",
27863 b"2",
27864 b"HIGHLIGHT",
27865 b"FIELDS",
27866 b"1",
27867 b"a",
27868 ]);
27869 assert!(split.contains("t1 t2 t3 t4 t5 t6 t7\r\n"), "{split}");
27870 }
27871
27872 #[test]
27874 fn a_highlight_does_not_mark_a_tag() {
27875 let mut f = Fixture::new();
27876 marking(&mut f);
27877 f.run(&[b"HSET", b"m:1", b"b", b"red and blue"]);
27878 let got = f.run(&[b"FT.SEARCH", b"mk", b"@g:{red}", b"HIGHLIGHT"]);
27879 assert!(!got.contains("<b>"), "{got}");
27880 assert!(got.contains("red and blue"), "{got}");
27881 }
27882
27883 #[test]
27886 fn a_search_answers_a_total_and_then_the_rows() {
27887 let mut f = Fixture::new();
27888 corpus(&mut f);
27889 assert_eq!(
27890 f.run(&[b"FT.SEARCH", b"sx", b"delta"]),
27891 "*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"
27892 );
27893 f.run(&[b"HSET", b"d:3", b"extra", b"more"]);
27896 assert!(f.run(&[b"FT.SEARCH", b"sx", b"delta"]).contains("extra"));
27897 assert_eq!(
27900 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
27901 "*2\r\n:1\r\n$3\r\nd:3\r\n"
27902 );
27903 assert_eq!(
27904 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
27905 "*1\r\n:3\r\n"
27906 );
27907 }
27908
27909 #[test]
27912 fn the_window_is_ten_rows_and_a_million_wide_at_most() {
27913 let mut f = Fixture::new();
27914 corpus(&mut f);
27915 assert_eq!(
27916 f.run(&[
27917 b"FT.SEARCH",
27918 b"sx",
27919 b"alpha",
27920 b"NOCONTENT",
27921 b"LIMIT",
27922 b"1",
27923 b"1"
27924 ]),
27925 "*2\r\n:3\r\n$3\r\nd:2\r\n"
27926 );
27927 assert_eq!(
27928 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0"]),
27929 "-SEARCH_PARSE_ARGS LIMIT requires two arguments\r\n"
27930 );
27931 assert_eq!(
27932 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"-1"]),
27933 "-SEARCH_PARSE_ARGS LIMIT needs two numeric arguments\r\n"
27934 );
27935 assert_eq!(
27936 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"0", b"1000001"]),
27937 "-SEARCH_LIMIT_OVER LIMIT exceeds maximum of 1000000\r\n"
27938 );
27939 assert_eq!(
27940 f.run(&[
27941 b"FT.SEARCH",
27942 b"sx",
27943 b"alpha",
27944 b"NOCONTENT",
27945 b"LIMIT",
27946 b"999999",
27947 b"1000000"
27948 ]),
27949 "*1\r\n:3\r\n"
27950 );
27951 }
27952
27953 #[test]
27957 fn a_return_of_nothing_is_not_the_same_as_nocontent() {
27958 let mut f = Fixture::new();
27959 corpus(&mut f);
27960 let bare = "*2\r\n:1\r\n$3\r\nd:3\r\n";
27961 assert_eq!(
27962 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"0"]),
27963 bare
27964 );
27965 assert_eq!(
27966 f.run(&[
27967 b"FT.SEARCH",
27968 b"sx",
27969 b"delta",
27970 b"NOCONTENT",
27971 b"RETURN",
27972 b"1",
27973 b"t"
27974 ]),
27975 bare
27976 );
27977 assert_eq!(
27978 f.run(&[
27979 b"FT.SEARCH",
27980 b"sx",
27981 b"delta",
27982 b"RETURN",
27983 b"0",
27984 b"RETURN",
27985 b"1",
27986 b"t"
27987 ]),
27988 "*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"
27989 );
27990 }
27991
27992 #[test]
27995 fn the_count_after_return_counts_words() {
27996 let mut f = Fixture::new();
27997 corpus(&mut f);
27998 assert_eq!(
28001 f.run(&[
28002 b"FT.SEARCH",
28003 b"sx",
28004 b"delta",
28005 b"RETURN",
28006 b"3",
28007 b"t",
28008 b"AS",
28009 b"x"
28010 ]),
28011 "*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"
28012 );
28013 assert_eq!(
28016 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"2", b"t", b"AS"]),
28017 "-SEARCH_PARSE_ARGS RETURN path AS name - must be accompanied with NAME\r\n"
28018 );
28019 assert_eq!(
28020 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"3", b"t", b"AS"]),
28021 "-SEARCH_PARSE_ARGS Bad arguments for RETURN: Expected an argument, but none provided\r\n"
28022 );
28023 assert_eq!(
28027 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"AS"]),
28028 "*3\r\n:1\r\n$3\r\nd:3\r\n*0\r\n"
28029 );
28030 }
28031
28032 #[test]
28035 fn a_filter_is_a_range_written_outside_the_query() {
28036 let mut f = Fixture::new();
28037 corpus(&mut f);
28038 assert_eq!(
28039 f.run(&[
28040 b"FT.SEARCH",
28041 b"sx",
28042 b"alpha",
28043 b"NOCONTENT",
28044 b"FILTER",
28045 b"n",
28046 b"2",
28047 b"4"
28048 ]),
28049 "*3\r\n:2\r\n$3\r\nd:2\r\n$3\r\nd:4\r\n"
28050 );
28051 assert_eq!(
28052 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2"]),
28053 "-SEARCH_PARSE_ARGS FILTER requires 3 arguments\r\n"
28054 );
28055 assert_eq!(
28056 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"x", b"1"]),
28057 "-SEARCH_PARSE_ARGS Bad lower range: x\r\n"
28058 );
28059 assert_eq!(
28060 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", b"n", b"2", b"1"]),
28061 "-SEARCH_SYNTAX Invalid numeric range (min > max): @n:[2.000000 1.000000]\r\n"
28062 );
28063 for field in [b"g".as_slice(), b"nope"] {
28066 assert_eq!(
28067 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"FILTER", field, b"2", b"1"]),
28068 "*1\r\n:0\r\n"
28069 );
28070 }
28071 }
28072
28073 #[test]
28076 fn the_index_is_found_before_the_arguments_are_read() {
28077 let mut f = Fixture::new();
28078 corpus(&mut f);
28079 let missing = "-SEARCH_INDEX_NOT_FOUND Index not found: nope\r\n";
28080 assert_eq!(f.run(&[b"FT.SEARCH", b"nope", b"alpha", b"BOGUS"]), missing);
28081 assert_eq!(
28082 f.run(&[b"FT.EXPLAIN", b"nope", b"alpha", b"BOGUS"]),
28083 missing
28084 );
28085 assert_eq!(
28088 f.run(&[b"FT.SEARCH", b"sx", b"@@@", b"BOGUS"]),
28089 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `BOGUS` at position 1 for <main>\r\n"
28090 );
28091 }
28092
28093 #[test]
28096 fn inkeys_comes_off_the_total() {
28097 let mut f = Fixture::new();
28098 corpus(&mut f);
28099 assert_eq!(
28100 f.run(&[
28101 b"FT.SEARCH",
28102 b"sx",
28103 b"alpha",
28104 b"NOCONTENT",
28105 b"INKEYS",
28106 b"1",
28107 b"d:1"
28108 ]),
28109 "*2\r\n:1\r\n$3\r\nd:1\r\n"
28110 );
28111 assert_eq!(
28112 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"NOCONTENT", b"INKEYS", b"0"]),
28113 "*1\r\n:0\r\n"
28114 );
28115 }
28116
28117 #[test]
28124 fn the_fields_are_read_from_the_session_database() {
28125 let mut f = Fixture::new();
28126 corpus(&mut f);
28127 f.run(&[b"SELECT", b"1"]);
28128 f.run(&[b"HSET", b"d:9", b"t", b"delta", b"n", b"9"]);
28129 assert_eq!(
28132 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"NOCONTENT"]),
28133 "*3\r\n:2\r\n$3\r\nd:3\r\n$3\r\nd:9\r\n"
28134 );
28135 assert_eq!(
28136 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
28137 "*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"
28138 );
28139 }
28140
28141 #[test]
28144 fn the_third_protocol_answers_a_map_of_five() {
28145 let mut f = Fixture::new();
28146 corpus(&mut f);
28147 f.out = Out::new(Proto::Resp3);
28148 assert_eq!(
28149 f.run(&[b"FT.SEARCH", b"sx", b"delta", b"RETURN", b"1", b"n"]),
28150 concat!(
28151 "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
28152 "%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",
28153 "+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
28154 )
28155 );
28156 }
28157
28158 #[test]
28162 fn a_window_of_nothing_has_to_start_at_the_top() {
28163 let mut f = Fixture::new();
28164 corpus(&mut f);
28165 let refused = "-SEARCH_LIMIT_OVER The `offset` of the LIMIT must be 0 when `num` is 0\r\n";
28166 assert_eq!(
28167 f.run(&[b"FT.SEARCH", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
28168 refused
28169 );
28170 assert_eq!(
28171 f.run(&[b"FT.EXPLAIN", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
28172 refused
28173 );
28174 assert_eq!(
28175 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"1", b"0"]),
28176 refused
28177 );
28178 assert_eq!(
28179 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LIMIT", b"0", b"0"]),
28180 "*1\r\n:3\r\n"
28181 );
28182 }
28183
28184 #[test]
28187 fn an_aggregation_answers_a_count_and_then_the_properties() {
28188 let mut f = Fixture::new();
28189 corpus(&mut f);
28190 assert_eq!(
28191 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha"]),
28192 "*4\r\n:1\r\n*0\r\n*0\r\n*0\r\n"
28193 );
28194 assert_eq!(
28198 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"1", b"@t"]),
28199 concat!(
28200 "*4\r\n:1\r\n*2\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
28201 "*2\r\n$1\r\nt\r\n$11\r\nalpha gamma\r\n",
28202 "*2\r\n$1\r\nt\r\n$16\r\nalpha beta gamma\r\n"
28203 )
28204 );
28205 assert_eq!(
28209 f.run(&[
28210 b"FT.AGGREGATE",
28211 b"sx",
28212 b"alpha",
28213 b"LOAD",
28214 b"1",
28215 b"@n",
28216 b"LIMIT",
28217 b"1",
28218 b"2"
28219 ]),
28220 "*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"
28221 );
28222 assert_eq!(
28224 f.run(&[b"FT.AGGREGATE", b"sx", b"nope", b"LOAD", b"1", b"@t"]),
28225 "*1\r\n:0\r\n"
28226 );
28227 }
28228
28229 #[test]
28233 fn a_load_counts_words_and_can_rename_what_it_reads() {
28234 let mut f = Fixture::new();
28235 corpus(&mut f);
28236 assert_eq!(
28238 f.run(&[
28239 b"FT.AGGREGATE",
28240 b"sx",
28241 b"alpha",
28242 b"LOAD",
28243 b"3",
28244 b"@t",
28245 b"AS",
28246 b"text"
28247 ]),
28248 concat!(
28249 "*4\r\n:1\r\n*2\r\n$4\r\ntext\r\n$10\r\nalpha beta\r\n",
28250 "*2\r\n$4\r\ntext\r\n$11\r\nalpha gamma\r\n",
28251 "*2\r\n$4\r\ntext\r\n$16\r\nalpha beta gamma\r\n"
28252 )
28253 );
28254 assert_eq!(
28255 f.run(&[
28256 b"FT.AGGREGATE",
28257 b"sx",
28258 b"alpha",
28259 b"LOAD",
28260 b"*",
28261 b"LIMIT",
28262 b"0",
28263 b"1"
28264 ]),
28265 concat!(
28266 "*2\r\n:1\r\n*6\r\n$1\r\nt\r\n$10\r\nalpha beta\r\n",
28267 "$1\r\ng\r\n$5\r\naa,bb\r\n$1\r\nn\r\n$1\r\n1\r\n"
28268 )
28269 );
28270 assert_eq!(
28272 f.run(&[
28273 b"FT.AGGREGATE",
28274 b"sx",
28275 b"alpha",
28276 b"LOAD",
28277 b"2",
28278 b"@n",
28279 b"@nope",
28280 b"LIMIT",
28281 b"0",
28282 b"2"
28283 ]),
28284 "*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"
28285 );
28286 }
28287
28288 #[test]
28291 fn a_load_refuses_a_count_it_cannot_use() {
28292 let mut f = Fixture::new();
28293 corpus(&mut f);
28294 let head = "-SEARCH_PARSE_ARGS Bad arguments for LOAD: ";
28295 assert_eq!(
28296 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"x"]),
28297 format!("{head}Expected number of fields or `*`\r\n")
28298 );
28299 assert_eq!(
28300 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"-1", b"@t"]),
28301 format!("{head}Value is outside acceptable bounds\r\n")
28302 );
28303 assert_eq!(
28304 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD", b"5", b"@t"]),
28305 format!("{head}Expected an argument, but none provided\r\n")
28306 );
28307 assert_eq!(
28308 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", b"LOAD"]),
28309 format!("{head}Expected an argument, but none provided\r\n")
28310 );
28311 assert_eq!(
28315 f.run(&[
28316 b"FT.AGGREGATE",
28317 b"sx",
28318 b"alpha",
28319 b"LOAD",
28320 b"2",
28321 b"@t",
28322 b"AS"
28323 ]),
28324 "-SEARCH_PARSE_ARGS LOAD path AS name - must be accompanied with NAME\r\n"
28325 );
28326 assert_eq!(
28329 f.run(&[
28330 b"FT.AGGREGATE",
28331 b"sx",
28332 b"alpha",
28333 b"LOAD",
28334 b"2",
28335 b"@t",
28336 b"AS",
28337 b"VERBATIM"
28338 ]),
28339 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 5 for <main>\r\n"
28340 );
28341 assert_eq!(
28345 f.run(&[
28346 b"FT.AGGREGATE",
28347 b"sx",
28348 b"alpha",
28349 b"LOAD",
28350 b"0",
28351 b"LIMIT",
28352 b"0",
28353 b"1"
28354 ]),
28355 "*2\r\n:1\r\n*0\r\n"
28356 );
28357 }
28358
28359 #[test]
28362 fn a_pipeline_step_closes_the_door_on_the_search_words() {
28363 let mut f = Fixture::new();
28364 corpus(&mut f);
28365 assert_eq!(
28366 f.run(&[
28367 b"FT.AGGREGATE",
28368 b"sx",
28369 b"alpha",
28370 b"LOAD",
28371 b"1",
28372 b"@t",
28373 b"VERBATIM"
28374 ]),
28375 "-SEARCH_ARG_UNRECOGNIZED Unknown argument `VERBATIM` at position 4 for <main>\r\n"
28376 );
28377 assert_eq!(
28378 f.run(&[
28379 b"FT.AGGREGATE",
28380 b"sx",
28381 b"alpha",
28382 b"LIMIT",
28383 b"0",
28384 b"1",
28385 b"VERBATIM"
28386 ]),
28387 "*2\r\n:1\r\n*0\r\n"
28388 );
28389 for word in [b"RETURN".as_slice(), b"SUMMARIZE", b"HIGHLIGHT"] {
28392 let name = core::str::from_utf8(word).expect("the three words are text");
28393 assert_eq!(
28394 f.run(&[b"FT.AGGREGATE", b"sx", b"alpha", word]),
28395 format!("-SEARCH_PARSE_ARGS {name} is not supported on FT.AGGREGATE\r\n")
28396 );
28397 }
28398 }
28399
28400 #[test]
28403 fn addscores_writes_a_shorter_score_than_withscores() {
28404 let mut f = Fixture::new();
28405 corpus(&mut f);
28406 assert_eq!(
28407 f.run(&[
28408 b"FT.AGGREGATE",
28409 b"sx",
28410 b"alpha",
28411 b"ADDSCORES",
28412 b"LOAD",
28413 b"1",
28414 b"@n",
28415 b"LIMIT",
28416 b"0",
28417 b"2"
28418 ]),
28419 concat!(
28420 "*3\r\n:1\r\n",
28421 "*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",
28422 "*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"
28423 )
28424 );
28425 assert_eq!(
28429 f.run(&[
28430 b"FT.AGGREGATE",
28431 b"sx",
28432 b"alpha",
28433 b"NOCONTENT",
28434 b"WITHSCORES",
28435 b"LIMIT",
28436 b"0",
28437 b"2"
28438 ]),
28439 "*3\r\n:1\r\n$18\r\n0.3566749439387324\r\n$18\r\n0.3566749439387324\r\n"
28440 );
28441 assert_eq!(
28442 f.run(&[
28443 b"FT.AGGREGATE",
28444 b"sx",
28445 b"alpha",
28446 b"WITHSORTKEYS",
28447 b"LOAD",
28448 b"1",
28449 b"@n",
28450 b"LIMIT",
28451 b"0",
28452 b"1"
28453 ]),
28454 "*3\r\n:1\r\n$-1\r\n*2\r\n$1\r\nn\r\n$1\r\n1\r\n"
28455 );
28456 }
28457
28458 #[test]
28461 fn a_normalising_scorer_answers_the_rows_backwards() {
28462 let mut f = Fixture::new();
28463 corpus(&mut f);
28464 assert_eq!(
28465 f.run(&[
28466 b"FT.AGGREGATE",
28467 b"sx",
28468 b"alpha",
28469 b"SCORER",
28470 b"BM25STD.NORM",
28471 b"ADDSCORES",
28472 b"LOAD",
28473 b"1",
28474 b"@n",
28475 b"LIMIT",
28476 b"1",
28477 b"2"
28478 ]),
28479 concat!(
28480 "*3\r\n:3\r\n",
28481 "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n2\r\n",
28482 "*4\r\n$7\r\n__score\r\n$1\r\n1\r\n$1\r\nn\r\n$1\r\n1\r\n"
28483 )
28484 );
28485 assert_eq!(
28488 f.run(&[
28489 b"FT.AGGREGATE",
28490 b"sx",
28491 b"alpha",
28492 b"SCORER",
28493 b"BM25STD.NORM",
28494 b"LOAD",
28495 b"1",
28496 b"@n",
28497 b"LIMIT",
28498 b"1",
28499 b"2"
28500 ]),
28501 "*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"
28502 );
28503 }
28504
28505 #[test]
28508 fn an_aggregation_answers_a_map_of_five_as_well() {
28509 let mut f = Fixture::new();
28510 corpus(&mut f);
28511 f.out = Out::new(Proto::Resp3);
28512 assert_eq!(
28513 f.run(&[
28514 b"FT.AGGREGATE",
28515 b"sx",
28516 b"alpha",
28517 b"ADDSCORES",
28518 b"WITHSCORES",
28519 b"WITHSORTKEYS",
28520 b"LOAD",
28521 b"1",
28522 b"@n",
28523 b"LIMIT",
28524 b"0",
28525 b"1"
28526 ]),
28527 concat!(
28528 "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
28529 "%4\r\n+score\r\n,0.3566749439387324\r\n+sortkey\r\n_\r\n",
28530 "+extra_attributes\r\n%2\r\n$7\r\n__score\r\n$14\r\n0.356674943939\r\n",
28531 "$1\r\nn\r\n$1\r\n1\r\n+values\r\n*0\r\n",
28532 "+total_results\r\n:1\r\n+warning\r\n*0\r\n"
28533 )
28534 );
28535 assert_eq!(
28538 f.run(&[
28539 b"FT.AGGREGATE",
28540 b"sx",
28541 b"alpha",
28542 b"NOCONTENT",
28543 b"LIMIT",
28544 b"0",
28545 b"1"
28546 ]),
28547 concat!(
28548 "%5\r\n+attributes\r\n*0\r\n+format\r\n+STRING\r\n+results\r\n*1\r\n",
28549 "%1\r\n+values\r\n*0\r\n+total_results\r\n:1\r\n+warning\r\n*0\r\n"
28550 )
28551 );
28552 }
28553}